Compare commits
29 Commits
work/kloec
...
work/use_i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f3553a59d | ||
|
|
882a0d4901 | ||
|
|
506d31f53f | ||
|
|
110f007b41 | ||
|
|
a90943d9ac | ||
|
|
cbe7d8c2c2 | ||
|
|
ae4943dd71 | ||
|
|
7bd84bf51e | ||
|
|
a6ce44eb24 | ||
|
|
b1c42c3d3d | ||
|
|
213aaf3ac4 | ||
|
|
c55b40c9c6 | ||
|
|
81928d8b93 | ||
|
|
b7bddba053 | ||
|
|
307a9370db | ||
|
|
2f65cbeb36 | ||
|
|
e0c0b1f0e8 | ||
|
|
74f767aa82 | ||
|
|
7176dd4476 | ||
|
|
f75fe31571 | ||
|
|
61bdb1ed5f | ||
|
|
527e9d93a5 | ||
|
|
7d22b30217 | ||
|
|
c0d2333a3d | ||
|
|
4a7e1d058c | ||
|
|
66974615f6 | ||
|
|
116c888686 | ||
|
|
f6d6a804d2 | ||
|
|
6d6d702b97 |
@@ -17,7 +17,7 @@ project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
set(KF_MIN_VERSION "5.105.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
if (ANDROID)
|
||||
set(QT_MIN_VERSION "5.15.8")
|
||||
set(QT_MIN_VERSION "5.15.10")
|
||||
endif()
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -178,3 +178,9 @@ file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
|
||||
# Fixes the test by excluding this directory
|
||||
list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX [[_(install|build)/.*]])
|
||||
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT NEOCHAT
|
||||
FILE neochat.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ android {
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
|
||||
BIN
icons/300-apps-neochat.png
Normal file
BIN
icons/300-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
icons/windows/promoimage-1920x1080.png
Normal file
BIN
icons/windows/promoimage-1920x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
icons/windows/storelogo-1080x1080.png
Normal file
BIN
icons/windows/storelogo-1080x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
BIN
icons/windows/storelogo-720x1080.png
Normal file
BIN
icons/windows/storelogo-720x1080.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -67,7 +67,7 @@
|
||||
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
|
||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldingsprotokollen</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix – protokollen for desentralisert kommunikasjon</summary>
|
||||
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
|
||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||
@@ -96,6 +96,7 @@ to provide a convergent experience across multiple platforms.</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>
|
||||
<p xml:lang="nl">NeoChat is een client voor Matrix, het gedecentraliseerde communicatieprotocol voor instant messages. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden. Het gebruik KDE frameworks en het meest opmerkelijk Kirigami om een convergente ervaring te leveren op meerdere platforms.</p>
|
||||
<p xml:lang="nn">NeoChat er ein klient for Matrix, ein protokoll for desentralisert kommunikasjon. Du kan utveksla tekst, lyd og videoar med kollegaar, vennar og familie. Programmet brukar KDE Frameworks og Kirigami for å gje ei brukarflate tilpassa ulike plattformer.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matriksa, protokołu rozproszonego porozumiewania się w czasie rzeczywistym. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół. Używa szkieletów KDE i głównie Kirigami, aby zapewnić spójne wrażenia na wielu platformach</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix. Usa as plataformas do KDE, e principalmente o Kirigami, para oferecer uma experiência convergente entre várias plataformas.</p>
|
||||
<p xml:lang="sl">Neochat je odjemalec za Matrix, decentralizirani komunikacijski protokol za takojšnje sporočanje. Omogoča vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek svoji družini, sodelavcem in prijateljem. Uporablja okvire ogrodje KDE frameworks in predvsem Kirigami za zagotavljanje konvergentne izkušnje na več platformah.</p>
|
||||
@@ -115,6 +116,7 @@ to provide a convergent experience across multiple platforms.</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>
|
||||
<p xml:lang="nl">NeoChat richt zich op het volledig bieden van alle mogelijkheden van de Matrix-specificatie. Alles in de huidige stabiele specificatie met merkbare uitzondering van VoIP, gekoppelde discussies en sommige aspecten van eind-tot-eind versleuteling worden ondersteund. Er zijn een paar andere kleinere omissies vanwege het feit dat de Matrix specificatie constant evolueert maar het doel blijft het eventueel bieden van ondersteuning van de gehele specificatie.</p>
|
||||
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
|
||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||
<p xml:lang="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>
|
||||
@@ -134,6 +136,7 @@ to provide a convergent experience across multiple platforms.</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>
|
||||
<p xml:lang="nl">Vanwege de aard van de ontwikkeling van de Matrix specificatie ondersteunt NeoChat ook talloze onstabiele mogelijkheden. Dit zijn nu:</p>
|
||||
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
|
||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
||||
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||
@@ -154,6 +157,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="it">Sondaggi - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
<li xml:lang="nl">Polls - MSC3381</li>
|
||||
<li xml:lang="nn">Avstemmingar – MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
|
||||
@@ -173,6 +177,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
<li xml:lang="nl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="nn">Klistremerke-pakkar – MSC2545</li>
|
||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
||||
@@ -192,6 +197,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="it">Località eventi - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
<li xml:lang="nl">Locatie gebeurtenissen - MSC3488</li>
|
||||
<li xml:lang="nn">Posisjonshendingar – MSC3488</li>
|
||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
|
||||
@@ -247,6 +253,32 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9PNXWVNRC29H</value>
|
||||
<value key="KDE::mastodon">https://kde.social/@neochat</value>
|
||||
<value key="KDE::windows_store::StoreLogo9x16">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-720x1080.png</value>
|
||||
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
||||
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||
<value key="KDE::windows_store::screenshots::1::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption">Main view with room list, chat, and room information</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="x-test">xxMain view with room list, chat, and room informationxx</value>
|
||||
<value key="KDE::windows_store::screenshots::2::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption">Login screen</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca-valencia">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="es">Pantalla de inicio de sesión</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ka">შესვლის ეკრანი</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="nl">Aanmeldscherm</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="sl">Prijavni zaslon</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="tr">Oturum açma ekranı</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="uk">Вікно входу</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="x-test">xxLogin screenxx</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -261,6 +293,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.04.3" date="2023-07-06"/>
|
||||
<release version="23.04.2" date="2023-06-08"/>
|
||||
<release version="23.04.1" date="2023-05-11"/>
|
||||
<release version="23.04.0" date="2023-04-20">
|
||||
|
||||
@@ -100,7 +100,7 @@ Comment[ka]=კლიენტი Matrix-ის პროტოკოლის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Lynmeldingsklient for Matrix-protokollen
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
|
||||
757
po/ar/neochat.po
757
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/az/neochat.po
747
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
739
po/ca/neochat.po
739
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
724
po/cs/neochat.po
724
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
715
po/da/neochat.po
715
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/de/neochat.po
747
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/el/neochat.po
747
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
739
po/es/neochat.po
739
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/eu/neochat.po
747
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/fi/neochat.po
747
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
805
po/fr/neochat.po
805
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/hu/neochat.po
747
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
757
po/ia/neochat.po
757
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/id/neochat.po
747
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
733
po/ie/neochat.po
733
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
757
po/it/neochat.po
757
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
709
po/ja/neochat.po
709
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
739
po/ka/neochat.po
739
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/ko/neochat.po
747
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
709
po/lt/neochat.po
709
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
739
po/nl/neochat.po
739
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1274
po/nn/neochat.po
1274
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/pa/neochat.po
743
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/pl/neochat.po
747
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
739
po/pt/neochat.po
739
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
747
po/ru/neochat.po
747
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/sk/neochat.po
743
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/sl/neochat.po
743
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
747
po/sv/neochat.po
747
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
757
po/ta/neochat.po
757
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
739
po/tr/neochat.po
739
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
739
po/uk/neochat.po
739
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
@@ -24,7 +24,7 @@ add_library(neochat STATIC
|
||||
models/userfiltermodel.cpp
|
||||
models/publicroomlistmodel.cpp
|
||||
models/userdirectorylistmodel.cpp
|
||||
models/keywordnotificationrulemodel.cpp
|
||||
models/pushrulemodel.cpp
|
||||
models/emoticonfiltermodel.cpp
|
||||
notificationsmanager.cpp
|
||||
models/sortfilterroomlistmodel.cpp
|
||||
@@ -36,6 +36,7 @@ add_library(neochat STATIC
|
||||
blurhash.cpp
|
||||
blurhashimageprovider.cpp
|
||||
models/collapsestateproxymodel.cpp
|
||||
models/mediamessagefiltermodel.cpp
|
||||
urlhelper.cpp
|
||||
windowcontroller.cpp
|
||||
linkpreviewer.cpp
|
||||
@@ -65,7 +66,9 @@ ecm_qt_declare_logging_category(neochat
|
||||
HEADER "messageeventmodel_logging.h"
|
||||
IDENTIFIER "MessageEvent"
|
||||
CATEGORY_NAME "org.kde.neochat.messageeventmodel"
|
||||
DESCRIPTION "Neochat: messageeventmodel"
|
||||
DEFAULT_SEVERITY Info
|
||||
EXPORT NEOCHAT
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "controller.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <qt5keychain/keychain.h>
|
||||
@@ -126,6 +127,10 @@ Controller::Controller(QObject *parent)
|
||||
oldAccountCount = Accounts.size();
|
||||
});
|
||||
#endif
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
m_pushRuleModel = new PushRuleModel;
|
||||
});
|
||||
}
|
||||
|
||||
Controller &Controller::instance()
|
||||
@@ -507,6 +512,11 @@ void Controller::setActiveConnection(Connection *connection)
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
|
||||
PushRuleModel *Controller::pushRuleModel() const
|
||||
{
|
||||
return m_pushRuleModel;
|
||||
}
|
||||
|
||||
void Controller::saveWindowGeometry()
|
||||
{
|
||||
WindowController::instance().saveGeometry();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "models/pushrulemodel.h"
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
|
||||
@@ -50,6 +51,11 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The PushRuleModel that has the active connection's push rules.
|
||||
*/
|
||||
Q_PROPERTY(PushRuleModel *pushRuleModel READ pushRuleModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The row number in the accounts directory of the active connection.
|
||||
*/
|
||||
@@ -119,6 +125,8 @@ public:
|
||||
void setActiveConnection(Quotient::Connection *connection);
|
||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||
|
||||
[[nodiscard]] PushRuleModel *pushRuleModel() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
@@ -236,6 +244,8 @@ private:
|
||||
|
||||
bool hasWindowSystem() const;
|
||||
|
||||
QPointer<PushRuleModel> m_pushRuleModel;
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
void showWindow();
|
||||
|
||||
@@ -105,7 +105,7 @@ void Login::init()
|
||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||
});
|
||||
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, []() {
|
||||
Q_EMIT Controller::instance().initiated();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,12 +57,13 @@
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/emoticonfiltermodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/keywordnotificationrulemodel.h"
|
||||
#include "models/livelocationsmodel.h"
|
||||
#include "models/locationsmodel.h"
|
||||
#include "models/mediamessagefiltermodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
#include "models/publicroomlistmodel.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/searchmodel.h"
|
||||
@@ -237,6 +238,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||
qmlRegisterType<ReactionModel>("org.kde.neochat", 1, 0, "ReactionModel");
|
||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||
qmlRegisterType<MediaMessageFilterModel>("org.kde.neochat", 1, 0, "MediaMessageFilterModel");
|
||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
|
||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||
@@ -255,13 +257,15 @@ int main(int argc, char *argv[])
|
||||
#ifdef QUOTIENT_07
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||
qmlRegisterType<PushRuleModel>("org.kde.neochat", 1, 0, "PushRuleModel");
|
||||
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
|
||||
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
|
||||
qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel");
|
||||
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
|
||||
qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationKind>("org.kde.neochat", 1, 0, "PushNotificationKind", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationSection>("org.kde.neochat", 1, 0, "PushNotificationSection", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "keywordnotificationrulemodel.h"
|
||||
#include "controller.h"
|
||||
#include "notificationsmanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <connection.h>
|
||||
#include <converters.h>
|
||||
#include <csapi/definitions/push_ruleset.h>
|
||||
#include <csapi/pushrules.h>
|
||||
#include <jobs/basejob.h>
|
||||
|
||||
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &KeywordNotificationRuleModel::updateNotificationRules);
|
||||
updateNotificationRules("m.push_rules");
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
|
||||
const QVector<Quotient::PushRule> contentRules = ruleData.content;
|
||||
|
||||
beginResetModel();
|
||||
m_notificationRules.clear();
|
||||
for (const auto &i : contentRules) {
|
||||
if (!m_notificationRules.contains(i.ruleId) && i.ruleId[0] != '.') {
|
||||
m_notificationRules.append(i.ruleId);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant KeywordNotificationRuleModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= m_notificationRules.count()) {
|
||||
qDebug() << "KeywordNotificationRuleModel, something's wrong: index.row() >= m_notificationRules.count()";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
return m_notificationRules.at(index.row());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int KeywordNotificationRuleModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return m_notificationRules.count();
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::addKeyword(const QString &keyword)
|
||||
{
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().initializeKeywordNotificationAction();
|
||||
}
|
||||
|
||||
const QVector<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
|
||||
|
||||
auto job = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QVector<Quotient::PushCondition>(), keyword);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, keyword]() {
|
||||
beginInsertRows(QModelIndex(), m_notificationRules.count(), m_notificationRules.count());
|
||||
m_notificationRules.append(keyword);
|
||||
endInsertRows();
|
||||
});
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::removeKeywordAtIndex(int index)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", "content", m_notificationRules[index]);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, index]() {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_notificationRules.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().deactivateKeywordNotificationAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> KeywordNotificationRuleModel::roleNames() const
|
||||
{
|
||||
return {{NameRole, QByteArrayLiteral("name")}};
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <csapi/definitions/push_rule.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
/**
|
||||
* @class KeywordNotificationRuleModel
|
||||
*
|
||||
* This class defines the model for managing notification push rule keywords.
|
||||
*/
|
||||
class KeywordNotificationRuleModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
|
||||
};
|
||||
|
||||
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Add a new keyword to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addKeyword(const QString &keyword);
|
||||
|
||||
/**
|
||||
* @brief Remove a keyword from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
||||
|
||||
private Q_SLOTS:
|
||||
void controllerConnectionChanged();
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
private:
|
||||
QList<QString> m_notificationRules;
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "livelocationsmodel.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <events/roommessageevent.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
79
src/models/mediamessagefiltermodel.cpp
Normal file
79
src/models/mediamessagefiltermodel.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#include "mediamessagefiltermodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include <room.h>
|
||||
|
||||
MediaMessageFilterModel::MediaMessageFilterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
if (index.data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image
|
||||
|| index.data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Video) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == SourceRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["source"].toUrl();
|
||||
} else if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Video) {
|
||||
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
|
||||
|
||||
if (progressInfo.completed()) {
|
||||
return mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>().localPath;
|
||||
} else {
|
||||
return QUrl();
|
||||
}
|
||||
} else {
|
||||
return QUrl();
|
||||
}
|
||||
}
|
||||
if (role == TempSourceRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["tempInfo"].toMap()["source"].toUrl();
|
||||
}
|
||||
if (role == TypeRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (role == CaptionRole) {
|
||||
return mapToSource(index).data(Qt::DisplayRole);
|
||||
}
|
||||
if (role == SourceWidthRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["width"].toFloat();
|
||||
}
|
||||
if (role == SourceHeightRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["height"].toFloat();
|
||||
}
|
||||
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MediaMessageFilterModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[SourceRole] = "source";
|
||||
roles[TempSourceRole] = "tempSource";
|
||||
roles[TypeRole] = "type";
|
||||
roles[CaptionRole] = "caption";
|
||||
roles[SourceWidthRole] = "sourceWidth";
|
||||
roles[SourceHeightRole] = "sourceHeight";
|
||||
return roles;
|
||||
}
|
||||
|
||||
int MediaMessageFilterModel::getRowForSourceItem(int sourceRow) const
|
||||
{
|
||||
return mapFromSource(sourceModel()->index(sourceRow, 0)).row();
|
||||
}
|
||||
56
src/models/mediamessagefiltermodel.h
Normal file
56
src/models/mediamessagefiltermodel.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
|
||||
/**
|
||||
* @class MediaMessageFilterModel
|
||||
*
|
||||
* This model filters a MessageEventModel for image and video messages.
|
||||
*
|
||||
* @sa MessageEventModel
|
||||
*/
|
||||
class MediaMessageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
SourceRole = CollapseStateProxyModel::LastRole + 1, /**< The mxc source URL for the item. */
|
||||
TempSourceRole, /**< Source for the temporary content (either blurhash or mxc URL). */
|
||||
TypeRole, /**< The type of the media (image or video). */
|
||||
CaptionRole, /**< The caption for the item. */
|
||||
SourceWidthRole, /**< The width of the source item. */
|
||||
SourceHeightRole, /**< The height of the source item. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
MediaMessageFilterModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter to show only image and video messages.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE int getRowForSourceItem(int sourceRow) const;
|
||||
};
|
||||
@@ -61,7 +61,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||
roles[ReactionRole] = "reaction";
|
||||
roles[ShowReactionsRole] = "showReactions";
|
||||
roles[SourceRole] = "source";
|
||||
roles[SourceRole] = "jsonSource";
|
||||
roles[MimeTypeRole] = "mimeType";
|
||||
roles[AuthorIdRole] = "authorId";
|
||||
roles[VerifiedRole] = "verified";
|
||||
|
||||
445
src/models/pushrulemodel.cpp
Normal file
445
src/models/pushrulemodel.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "pushrulemodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <connection.h>
|
||||
#include <converters.h>
|
||||
#include <csapi/definitions/push_ruleset.h>
|
||||
#include <csapi/pushrules.h>
|
||||
#include <jobs/basejob.h>
|
||||
#include <qobjectdefs.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "notificationsmanager.h"
|
||||
|
||||
// Alternate name text for default rules.
|
||||
static const QHash<QString, QString> defaultRuleNames = {
|
||||
{QStringLiteral(".m.rule.master"), QStringLiteral("Enable notifications for this account")},
|
||||
{QStringLiteral(".m.rule.room_one_to_one"), QStringLiteral("Messages in one-to-one chats")},
|
||||
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), QStringLiteral("Encrypted messages in one-to-one chats")},
|
||||
{QStringLiteral(".m.rule.message"), QStringLiteral("Messages in group chats")},
|
||||
{QStringLiteral(".m.rule.encrypted"), QStringLiteral("Messages in encrypted group chats")},
|
||||
{QStringLiteral(".m.rule.tombstone"), QStringLiteral("Room upgrade messages")},
|
||||
{QStringLiteral(".m.rule.contains_display_name"), QStringLiteral("Messages containing my display name")},
|
||||
{QStringLiteral(".m.rule.roomnotif"), QStringLiteral("Whole room (@room) notifications")},
|
||||
{QStringLiteral(".m.rule.invite_for_me"), QStringLiteral("Invites to a room")},
|
||||
{QStringLiteral(".m.rule.call"), QStringLiteral("Call invitation")},
|
||||
};
|
||||
|
||||
// Sections for default rules.
|
||||
static const QHash<QString, PushNotificationSection::Section> defaultSections = {
|
||||
{QStringLiteral(".m.rule.master"), PushNotificationSection::Master},
|
||||
{QStringLiteral(".m.rule.room_one_to_one"), PushNotificationSection::Room},
|
||||
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), PushNotificationSection::Room},
|
||||
{QStringLiteral(".m.rule.message"), PushNotificationSection::Room},
|
||||
{QStringLiteral(".m.rule.encrypted"), PushNotificationSection::Room},
|
||||
{QStringLiteral(".m.rule.tombstone"), PushNotificationSection::Room},
|
||||
{QStringLiteral(".m.rule.contains_display_name"), PushNotificationSection::Mentions},
|
||||
{QStringLiteral(".m.rule.roomnotif"), PushNotificationSection::Mentions},
|
||||
{QStringLiteral(".m.rule.invite_for_me"), PushNotificationSection::Invites},
|
||||
{QStringLiteral(".m.rule.call"), PushNotificationSection::Undefined}, // TODO: make invites when VOIP added.
|
||||
{QStringLiteral(".m.rule.suppress_notices"), PushNotificationSection::Undefined},
|
||||
{QStringLiteral(".m.rule.member_event"), PushNotificationSection::Undefined},
|
||||
{QStringLiteral(".m.rule.reaction"), PushNotificationSection::Undefined},
|
||||
{QStringLiteral(".m.rule.room.server_acl"), PushNotificationSection::Undefined},
|
||||
{QStringLiteral(".im.vector.jitsi"), PushNotificationSection::Undefined},
|
||||
};
|
||||
|
||||
// Default rules that don't have a highlight option as it would lead to all messages
|
||||
// in a room being highlighted.
|
||||
static const QStringList noHighlight = {
|
||||
QStringLiteral(".m.rule.room_one_to_one"),
|
||||
QStringLiteral(".m.rule.encrypted_room_one_to_one"),
|
||||
QStringLiteral(".m.rule.message"),
|
||||
QStringLiteral(".m.rule.encrypted"),
|
||||
};
|
||||
|
||||
PushRuleModel::PushRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
m_defaultKeywordAction = static_cast<PushNotificationAction::Action>(NeoChatConfig::self()->keywordPushRuleDefault());
|
||||
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &PushRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
void PushRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
|
||||
updateNotificationRules("m.push_rules");
|
||||
}
|
||||
|
||||
void PushRuleModel::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
|
||||
|
||||
beginResetModel();
|
||||
m_rules.clear();
|
||||
|
||||
// Doing this 5 times because PushRuleset is a struct.
|
||||
setRules(ruleData.override, PushNotificationKind::Override);
|
||||
setRules(ruleData.content, PushNotificationKind::Content);
|
||||
setRules(ruleData.room, PushNotificationKind::Room);
|
||||
setRules(ruleData.sender, PushNotificationKind::Sender);
|
||||
setRules(ruleData.underride, PushNotificationKind::Underride);
|
||||
|
||||
Q_EMIT globalNotificationsEnabledChanged();
|
||||
Q_EMIT globalNotificationsSetChanged();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void PushRuleModel::setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind)
|
||||
{
|
||||
for (const auto &rule : rules) {
|
||||
QString roomId;
|
||||
if (rule.conditions.size() > 0) {
|
||||
for (const auto &condition : rule.conditions) {
|
||||
if (condition.key == QStringLiteral("room_id")) {
|
||||
roomId = condition.pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_rules.append(Rule{
|
||||
rule.ruleId,
|
||||
kind,
|
||||
variantToAction(rule.actions, rule.enabled),
|
||||
getSection(rule),
|
||||
rule.enabled,
|
||||
roomId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int PushRuleModel::getRuleIndex(const QString &ruleId) const
|
||||
{
|
||||
for (auto i = 0; i < m_rules.count(); i++) {
|
||||
if (m_rules[i].id == ruleId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
|
||||
{
|
||||
auto ruleId = rule.ruleId;
|
||||
|
||||
if (defaultSections.contains(ruleId)) {
|
||||
return defaultSections.value(ruleId);
|
||||
} else {
|
||||
/**
|
||||
* If the rule name resolves to a matrix id for a room that the user is part
|
||||
* of it shouldn't appear in the global list as it's overriding the global
|
||||
* state for that room.
|
||||
*
|
||||
* Rooms that the user hasn't joined shouldn't have a rule.
|
||||
*/
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (connection->room(ruleId) != nullptr) {
|
||||
return PushNotificationSection::Undefined;
|
||||
}
|
||||
/**
|
||||
* If the rule name resolves to a matrix id for a user it shouldn't appear
|
||||
* in the global list as it's a rule to block notifications from a user and
|
||||
* is handled elsewhere.
|
||||
*/
|
||||
auto testUserId = ruleId;
|
||||
// Rules for user matrix IDs often don't have the @ on the beginning so add
|
||||
// if not there to avoid malformed ID.
|
||||
if (!testUserId.startsWith(u'@')) {
|
||||
testUserId.prepend(u'@');
|
||||
}
|
||||
if (connection->user(testUserId) != nullptr) {
|
||||
return PushNotificationSection::Undefined;
|
||||
}
|
||||
// If the rule has push conditions and one is a room ID it is a room only keyword.
|
||||
if (!rule.conditions.isEmpty()) {
|
||||
for (auto condition : rule.conditions) {
|
||||
if (condition.key == QStringLiteral("room_id")) {
|
||||
return PushNotificationSection::RoomKeywords;
|
||||
}
|
||||
}
|
||||
}
|
||||
return PushNotificationSection::Keywords;
|
||||
}
|
||||
}
|
||||
|
||||
PushNotificationAction::Action PushRuleModel::defaultState() const
|
||||
{
|
||||
return m_defaultKeywordAction;
|
||||
}
|
||||
|
||||
void PushRuleModel::setDefaultState(PushNotificationAction::Action defaultState)
|
||||
{
|
||||
if (defaultState == m_defaultKeywordAction) {
|
||||
return;
|
||||
}
|
||||
m_defaultKeywordAction = defaultState;
|
||||
NeoChatConfig::setKeywordPushRuleDefault(m_defaultKeywordAction);
|
||||
Q_EMIT defaultStateChanged();
|
||||
}
|
||||
|
||||
bool PushRuleModel::globalNotificationsEnabled() const
|
||||
{
|
||||
auto masterIndex = getRuleIndex(QStringLiteral(".m.rule.master"));
|
||||
if (masterIndex > -1) {
|
||||
return !m_rules[masterIndex].enabled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PushRuleModel::setGlobalNotificationsEnabled(bool enabled)
|
||||
{
|
||||
setNotificationRuleEnabled("override", ".m.rule.master", !enabled);
|
||||
}
|
||||
|
||||
bool PushRuleModel::globalNotificationsSet() const
|
||||
{
|
||||
return getRuleIndex(QStringLiteral(".m.rule.master")) > -1;
|
||||
}
|
||||
|
||||
QVariant PushRuleModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= rowCount()) {
|
||||
qDebug() << "PushRuleModel, something's wrong: index.row() >= m_rules.count()";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
auto ruleId = m_rules.at(index.row()).id;
|
||||
if (defaultRuleNames.contains(ruleId)) {
|
||||
return defaultRuleNames.value(ruleId);
|
||||
} else {
|
||||
return ruleId;
|
||||
}
|
||||
}
|
||||
if (role == IdRole) {
|
||||
return m_rules.at(index.row()).id;
|
||||
}
|
||||
if (role == KindRole) {
|
||||
return m_rules.at(index.row()).kind;
|
||||
}
|
||||
if (role == ActionRole) {
|
||||
return m_rules.at(index.row()).action;
|
||||
}
|
||||
if (role == HighlightableRole) {
|
||||
return !noHighlight.contains(m_rules.at(index.row()).id);
|
||||
}
|
||||
if (role == DeletableRole) {
|
||||
return !m_rules.at(index.row()).id.startsWith(QStringLiteral("."));
|
||||
}
|
||||
if (role == SectionRole) {
|
||||
return m_rules.at(index.row()).section;
|
||||
}
|
||||
if (role == RoomIdRole) {
|
||||
return m_rules.at(index.row()).roomId;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int PushRuleModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return m_rules.count();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> PushRuleModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[NameRole] = "name";
|
||||
roles[IdRole] = "id";
|
||||
roles[KindRole] = "kind";
|
||||
roles[ActionRole] = "ruleAction";
|
||||
roles[HighlightableRole] = "highlightable";
|
||||
roles[DeletableRole] = "deletable";
|
||||
roles[SectionRole] = "section";
|
||||
roles[RoomIdRole] = "roomId";
|
||||
return roles;
|
||||
}
|
||||
|
||||
void PushRuleModel::setPushRuleAction(const QString &id, PushNotificationAction::Action action)
|
||||
{
|
||||
int index = getRuleIndex(id);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rule = m_rules[index];
|
||||
|
||||
// Override rules need to be disabled when off so that other rules can match the message if they apply.
|
||||
if (action == PushNotificationAction::Off && rule.kind == PushNotificationKind::Override) {
|
||||
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, false);
|
||||
} else if (rule.kind == PushNotificationKind::Override) {
|
||||
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, true);
|
||||
}
|
||||
|
||||
setNotificationRuleActions(PushNotificationKind::kindString(rule.kind), rule.id, action);
|
||||
}
|
||||
|
||||
void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||
{
|
||||
PushNotificationKind::Kind kind = PushNotificationKind::Content;
|
||||
const QVector<QVariant> actions = actionToVariant(m_defaultKeywordAction);
|
||||
QVector<Quotient::PushCondition> pushConditions;
|
||||
if (!roomId.isEmpty()) {
|
||||
kind = PushNotificationKind::Override;
|
||||
|
||||
Quotient::PushCondition roomCondition;
|
||||
roomCondition.kind = "event_match";
|
||||
roomCondition.key = "room_id";
|
||||
roomCondition.pattern = roomId;
|
||||
pushConditions.append(roomCondition);
|
||||
|
||||
Quotient::PushCondition keywordCondition;
|
||||
keywordCondition.kind = "event_match";
|
||||
keywordCondition.key = "content.body";
|
||||
keywordCondition.pattern = keyword;
|
||||
pushConditions.append(keywordCondition);
|
||||
}
|
||||
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleJob>("global",
|
||||
PushNotificationKind::kindString(kind),
|
||||
keyword,
|
||||
actions,
|
||||
QLatin1String(""),
|
||||
QLatin1String(""),
|
||||
pushConditions,
|
||||
roomId.isEmpty() ? keyword : QLatin1String(""));
|
||||
connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() {
|
||||
qWarning() << QLatin1String("Unable to set push rule for keyword %1: ").arg(keyword) << job->errorString();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The rule never being removed from the list by this function is intentional. When
|
||||
* the server is updated the new push rule account data will be synced and it will
|
||||
* be removed when the model is updated then.
|
||||
*/
|
||||
void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
{
|
||||
int index = getRuleIndex(keyword);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto kind = PushNotificationKind::kindString(m_rules[index].kind);
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", kind, m_rules[index].id);
|
||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||
});
|
||||
}
|
||||
|
||||
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::IsPushRuleEnabledJob>("global", kind, ruleId);
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled]() {
|
||||
if (job->enabled() != enabled) {
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleEnabledJob>("global", kind, ruleId, enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
|
||||
{
|
||||
QVector<QVariant> actions;
|
||||
if (ruleId == ".m.rule.call") {
|
||||
actions = actionToVariant(action, "ring");
|
||||
} else {
|
||||
actions = actionToVariant(action);
|
||||
}
|
||||
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>("global", kind, ruleId, actions);
|
||||
}
|
||||
|
||||
PushNotificationAction::Action PushRuleModel::variantToAction(const QVector<QVariant> &actions, bool enabled)
|
||||
{
|
||||
bool notify = false;
|
||||
bool isNoisy = false;
|
||||
bool highlightEnabled = false;
|
||||
for (const auto &i : actions) {
|
||||
auto actionString = i.toString();
|
||||
if (!actionString.isEmpty()) {
|
||||
if (actionString == QLatin1String("notify")) {
|
||||
notify = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject action = i.toJsonObject();
|
||||
if (action["set_tweak"].toString() == "sound") {
|
||||
isNoisy = true;
|
||||
} else if (action["set_tweak"].toString() == "highlight") {
|
||||
if (action["value"].toString() != "false") {
|
||||
highlightEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return PushNotificationAction::Off;
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
if (isNoisy && highlightEnabled) {
|
||||
return PushNotificationAction::NoisyHighlight;
|
||||
} else if (isNoisy) {
|
||||
return PushNotificationAction::Noisy;
|
||||
} else if (highlightEnabled) {
|
||||
return PushNotificationAction::Highlight;
|
||||
} else {
|
||||
return PushNotificationAction::On;
|
||||
}
|
||||
} else {
|
||||
return PushNotificationAction::Off;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action action, const QString &sound)
|
||||
{
|
||||
// The caller should never try to set the state to unknown.
|
||||
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
|
||||
if (action == PushNotificationAction::Unknown) {
|
||||
Q_ASSERT(false);
|
||||
return QVector<QVariant>();
|
||||
}
|
||||
|
||||
QVector<QVariant> actions;
|
||||
|
||||
if (action != PushNotificationAction::Off) {
|
||||
actions.append("notify");
|
||||
} else {
|
||||
actions.append("dont_notify");
|
||||
}
|
||||
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject soundTweak;
|
||||
soundTweak.insert("set_tweak", "sound");
|
||||
soundTweak.insert("value", sound);
|
||||
actions.append(soundTweak);
|
||||
}
|
||||
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject highlightTweak;
|
||||
highlightTweak.insert("set_tweak", "highlight");
|
||||
actions.append(highlightTweak);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
241
src/models/pushrulemodel.h
Normal file
241
src/models/pushrulemodel.h
Normal file
@@ -0,0 +1,241 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <csapi/definitions/push_rule.h>
|
||||
|
||||
#include "notificationsmanager.h"
|
||||
|
||||
/**
|
||||
* @class PushNotificationKind
|
||||
*
|
||||
* A class with the Kind enum for push notifications and helper functions.
|
||||
*
|
||||
* The kind relates to the kinds of push rule definied in the matrix spec, see
|
||||
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
|
||||
*/
|
||||
class PushNotificationKind : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the different kinds of push rule.
|
||||
*/
|
||||
enum Kind {
|
||||
Override = 0, /**< The highest priority rules. */
|
||||
Content, /**< These configure behaviour for messages that match certain patterns. */
|
||||
Room, /**< These rules change the behaviour of all messages for a given room. */
|
||||
Sender, /**< These rules configure notification behaviour for messages from a specific Matrix user ID. */
|
||||
Underride, /**< These are identical to override rules, but have a lower priority than content, room and sender rules. */
|
||||
};
|
||||
Q_ENUM(Kind);
|
||||
|
||||
/**
|
||||
* @brief Translate the Kind enum value to a human readable string.
|
||||
*
|
||||
* @sa Kind
|
||||
*/
|
||||
static QString kindString(Kind kind)
|
||||
{
|
||||
switch (kind) {
|
||||
case Kind::Override:
|
||||
return QLatin1String("override");
|
||||
case Kind::Content:
|
||||
return QLatin1String("content");
|
||||
case Kind::Room:
|
||||
return QLatin1String("room");
|
||||
case Kind::Sender:
|
||||
return QLatin1String("sender");
|
||||
case Kind::Underride:
|
||||
return QLatin1String("underride");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PushNotificationSection
|
||||
*
|
||||
* A class with the Section enum for push notifications and helper functions.
|
||||
*
|
||||
* @note This is different from the PushNotificationKind and instead is used for sorting
|
||||
* in the settings page which is not necessarily by Kind.
|
||||
*
|
||||
* @sa PushNotificationKind
|
||||
*/
|
||||
class PushNotificationSection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the sections to sort push rules into.
|
||||
*/
|
||||
enum Section {
|
||||
Master = 0, /**< The master push rule */
|
||||
Room, /**< Push rules relating to all rooms. */
|
||||
Mentions, /**< Push rules relating to user mentions. */
|
||||
Keywords, /**< Global Keyword push rules. */
|
||||
RoomKeywords, /**< Keyword push rules that only apply to a specific room. */
|
||||
Invites, /**< Push rules relating to invites. */
|
||||
/**
|
||||
* @brief Push rules that should never be shown.
|
||||
*
|
||||
* There are numerous rules that get set that shouldn't be shown in the general
|
||||
* list e.g. The array of rules used to override global settings in individual
|
||||
* rooms.
|
||||
*/
|
||||
Undefined,
|
||||
};
|
||||
Q_ENUM(Section);
|
||||
|
||||
/**
|
||||
* @brief Translate the Section enum value to a human readable string.
|
||||
*
|
||||
* @sa Section
|
||||
*/
|
||||
static QString sectionString(Section section)
|
||||
{
|
||||
switch (section) {
|
||||
case Section::Master:
|
||||
return QLatin1String("Master");
|
||||
case Section::Room:
|
||||
return QLatin1String("Room Notifications");
|
||||
case Section::Mentions:
|
||||
return QLatin1String("@Mentions");
|
||||
case Section::Keywords:
|
||||
return QLatin1String("Keywords");
|
||||
case Section::Invites:
|
||||
return QLatin1String("Invites");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PushRuleModel
|
||||
*
|
||||
* This class defines the model for managing notification push rule keywords.
|
||||
*/
|
||||
class PushRuleModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The default state for any newly created keyword rule.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged)
|
||||
|
||||
/**
|
||||
* @brief The global notification state.
|
||||
*
|
||||
* If this rule is set to off all push notifications are disabled regardless
|
||||
* of other settings.
|
||||
*/
|
||||
Q_PROPERTY(bool globalNotificationsEnabled READ globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the global notification state has been retrieved from the server.
|
||||
*
|
||||
* @sa globalNotificationsEnabled, PushNotificationAction::Action
|
||||
*/
|
||||
Q_PROPERTY(bool globalNotificationsSet READ globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
||||
|
||||
public:
|
||||
struct Rule {
|
||||
QString id;
|
||||
PushNotificationKind::Kind kind;
|
||||
PushNotificationAction::Action action;
|
||||
PushNotificationSection::Section section;
|
||||
bool enabled;
|
||||
QString roomId;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole, /**< The push rule name. */
|
||||
IdRole, /**< The push rule ID. */
|
||||
KindRole, /**< The kind of notification rule; override, content, etc. */
|
||||
ActionRole, /**< The PushNotificationAction for the rule. */
|
||||
HighlightableRole, /**< Whether the rule can have a highlight action. */
|
||||
DeletableRole, /**< Whether the rule can be deleted the rule. */
|
||||
SectionRole, /**< The section to sort into in the settings page. */
|
||||
RoomIdRole, /**< The room the rule applies to (blank if global). */
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
PushRuleModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] PushNotificationAction::Action defaultState() const;
|
||||
void setDefaultState(PushNotificationAction::Action defaultState);
|
||||
|
||||
[[nodiscard]] bool globalNotificationsEnabled() const;
|
||||
void setGlobalNotificationsEnabled(bool enabled);
|
||||
|
||||
[[nodiscard]] bool globalNotificationsSet() const;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void setPushRuleAction(const QString &id, PushNotificationAction::Action action);
|
||||
|
||||
/**
|
||||
* @brief Add a new keyword to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addKeyword(const QString &keyword, const QString &roomId = {});
|
||||
|
||||
/**
|
||||
* @brief Remove a keyword from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeKeyword(const QString &keyword);
|
||||
|
||||
Q_SIGNALS:
|
||||
void defaultStateChanged();
|
||||
void globalNotificationsEnabledChanged();
|
||||
void globalNotificationsSetChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void controllerConnectionChanged();
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
private:
|
||||
PushNotificationAction::Action m_defaultKeywordAction;
|
||||
QList<Rule> m_rules;
|
||||
|
||||
void setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
|
||||
|
||||
int getRuleIndex(const QString &ruleId) const;
|
||||
PushNotificationSection::Section getSection(Quotient::PushRule rule);
|
||||
|
||||
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
|
||||
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
|
||||
PushNotificationAction::Action variantToAction(const QVector<QVariant> &actions, bool enabled);
|
||||
QVector<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = "default");
|
||||
};
|
||||
Q_DECLARE_METATYPE(PushRuleModel *)
|
||||
@@ -62,7 +62,7 @@ Comment[ka]=კლიენტი Matrix-სთვის, დეცენტრ
|
||||
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
||||
Comment[lt]=Matrix decentralizuoto bendravimo protokolo kliento programa
|
||||
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
|
||||
Comment[nn]=Klient for Matrix, den desentraliserte lynmeldingsprotokollen.
|
||||
Comment[nn]=Ein klient for Matrix – protokollen for desentralisert kommunikasjon
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewania się
|
||||
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
|
||||
@@ -178,6 +178,7 @@ Name[it]=Nuovo invito
|
||||
Name[ka]=ახალი მოსაწვევი
|
||||
Name[ko]=새 초대장
|
||||
Name[nl]=Nieuwe uitnodiging
|
||||
Name[nn]=Ny invitasjon
|
||||
Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
||||
Name[pl]=Nowe zaproszenie
|
||||
Name[pt]=Novo Convite
|
||||
@@ -210,6 +211,7 @@ Comment[it]=È presente un nuovo invito a una stanza
|
||||
Comment[ka]=გაქვთ ახალი ოთახის მოსაწვევი
|
||||
Comment[ko]=새로운 대화방 초대장을 받음
|
||||
Comment[nl]=Er is een nieuwe uitnodiging naar een room
|
||||
Comment[nn]=Du har ein ny invitasjon til eit rom
|
||||
Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
||||
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
||||
Comment[pt]=Existe um novo convite para uma sala
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
<entry name="LastSaveDirectory" type="String">
|
||||
<label>Directory last used for saving a file</label>
|
||||
</entry>
|
||||
<entry name="KeywordPushRuleDefault" type="int">
|
||||
<label>The default setting for a new keyword push rule.</label>
|
||||
<default>4</default>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Timeline">
|
||||
<entry name="ShowAvatarInTimeline" type="bool">
|
||||
|
||||
@@ -696,6 +696,9 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
}
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"].toString());
|
||||
}
|
||||
if (e.matrixType() == "org.matrix.msc3672.beacon_info"_ls) {
|
||||
return e.contentJson()["description"_ls].toString();
|
||||
}
|
||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
||||
},
|
||||
|
||||
@@ -41,12 +41,6 @@ NotificationsManager &NotificationsManager::instance()
|
||||
NotificationsManager::NotificationsManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
// Can't connect the signal up until the active connection has been established by the controller
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
|
||||
connect(Controller::instance().activeConnection(), &Connection::accountDataChanged, this, &NotificationsManager::updateNotificationRules);
|
||||
// Ensure that the push rule states are retrieved after the connection is changed
|
||||
updateNotificationRules("m.push_rules");
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -300,309 +294,3 @@ void NotificationsManager::clearInvitationNotification(const QString &roomId)
|
||||
m_invitations[roomId]->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The master push rule sets all notifications to off when enabled
|
||||
* see https://spec.matrix.org/v1.3/client-server-api/#default-override-rules
|
||||
* therefore to enable push rules the master rule needs to be disabled and vice versa
|
||||
*/
|
||||
void NotificationsManager::setGlobalNotificationsEnabled(bool enabled)
|
||||
{
|
||||
setNotificationRuleEnabled("override", ".m.rule.master", !enabled);
|
||||
}
|
||||
|
||||
void NotificationsManager::setOneToOneNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
setNotificationRuleActions("underride", ".m.rule.room_one_to_one", action);
|
||||
}
|
||||
|
||||
void NotificationsManager::setEncryptedOneToOneNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
setNotificationRuleActions("underride", ".m.rule.encrypted_room_one_to_one", action);
|
||||
}
|
||||
|
||||
void NotificationsManager::setGroupChatNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
setNotificationRuleActions("underride", ".m.rule.message", action);
|
||||
}
|
||||
|
||||
void NotificationsManager::setEncryptedGroupChatNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
setNotificationRuleActions("underride", ".m.rule.encrypted", action);
|
||||
}
|
||||
|
||||
/*
|
||||
* .m.rule.contains_display_name is an override rule so it needs to be disabled when off
|
||||
* so that other rules can match the message if they apply.
|
||||
*/
|
||||
void NotificationsManager::setDisplayNameNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
if (action == PushNotificationAction::Off) {
|
||||
setNotificationRuleEnabled("override", ".m.rule.contains_display_name", false);
|
||||
} else {
|
||||
setNotificationRuleActions("override", ".m.rule.contains_display_name", action);
|
||||
setNotificationRuleEnabled("override", ".m.rule.contains_display_name", true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* .m.rule.roomnotif is an override rule so it needs to be disabled when off
|
||||
* so that other rules can match the message if they apply.
|
||||
*/
|
||||
void NotificationsManager::setRoomNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
if (action == PushNotificationAction::Off) {
|
||||
setNotificationRuleEnabled("override", ".m.rule.roomnotif", false);
|
||||
} else {
|
||||
setNotificationRuleActions("override", ".m.rule.roomnotif", action);
|
||||
setNotificationRuleEnabled("override", ".m.rule.roomnotif", true);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::initializeKeywordNotificationAction()
|
||||
{
|
||||
m_keywordNotificationAction = PushNotificationAction::Highlight;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
}
|
||||
|
||||
void NotificationsManager::deactivateKeywordNotificationAction()
|
||||
{
|
||||
m_keywordNotificationAction = PushNotificationAction::Off;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
}
|
||||
|
||||
QVector<QVariant> NotificationsManager::getKeywordNotificationActions()
|
||||
{
|
||||
return toActions(m_keywordNotificationAction);
|
||||
}
|
||||
|
||||
void NotificationsManager::setKeywordNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
// Unlike the other rules this needs to be set here for the case where there are no keyords.
|
||||
m_keywordNotificationAction = action;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
|
||||
const QJsonObject accountData = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const QJsonArray contentRuleArray = accountData["global"].toObject()["content"].toArray();
|
||||
for (const auto &i : contentRuleArray) {
|
||||
const QJsonObject contentRule = i.toObject();
|
||||
if (contentRule["rule_id"].toString()[0] != '.') {
|
||||
setNotificationRuleActions("content", contentRule["rule_id"].toString(), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* .m.rule.invite_for_me is an override rule so it needs to be disabled when off
|
||||
* so that other rules can match the message if they apply.
|
||||
*/
|
||||
void NotificationsManager::setInviteNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
if (action == PushNotificationAction::Off) {
|
||||
setNotificationRuleEnabled("override", ".m.rule.invite_for_me", false);
|
||||
} else {
|
||||
setNotificationRuleActions("override", ".m.rule.invite_for_me", action);
|
||||
setNotificationRuleEnabled("override", ".m.rule.invite_for_me", true);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::setCallInviteNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
setNotificationRuleActions("underride", ".m.rule.call", action);
|
||||
}
|
||||
|
||||
/*
|
||||
* .m.rule.tombstone is an override rule so it needs to be disabled when off
|
||||
* so that other rules can match the message if they apply.
|
||||
*/
|
||||
void NotificationsManager::setTombstoneNotificationAction(PushNotificationAction::Action action)
|
||||
{
|
||||
if (action == PushNotificationAction::Off) {
|
||||
setNotificationRuleEnabled("override", ".m.rule.tombstone", false);
|
||||
} else {
|
||||
setNotificationRuleActions("override", ".m.rule.tombstone", action);
|
||||
setNotificationRuleEnabled("override", ".m.rule.tombstone", true);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject accountData = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
|
||||
// Update override rules
|
||||
const QJsonArray overrideRuleArray = accountData["global"].toObject()["override"].toArray();
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
const QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"] == ".m.rule.master") {
|
||||
bool ruleEnabled = overrideRule["enabled"].toBool();
|
||||
m_globalNotificationsEnabled = !ruleEnabled;
|
||||
if (!m_globalNotificationsSet) {
|
||||
m_globalNotificationsSet = true;
|
||||
}
|
||||
Q_EMIT globalNotificationsEnabledChanged(m_globalNotificationsEnabled);
|
||||
}
|
||||
|
||||
const PushNotificationAction::Action action = toAction(overrideRule);
|
||||
|
||||
if (overrideRule["rule_id"] == ".m.rule.contains_display_name") {
|
||||
m_displayNameNotificationAction = action;
|
||||
Q_EMIT displayNameNotificationActionChanged(m_displayNameNotificationAction);
|
||||
} else if (overrideRule["rule_id"] == ".m.rule.roomnotif") {
|
||||
m_roomNotificationAction = action;
|
||||
Q_EMIT roomNotificationActionChanged(m_roomNotificationAction);
|
||||
} else if (overrideRule["rule_id"] == ".m.rule.invite_for_me") {
|
||||
m_inviteNotificationAction = action;
|
||||
Q_EMIT inviteNotificationActionChanged(m_inviteNotificationAction);
|
||||
} else if (overrideRule["rule_id"] == ".m.rule.tombstone") {
|
||||
m_tombstoneNotificationAction = action;
|
||||
Q_EMIT tombstoneNotificationActionChanged(m_tombstoneNotificationAction);
|
||||
}
|
||||
}
|
||||
|
||||
// Update content rules
|
||||
const QJsonArray contentRuleArray = accountData["global"].toObject()["content"].toArray();
|
||||
PushNotificationAction::Action keywordAction = PushNotificationAction::Unknown;
|
||||
for (const auto &i : contentRuleArray) {
|
||||
const QJsonObject contentRule = i.toObject();
|
||||
const PushNotificationAction::Action action = toAction(contentRule);
|
||||
bool actionMismatch = false;
|
||||
|
||||
if (contentRule["rule_id"].toString()[0] != '.' && !actionMismatch) {
|
||||
if (keywordAction == PushNotificationAction::Unknown) {
|
||||
keywordAction = action;
|
||||
m_keywordNotificationAction = action;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
} else if (action != keywordAction) {
|
||||
actionMismatch = true;
|
||||
m_keywordNotificationAction = PushNotificationAction::On;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are no keywords set the state to off, this is the only time it'll be in the off state
|
||||
if (keywordAction == PushNotificationAction::Unknown) {
|
||||
m_keywordNotificationAction = PushNotificationAction::Off;
|
||||
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
|
||||
}
|
||||
|
||||
// Update underride rules
|
||||
const QJsonArray underrideRuleArray = accountData["global"].toObject()["underride"].toArray();
|
||||
for (const auto &i : underrideRuleArray) {
|
||||
const QJsonObject underrideRule = i.toObject();
|
||||
const PushNotificationAction::Action action = toAction(underrideRule);
|
||||
|
||||
if (underrideRule["rule_id"] == ".m.rule.room_one_to_one") {
|
||||
m_oneToOneNotificationAction = action;
|
||||
Q_EMIT oneToOneNotificationActionChanged(m_oneToOneNotificationAction);
|
||||
} else if (underrideRule["rule_id"] == ".m.rule.encrypted_room_one_to_one") {
|
||||
m_encryptedOneToOneNotificationAction = action;
|
||||
Q_EMIT encryptedOneToOneNotificationActionChanged(m_encryptedOneToOneNotificationAction);
|
||||
} else if (underrideRule["rule_id"] == ".m.rule.message") {
|
||||
m_groupChatNotificationAction = action;
|
||||
Q_EMIT groupChatNotificationActionChanged(m_groupChatNotificationAction);
|
||||
} else if (underrideRule["rule_id"] == ".m.rule.encrypted") {
|
||||
m_encryptedGroupChatNotificationAction = action;
|
||||
Q_EMIT encryptedGroupChatNotificationActionChanged(m_encryptedGroupChatNotificationAction);
|
||||
} else if (underrideRule["rule_id"] == ".m.rule.call") {
|
||||
m_callInviteNotificationAction = action;
|
||||
Q_EMIT callInviteNotificationActionChanged(m_callInviteNotificationAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<IsPushRuleEnabledJob>("global", kind, ruleId);
|
||||
connect(job, &BaseJob::success, this, [job, kind, ruleId, enabled]() {
|
||||
if (job->enabled() != enabled) {
|
||||
Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global", kind, ruleId, enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NotificationsManager::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
|
||||
{
|
||||
QVector<QVariant> actions;
|
||||
if (ruleId == ".m.rule.call") {
|
||||
actions = toActions(action, "ring");
|
||||
} else {
|
||||
actions = toActions(action);
|
||||
}
|
||||
|
||||
Controller::instance().activeConnection()->callApi<SetPushRuleActionsJob>("global", kind, ruleId, actions);
|
||||
}
|
||||
|
||||
PushNotificationAction::Action NotificationsManager::toAction(const QJsonObject &rule)
|
||||
{
|
||||
const QJsonArray actions = rule["actions"].toArray();
|
||||
bool isNoisy = false;
|
||||
bool highlightEnabled = false;
|
||||
const bool enabled = rule["enabled"].toBool();
|
||||
for (const auto &i : actions) {
|
||||
QJsonObject action = i.toObject();
|
||||
if (action["set_tweak"].toString() == "sound") {
|
||||
isNoisy = true;
|
||||
} else if (action["set_tweak"].toString() == "highlight") {
|
||||
if (action["value"].toString() != "false") {
|
||||
highlightEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return PushNotificationAction::Off;
|
||||
}
|
||||
|
||||
if (actions[0] == "notify") {
|
||||
if (isNoisy && highlightEnabled) {
|
||||
return PushNotificationAction::NoisyHighlight;
|
||||
} else if (isNoisy) {
|
||||
return PushNotificationAction::Noisy;
|
||||
} else if (highlightEnabled) {
|
||||
return PushNotificationAction::Highlight;
|
||||
} else {
|
||||
return PushNotificationAction::On;
|
||||
}
|
||||
} else {
|
||||
return PushNotificationAction::Off;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<QVariant> NotificationsManager::toActions(PushNotificationAction::Action action, const QString &sound)
|
||||
{
|
||||
// The caller should never try to set the state to unknown.
|
||||
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
|
||||
if (action == PushNotificationAction::Unknown) {
|
||||
Q_ASSERT(false);
|
||||
return QVector<QVariant>();
|
||||
}
|
||||
|
||||
QVector<QVariant> actions;
|
||||
|
||||
if (action != PushNotificationAction::Off) {
|
||||
actions.append("notify");
|
||||
} else {
|
||||
actions.append("dont_notify");
|
||||
}
|
||||
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject soundTweak;
|
||||
soundTweak.insert("set_tweak", "sound");
|
||||
soundTweak.insert("value", sound);
|
||||
actions.append(soundTweak);
|
||||
}
|
||||
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject highlightTweak;
|
||||
highlightTweak.insert("set_tweak", "highlight");
|
||||
actions.append(highlightTweak);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -56,93 +56,6 @@ class NotificationsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The global notification state.
|
||||
*
|
||||
* If this rule is set to off all push notifications are disabled regardless
|
||||
* of other settings.
|
||||
*/
|
||||
Q_PROPERTY(bool globalNotificationsEnabled MEMBER m_globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the global notification state has been retrieved from the server.
|
||||
*
|
||||
* This is different to the others below as globalNotificationsEnabled is only
|
||||
* a bool rather than a PushNotificationAction::Action so a separate property
|
||||
* is required to track if the state has been retrieved from the server.
|
||||
*
|
||||
* @sa globalNotificationsEnabled, PushNotificationAction::Action
|
||||
*/
|
||||
Q_PROPERTY(bool globalNotificationsSet MEMBER m_globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for direct chats.
|
||||
*
|
||||
* @note Encrypted direct chats have a separate setting.
|
||||
*
|
||||
* @sa encryptedOneToOneNotificationAction
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action oneToOneNotificationAction MEMBER m_oneToOneNotificationAction WRITE setOneToOneNotificationAction NOTIFY
|
||||
oneToOneNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for encrypted direct chats.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action encryptedOneToOneNotificationAction MEMBER m_encryptedOneToOneNotificationAction WRITE
|
||||
setEncryptedOneToOneNotificationAction NOTIFY encryptedOneToOneNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for group chats.
|
||||
*
|
||||
* @note Encrypted group chats have a separate setting.
|
||||
*
|
||||
* @sa encryptedGroupChatNotificationAction
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action groupChatNotificationAction MEMBER m_groupChatNotificationAction WRITE setGroupChatNotificationAction NOTIFY
|
||||
groupChatNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for encrypted group chats.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action encryptedGroupChatNotificationAction MEMBER m_encryptedGroupChatNotificationAction WRITE
|
||||
setEncryptedGroupChatNotificationAction NOTIFY encryptedGroupChatNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for events containing the local user's display name.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action displayNameNotificationAction MEMBER m_displayNameNotificationAction WRITE setDisplayNameNotificationAction NOTIFY
|
||||
displayNameNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for room events.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action roomNotificationAction MEMBER m_roomNotificationAction WRITE setRoomNotificationAction NOTIFY
|
||||
roomNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for keyword push rules.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action keywordNotificationAction MEMBER m_keywordNotificationAction WRITE setKeywordNotificationAction NOTIFY
|
||||
keywordNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for invites to chats.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action inviteNotificationAction MEMBER m_inviteNotificationAction WRITE setInviteNotificationAction NOTIFY
|
||||
inviteNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for voice calls.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action callInviteNotificationAction MEMBER m_callInviteNotificationAction WRITE setCallInviteNotificationAction NOTIFY
|
||||
callInviteNotificationActionChanged)
|
||||
|
||||
/**
|
||||
* @brief The notification action for room upgrade events.
|
||||
*/
|
||||
Q_PROPERTY(PushNotificationAction::Action tombstoneNotificationAction MEMBER m_tombstoneNotificationAction WRITE setTombstoneNotificationAction NOTIFY
|
||||
tombstoneNotificationActionChanged)
|
||||
|
||||
public:
|
||||
static NotificationsManager &instance();
|
||||
|
||||
@@ -164,30 +77,6 @@ public:
|
||||
*/
|
||||
void clearInvitationNotification(const QString &roomId);
|
||||
|
||||
/**
|
||||
* @brief Set the initial keyword notification action.
|
||||
*
|
||||
* This is only required if no keyword rules exist and one is created. The default
|
||||
* action is PushNotificationAction::Action::Highlight.
|
||||
*
|
||||
* @sa PushNotificationAction::Action
|
||||
*/
|
||||
void initializeKeywordNotificationAction();
|
||||
|
||||
/**
|
||||
* @brief Set the keyword notification action to PushNotificationAction::Action::Off.
|
||||
*
|
||||
* This is only required the last keyword rule is deleted.
|
||||
*
|
||||
* @sa PushNotificationAction::Action
|
||||
*/
|
||||
void deactivateKeywordNotificationAction();
|
||||
|
||||
/**
|
||||
* @brief Return the keyword notification action in a form required for the server push rule.
|
||||
*/
|
||||
QVector<QVariant> getKeywordNotificationActions();
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
/**
|
||||
* @brief Handle the notifications for the given connection.
|
||||
@@ -208,52 +97,6 @@ private:
|
||||
QHash<QString, KNotification *> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
|
||||
bool m_globalNotificationsEnabled = false;
|
||||
bool m_globalNotificationsSet = false;
|
||||
PushNotificationAction::Action m_oneToOneNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_encryptedOneToOneNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_groupChatNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_encryptedGroupChatNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_displayNameNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_roomNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_keywordNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_inviteNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_callInviteNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_tombstoneNotificationAction = PushNotificationAction::Unknown;
|
||||
|
||||
void setGlobalNotificationsEnabled(bool enabled);
|
||||
void setOneToOneNotificationAction(PushNotificationAction::Action action);
|
||||
void setEncryptedOneToOneNotificationAction(PushNotificationAction::Action action);
|
||||
void setGroupChatNotificationAction(PushNotificationAction::Action action);
|
||||
void setEncryptedGroupChatNotificationAction(PushNotificationAction::Action action);
|
||||
void setDisplayNameNotificationAction(PushNotificationAction::Action action);
|
||||
void setRoomNotificationAction(PushNotificationAction::Action action);
|
||||
void setKeywordNotificationAction(PushNotificationAction::Action action);
|
||||
void setInviteNotificationAction(PushNotificationAction::Action action);
|
||||
void setCallInviteNotificationAction(PushNotificationAction::Action action);
|
||||
void setTombstoneNotificationAction(PushNotificationAction::Action action);
|
||||
|
||||
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
|
||||
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
|
||||
PushNotificationAction::Action toAction(const QJsonObject &rule);
|
||||
QVector<QVariant> toActions(PushNotificationAction::Action action, const QString &sound = "default");
|
||||
|
||||
private Q_SLOTS:
|
||||
void processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
Q_SIGNALS:
|
||||
void globalNotificationsEnabledChanged(bool newState);
|
||||
void globalNotificationsSetChanged(bool newState);
|
||||
void oneToOneNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void encryptedOneToOneNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void groupChatNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void encryptedGroupChatNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void displayNameNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void roomNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void keywordNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void inviteNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void callInviteNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void tombstoneNotificationActionChanged(PushNotificationAction::Action action);
|
||||
};
|
||||
|
||||
@@ -59,6 +59,7 @@ Comment[it]=Trova stanze in NeoChat
|
||||
Comment[ka]=იპოვე ოთახები NeoChat-ში
|
||||
Comment[ko]=NeoChat에서 대화방 찾기
|
||||
Comment[nl]=Rooms zoeken in NeoChat
|
||||
Comment[nn]=Finn rom i NeoChat
|
||||
Comment[pl]=Znajdź pokoje w NeoChat
|
||||
Comment[pt]=Procurar salas no NeoChat
|
||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||
|
||||
@@ -75,7 +75,7 @@ ColumnLayout {
|
||||
visible: currentRoom.canSendEvent("m.room.message")
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: implicitHeight + Kirigami.Units.largeSpacing
|
||||
Layout.minimumHeight: Math.max(Kirigami.Units.gridUnit * 2, implicitHeight + Kirigami.Units.largeSpacing)
|
||||
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
|
||||
Layout.maximumHeight: chatBarFontMetrics.lineSpacing * 8 - chatBarFontMetrics.leading + textField.topPadding + textField.bottomPadding
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
|
||||
import org.kde.kirigami 2.13 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.components 1.0 as Components
|
||||
@@ -14,39 +14,46 @@ import org.kde.neochat 1.0
|
||||
Components.AlbumMaximizeComponent {
|
||||
id: root
|
||||
|
||||
required property string eventId
|
||||
readonly property string currentEventId: model.data(model.index(content.currentIndex, 0), MessageEventModel.EventIdRole)
|
||||
|
||||
required property var time
|
||||
readonly property var currentAuthor: model.data(model.index(content.currentIndex, 0), MessageEventModel.AuthorRole)
|
||||
|
||||
required property var author
|
||||
readonly property var currentTime: model.data(model.index(content.currentIndex, 0), MessageEventModel.TimeRole)
|
||||
|
||||
required property int delegateType
|
||||
readonly property string currentPlainText: model.data(model.index(content.currentIndex, 0), MessageEventModel.PlainText)
|
||||
|
||||
required property string plainText
|
||||
readonly property var currentMimeType: model.data(model.index(content.currentIndex, 0), MessageEventModel.MimeTypeRole)
|
||||
|
||||
required property string caption
|
||||
readonly property var currentProgressInfo: model.data(model.index(content.currentIndex, 0), MessageEventModel.ProgressInfoRole)
|
||||
|
||||
required property var mediaInfo
|
||||
readonly property var currentJsonSource: model.data(model.index(content.currentIndex, 0), MessageEventModel.SourceRole)
|
||||
|
||||
required property var progressInfo
|
||||
autoLoad: false
|
||||
|
||||
required property var mimeType
|
||||
|
||||
required property var source
|
||||
|
||||
property list<Components.AlbumModelItem> items: [
|
||||
Components.AlbumModelItem {
|
||||
type: root.delegateType === MessageEventModel.Image || root.delegateType === MessageEventModel.Sticker ? Components.AlbumModelItem.Image : Components.AlbumModelItem.Video
|
||||
source: root.delegateType === MessageEventModel.Video ? root.progressInfo.localPath : root.mediaInfo.source
|
||||
tempSource: root.mediaInfo.tempInfo.source
|
||||
caption: root.caption
|
||||
sourceWidth: root.mediaInfo.width
|
||||
sourceHeight: root.mediaInfo.height
|
||||
downloadAction: Components.DownloadAction {
|
||||
id: downloadAction
|
||||
onTriggered: {
|
||||
currentRoom.downloadFile(root.currentEventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId))
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
model: items
|
||||
initialIndex: 0
|
||||
Connections {
|
||||
target: currentRoom
|
||||
|
||||
function onFileTransferProgress(id, progress, total) {
|
||||
if (id == root.currentEventId) {
|
||||
downloadAction.progress = progress / total * 100.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: content
|
||||
|
||||
function onCurrentIndexChanged() {
|
||||
downloadAction.progress = currentProgressInfo.progress / currentProgressInfo.total * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
leading: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
@@ -54,22 +61,23 @@ Components.AlbumMaximizeComponent {
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: root.author.displayName
|
||||
source: root.author.avatarSource
|
||||
color: root.author.color
|
||||
name: root.currentAuthor.name ?? root.currentAuthor.displayName
|
||||
source: root.currentAuthor.avatarSource
|
||||
color: root.currentAuthor.color
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
QQC2.Label {
|
||||
id: userLabel
|
||||
text: root.author.displayName
|
||||
color: root.author.color
|
||||
|
||||
text: root.currentAuthor.name ?? root.currentAuthor.displayName
|
||||
color: root.currentAuthor.color
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QQC2.Label {
|
||||
id: dateTimeLabel
|
||||
text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||
text: root.currentTime.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
@@ -77,13 +85,13 @@ Components.AlbumMaximizeComponent {
|
||||
}
|
||||
onItemRightClicked: {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(parent, {
|
||||
author: root.author,
|
||||
eventId: root.eventId,
|
||||
source: root.source,
|
||||
author: root.currentAuthor,
|
||||
eventId: root.currentEventId,
|
||||
source: root.currentJsonSource,
|
||||
file: parent,
|
||||
mimeType: root.mimeType,
|
||||
progressInfo: root.progressInfo,
|
||||
plainText: root.plainText,
|
||||
mimeType: root.currentMimeType,
|
||||
progressInfo: root.currentProgressInfo,
|
||||
plainMessage: root.currentPlainText
|
||||
});
|
||||
contextMenu.closeFullscreen.connect(root.close)
|
||||
contextMenu.open();
|
||||
@@ -91,12 +99,12 @@ Components.AlbumMaximizeComponent {
|
||||
onSaveItem: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.eventId)
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId)
|
||||
}
|
||||
|
||||
Component {
|
||||
id: saveAsDialog
|
||||
FileDialog {
|
||||
Platform.FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: root.saveFolder
|
||||
onAccepted: {
|
||||
|
||||
@@ -147,32 +147,10 @@ TimelineContainer {
|
||||
img.QQC2.ToolTip.hide()
|
||||
img.paused = true
|
||||
root.ListView.view.interactive = false
|
||||
var popup = maximizeImageComponent.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
eventId: root.eventId,
|
||||
time: root.time,
|
||||
author: root.author,
|
||||
delegateType: root.delegateType,
|
||||
plainText: root.plainText,
|
||||
caption: root.display,
|
||||
mediaInfo: root.mediaInfo,
|
||||
progressInfo: root.progressInfo,
|
||||
mimeType: root.mimeType,
|
||||
source: root.source
|
||||
})
|
||||
popup.closed.connect(() => {
|
||||
root.ListView.view.interactive = true
|
||||
img.paused = false
|
||||
popup.destroy()
|
||||
})
|
||||
popup.open()
|
||||
root.ListView.view.showMaximizedMedia(root.index)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: maximizeImageComponent
|
||||
NeochatMaximizeComponent {}
|
||||
}
|
||||
|
||||
function downloadAndOpen() {
|
||||
if (downloaded) {
|
||||
openSavedFile()
|
||||
|
||||
@@ -62,5 +62,10 @@ TimelineContainer {
|
||||
id: fullScreenMap
|
||||
FullScreenMap {}
|
||||
}
|
||||
|
||||
RichLabel {
|
||||
textMessage: root.display
|
||||
visible: root.display !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,5 +76,10 @@ TimelineContainer {
|
||||
id: fullScreenMap
|
||||
FullScreenMap { }
|
||||
}
|
||||
|
||||
RichLabel {
|
||||
textMessage: root.display
|
||||
visible: root.display !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ ColumnLayout {
|
||||
/**
|
||||
* @brief The full message source JSON.
|
||||
*/
|
||||
required property var source
|
||||
required property var jsonSource
|
||||
|
||||
/**
|
||||
* @brief The x position of the message bubble.
|
||||
@@ -590,7 +590,7 @@ ColumnLayout {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
author: root.author,
|
||||
eventId: root.eventId,
|
||||
source: root.source,
|
||||
source: root.jsonSource,
|
||||
file: file,
|
||||
mimeType: root.mimeType,
|
||||
progressInfo: root.progressInfo,
|
||||
@@ -605,7 +605,7 @@ ColumnLayout {
|
||||
selectedText: selectedText,
|
||||
author: root.author,
|
||||
eventId: root.eventId,
|
||||
source: root.source,
|
||||
source: root.jsonSource,
|
||||
eventType: root.delegateType,
|
||||
plainText: root.plainText,
|
||||
});
|
||||
|
||||
@@ -355,30 +355,10 @@ TimelineContainer {
|
||||
onTriggered: {
|
||||
root.ListView.view.interactive = false
|
||||
vid.pause()
|
||||
var popup = maximizeVideoComponent.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
eventId: root.eventId,
|
||||
time: root.time,
|
||||
author: root.author,
|
||||
delegateType: root.delegateType,
|
||||
plainText: root.plainText,
|
||||
caption: root.display,
|
||||
mediaInfo: root.mediaInfo,
|
||||
progressInfo: root.progressInfo,
|
||||
mimeType: root.mimeType,
|
||||
source: root.source
|
||||
})
|
||||
popup.closed.connect(() => {
|
||||
root.ListView.view.interactive = true
|
||||
popup.destroy()
|
||||
})
|
||||
popup.open()
|
||||
root.ListView.view.showMaximizedMedia(root.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: maximizeVideoComponent
|
||||
NeochatMaximizeComponent {}
|
||||
}
|
||||
}
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
radius: 4
|
||||
|
||||
@@ -40,14 +40,16 @@ QQC2.ScrollView {
|
||||
|
||||
model: !isLoaded ? undefined : collapseStateProxyModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
room: root.currentRoom
|
||||
}
|
||||
|
||||
CollapseStateProxyModel {
|
||||
id: collapseStateProxyModel
|
||||
sourceModel: MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
sourceModel: MessageEventModel {
|
||||
id: messageEventModel
|
||||
room: root.currentRoom
|
||||
}
|
||||
sourceModel: messageEventModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +397,28 @@ QQC2.ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
MediaMessageFilterModel {
|
||||
id: mediaMessageFilterModel
|
||||
sourceModel: collapseStateProxyModel
|
||||
}
|
||||
|
||||
Component {
|
||||
id: maximizeComponent
|
||||
NeochatMaximizeComponent {
|
||||
model: mediaMessageFilterModel
|
||||
}
|
||||
}
|
||||
|
||||
function showMaximizedMedia(index) {
|
||||
var popup = maximizeComponent.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
initialIndex: index === -1 ? -1 : mediaMessageFilterModel.getRowForSourceItem(index)
|
||||
})
|
||||
popup.closed.connect(() => {
|
||||
messageListView.interactive = true
|
||||
popup.destroy()
|
||||
})
|
||||
popup.open()
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
|
||||
@@ -13,13 +13,10 @@ QQC2.ToolBar {
|
||||
|
||||
padding: 0
|
||||
|
||||
height: content.height
|
||||
|
||||
property var addAccount
|
||||
|
||||
ColumnLayout {
|
||||
contentItem: ColumnLayout {
|
||||
id: content
|
||||
width: parent.width
|
||||
|
||||
spacing: 0
|
||||
|
||||
@@ -53,7 +50,7 @@ QQC2.ToolBar {
|
||||
}
|
||||
}
|
||||
Component.onCompleted: userInfo.addAccount = this
|
||||
icon: "list-add"
|
||||
icon.name: "list-add"
|
||||
text: i18n("Add Account")
|
||||
subtitle: i18n("Log in to an existing account")
|
||||
onClicked: {
|
||||
@@ -107,7 +104,12 @@ QQC2.ToolBar {
|
||||
delegate: Kirigami.BasicListItem {
|
||||
leftPadding: topPadding
|
||||
leading: Kirigami.Avatar {
|
||||
width: height
|
||||
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
sourceSize {
|
||||
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
}
|
||||
source: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : ""
|
||||
name: model.connection.localUser.displayName ?? model.connection.localUser.id
|
||||
}
|
||||
@@ -131,32 +133,37 @@ QQC2.ToolBar {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
Kirigami.Avatar {
|
||||
readonly property string mediaId: Controller.activeConnection.localUser.avatarMediaId
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.smallSpacing
|
||||
source: mediaId ? ("image://mxc/" + mediaId) : ""
|
||||
name: Controller.activeConnection.localUser.displayName ?? Controller.activeConnection.localUser.id
|
||||
actions.main: Kirigami.Action {
|
||||
text: i18n("Edit this account")
|
||||
icon.name: "document-edit"
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl('qrc:/AccountEditorPage.qml'), {
|
||||
connection: Controller.activeConnection
|
||||
}, {
|
||||
title: i18n("Account editor")
|
||||
});
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
onTapped: accountMenu.open()
|
||||
}
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2 - 2 // HACK: -2 here is to ensure the ChatBox and the UserInfo have the same height
|
||||
|
||||
Kirigami.Avatar {
|
||||
readonly property string mediaId: Controller.activeConnection.localUser.avatarMediaId
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
source: mediaId ? ("image://mxc/" + mediaId) : ""
|
||||
name: Controller.activeConnection.localUser.displayName ?? Controller.activeConnection.localUser.id
|
||||
actions.main: Kirigami.Action {
|
||||
text: i18n("Edit this account")
|
||||
icon.name: "document-edit"
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.resolvedUrl('qrc:/AccountEditorPage.qml'), {
|
||||
connection: Controller.activeConnection
|
||||
}, {
|
||||
title: i18n("Account editor")
|
||||
});
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
onTapped: accountMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
@@ -212,9 +219,10 @@ QQC2.ToolBar {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
Layout.minimumWidth: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignRight
|
||||
QQC2.ToolTip {
|
||||
text: parent.text
|
||||
}
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
QQC2.ToolTip.text: parent.text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
Item {
|
||||
width: 1
|
||||
|
||||
@@ -48,7 +48,7 @@ ColumnLayout {
|
||||
room: room,
|
||||
user: room.getUser(room.directChatRemoteUser.id),
|
||||
})
|
||||
popup.closed.connect(function() {
|
||||
popup.closed.connect(() => {
|
||||
userListItem.highlighted = false
|
||||
})
|
||||
if (roomDrawer.modal) {
|
||||
@@ -61,7 +61,6 @@ ColumnLayout {
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 1
|
||||
type: Kirigami.Heading.Type.Primary
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
text: room.displayName
|
||||
|
||||
@@ -11,7 +11,9 @@ import org.kde.neochat 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
@@ -30,8 +32,11 @@ ColumnLayout {
|
||||
|
||||
width: Kirigami.Units.gridUnit
|
||||
height: Kirigami.Units.gridUnit
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
radius: Math.round(width / 2)
|
||||
|
||||
@@ -49,12 +54,12 @@ ColumnLayout {
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 1
|
||||
type: Kirigami.Heading.Type.Primary
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
Layout.fillWidth: true
|
||||
textFormat: TextEdit.PlainText
|
||||
@@ -63,36 +68,15 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Kirigami.SelectableLabel {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumHeight: Math.min(topicText.contentHeight, Kirigami.Units.gridUnit * 15)
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
QQC2.TextArea {
|
||||
id: topicText
|
||||
padding: 0
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.Wrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
onLinkActivated: UrlHelper.openUrl(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
background: Item {}
|
||||
}
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.Wrap
|
||||
onLinkActivated: UrlHelper.openUrl(link)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,26 +71,24 @@ Kirigami.OverlayDrawer {
|
||||
contentItem: Loader {
|
||||
id: loader
|
||||
active: roomDrawer.drawerOpen
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
id: columnLayout
|
||||
property alias userSearchText: userListSearchField.text
|
||||
readonly property string userSearchText: userListView.headerItem.userListSearchField.text
|
||||
property alias highlightedUser: userListView.currentIndex
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.AbstractApplicationHeader {
|
||||
spacing: 0
|
||||
|
||||
QQC2.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
Layout.preferredHeight: pageStack.globalToolBar.preferredHeight
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Room information")
|
||||
level: 1
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: settingsButton
|
||||
|
||||
@@ -99,124 +97,14 @@ Kirigami.OverlayDrawer {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||
QQC2.ToolTip {
|
||||
text: settingsButton.text
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: true
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Options")
|
||||
activeFocusOnTab: false
|
||||
}
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
id: devtoolsButton
|
||||
|
||||
icon: "tools"
|
||||
text: i18n("Open developer tools")
|
||||
visible: Config.developerTools
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/DevtoolsPage.qml", {room: room}, {title: i18n("Developer Tools")})
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
id: searchButton
|
||||
|
||||
icon: "search"
|
||||
text: i18n("Search in this room")
|
||||
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
|
||||
currentRoom: room
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
})
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
id: locationsButton
|
||||
|
||||
icon: "map-flat"
|
||||
text: i18n("Show locations for this room")
|
||||
|
||||
onClicked: pageStack.pushDialogLayer("qrc:/LocationsPage.qml", {
|
||||
room: room
|
||||
}, {
|
||||
title: i18nc("Locations on a map", "Locations")
|
||||
})
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
id: favouriteButton
|
||||
|
||||
icon: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
spacing: 0
|
||||
visible: !room.isDirectChat()
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: memberSearchToggle
|
||||
checkable: true
|
||||
icon.name: "search"
|
||||
QQC2.ToolTip.text: i18n("Search user in room")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onToggled: {
|
||||
userListSearchField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
visible: roomDrawer.room.canSendState("invite")
|
||||
icon.name: "list-add-user"
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/InviteUserPage.qml", {room: roomDrawer.room})
|
||||
roomDrawer.close();
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: i18n("Invite user to room")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 member", "%1 members", room.joinedCount) : i18n("No member count")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
visible: memberSearchToggle.checked
|
||||
|
||||
onVisibleChanged: if (visible) forceActiveFocus()
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
focusSequence: "Ctrl+Shift+F"
|
||||
|
||||
onAccepted: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
@@ -226,11 +114,130 @@ Kirigami.OverlayDrawer {
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
activeFocusOnTab: true
|
||||
visible: !room.isDirectChat()
|
||||
|
||||
model: KSortFilterProxyModel {
|
||||
header: ColumnLayout {
|
||||
id: columnLayout
|
||||
|
||||
property alias userListSearchField: userListSearchField
|
||||
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
width: userListView.width
|
||||
|
||||
Loader {
|
||||
active: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
sourceComponent: room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Options")
|
||||
activeFocusOnTab: false
|
||||
}
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
id: devtoolsButton
|
||||
|
||||
icon.name: "tools"
|
||||
text: i18n("Open developer tools")
|
||||
visible: Config.developerTools
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/DevtoolsPage.qml", {room: room}, {title: i18n("Developer Tools")})
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
id: searchButton
|
||||
|
||||
icon.name: "search"
|
||||
text: i18n("Search in this room")
|
||||
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
|
||||
currentRoom: room
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
})
|
||||
}
|
||||
}
|
||||
Kirigami.BasicListItem {
|
||||
id: favouriteButton
|
||||
|
||||
icon: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
}
|
||||
|
||||
Kirigami.BasicListItem {
|
||||
id: locationsButton
|
||||
|
||||
icon.name: "map-flat"
|
||||
text: i18n("Show locations for this room")
|
||||
|
||||
onClicked: pageStack.pushDialogLayer("qrc:/LocationsPage.qml", {
|
||||
room: room
|
||||
}, {
|
||||
title: i18nc("Locations on a map", "Locations")
|
||||
})
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
spacing: 0
|
||||
visible: !room.isDirectChat()
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: memberSearchToggle
|
||||
checkable: true
|
||||
icon.name: "search"
|
||||
QQC2.ToolTip.text: i18n("Search user in room")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onToggled: {
|
||||
userListSearchField.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
visible: roomDrawer.room.canSendState("invite")
|
||||
icon.name: "list-add-user"
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/InviteUserPage.qml", {room: roomDrawer.room})
|
||||
roomDrawer.close();
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: i18n("Invite user to room")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 member", "%1 members", room.joinedCount) : i18n("No member count")
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
visible: memberSearchToggle.checked
|
||||
|
||||
onVisibleChanged: if (visible) forceActiveFocus()
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
|
||||
focusSequence: "Ctrl+Shift+F"
|
||||
|
||||
onAccepted: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
}
|
||||
|
||||
KSortFilterProxyModel {
|
||||
id: sortedMessageEventModel
|
||||
|
||||
sourceModel: UserListModel {
|
||||
@@ -243,6 +250,11 @@ Kirigami.OverlayDrawer {
|
||||
filterCaseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
|
||||
model: room.isDirectChat() ? 0 : sortedMessageEventModel
|
||||
|
||||
clip: true
|
||||
activeFocusOnTab: true
|
||||
|
||||
delegate: Kirigami.BasicListItem {
|
||||
id: userListItem
|
||||
|
||||
|
||||
@@ -18,18 +18,19 @@ Kirigami.ScrollablePage {
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18n("General")
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Room Information")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Room Information")
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
background: Item {}
|
||||
@@ -165,13 +166,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Aliases")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Aliases")
|
||||
}
|
||||
MobileForm.FormTextDelegate {
|
||||
visible: room.aliases.length <= 0
|
||||
text: i18n("No canonical alias set")
|
||||
@@ -263,13 +266,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("URL Previews")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("URL Previews")
|
||||
}
|
||||
MobileForm.FormCheckDelegate {
|
||||
text: i18n("Enable URL previews by default for room members")
|
||||
checked: room.defaultUrlPreviewState
|
||||
|
||||
@@ -17,7 +17,7 @@ Kirigami.ScrollablePage {
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18nc('@title:window', 'Permissions')
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
@@ -31,13 +31,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Privileged Users")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Privileged Users")
|
||||
}
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: userListModel
|
||||
@@ -210,15 +212,17 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
title: i18n("Default permissions")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Default permissions")
|
||||
}
|
||||
MobileForm.FormComboBoxDelegate {
|
||||
text: i18n("Default user power level")
|
||||
description: i18n("This is power level for all new users when joining the room")
|
||||
@@ -248,15 +252,17 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
title: i18n("Basic permissions")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Basic permissions")
|
||||
}
|
||||
MobileForm.FormComboBoxDelegate {
|
||||
text: i18n("Invite users")
|
||||
textRole: "text"
|
||||
@@ -291,15 +297,17 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
title: i18n("Event permissions")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
visible: room.canSendState("m.room.power_levels")
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Event permissions")
|
||||
}
|
||||
MobileForm.FormComboBoxDelegate {
|
||||
text: i18n("Change user permissions")
|
||||
description: "m.room.power_levels"
|
||||
|
||||
@@ -7,27 +7,29 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18nc('@title:window', 'Notifications')
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Room notifications setting")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Room notifications setting")
|
||||
}
|
||||
MobileForm.FormRadioDelegate {
|
||||
text: i18n("Follow global setting")
|
||||
checked: room.pushNotificationState === PushNotificationState.Default
|
||||
@@ -62,5 +64,86 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Keywords")
|
||||
}
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
let roomIdRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.RoomIdRole)
|
||||
return sectionRole == PushNotificationSection.RoomKeywords && roomIdRole == root.room.id;
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ruleDelegate
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem : RowLayout {
|
||||
Kirigami.ActionTextField {
|
||||
id: keywordAddField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
placeholderText: i18n("Keyword…")
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
rightActions: Kirigami.Action {
|
||||
icon.name: "edit-clear"
|
||||
visible: keywordAddField.text.length > 0
|
||||
onTriggered: {
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: addButton
|
||||
|
||||
text: i18n("Add keyword")
|
||||
Accessible.name: text
|
||||
icon.name: "list-add"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
onClicked: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: addButton.text
|
||||
delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: ruleDelegate
|
||||
NotificationRuleItem {
|
||||
onDeleteRule: {
|
||||
Controller.pushRuleModel.removeKeyword(id)
|
||||
}
|
||||
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,20 @@ Kirigami.ScrollablePage {
|
||||
property string needUpgradeRoom: i18n("You need to upgrade this room to a newer version to enable this setting.")
|
||||
|
||||
title: i18n("Security")
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18nc("@option:check", "Encryption")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
visible: Controller.encryptionSupported
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18nc("@option:check", "Encryption")
|
||||
}
|
||||
MobileForm.FormSwitchDelegate {
|
||||
id: enableEncryptionSwitch
|
||||
text: i18n("Enable encryption")
|
||||
@@ -45,14 +45,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18nc("@option:check", "Access")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18nc("@option:check", "Access")
|
||||
}
|
||||
MobileForm.FormRadioDelegate {
|
||||
text: i18nc("@option:check", "Private (invite only)")
|
||||
description: i18n("Only invited people can join.")
|
||||
@@ -95,14 +95,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18nc("@option:check", "Message history visibility")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18nc("@option:check", "Message history visibility")
|
||||
}
|
||||
MobileForm.FormRadioDelegate {
|
||||
text: i18nc("@option:check", "Anyone")
|
||||
description: i18nc("@option:check", "Anyone, regardless of whether they have joined, can view history.")
|
||||
|
||||
@@ -19,17 +19,19 @@ Kirigami.ScrollablePage {
|
||||
|
||||
readonly property bool compact: width > Kirigami.Units.gridUnit * 30 ? 2 : 1
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("User information")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("User information")
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
background: Item {}
|
||||
@@ -122,14 +124,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Password")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Password")
|
||||
}
|
||||
MobileForm.FormTextDelegate {
|
||||
visible: root.connection !== undefined && root.connection.canChangePassword === false
|
||||
text: i18n("Your server doesn't support changing your password")
|
||||
@@ -191,14 +193,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Server Information")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Server Information")
|
||||
}
|
||||
MobileForm.FormTextDelegate {
|
||||
text: i18n("Homeserver url")
|
||||
description: root.connection.homeserver
|
||||
@@ -222,14 +224,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Sign out")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Sign out")
|
||||
}
|
||||
MobileForm.FormButtonDelegate {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Sign out")
|
||||
|
||||
@@ -13,19 +13,20 @@ import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Accounts")
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Accounts")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Accounts")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: AccountRegistry
|
||||
delegate: MobileForm.AbstractFormDelegate {
|
||||
|
||||
@@ -13,18 +13,19 @@ import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "Appearance")
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("General theme")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("General theme")
|
||||
}
|
||||
|
||||
MobileForm.AbstractFormDelegate {
|
||||
id: timelineModeSetting
|
||||
Layout.fillWidth: true
|
||||
@@ -316,16 +317,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Show Avatar")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Show Avatar")
|
||||
}
|
||||
|
||||
MobileForm.FormCheckDelegate {
|
||||
text: i18n("In chat")
|
||||
checked: Config.showAvatarInTimeline
|
||||
|
||||
@@ -12,19 +12,20 @@ import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Devices")
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Devices")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Devices")
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
visible: Controller.activeConnection && deviceRepeater.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
|
||||
@@ -24,6 +24,7 @@ Kirigami.ScrollablePage {
|
||||
property bool newEmoticon: false
|
||||
required property var emoticonType
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
@@ -31,14 +32,15 @@ Kirigami.ScrollablePage {
|
||||
: (newEmoticon ? i18nc("@title", "Add Emoji") : i18nc("@title", "Edit Emoji"))
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: emoticonType === EmoticonFormCard.Stickers ? i18n("Sticker") : i18n("Emoji")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: emoticonType === EmoticonFormCard.Stickers ? i18n("Sticker") : i18n("Emoji")
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
background: Item {}
|
||||
|
||||
@@ -20,13 +20,9 @@ MobileForm.FormCard {
|
||||
|
||||
property var emoticonType
|
||||
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: emoticonFormCard.emoticonType === EmoticonFormCard.Emojis ? i18n("Emojis") : i18n("Stickers")
|
||||
}
|
||||
Flow {
|
||||
id: stickerFlow
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -14,13 +14,23 @@ Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
title: emoticonType === EmoticonFormCard.Emojis ? i18n("Emojis") : i18n("Stickers")
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Emojis")
|
||||
}
|
||||
EmoticonFormCard {
|
||||
emoticonType: EmoticonFormCard.Emojis
|
||||
}
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Stickers")
|
||||
}
|
||||
EmoticonFormCard {
|
||||
emoticonType: EmoticonFormCard.Stickers
|
||||
}
|
||||
|
||||
@@ -13,18 +13,20 @@ import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "General")
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("General settings")
|
||||
visible: Qt.platform.os !== "android"
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("General settings")
|
||||
visible: Qt.platform.os !== "android"
|
||||
}
|
||||
|
||||
MobileForm.FormCheckDelegate {
|
||||
id: closeDelegate
|
||||
@@ -67,15 +69,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Timeline Events")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Timeline Events")
|
||||
}
|
||||
|
||||
MobileForm.FormCheckDelegate {
|
||||
id: showDeletedMessages
|
||||
@@ -155,15 +156,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Rooms and private chats")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Rooms and private chats")
|
||||
}
|
||||
MobileForm.FormRadioDelegate {
|
||||
text: i18n("Separated")
|
||||
checked: !Config.mergeRoomList
|
||||
@@ -184,15 +184,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18nc("Chat Editor", "Editor")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18nc("Chat Editor", "Editor")
|
||||
}
|
||||
MobileForm.FormCheckDelegate {
|
||||
id: quickEditCheckbox
|
||||
text: i18n("Use s/text/replacement syntax to edit your last message")
|
||||
@@ -216,15 +215,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Developer Settings")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Developer Settings")
|
||||
}
|
||||
MobileForm.FormCheckDelegate {
|
||||
text: i18n("Enable developer tools")
|
||||
checked: Config.developerTools
|
||||
|
||||
@@ -7,211 +7,103 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title:window", "Notifications")
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: notificationLayout
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
contentItem: MobileForm.FormCheckDelegate {
|
||||
text: i18n("Enable notifications for this account")
|
||||
description: i18n("Whether push notifications are generated by your Matrix server")
|
||||
checked: NotificationsManager.globalNotificationsEnabled
|
||||
enabled: NotificationsManager.globalNotificationsSet
|
||||
checked: Controller.pushRuleModel.globalNotificationsEnabled
|
||||
enabled: Controller.pushRuleModel.globalNotificationsSet
|
||||
onToggled: {
|
||||
NotificationsManager.globalNotificationsEnabled = checked
|
||||
Controller.pushRuleModel.globalNotificationsEnabled = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Room Notifications")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Room Notifications")
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Messages in one-to-one chats")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.oneToOneNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.oneToOneNotificationAction)
|
||||
enabled: NotificationsManager.oneToOneNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.oneToOneNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.oneToOneNotificationAction != notificationAction) {
|
||||
NotificationsManager.oneToOneNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Encrypted messages in one-to-one chats")
|
||||
|
||||
visible: Controller.encryptionSupported
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.encryptedOneToOneNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.encryptedOneToOneNotificationAction)
|
||||
enabled: NotificationsManager.encryptedOneToOneNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.encryptedOneToOneNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.encryptedOneToOneNotificationAction != notificationAction) {
|
||||
NotificationsManager.encryptedOneToOneNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Messages in group chats")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.groupChatNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.groupChatNotificationAction)
|
||||
enabled: NotificationsManager.groupChatNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.groupChatNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.groupChatNotificationAction != notificationAction) {
|
||||
NotificationsManager.groupChatNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Messages in encrypted group chats")
|
||||
|
||||
visible: Controller.encryptionSupported
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.encryptedGroupChatNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.encryptedGroupChatNotificationAction)
|
||||
enabled: NotificationsManager.encryptedGroupChatNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.encryptedGroupChatNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.encryptedGroupChatNotificationAction != notificationAction) {
|
||||
NotificationsManager.encryptedGroupChatNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Room upgrade messages")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.tombstoneNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.tombstoneNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.tombstoneNotificationAction)
|
||||
enabled: NotificationsManager.tombstoneNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.tombstoneNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.tombstoneNotificationAction != notificationAction) {
|
||||
NotificationsManager.tombstoneNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("@Mentions")
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Messages containing my display name")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.displayNameNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.displayNameNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.displayNameNotificationAction)
|
||||
enabled: NotificationsManager.displayNameNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.displayNameNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.displayNameNotificationAction != notificationAction) {
|
||||
NotificationsManager.displayNameNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Whole room (@room) notifications")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.roomNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.roomNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.roomNotificationAction)
|
||||
enabled: NotificationsManager.roomNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.roomNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.roomNotificationAction != notificationAction) {
|
||||
NotificationsManager.roomNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Keywords")
|
||||
}
|
||||
NotificationRuleItem {
|
||||
id: keywordNotificationAction
|
||||
text: i18n("Messages containing my keywords")
|
||||
|
||||
notificationsOn: true
|
||||
notificationsOnModifiable: false
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.keywordNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.keywordNotificationAction)
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown &&
|
||||
NotificationsManager.keywordNotificationAction !== PushNotificationAction.Off
|
||||
|
||||
notificationAction: NotificationsManager.keywordNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.keywordNotificationAction != notificationAction) {
|
||||
NotificationsManager.keywordNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.FormDelegateSeparator {}
|
||||
Repeater {
|
||||
model: KeywordNotificationRuleModel {
|
||||
id: keywordNotificationRuleModel
|
||||
}
|
||||
|
||||
delegate: NotificationRuleItem {
|
||||
text: name
|
||||
notificationAction: keywordNotificationAction.notificationAction
|
||||
notificationsOn: keywordNotificationAction.notificationsOn
|
||||
notificationsOnModifiable: false
|
||||
noisyOn: keywordNotificationAction.noisyOn
|
||||
noisyModifiable: false
|
||||
highlightOn: keywordNotificationAction.highlightOn
|
||||
deletable: true
|
||||
|
||||
onDeleteItemChanged: {
|
||||
if (deleteItem && deletable) {
|
||||
keywordNotificationRuleModel.removeKeywordAtIndex(index)
|
||||
}
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Room;
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ruleDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("@Mentions")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Mentions;
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ruleDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Keywords")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Keywords;
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ruleDelegate
|
||||
}
|
||||
MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
@@ -234,7 +126,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
}
|
||||
@@ -248,7 +140,7 @@ Kirigami.ScrollablePage {
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
onClicked: {
|
||||
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
|
||||
@@ -262,68 +154,37 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Invites")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Invites")
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Invites to a room")
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.inviteNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.inviteNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.inviteNotificationAction)
|
||||
enabled: NotificationsManager.inviteNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.inviteNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.inviteNotificationAction != notificationAction) {
|
||||
NotificationsManager.inviteNotificationAction = notificationAction
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Invites;
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationRuleItem {
|
||||
text: i18n("Call invitation")
|
||||
|
||||
// TODO enable this option when calls are supported
|
||||
visible: false
|
||||
|
||||
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.callInviteNotificationAction)
|
||||
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.callInviteNotificationAction)
|
||||
highlightable: true
|
||||
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.callInviteNotificationAction)
|
||||
enabled: NotificationsManager.callInviteNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
notificationAction: NotificationsManager.callInviteNotificationAction
|
||||
onNotificationActionChanged: {
|
||||
if (notificationAction && NotificationsManager.callInviteNotificationAction != notificationAction) {
|
||||
NotificationsManager.callInviteNotificationAction = notificationAction
|
||||
}
|
||||
}
|
||||
delegate: ruleDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNotificationRuleOn(action) {
|
||||
return action == PushNotificationAction.On ||
|
||||
action == PushNotificationAction.Noisy ||
|
||||
action == PushNotificationAction.Highlight ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
}
|
||||
|
||||
function isNotificationRuleNoisy(action) {
|
||||
return action == PushNotificationAction.Noisy ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
}
|
||||
|
||||
function isNotificationRuleHighlight(action) {
|
||||
return action == PushNotificationAction.Highlight ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
Component {
|
||||
id: ruleDelegate
|
||||
NotificationRuleItem {
|
||||
onDeleteRule: {
|
||||
Controller.pushRuleModel.removeKeyword(id)
|
||||
}
|
||||
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,19 @@ Kirigami.ScrollablePage {
|
||||
property int currentType
|
||||
property bool proxyConfigChanged: false
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Network Proxy")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Network Proxy")
|
||||
}
|
||||
MobileForm.FormRadioDelegate {
|
||||
text: i18n("System Default")
|
||||
checked: currentType === 0
|
||||
@@ -53,14 +55,14 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Proxy Settings")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Proxy Settings")
|
||||
}
|
||||
MobileForm.FormTextFieldDelegate {
|
||||
id: hostField
|
||||
label: i18n("Host")
|
||||
|
||||
@@ -11,19 +11,25 @@ import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
MobileForm.AbstractFormDelegate {
|
||||
id: notificationRuleItem
|
||||
id: root
|
||||
|
||||
property var notificationAction: PushNotificationAction.Unkown
|
||||
property bool notificationsOn: false
|
||||
property bool notificationsOnModifiable: true
|
||||
property bool noisyOn: false
|
||||
property bool noisyModifiable: true
|
||||
property bool highlightOn: false
|
||||
property bool highlightable: false
|
||||
property bool deleteItem: false
|
||||
property bool deletable: false
|
||||
required property string id
|
||||
required property string name
|
||||
required property int ruleAction
|
||||
required property bool highlightable
|
||||
required property bool deletable
|
||||
|
||||
readonly property bool notificationsOn: isNotificationRuleOn(ruleAction)
|
||||
readonly property bool notificationsOnModifiable: !deletable
|
||||
readonly property bool highlightOn: isNotificationRuleHighlight(ruleAction)
|
||||
|
||||
signal actionChanged(int action)
|
||||
signal deleteRule()
|
||||
|
||||
Layout.fillWidth: true
|
||||
enabled: ruleAction !== PushNotificationAction.Unknown
|
||||
|
||||
text: name
|
||||
|
||||
onClicked: {
|
||||
notificationAction = nextNotificationRuleAction(notificationAction)
|
||||
@@ -42,8 +48,8 @@ MobileForm.AbstractFormDelegate {
|
||||
background: Rectangle {
|
||||
visible: notificationsOn
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
color: highlightOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: highlightOn ? 1 : 0.3
|
||||
color: highlightOn && highlightable ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: highlightOn && highlightable ? 1 : 0.3
|
||||
radius: height / 2
|
||||
}
|
||||
}
|
||||
@@ -51,7 +57,7 @@ MobileForm.AbstractFormDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
text: notificationRuleItem.text
|
||||
text: root.text
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
@@ -67,13 +73,13 @@ MobileForm.AbstractFormDelegate {
|
||||
icon.name: checked ? "notifications" : "notifications-disabled"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
visible: notificationRuleItem.notificationsOnModifiable
|
||||
visible: root.notificationsOnModifiable
|
||||
checkable: true
|
||||
checked: notificationRuleItem.notificationsOn
|
||||
enabled: notificationRuleItem.enabled
|
||||
checked: root.notificationsOn
|
||||
enabled: root.enabled
|
||||
down: checked
|
||||
onToggled: {
|
||||
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
|
||||
root.actionChanged(root.notifcationRuleAction())
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
@@ -89,13 +95,12 @@ MobileForm.AbstractFormDelegate {
|
||||
icon.name: checked ? "audio-volume-high" : "audio-volume-muted"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
visible: notificationRuleItem.noisyModifiable
|
||||
checkable: true
|
||||
checked: notificationRuleItem.noisyOn
|
||||
enabled: (onButton.checked || !notificationRuleItem.notificationsOnModifiable) && notificationRuleItem.enabled
|
||||
checked: isNotificationRuleNoisy(root.ruleAction)
|
||||
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
|
||||
down: checked
|
||||
onToggled: {
|
||||
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
|
||||
root.actionChanged(root.notifcationRuleAction())
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
@@ -111,13 +116,13 @@ MobileForm.AbstractFormDelegate {
|
||||
icon.name: "draw-highlight"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
visible: notificationRuleItem.highlightable
|
||||
visible: root.highlightable
|
||||
checkable: true
|
||||
checked: notificationRuleItem.highlightOn
|
||||
enabled: (onButton.checked || !notificationRuleItem.notificationsOnModifiable) && notificationRuleItem.enabled
|
||||
checked: root.highlightOn
|
||||
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
|
||||
down: checked
|
||||
onToggled: {
|
||||
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
|
||||
root.actionChanged(root.notifcationRuleAction())
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
@@ -131,10 +136,10 @@ MobileForm.AbstractFormDelegate {
|
||||
Accessible.name: i18n("Delete keyword")
|
||||
icon.name: "edit-delete-remove"
|
||||
|
||||
visible: notificationRuleItem.deletable
|
||||
visible: root.deletable
|
||||
|
||||
onClicked: {
|
||||
notificationRuleItem.deleteItem = !notificationRuleItem.deleteItem
|
||||
root.deleteRule()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,11 +147,11 @@ MobileForm.AbstractFormDelegate {
|
||||
|
||||
function notifcationRuleAction() {
|
||||
if (onButton.checked) {
|
||||
if (noisyButton.checked && highlightButton.checked) {
|
||||
if (noisyButton.checked && highlightButton.checked && root.highlightable) {
|
||||
return PushNotificationAction.NoisyHighlight
|
||||
} else if (noisyButton.checked) {
|
||||
return PushNotificationAction.Noisy
|
||||
} else if (highlightButton.checked) {
|
||||
} else if (highlightButton.checked && root.highlightable) {
|
||||
return PushNotificationAction.Highlight
|
||||
} else {
|
||||
return PushNotificationAction.On
|
||||
@@ -166,17 +171,34 @@ MobileForm.AbstractFormDelegate {
|
||||
}
|
||||
|
||||
while (!finished) {
|
||||
if (action == PushNotificationAction.Off && !notificationRuleItem.notificationsOnModifiable) {
|
||||
if (action == PushNotificationAction.Off && !root.notificationsOnModifiable) {
|
||||
action = PushNotificationAction.On
|
||||
} else if (action == PushNotificationAction.Noisy && !notificationRuleItem.noisyModifiable) {
|
||||
} else if (action == PushNotificationAction.Noisy) {
|
||||
action = PushNotificationAction.Highlight
|
||||
} else if (action == PushNotificationAction.Highlight && !notificationRuleItem.highlightable) {
|
||||
} else if (action == PushNotificationAction.Highlight && !root.highlightable) {
|
||||
action = PushNotificationAction.Off
|
||||
} else {
|
||||
finished = true
|
||||
}
|
||||
}
|
||||
|
||||
return action
|
||||
actionChanged(action)
|
||||
}
|
||||
|
||||
function isNotificationRuleOn(action) {
|
||||
return action == PushNotificationAction.On ||
|
||||
action == PushNotificationAction.Noisy ||
|
||||
action == PushNotificationAction.Highlight ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
}
|
||||
|
||||
function isNotificationRuleNoisy(action) {
|
||||
return action == PushNotificationAction.Noisy ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
}
|
||||
|
||||
function isNotificationRuleHighlight(action) {
|
||||
return action == PushNotificationAction.Highlight ||
|
||||
action == PushNotificationAction.NoisyHighlight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,23 +11,26 @@ import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: page
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormHeader {
|
||||
Layout.fillWidth: true
|
||||
title: i18n("Spellchecking")
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
id: card
|
||||
Sonnet.Settings {
|
||||
id: settings
|
||||
}
|
||||
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Spellchecking")
|
||||
}
|
||||
|
||||
MobileForm.FormCheckDelegate {
|
||||
id: enable
|
||||
checked: settings.checkerEnabledByDefault
|
||||
|
||||
Reference in New Issue
Block a user