Compare commits

...

29 Commits

Author SHA1 Message Date
Laurent Montel
4f3553a59d [QT6] Use icon.name 2023-07-12 09:51:40 +00:00
Laurent Montel
882a0d4901 Autogenerate + Install debug category file 2023-07-12 09:50:58 +00:00
Laurent Montel
506d31f53f "This" is not used 2023-07-12 09:50:41 +00:00
l10n daemon script
110f007b41 GIT_SILENT made messages (after extraction) 2023-07-12 00:47:42 +00:00
l10n daemon script
a90943d9ac GIT_SILENT Sync po/docbooks with svn 2023-07-11 01:50:22 +00:00
l10n daemon script
cbe7d8c2c2 GIT_SILENT made messages (after extraction) 2023-07-11 00:47:06 +00:00
James Graham
ae4943dd71 Use new mobileform heading
Does what is says on the tin. Use the recently merged new `MobileForm.FormHeader` component in all settings pages.
2023-07-10 16:46:35 +00:00
James Graham
7bd84bf51e Push Rule Model Rework
This is a significant rework of the handling of push rules. Rather than using a lot of boilerplate code for the default models `KeywordNotificationModel` has been converted to `PushRuleModel` and now handles all push rules.

The new model has the following features:
- Handles all push rules
- Has special handling for the names of default keywords (i.e. it still gives the same text as previously for showing in the settings menus)
- Push rules for blocking individuals or room overrides are still there but hidden so will be available for developer tools (to follow)
- Room specific keywords are now supported.

The notification settings pages have also been refactored to take advantage of the new models. Each section is now just a repeater with a filter for the rules that it should contain. The push rule delegate has now been cleaned up and uses required properties.

Implements network/neochat#574
2023-07-10 16:17:17 +00:00
l10n daemon script
a6ce44eb24 GIT_SILENT Sync po/docbooks with svn 2023-07-10 01:56:36 +00:00
Carl Schwan
b1c42c3d3d Make the whole right sidebar scrollable
The sidebar grew a lot and now contains some tools. This makes the space
left for the room members very small. Particuliary on mobile.

So make the whole sidebar contained inside the ScrollView

This also refactor a few things (e.g. use a plain ToolBar for the header)
2023-07-09 22:41:28 +00:00
Carl Schwan
213aaf3ac4 Ensure chat box and userInfo have the same height 2023-07-09 22:32:46 +00:00
James Graham
c55b40c9c6 Media Model
Create a media model for all the media message in the timeline and then setup `NeoChatMaximizeComponent` so that it can use the media model to scroll through all loaded images and video in the current room.

Depends upon libraries/kirigami-addons!105

FEATURE: 467411
2023-07-08 11:07:04 +00:00
Volker Krause
81928d8b93 Fix binary stripping of APKs with Qt 5.15.10
This fixes APKs on binary factory having exploded in size 3x-5x after
the Qt 5.15.10 update. This also affects release packages and will need
to be backported to those (in Craft).
2023-07-08 07:51:24 +02:00
Ingo Klöcker
b7bddba053 Add Windows-specific screenshots as custom values for Microsoft Store
This is an interim solution until AppStream (hopefully) gets support
for platform-specific screenshots. The captions will be extracted for
translation.
2023-07-07 15:55:11 +02:00
l10n daemon script
307a9370db GIT_SILENT Sync po/docbooks with svn 2023-07-07 01:54:27 +00:00
Volker Krause
2f65cbeb36 Show location descriptions in the timeline when available 2023-07-06 15:08:42 +00:00
l10n daemon script
e0c0b1f0e8 GIT_SILENT Sync po/docbooks with svn 2023-07-06 02:28:47 +00:00
l10n daemon script
74f767aa82 GIT_SILENT Sync po/docbooks with svn 2023-07-04 02:36:05 +00:00
l10n daemon script
7176dd4476 GIT_SILENT Sync po/docbooks with svn 2023-07-03 02:15:16 +00:00
l10n daemon script
f75fe31571 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2023-07-03 01:58:53 +00:00
l10n daemon script
61bdb1ed5f GIT_SILENT made messages (after extraction) 2023-07-03 00:55:03 +00:00
l10n daemon script
527e9d93a5 GIT_SILENT Sync po/docbooks with svn 2023-07-02 02:21:48 +00:00
l10n daemon script
7d22b30217 GIT_SILENT Sync po/docbooks with svn 2023-06-30 01:54:09 +00:00
Heiko Becker
c0d2333a3d GIT_SILENT Update Appstream for new release
(cherry picked from commit c72cf64369)
2023-06-30 01:16:59 +02:00
l10n daemon script
4a7e1d058c GIT_SILENT Sync po/docbooks with svn 2023-06-29 01:52:16 +00:00
l10n daemon script
66974615f6 GIT_SILENT Sync po/docbooks with svn 2023-06-28 01:55:40 +00:00
Ingo Klöcker
116c888686 Add "16:9 Super hero art" for Microsoft Store 2023-06-27 12:29:01 +02:00
Ingo Klöcker
f6d6a804d2 Add some logos in different sizes for the Microsoft Store 2023-06-27 12:28:06 +02:00
l10n daemon script
6d6d702b97 GIT_SILENT Sync po/docbooks with svn 2023-06-27 01:59:23 +00:00
94 changed files with 15462 additions and 16199 deletions

View File

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

View File

@@ -47,6 +47,7 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
sourceSets {
main {

BIN
icons/300-apps-neochat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -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 lynmeldings­protokollen</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">

View File

@@ -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]=Lynmeldings­klient 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();
});
}

View File

@@ -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");

View File

@@ -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
};
/**

View File

@@ -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")}};
}

View File

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

View File

@@ -4,7 +4,7 @@
#include "livelocationsmodel.h"
#include <Quotient/events/roommessageevent.h>
#include <events/roommessageevent.h>
#include <QDebug>

View 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();
}

View 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;
};

View File

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

View 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
View 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 *)

View File

@@ -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 lynmeldings­protokollen.
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

View File

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

View File

@@ -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());
},

View File

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

View File

@@ -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);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -62,5 +62,10 @@ TimelineContainer {
id: fullScreenMap
FullScreenMap {}
}
RichLabel {
textMessage: root.display
visible: root.display !== ""
}
}
}

View File

@@ -76,5 +76,10 @@ TimelineContainer {
id: fullScreenMap
FullScreenMap { }
}
RichLabel {
textMessage: root.display
visible: root.display !== ""
}
}
}

View File

@@ -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,
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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