Compare commits
47 Commits
work/carl/
...
work/fix_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a244293f9f | ||
|
|
50ad18d095 | ||
|
|
9fcfad7058 | ||
|
|
cfcc1756dd | ||
|
|
e15bec2295 | ||
|
|
ca53d163eb | ||
|
|
9071cf827f | ||
|
|
c7deaaba84 | ||
|
|
414035de8b | ||
|
|
3bac752463 | ||
|
|
442612d31d | ||
|
|
2ba3d970e1 | ||
|
|
59164d3bb2 | ||
|
|
cc60dde62d | ||
|
|
a19502ca40 | ||
|
|
cbed8148a3 | ||
|
|
1b2919587a | ||
|
|
e5420973f5 | ||
|
|
50e8b9ebf6 | ||
|
|
f5ad2ad162 | ||
|
|
931d20fecd | ||
|
|
d11d6c74b3 | ||
|
|
725c6c30ce | ||
|
|
63ed69a5d4 | ||
|
|
a8aa775575 | ||
|
|
6305359b3c | ||
|
|
7e859364af | ||
|
|
8abd0db012 | ||
|
|
dbc10685f0 | ||
|
|
96582a12bc | ||
|
|
0ac61854bd | ||
|
|
68298f038d | ||
|
|
405fd5841a | ||
|
|
5da9bba844 | ||
|
|
84373712ef | ||
|
|
50f4f96341 | ||
|
|
1b27b1a4e2 | ||
|
|
33811a4c49 | ||
|
|
43715486e5 | ||
|
|
5c72bd4ab7 | ||
|
|
092f1be99b | ||
|
|
550d55cb1a | ||
|
|
e9edb61245 | ||
|
|
89aae665b1 | ||
|
|
1e3c3dd1f4 | ||
|
|
6f4d2c0216 | ||
|
|
803cd2b4e4 |
@@ -77,7 +77,7 @@ ecm_setup_version(${PROJECT_VERSION}
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:versionName="${versionName}"
|
||||
android:versionCode="${versionCode}"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
@@ -267,52 +268,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<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="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</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="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</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="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</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="nn">Hovudvising med romliste, pratevindauge og rominformasjon</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</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="sv">Huvudvy med rumslista, chatt, och rumsinformation</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</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="ar">شاشة الدخول</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="eo">Ensaluta ekrano</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="eu">Saio-hasteko pantaila</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fi">Kirjautumisnäkymä</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fr">Écran de connexion</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="gl">Pantalla de identificación.</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="it">Schermata di accesso</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ka">შესვლის ეკრანი</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ko">로그인 화면</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="nl">Aanmeldscherm</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="nn">Innloggingsbilete</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="pt">Ecrã de autenticação</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="sv">Inloggningsfönster</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ta">நுழைவுத் திரை</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>
|
||||
@@ -322,16 +277,77 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
|
||||
<caption>Main view with room list, chat, and room information</caption>
|
||||
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
|
||||
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
|
||||
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
|
||||
<caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption>
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
|
||||
<caption xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</caption>
|
||||
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
|
||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
|
||||
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
|
||||
<caption>Login screen</caption>
|
||||
<caption xml:lang="ar">شاشة الدخول</caption>
|
||||
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
||||
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
||||
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
|
||||
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
|
||||
<caption xml:lang="fr">Écran de connexion</caption>
|
||||
<caption xml:lang="gl">Pantalla de identificación.</caption>
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
<caption xml:lang="ka">შესვლის ეკრანი</caption>
|
||||
<caption xml:lang="ko">로그인 화면</caption>
|
||||
<caption xml:lang="nl">Aanmeldscherm</caption>
|
||||
<caption xml:lang="nn">Innloggingsbilete</caption>
|
||||
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
||||
<caption xml:lang="sl">Prijavni zaslon</caption>
|
||||
<caption xml:lang="sv">Inloggningsfönster</caption>
|
||||
<caption xml:lang="ta">நுழைவுத் திரை</caption>
|
||||
<caption xml:lang="tr">Oturum açma ekranı</caption>
|
||||
<caption xml:lang="uk">Вікно входу</caption>
|
||||
<caption xml:lang="x-test">xxLogin screenxx</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.08.0" date="2023-08-24"/>
|
||||
<release version="23.08.0" date="2023-08-24">
|
||||
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>Apart from a visual overhaul, NeoChat can now display location events and also a map with the location of all the users currently broadcasting their location using Itineray's Matrix integration. Great for locating where your friends are.</p>
|
||||
</description>
|
||||
</release>
|
||||
<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">
|
||||
<url>https://kde.org/announcements/gear/23.04.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>NeoChat improves its design with tweaks that provide a more compact layout and a simpler menu which works better for the collapsed room list.</p>
|
||||
<p>We have also improved the video controls, added a new command /knock <room-id> to send a knock event to a room, and you can now edit a prior message inline, within the chat pane.</p>
|
||||
<p>Other usability improvements include an overhaul of the keyboard navigation and shortcuts like Ctrl+PgUp/PgDn that allow you to skip from room to room.</p>
|
||||
</description>
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
<location>https://download.kde.org/stable/release-service/23.04.0/windows/neochat-23.04.0-512-windows-cl-msvc2019-x86_64.exe</location>
|
||||
|
||||
711
po/ar/neochat.po
711
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
685
po/az/neochat.po
685
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
685
po/ca/neochat.po
685
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
675
po/cs/neochat.po
675
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
649
po/da/neochat.po
649
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
682
po/de/neochat.po
682
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
689
po/el/neochat.po
689
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
688
po/es/neochat.po
688
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/eu/neochat.po
697
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/fi/neochat.po
698
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/fr/neochat.po
698
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
682
po/hu/neochat.po
682
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
693
po/ia/neochat.po
693
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
679
po/id/neochat.po
679
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/ie/neochat.po
680
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/it/neochat.po
698
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
609
po/ja/neochat.po
609
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
703
po/ka/neochat.po
703
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1953
po/ko/neochat.po
1953
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
617
po/lt/neochat.po
617
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
688
po/nl/neochat.po
688
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
317
po/nn/neochat.po
317
po/nn/neochat.po
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: neochat\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2023-08-21 00:46+0000\n"
|
||||
"PO-Revision-Date: 2023-08-10 18:43+0200\n"
|
||||
"POT-Creation-Date: 2023-08-27 00:46+0000\n"
|
||||
"PO-Revision-Date: 2023-08-24 21:25+0200\n"
|
||||
"Last-Translator: Karl Ove Hufthammer <karl@huftis.org>\n"
|
||||
"Language-Team: Norwegian Nynorsk <l10n-no@lister.huftis.org>\n"
|
||||
"Language: nn\n"
|
||||
@@ -20,77 +20,77 @@ msgstr ""
|
||||
"X-Accelerator-Marker: &\n"
|
||||
"X-Text-Markup: kde4\n"
|
||||
|
||||
#: src/controller.cpp:233
|
||||
#: src/controller.cpp:245
|
||||
#, kde-format
|
||||
msgid "Login Failed: Access Token invalid or revoked"
|
||||
msgstr "Feil ved innlogging: Ugyldig eller tilbaketrekt tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:236 src/controller.cpp:241 src/login.cpp:90
|
||||
#: src/controller.cpp:248 src/controller.cpp:253 src/login.cpp:90
|
||||
#, kde-format
|
||||
msgid "Login Failed: %1"
|
||||
msgstr "Feil ved innlogging: %1"
|
||||
|
||||
#: src/controller.cpp:247
|
||||
#: src/controller.cpp:259
|
||||
#, kde-format
|
||||
msgid "Network Error: %1"
|
||||
msgstr "Nettverksfeil: %1"
|
||||
|
||||
#: src/controller.cpp:272
|
||||
#: src/controller.cpp:284
|
||||
#, kde-format
|
||||
msgid "Access token wasn't found"
|
||||
msgstr "Fann ikkje tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:272
|
||||
#: src/controller.cpp:284
|
||||
#, kde-format
|
||||
msgid "Maybe it was deleted?"
|
||||
msgstr "Kanskje han er sletta?"
|
||||
|
||||
#: src/controller.cpp:276
|
||||
#: src/controller.cpp:288
|
||||
#, kde-format
|
||||
msgid "Access to keychain was denied."
|
||||
msgstr "Vart nekta tilgang til nøkkelring."
|
||||
|
||||
#: src/controller.cpp:276
|
||||
#: src/controller.cpp:288
|
||||
#, kde-format
|
||||
msgid "Please allow NeoChat to read the access token"
|
||||
msgstr "Gje NeoChat løyve til å lesa tilgangspolletten"
|
||||
|
||||
#: src/controller.cpp:279
|
||||
#: src/controller.cpp:291
|
||||
#, kde-format
|
||||
msgid "No keychain available."
|
||||
msgstr "Ingen nøkkelring er tilgjengeleg."
|
||||
|
||||
#: src/controller.cpp:279
|
||||
#: src/controller.cpp:291
|
||||
#, kde-format
|
||||
msgid "Please install a keychain, e.g. KWallet or GNOME keyring on Linux"
|
||||
msgstr "Installer ein nøkkelring, for eksempel KWallet eller GNOME Keyring"
|
||||
|
||||
#: src/controller.cpp:282
|
||||
#: src/controller.cpp:294
|
||||
#, kde-format
|
||||
msgid "Unable to read access token"
|
||||
msgstr "Klarte ikkje lesa tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:452
|
||||
#: src/controller.cpp:464
|
||||
#, kde-format
|
||||
msgid "File too large to download."
|
||||
msgstr "Fila er for stor til å kunna lastast ned."
|
||||
|
||||
#: src/controller.cpp:452
|
||||
#: src/controller.cpp:464
|
||||
#, kde-format
|
||||
msgid "Contact your matrix server administrator for support."
|
||||
msgstr "Ta kontakt med administratoren av Matrix-tenaren for brukarstøtte."
|
||||
|
||||
#: src/controller.cpp:491
|
||||
#: src/controller.cpp:503
|
||||
#, kde-format
|
||||
msgid "Room creation failed: %1"
|
||||
msgstr "Feil ved romregistrering: %1"
|
||||
|
||||
#: src/controller.cpp:512
|
||||
#: src/controller.cpp:524
|
||||
#, kde-format
|
||||
msgid "Space creation failed: %1"
|
||||
msgstr "Feil ved registrering av område: %1"
|
||||
|
||||
#: src/controller.cpp:526
|
||||
#: src/controller.cpp:538
|
||||
#, kde-format
|
||||
msgid "The room id you are trying to join is not valid"
|
||||
msgstr "Rom-ID-en du prøver å bruka, er ikkje gyldig"
|
||||
@@ -202,17 +202,17 @@ msgctxt "<version number> (built against <possibly different version number>)"
|
||||
msgid "%1 (built against %2)"
|
||||
msgstr "%1 (bygd mot %2)"
|
||||
|
||||
#: src/main.cpp:333
|
||||
#: src/main.cpp:334
|
||||
#, kde-format
|
||||
msgid "Client for the matrix communication protocol"
|
||||
msgstr "Lynmeldingsklient for Matrix-protokollen"
|
||||
|
||||
#: src/main.cpp:334
|
||||
#: src/main.cpp:335
|
||||
#, kde-format
|
||||
msgid "Supports matrix: url scheme"
|
||||
msgstr "Støttar «matrix:»-adresser"
|
||||
|
||||
#: src/main.cpp:335
|
||||
#: src/main.cpp:336
|
||||
#, kde-format
|
||||
msgid "Ignore all SSL Errors, e.g., unsigned certificates."
|
||||
msgstr "Ignorer alle SSL-feil, for eksempel usignerte sertifikat."
|
||||
@@ -227,312 +227,312 @@ msgstr "Medie-ID-en «%1» følgjer ikkje mønsteret «tenar/medie-ID»"
|
||||
msgid "Image request has been cancelled"
|
||||
msgstr "Biletførespurnaden vart avbroten"
|
||||
|
||||
#: src/models/actionsmodel.cpp:23
|
||||
#: src/models/actionsmodel.cpp:24
|
||||
#, kde-format
|
||||
msgid "Leaving this room."
|
||||
msgstr "Forlèt rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:30 src/models/actionsmodel.cpp:227
|
||||
#: src/models/actionsmodel.cpp:253 src/models/actionsmodel.cpp:283
|
||||
#: src/models/actionsmodel.cpp:31 src/models/actionsmodel.cpp:228
|
||||
#: src/models/actionsmodel.cpp:254 src/models/actionsmodel.cpp:284
|
||||
#, kde-format
|
||||
msgctxt "'<text>' does not look like a room id or alias."
|
||||
msgid "'%1' does not look like a room id or alias."
|
||||
msgstr "«%1» ser ikkje ut til å vera ein rom-ID eller eit alias."
|
||||
|
||||
#: src/models/actionsmodel.cpp:38
|
||||
#: src/models/actionsmodel.cpp:39
|
||||
#, kde-format
|
||||
msgctxt "Leaving room <roomname>."
|
||||
msgid "Leaving room %1."
|
||||
msgstr "Forlèt rom %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:41
|
||||
#: src/models/actionsmodel.cpp:42
|
||||
#, kde-format
|
||||
msgctxt "Room <roomname> not found"
|
||||
msgid "Room %1 not found."
|
||||
msgstr "Fann ikkje rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:49 src/models/actionsmodel.cpp:319
|
||||
#: src/models/actionsmodel.cpp:50 src/models/actionsmodel.cpp:320
|
||||
#, kde-format
|
||||
msgid "No new nickname provided, no changes will happen."
|
||||
msgstr "Nytt kallenamn ikkje oppgjeve. Ingen endringar vert gjort."
|
||||
|
||||
#: src/models/actionsmodel.cpp:64 src/models/actionsmodel.cpp:74
|
||||
#: src/models/actionsmodel.cpp:84 src/models/actionsmodel.cpp:94
|
||||
#: src/models/actionsmodel.cpp:114 src/models/actionsmodel.cpp:134
|
||||
#: src/models/actionsmodel.cpp:145 src/models/actionsmodel.cpp:161
|
||||
#: src/models/actionsmodel.cpp:171 src/models/actionsmodel.cpp:181
|
||||
#: src/models/actionsmodel.cpp:65 src/models/actionsmodel.cpp:75
|
||||
#: src/models/actionsmodel.cpp:85 src/models/actionsmodel.cpp:95
|
||||
#: src/models/actionsmodel.cpp:115 src/models/actionsmodel.cpp:135
|
||||
#: src/models/actionsmodel.cpp:146 src/models/actionsmodel.cpp:162
|
||||
#: src/models/actionsmodel.cpp:172 src/models/actionsmodel.cpp:182
|
||||
msgid "<message>"
|
||||
msgstr "<melding>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:65
|
||||
#: src/models/actionsmodel.cpp:66
|
||||
msgid "Prepends ¯\\_(ツ)_/¯ to a plain-text message"
|
||||
msgstr "Legg til ¯\\_(ツ)_/¯ i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:75
|
||||
#: src/models/actionsmodel.cpp:76
|
||||
msgid "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message"
|
||||
msgstr "Legg til ( ͡° ͜ʖ ͡°) i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:85
|
||||
#: src/models/actionsmodel.cpp:86
|
||||
msgid "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message"
|
||||
msgstr "Legg til (╯°□°)╯︵ ┻━┻ i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:95
|
||||
#: src/models/actionsmodel.cpp:96
|
||||
msgid "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message"
|
||||
msgstr "Legg til ┬──┬ ノ( ゜-゜ノ) i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:115
|
||||
#: src/models/actionsmodel.cpp:116
|
||||
msgid "Sends the given message colored as a rainbow"
|
||||
msgstr "Sender oppgjeven melding fargelagd som ein regnboge"
|
||||
|
||||
#: src/models/actionsmodel.cpp:135
|
||||
#: src/models/actionsmodel.cpp:136
|
||||
msgid "Sends the given emote colored as a rainbow"
|
||||
msgstr "Sender oppgjeve uttrykk fargelagd som ein regnboge"
|
||||
|
||||
#: src/models/actionsmodel.cpp:146
|
||||
#: src/models/actionsmodel.cpp:147
|
||||
msgid "Sends the given message as plain text"
|
||||
msgstr "Sender oppgjeven melding som reintekst"
|
||||
|
||||
#: src/models/actionsmodel.cpp:162
|
||||
#: src/models/actionsmodel.cpp:163
|
||||
msgid "Sends the given message as a spoiler"
|
||||
msgstr "Sender oppgjeven melding med røpealarm"
|
||||
|
||||
#: src/models/actionsmodel.cpp:172
|
||||
#: src/models/actionsmodel.cpp:173
|
||||
msgid "Sends the given emote"
|
||||
msgstr "Sender oppgjeve uttrykk"
|
||||
|
||||
#: src/models/actionsmodel.cpp:182
|
||||
#: src/models/actionsmodel.cpp:183
|
||||
msgid "Sends the given message as a notice"
|
||||
msgstr "Sender oppgjeven melding som ei varsling"
|
||||
|
||||
#: src/models/actionsmodel.cpp:191 src/models/actionsmodel.cpp:353
|
||||
#: src/models/actionsmodel.cpp:381 src/models/actionsmodel.cpp:431
|
||||
#: src/models/actionsmodel.cpp:469 src/models/actionsmodel.cpp:504
|
||||
#: src/models/actionsmodel.cpp:192 src/models/actionsmodel.cpp:354
|
||||
#: src/models/actionsmodel.cpp:382 src/models/actionsmodel.cpp:432
|
||||
#: src/models/actionsmodel.cpp:470 src/models/actionsmodel.cpp:505
|
||||
#, kde-format
|
||||
msgctxt "'<text>' does not look like a matrix id."
|
||||
msgid "'%1' does not look like a matrix id."
|
||||
msgstr "«%1» ser ikkje ut til å vera ein Matrix-ID."
|
||||
|
||||
#: src/models/actionsmodel.cpp:196
|
||||
#: src/models/actionsmodel.cpp:197
|
||||
#, kde-format
|
||||
msgctxt "<user> is already invited to this room."
|
||||
msgid "%1 is already invited to this room."
|
||||
msgstr "%1 er alt invitert til dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:200
|
||||
#: src/models/actionsmodel.cpp:201
|
||||
#, kde-format
|
||||
msgctxt "<user> is banned from this room."
|
||||
msgid "%1 is banned from this room."
|
||||
msgstr "%1 er utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:204
|
||||
#: src/models/actionsmodel.cpp:205
|
||||
#, kde-format
|
||||
msgid "You are already in this room."
|
||||
msgstr "Du er alt i dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:208
|
||||
#: src/models/actionsmodel.cpp:209
|
||||
#, kde-format
|
||||
msgctxt "<user> is already in this room."
|
||||
msgid "%1 is already in this room."
|
||||
msgstr "%1 er alt i dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:212
|
||||
#: src/models/actionsmodel.cpp:213
|
||||
#, kde-format
|
||||
msgctxt "<username> was invited into this room"
|
||||
msgid "%1 was invited into this room"
|
||||
msgstr "%1 vart invitert til rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:217 src/models/actionsmodel.cpp:371
|
||||
#: src/models/actionsmodel.cpp:399 src/models/actionsmodel.cpp:492
|
||||
#: src/models/actionsmodel.cpp:218 src/models/actionsmodel.cpp:372
|
||||
#: src/models/actionsmodel.cpp:400 src/models/actionsmodel.cpp:493
|
||||
msgid "<user id>"
|
||||
msgstr "<brukar-ID>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:218
|
||||
#: src/models/actionsmodel.cpp:219
|
||||
msgid "Invites the user to this room"
|
||||
msgstr "Inviterer brukaren til rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:235 src/models/actionsmodel.cpp:290
|
||||
#: src/models/actionsmodel.cpp:236 src/models/actionsmodel.cpp:291
|
||||
#, kde-format
|
||||
msgctxt "Joining room <roomname>."
|
||||
msgid "Joining room %1."
|
||||
msgstr "Vert med i rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:241 src/models/actionsmodel.cpp:296
|
||||
#: src/models/actionsmodel.cpp:242 src/models/actionsmodel.cpp:297
|
||||
msgid "<room alias or id>"
|
||||
msgstr "<rom-alias eller -ID>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:242 src/models/actionsmodel.cpp:297
|
||||
#: src/models/actionsmodel.cpp:243 src/models/actionsmodel.cpp:298
|
||||
msgid "Joins the given room"
|
||||
msgstr "Vert med i rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:261
|
||||
#: src/models/actionsmodel.cpp:262
|
||||
#, kde-format
|
||||
msgctxt "Knocking room <roomname>."
|
||||
msgid "Knocking room %1."
|
||||
msgstr "Bankar på rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:273
|
||||
#: src/models/actionsmodel.cpp:274
|
||||
msgid "<room alias or id> [<reason>]"
|
||||
msgstr "<rom-alias eller -id> [<grunngjeving>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:274
|
||||
#: src/models/actionsmodel.cpp:275
|
||||
msgid "Requests to join the given room"
|
||||
msgstr "Førespurnad om å få verta med i rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:287
|
||||
#: src/models/actionsmodel.cpp:288
|
||||
#, kde-format
|
||||
msgctxt "You are already in room <roomname>."
|
||||
msgid "You are already in room %1."
|
||||
msgstr "Du er alt med i rom %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:304 src/models/actionsmodel.cpp:312
|
||||
#: src/models/actionsmodel.cpp:305 src/models/actionsmodel.cpp:313
|
||||
msgid "[<room alias or id>]"
|
||||
msgstr "[<rom-alias eller -id>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:305 src/models/actionsmodel.cpp:313
|
||||
#: src/models/actionsmodel.cpp:306 src/models/actionsmodel.cpp:314
|
||||
msgid "Leaves the given room or this room, if there is none given"
|
||||
msgstr "Forlèt valt eller (viss romnamn ikkje er oppgjeve) gjeldande rom"
|
||||
|
||||
#: src/models/actionsmodel.cpp:327 src/models/actionsmodel.cpp:335
|
||||
#: src/models/actionsmodel.cpp:343
|
||||
#: src/models/actionsmodel.cpp:328 src/models/actionsmodel.cpp:336
|
||||
#: src/models/actionsmodel.cpp:344
|
||||
msgid "<display name>"
|
||||
msgstr "<visingsnamn>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:328
|
||||
#: src/models/actionsmodel.cpp:329
|
||||
msgid "Changes your global display name"
|
||||
msgstr "Byter ditt globale visingsnamn"
|
||||
|
||||
#: src/models/actionsmodel.cpp:336 src/models/actionsmodel.cpp:344
|
||||
#: src/models/actionsmodel.cpp:337 src/models/actionsmodel.cpp:345
|
||||
msgid "Changes your display name in this room"
|
||||
msgstr "Byter visingsnamnet ditt i dette rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:358
|
||||
#: src/models/actionsmodel.cpp:359
|
||||
#, kde-format
|
||||
msgctxt "<username> is already ignored."
|
||||
msgid "%1 is already ignored."
|
||||
msgstr "%1 er ignorert frå før."
|
||||
|
||||
#: src/models/actionsmodel.cpp:363
|
||||
#: src/models/actionsmodel.cpp:364
|
||||
#, kde-format
|
||||
msgctxt "<username> is now ignored"
|
||||
msgid "%1 is now ignored."
|
||||
msgstr "%1 er no ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:365 src/models/actionsmodel.cpp:393
|
||||
#: src/models/actionsmodel.cpp:366 src/models/actionsmodel.cpp:394
|
||||
#, kde-format
|
||||
msgctxt "<username> is not a known user"
|
||||
msgid "%1 is not a known user."
|
||||
msgstr "%1 er ein ukjend brukar."
|
||||
|
||||
#: src/models/actionsmodel.cpp:372
|
||||
#: src/models/actionsmodel.cpp:373
|
||||
msgid "Ignores the given user"
|
||||
msgstr "Ignorerer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:387
|
||||
#: src/models/actionsmodel.cpp:388
|
||||
#, kde-format
|
||||
msgctxt "<username> is not ignored."
|
||||
msgid "%1 is not ignored."
|
||||
msgstr "%1 er ikkje ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:391
|
||||
#: src/models/actionsmodel.cpp:392
|
||||
#, kde-format
|
||||
msgctxt "<username> is no longer ignored."
|
||||
msgid "%1 is no longer ignored."
|
||||
msgstr "%1 er ikkje lenger ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:400
|
||||
#: src/models/actionsmodel.cpp:401
|
||||
msgid "Unignores the given user"
|
||||
msgstr "Avignorerer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:420
|
||||
#: src/models/actionsmodel.cpp:421
|
||||
msgid "<reaction text>"
|
||||
msgstr "<reaksjonstekst>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:421
|
||||
#: src/models/actionsmodel.cpp:422
|
||||
msgid "React to the message with the given text"
|
||||
msgstr "Reager på meldinga med ein tekst"
|
||||
|
||||
#: src/models/actionsmodel.cpp:436
|
||||
#: src/models/actionsmodel.cpp:437
|
||||
#, kde-format
|
||||
msgctxt "<user> is already banned from this room."
|
||||
msgid "%1 is already banned from this room."
|
||||
msgstr "%1 er alt utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:444
|
||||
#: src/models/actionsmodel.cpp:445
|
||||
#, kde-format
|
||||
msgid "You are not allowed to ban users from this room."
|
||||
msgstr "Du har ikkje løyve til å utestengja brukarar frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:450
|
||||
#: src/models/actionsmodel.cpp:451
|
||||
#, kde-format
|
||||
msgctxt "You are not allowed to ban <username> from this room."
|
||||
msgid "You are not allowed to ban %1 from this room."
|
||||
msgstr "Du har ikkje løyve til å utestengja %1 frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:454
|
||||
#: src/models/actionsmodel.cpp:455
|
||||
#, kde-format
|
||||
msgctxt "<username> was banned from this room."
|
||||
msgid "%1 was banned from this room."
|
||||
msgstr "%1 vart utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:459 src/models/actionsmodel.cpp:536
|
||||
#: src/models/actionsmodel.cpp:460 src/models/actionsmodel.cpp:537
|
||||
msgid "<user id> [<reason>]"
|
||||
msgstr "<brukar-ID> [<grunngjeving>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:460
|
||||
#: src/models/actionsmodel.cpp:461
|
||||
msgid "Bans the given user"
|
||||
msgstr "Utestengjer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:477
|
||||
#: src/models/actionsmodel.cpp:478
|
||||
#, kde-format
|
||||
msgid "You are not allowed to unban users from this room."
|
||||
msgstr "Du har ikkje løyve til å oppheva utestenging frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:482
|
||||
#: src/models/actionsmodel.cpp:483
|
||||
#, kde-format
|
||||
msgctxt "<user> is not banned from this room."
|
||||
msgid "%1 is not banned from this room."
|
||||
msgstr "%1 er ikkje utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:486
|
||||
#: src/models/actionsmodel.cpp:487
|
||||
#, kde-format
|
||||
msgctxt "<username> was unbanned from this room."
|
||||
msgid "%1 was unbanned from this room."
|
||||
msgstr "%1 er ikkje lenger utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:493
|
||||
#: src/models/actionsmodel.cpp:494
|
||||
msgid "Removes the ban of the given user"
|
||||
msgstr "Opphevar utestenging av brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:508
|
||||
#: src/models/actionsmodel.cpp:509
|
||||
#, kde-format
|
||||
msgid "You cannot kick yourself from the room."
|
||||
msgstr "Du kan ikkje kasta deg sjølv ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:512
|
||||
#: src/models/actionsmodel.cpp:513
|
||||
#, kde-format
|
||||
msgctxt "<username> is not in this room"
|
||||
msgid "%1 is not in this room."
|
||||
msgstr "%1 er ikkje med i rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:521
|
||||
#: src/models/actionsmodel.cpp:522
|
||||
#, kde-format
|
||||
msgid "You are not allowed to kick users from this room."
|
||||
msgstr "Du har ikkje løyve til å kasta brukarar ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:527
|
||||
#: src/models/actionsmodel.cpp:528
|
||||
#, kde-format
|
||||
msgctxt "You are not allowed to kick <username> from this room"
|
||||
msgid "You are not allowed to kick %1 from this room."
|
||||
msgstr "Du har ikkje løyve til å kasta %1 ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:531
|
||||
#: src/models/actionsmodel.cpp:532
|
||||
#, kde-format
|
||||
msgctxt "<username> was kicked from this room."
|
||||
msgid "%1 was kicked from this room."
|
||||
msgstr "%1 vart kasta ut av rommet."
|
||||
|
||||
# Er dette noko anna enn å kasta brukaren ut?
|
||||
#: src/models/actionsmodel.cpp:537
|
||||
#: src/models/actionsmodel.cpp:538
|
||||
msgid "Removes the user from the room"
|
||||
msgstr "Fjernar brukaren frå rommet"
|
||||
|
||||
@@ -636,13 +636,13 @@ msgctxt "'Custom' is a category of emoji"
|
||||
msgid "Custom"
|
||||
msgstr "Tilpassa"
|
||||
|
||||
#: src/models/imagepacksmodel.cpp:86
|
||||
#: src/models/imagepacksmodel.cpp:87
|
||||
#, kde-format
|
||||
msgctxt "As in 'The user's own Stickers'"
|
||||
msgid "Own Stickers"
|
||||
msgstr "Eigne klistremerke"
|
||||
|
||||
#: src/models/imagepacksmodel.cpp:86
|
||||
#: src/models/imagepacksmodel.cpp:87
|
||||
#, kde-format
|
||||
msgctxt "As in 'The user's own emojis"
|
||||
msgid "Own Emojis"
|
||||
@@ -1079,12 +1079,12 @@ msgstr "oppdaterte tilstanden"
|
||||
msgid "started a poll"
|
||||
msgstr "starta ei avstemming"
|
||||
|
||||
#: src/neochatroom.cpp:1627 src/neochatroom.cpp:1628
|
||||
#: src/neochatroom.cpp:1631 src/neochatroom.cpp:1632
|
||||
#, kde-format
|
||||
msgid "Report sent successfully."
|
||||
msgstr "Rapporten er no send."
|
||||
|
||||
#: src/neochatroom.cpp:1924 src/neochatroom.cpp:1932
|
||||
#: src/neochatroom.cpp:1928 src/neochatroom.cpp:1936
|
||||
#, kde-format
|
||||
msgctxt "'Lat' and 'Lon' as in Latitude and Longitude"
|
||||
msgid "Lat: %1, Lon: %2"
|
||||
@@ -1095,7 +1095,7 @@ msgstr "Breiddegr.: %1 – lengdegr.: %2"
|
||||
msgid "Encrypted Message"
|
||||
msgstr "Kryptert melding"
|
||||
|
||||
#: src/notificationsmanager.cpp:201 src/qml/main.qml:253
|
||||
#: src/notificationsmanager.cpp:201 src/qml/main.qml:257
|
||||
#, kde-format
|
||||
msgid "%1: %2"
|
||||
msgstr "%1: %2"
|
||||
@@ -1362,7 +1362,8 @@ msgstr "Avvis"
|
||||
msgid "Accept"
|
||||
msgstr "Godta"
|
||||
|
||||
#: src/qml/Component/LocationPage.qml:17 src/qml/Panel/RoomDrawer.qml:200
|
||||
#: src/qml/Component/LocationPage.qml:17
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:116
|
||||
#, kde-format
|
||||
msgctxt "Locations on a map"
|
||||
msgid "Locations"
|
||||
@@ -1608,22 +1609,22 @@ msgstr "Lydstyrke"
|
||||
msgid "Maximize"
|
||||
msgstr "Maksimer"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:153
|
||||
#: src/qml/Component/TimelineView.qml:160
|
||||
#, kde-format
|
||||
msgid "Jump to first unread message"
|
||||
msgstr "Gå til første ulesne melding"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:176
|
||||
#: src/qml/Component/TimelineView.qml:183
|
||||
#, kde-format
|
||||
msgid "Jump to latest message"
|
||||
msgstr "Gå til nyaste melding"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:202
|
||||
#: src/qml/Component/TimelineView.qml:209
|
||||
#, kde-format
|
||||
msgid "Drag items here to share them"
|
||||
msgstr "Dra element her for å dela dei"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:228
|
||||
#: src/qml/Component/TimelineView.qml:235
|
||||
#, kde-format
|
||||
msgctxt "Message displayed when some users are typing"
|
||||
msgid "%2 is typing"
|
||||
@@ -1956,80 +1957,80 @@ msgctxt "@title:menu Account detail dialog"
|
||||
msgid "Account detail"
|
||||
msgstr "Kontodetaljar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:80
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:81
|
||||
#, kde-format
|
||||
msgid "Unignore this user"
|
||||
msgstr "Avignorer brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:80
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:81
|
||||
#, kde-format
|
||||
msgid "Ignore this user"
|
||||
msgstr "Ignorer brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:92
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:93
|
||||
#, kde-format
|
||||
msgid "Kick this user"
|
||||
msgstr "Kast ut brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:105
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:106
|
||||
#, kde-format
|
||||
msgid "Invite this user"
|
||||
msgstr "Inviter brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:117
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:118
|
||||
#, kde-format
|
||||
msgid "Ban this user"
|
||||
msgstr "Utesteng brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:122
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:123
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Ban User"
|
||||
msgstr "Utesteng brukar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:133
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:134
|
||||
#, kde-format
|
||||
msgid "Unban this user"
|
||||
msgstr "Opphev utestenging av brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:145
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:146
|
||||
#, kde-format
|
||||
msgid "Set user power level"
|
||||
msgstr "Vel maktnivå for brukar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:169
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:170
|
||||
#, kde-format
|
||||
msgid "Remove recent messages by this user"
|
||||
msgstr "Fjern nylege meldingar frå brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:174
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:175
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Remove Messages"
|
||||
msgstr "Fjern meldingar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:184
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:185
|
||||
#, kde-format
|
||||
msgid "Open a private chat"
|
||||
msgstr "Start privat prat"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:194
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:195
|
||||
#, kde-format
|
||||
msgid "Copy link"
|
||||
msgstr "Kopier lenkje"
|
||||
|
||||
#: src/qml/main.qml:293
|
||||
#: src/qml/main.qml:297
|
||||
#, kde-format
|
||||
msgctxt "@title:window"
|
||||
msgid "Session Verification"
|
||||
msgstr "Øktstadfesting"
|
||||
|
||||
#: src/qml/main.qml:305
|
||||
#: src/qml/main.qml:309
|
||||
#, kde-format
|
||||
msgid "User consent"
|
||||
msgstr "Brukarsamtykke"
|
||||
|
||||
#: src/qml/main.qml:310
|
||||
#: src/qml/main.qml:314
|
||||
#, kde-format
|
||||
msgid ""
|
||||
"Your homeserver requires you to agree to its terms and conditions before "
|
||||
@@ -2038,17 +2039,17 @@ msgstr ""
|
||||
"Heimetenaren krev at du godtek brukarvilkåra før du kan ta han i bruk. Trykk "
|
||||
"på knappen nedanfor for å lesa vilkåra."
|
||||
|
||||
#: src/qml/main.qml:315
|
||||
#: src/qml/main.qml:319
|
||||
#, kde-format
|
||||
msgid "Open"
|
||||
msgstr "Opna"
|
||||
|
||||
#: src/qml/main.qml:350
|
||||
#: src/qml/main.qml:354
|
||||
#, kde-format
|
||||
msgid "Start a chat"
|
||||
msgstr "Start prat"
|
||||
|
||||
#: src/qml/main.qml:352
|
||||
#: src/qml/main.qml:356
|
||||
#, kde-format
|
||||
msgid "Do you want to start a chat with %1?"
|
||||
msgstr "Vil du starta ein prat med %1?"
|
||||
@@ -2226,25 +2227,25 @@ msgstr "Feil ved deling"
|
||||
msgid "Shared url for image is <a href='%1'>%1</a>"
|
||||
msgstr "Delt adresse for biletet er <a href='%1'>%1</a>"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:16
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:18
|
||||
#, kde-format
|
||||
msgid "Ban User"
|
||||
msgstr "Utesteng brukar"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:20
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:22
|
||||
#, kde-format
|
||||
msgid "Reason for banning this user"
|
||||
msgstr "Grunngjeving for utestenging av brukaren"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:32
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:34
|
||||
#, kde-format
|
||||
msgctxt "@action:button 'Ban' as in 'Ban this user'"
|
||||
msgid "Ban"
|
||||
msgstr "Utesteng"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:41
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:43
|
||||
#: src/qml/Menu/Timeline/RemoveSheet.qml:49
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:41 src/qml/Page/InviteUserPage.qml:24
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:43 src/qml/Page/InviteUserPage.qml:24
|
||||
#, kde-format
|
||||
msgctxt "@action"
|
||||
msgid "Cancel"
|
||||
@@ -2281,7 +2282,7 @@ msgstr "Fjern melding"
|
||||
|
||||
#: src/qml/Menu/Timeline/FileDelegateContextMenu.qml:83
|
||||
#: src/qml/Menu/Timeline/MessageDelegateContextMenu.qml:59
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:32
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:34
|
||||
#, kde-format
|
||||
msgctxt ""
|
||||
"@action:button 'Report' as in 'Report this event to the administrators'"
|
||||
@@ -2348,17 +2349,17 @@ msgctxt "@action:button 'Remove' as in 'Remove this message'"
|
||||
msgid "Remove"
|
||||
msgstr "Fjern"
|
||||
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:16
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:18
|
||||
#, kde-format
|
||||
msgid "Report Message"
|
||||
msgstr "Rapporter melding"
|
||||
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:20
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:22
|
||||
#, kde-format
|
||||
msgid "Reason for reporting this message"
|
||||
msgstr "Grunngjeving for rapportering av meldinga"
|
||||
|
||||
#: src/qml/Page/DevtoolsPage.qml:17 src/qml/Panel/RoomDrawer.qml:159
|
||||
#: src/qml/Page/DevtoolsPage.qml:17 src/qml/RoomDrawer/RoomInformation.qml:75
|
||||
#, kde-format
|
||||
msgid "Developer Tools"
|
||||
msgstr "Utviklarverktøy"
|
||||
@@ -2519,7 +2520,7 @@ msgstr "Vart med"
|
||||
#, kde-format
|
||||
msgctxt "@info:label"
|
||||
msgid "No rooms found"
|
||||
msgstr ""
|
||||
msgstr "Fann ingen rom"
|
||||
|
||||
#: src/qml/Page/RoomList/AccountMenu.qml:18
|
||||
#: src/qml/Page/RoomList/UserInfo.qml:175
|
||||
@@ -2634,7 +2635,9 @@ msgstr "Av"
|
||||
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:117
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:119
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:186 src/qml/Panel/RoomDrawer.qml:105
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:186
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:98
|
||||
#: src/qml/RoomDrawer/RoomDrawerPage.qml:37
|
||||
#, kde-format
|
||||
msgid "Room Settings"
|
||||
msgstr "Romval"
|
||||
@@ -2646,7 +2649,7 @@ msgid "Leave Room"
|
||||
msgstr "Forlat rommet"
|
||||
|
||||
#: src/qml/Page/RoomList/ExploreComponent.qml:19
|
||||
#: src/qml/Page/RoomList/Page.qml:154
|
||||
#: src/qml/Page/RoomList/Page.qml:153
|
||||
#, kde-format
|
||||
msgid "Explore rooms"
|
||||
msgstr "Utforsk rom"
|
||||
@@ -2657,28 +2660,28 @@ msgstr "Utforsk rom"
|
||||
msgid "Create rooms and chats"
|
||||
msgstr "Opprett rom og diskusjonar"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:151
|
||||
#: src/qml/Page/RoomList/Page.qml:150
|
||||
#, kde-format
|
||||
msgid "No rooms found"
|
||||
msgstr "Fann ingen rom"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:151
|
||||
#: src/qml/Page/RoomList/Page.qml:150
|
||||
#, kde-format
|
||||
msgid "Join some rooms to get started"
|
||||
msgstr "Start ved å verta med i nokre rom"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:154
|
||||
#: src/qml/Page/RoomList/Page.qml:153
|
||||
#, kde-format
|
||||
msgid "Search in room directory"
|
||||
msgstr "Søk i romkatalogen"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:196
|
||||
#: src/qml/Page/RoomList/Page.qml:195
|
||||
#, kde-format
|
||||
msgctxt "Collapse <section name>"
|
||||
msgid "Collapse %1"
|
||||
msgstr "Fald saman %1"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:196
|
||||
#: src/qml/Page/RoomList/Page.qml:195
|
||||
#, kde-format
|
||||
msgctxt "Expand <section name"
|
||||
msgid "Expand %1"
|
||||
@@ -2750,7 +2753,7 @@ msgstr "Byt brukar"
|
||||
msgid "Open Settings"
|
||||
msgstr "Opna innstillingar"
|
||||
|
||||
#: src/qml/Page/RoomPage.qml:42
|
||||
#: src/qml/Page/RoomPage.qml:50
|
||||
#, kde-format
|
||||
msgid "NeoChat is offline. Please check your network connection."
|
||||
msgstr "NeoChat er fråkopla. Sjå til at du er kopla til nettet."
|
||||
@@ -2791,97 +2794,103 @@ msgstr "Start ny prat"
|
||||
msgid "Welcome to Matrix"
|
||||
msgstr "Velkommen til Matrix"
|
||||
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:62
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:62
|
||||
#, kde-format
|
||||
msgid "No name"
|
||||
msgstr "Namnlaus"
|
||||
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:71
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:71
|
||||
#, kde-format
|
||||
msgid "No Canonical Alias"
|
||||
msgstr "Manglar kanonisk alias"
|
||||
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:81
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:81
|
||||
#, kde-format
|
||||
msgid "No Topic"
|
||||
msgstr "Manglar emne"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:95
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:88
|
||||
#, kde-format
|
||||
msgid "Room information"
|
||||
msgstr "Rominformasjon"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:102
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:95
|
||||
#, kde-format
|
||||
msgid "Room settings"
|
||||
msgstr "Romval"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:38
|
||||
#, kde-format
|
||||
msgctxt "@action:title"
|
||||
msgid "Room information"
|
||||
msgstr ""
|
||||
|
||||
# Er ulike handlingar ein kan gjera, for eksempel gjera rommet til favoritt. «Handlingar» fungerer derfor betre enn «Val» eller «Alternativ».
|
||||
#: src/qml/Panel/RoomDrawer.qml:143
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:59
|
||||
#, kde-format
|
||||
msgid "Options"
|
||||
msgstr "Handlingar"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:153
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:69
|
||||
#, kde-format
|
||||
msgid "Open developer tools"
|
||||
msgstr "Opna utviklarverktøy"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:167
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:83
|
||||
#, kde-format
|
||||
msgid "Search in this room"
|
||||
msgstr "Søk i rommet"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:175
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:91
|
||||
#, kde-format
|
||||
msgctxt "@action:title"
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:184
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:100
|
||||
#, kde-format
|
||||
msgid "Remove room from favorites"
|
||||
msgstr "Fjern rommet frå favorittar"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:184
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:100
|
||||
#, kde-format
|
||||
msgid "Make room favorite"
|
||||
msgstr "Gjer rommet til favoritt"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:195
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:111
|
||||
#, kde-format
|
||||
msgid "Show locations for this room"
|
||||
msgstr "Vis posisjonar i rommet"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:207
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:123
|
||||
#, kde-format
|
||||
msgid "Members"
|
||||
msgstr "Medlemmar"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:218
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:134
|
||||
#, kde-format
|
||||
msgid "Search user in room"
|
||||
msgstr "Søk etter brukarar i rommet"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:231
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:147
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Invite a User"
|
||||
msgstr "Inviter ein brukar"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:234
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:150
|
||||
#, kde-format
|
||||
msgid "Invite user to room"
|
||||
msgstr "Inviter brukar til rommet"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:241
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:157
|
||||
#, kde-format
|
||||
msgid "%1 member"
|
||||
msgid_plural "%1 members"
|
||||
msgstr[0] "%1 medlem"
|
||||
msgstr[1] "%1 medlemmar"
|
||||
|
||||
#: src/qml/Panel/RoomDrawer.qml:241
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:157
|
||||
#, kde-format
|
||||
msgid "No member count"
|
||||
msgstr "Manglar medlemstal"
|
||||
|
||||
680
po/pa/neochat.po
680
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
686
po/pl/neochat.po
686
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
679
po/pt/neochat.po
679
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
686
po/ru/neochat.po
686
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
693
po/sk/neochat.po
693
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/sl/neochat.po
697
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
690
po/sv/neochat.po
690
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
695
po/ta/neochat.po
695
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
689
po/tr/neochat.po
689
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/uk/neochat.po
696
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -74,8 +74,6 @@ add_library(neochat STATIC
|
||||
blurhash.h
|
||||
blurhashimageprovider.cpp
|
||||
blurhashimageprovider.h
|
||||
models/collapsestateproxymodel.cpp
|
||||
models/collapsestateproxymodel.h
|
||||
models/mediamessagefiltermodel.cpp
|
||||
models/mediamessagefiltermodel.h
|
||||
urlhelper.cpp
|
||||
@@ -125,6 +123,9 @@ add_library(neochat STATIC
|
||||
events/pollevent.cpp
|
||||
pollhandler.cpp
|
||||
utils.h
|
||||
registration.cpp
|
||||
neochatconnection.cpp
|
||||
neochatconnection.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
@@ -141,6 +142,11 @@ add_executable(neochat-app
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res.generated.qrc
|
||||
)
|
||||
|
||||
if(TARGET Qt::WebView)
|
||||
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
||||
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
target_link_libraries(neochat-app PRIVATE
|
||||
@@ -177,6 +183,7 @@ endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark QCoro::Core)
|
||||
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkProxy>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QQuickWindow>
|
||||
@@ -33,10 +32,8 @@
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/logout.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
@@ -105,8 +102,8 @@ Controller::Controller(QObject *parent)
|
||||
static int oldAccountCount = 0;
|
||||
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
|
||||
if (m_accountRegistry.size() > oldAccountCount) {
|
||||
auto connection = m_accountRegistry.accounts()[m_accountRegistry.size() - 1];
|
||||
connect(connection, &Connection::syncDone, this, [connection]() {
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
|
||||
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
|
||||
NotificationsManager::instance().handleNotifications(connection);
|
||||
});
|
||||
}
|
||||
@@ -141,38 +138,7 @@ void Controller::toggleWindow()
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
{
|
||||
if (!conn) {
|
||||
qCritical() << "Attempt to logout null connection";
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsGroup("Accounts"_ls).remove(conn->userId());
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
job.setKey(conn->userId());
|
||||
QEventLoop loop;
|
||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
if (m_accountRegistry.count() > 1) {
|
||||
// Only set the connection if the the account being logged out is currently active
|
||||
if (conn == activeConnection()) {
|
||||
setActiveConnection(m_accountRegistry.accounts()[0]);
|
||||
}
|
||||
} else {
|
||||
setActiveConnection(nullptr);
|
||||
}
|
||||
if (!serverSideLogout) {
|
||||
return;
|
||||
}
|
||||
conn->logout();
|
||||
}
|
||||
|
||||
void Controller::addConnection(Connection *c)
|
||||
void Controller::addConnection(NeoChatConnection *c)
|
||||
{
|
||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
||||
|
||||
@@ -180,17 +146,17 @@ void Controller::addConnection(Connection *c)
|
||||
|
||||
c->setLazyLoading(true);
|
||||
|
||||
connect(c, &Connection::syncDone, this, [this, c] {
|
||||
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
|
||||
Q_EMIT syncDone();
|
||||
|
||||
c->sync(30000);
|
||||
c->saveState();
|
||||
});
|
||||
connect(c, &Connection::loggedOut, this, [this, c] {
|
||||
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
|
||||
dropConnection(c);
|
||||
});
|
||||
|
||||
connect(c, &Connection::requestFailed, this, [this](BaseJob *job) {
|
||||
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
||||
if (job->error() == BaseJob::UserConsentRequired) {
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
@@ -202,7 +168,7 @@ void Controller::addConnection(Connection *c)
|
||||
Q_EMIT accountCountChanged();
|
||||
}
|
||||
|
||||
void Controller::dropConnection(Connection *c)
|
||||
void Controller::dropConnection(NeoChatConnection *c)
|
||||
{
|
||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
||||
|
||||
@@ -231,19 +197,19 @@ void Controller::invokeLogin()
|
||||
return;
|
||||
}
|
||||
|
||||
auto connection = new Connection(account.homeserver());
|
||||
connect(connection, &Connection::connected, this, [this, connection, id] {
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
if (connection->userId() == id) {
|
||||
setActiveConnection(connection);
|
||||
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
|
||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
logout(connection, false);
|
||||
connection->logout(false);
|
||||
} else if (error == "Connection closed"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
// Failed due to network connection issue. This might happen when the homeserver is
|
||||
@@ -251,11 +217,11 @@ void Controller::invokeLogin()
|
||||
// connect to the homeserver. In this case, we don't want to do logout().
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
logout(connection, true);
|
||||
connection->logout(true);
|
||||
}
|
||||
Q_EMIT initiated();
|
||||
});
|
||||
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
@@ -321,22 +287,6 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const
|
||||
return true;
|
||||
}
|
||||
|
||||
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
||||
{
|
||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||
connect(job, &BaseJob::success, this, [conn, job] {
|
||||
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::markAllMessagesAsRead(Connection *conn)
|
||||
{
|
||||
const auto rooms = conn->allRooms();
|
||||
for (auto room : rooms) {
|
||||
room->markAllMessagesAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::supportSystemTray() const
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -347,49 +297,6 @@ bool Controller::supportSystemTray() const
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = currentPassword;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = connection->user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
|
||||
if (innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls) {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Wrong);
|
||||
} else {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Other);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
||||
{
|
||||
User *localUser = connection->user();
|
||||
QString decoded = avatarSource.path();
|
||||
if (decoded.isEmpty()) {
|
||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
|
||||
return true;
|
||||
}
|
||||
if (QImageReader(decoded).read().isNull()) {
|
||||
return false;
|
||||
} else {
|
||||
return localUser->setAvatar(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
||||
{
|
||||
@@ -400,6 +307,14 @@ NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, b
|
||||
setRequestData(_data);
|
||||
}
|
||||
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("DisableDeviceJob"), "_matrix/client/v3/account/deactivate")
|
||||
{
|
||||
QJsonObject data;
|
||||
addParam<IfNotEmpty>(data, QStringLiteral("auth"), auth);
|
||||
setRequestData(data);
|
||||
}
|
||||
|
||||
int Controller::accountCount() const
|
||||
{
|
||||
return m_accountRegistry.count();
|
||||
@@ -425,7 +340,7 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
#endif
|
||||
}
|
||||
|
||||
Connection *Controller::activeConnection() const
|
||||
NeoChatConnection *Controller::activeConnection() const
|
||||
{
|
||||
if (m_connection.isNull()) {
|
||||
return nullptr;
|
||||
@@ -433,49 +348,43 @@ Connection *Controller::activeConnection() const
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void Controller::setActiveConnection(Connection *connection)
|
||||
void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (connection == m_connection) {
|
||||
return;
|
||||
}
|
||||
if (m_connection != nullptr) {
|
||||
disconnect(m_connection, &Connection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &Connection::accountDataChanged, this, nullptr);
|
||||
disconnect(m_connection, &NeoChatConnection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &NeoChatConnection::accountDataChanged, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
if (connection != nullptr) {
|
||||
NeoChatConfig::self()->setActiveConnection(connection->userId());
|
||||
connect(connection, &Connection::networkError, this, [this]() {
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this]() {
|
||||
if (!m_isOnline) {
|
||||
return;
|
||||
}
|
||||
m_isOnline = false;
|
||||
Q_EMIT isOnlineChanged(false);
|
||||
});
|
||||
connect(connection, &Connection::syncDone, this, [this] {
|
||||
connect(connection, &NeoChatConnection::syncDone, this, [this] {
|
||||
if (m_isOnline) {
|
||||
return;
|
||||
}
|
||||
m_isOnline = true;
|
||||
Q_EMIT isOnlineChanged(true);
|
||||
});
|
||||
connect(connection, &Connection::requestFailed, this, [](BaseJob *job) {
|
||||
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NeoChatConfig::self()->setActiveConnection(QString());
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionIndexChanged();
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
|
||||
PushRuleModel *Controller::pushRuleModel() const
|
||||
@@ -646,41 +555,6 @@ bool Controller::isFlatpak() const
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::setActiveAccountLabel(const QString &label)
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
QJsonObject json{
|
||||
{"account_label"_ls, label},
|
||||
};
|
||||
m_connection->setAccountData("org.kde.neochat.account_label"_ls, json);
|
||||
}
|
||||
|
||||
QString Controller::activeAccountLabel() const
|
||||
{
|
||||
if (!m_connection) {
|
||||
return {};
|
||||
}
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString();
|
||||
}
|
||||
|
||||
QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
|
||||
{
|
||||
auto roomVersions = connection->availableRoomVersions();
|
||||
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id"_ls, v.id);
|
||||
roomVersionMap.insert("status"_ls, v.status);
|
||||
roomVersionMap.insert("isStable"_ls, v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
AccountRegistry &Controller::accounts()
|
||||
{
|
||||
return m_accountRegistry;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <KFormat>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/settings.h>
|
||||
@@ -20,7 +21,6 @@ class QQuickTextDocument;
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
class Room;
|
||||
class User;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class Controller : public QObject
|
||||
/**
|
||||
* @brief The current connection for the rest of NeoChat to use.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
Q_PROPERTY(NeoChatConnection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The PushRuleModel that has the active connection's push rules.
|
||||
@@ -62,16 +62,6 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief The account label for the active account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString activeAccountLabel READ activeAccountLabel WRITE setActiveAccountLabel NOTIFY activeAccountLabelChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
|
||||
*/
|
||||
@@ -109,46 +99,28 @@ public:
|
||||
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
void setActiveConnection(Quotient::Connection *connection);
|
||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||
void setActiveConnection(NeoChatConnection *connection);
|
||||
[[nodiscard]] NeoChatConnection *activeConnection() const;
|
||||
|
||||
[[nodiscard]] PushRuleModel *pushRuleModel() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
void addConnection(Quotient::Connection *c);
|
||||
void addConnection(NeoChatConnection *c);
|
||||
|
||||
/**
|
||||
* @brief Drop a connection from the account registry.
|
||||
*/
|
||||
void dropConnection(Quotient::Connection *c);
|
||||
void dropConnection(NeoChatConnection *c);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
|
||||
[[nodiscard]] QString activeAccountLabel() const;
|
||||
void setActiveAccountLabel(const QString &label);
|
||||
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||
|
||||
/**
|
||||
* @brief Create new room for a group chat.
|
||||
*/
|
||||
@@ -210,14 +182,12 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions(Quotient::Connection *connection);
|
||||
|
||||
Quotient::AccountRegistry &accounts();
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
QPointer<Quotient::Connection> m_connection;
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
||||
@@ -244,8 +214,8 @@ Q_SIGNALS:
|
||||
/// Error occurred because of server or bug in NeoChat
|
||||
void globalErrorOccured(QString error, QString detail);
|
||||
void syncDone();
|
||||
void connectionAdded(Quotient::Connection *_t1);
|
||||
void connectionDropped(Quotient::Connection *_t1);
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void accountCountChanged();
|
||||
void initiated();
|
||||
void notificationClicked(const QString &_t1, const QString &_t2);
|
||||
@@ -256,18 +226,14 @@ Q_SIGNALS:
|
||||
void userConsentRequired(QUrl url);
|
||||
void testConnectionResult(const QString &connection, bool usable);
|
||||
void isOnlineChanged(bool isOnline);
|
||||
void keyVerificationRequest(int timeLeft, Quotient::Connection *connection, const QString &transactionId, const QString &deviceId);
|
||||
void keyVerificationRequest(int timeLeft, NeoChatConnection *connection, const QString &transactionId, const QString &deviceId);
|
||||
void keyVerificationStart();
|
||||
void keyVerificationAccept(const QString &commitment);
|
||||
void keyVerificationKey(const QString &sas);
|
||||
void activeConnectionIndexChanged();
|
||||
void roomAdded(NeoChatRoom *room);
|
||||
void activeAccountLabelChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
||||
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
|
||||
static void markAllMessagesAsRead(Quotient::Connection *conn);
|
||||
void saveWindowGeometry();
|
||||
};
|
||||
|
||||
@@ -283,3 +249,9 @@ class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
||||
public:
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
|
||||
};
|
||||
|
||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ int DelegateSizeHelper::calculateCurrentPercentageWidth() const
|
||||
int maxPercentWidth = endPercentBigger ? m_endPercentWidth : m_startPercentWidth;
|
||||
int minPercentWidth = endPercentBigger ? m_startPercentWidth : m_endPercentWidth;
|
||||
|
||||
int calcPercentWidth = std::ceil(m * m_parentWidth + c);
|
||||
int calcPercentWidth = std::round(m * m_parentWidth + c);
|
||||
return std::clamp(calcPercentWidth, minPercentWidth, maxPercentWidth);
|
||||
}
|
||||
|
||||
@@ -146,9 +146,9 @@ qreal DelegateSizeHelper::currentWidth() const
|
||||
|
||||
qreal absoluteWidth = m_parentWidth * percentWidth * 0.01;
|
||||
if (m_maxWidth < 0.0) {
|
||||
return std::ceil(absoluteWidth);
|
||||
return std::round(absoluteWidth);
|
||||
} else {
|
||||
return std::ceil(std::min(absoluteWidth, m_maxWidth));
|
||||
return std::round(std::min(absoluteWidth, m_maxWidth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -19,6 +20,10 @@ LinkPreviewer::LinkPreviewer(QObject *parent, NeoChatRoom *room, const QUrl &url
|
||||
, m_url(url)
|
||||
{
|
||||
loadUrlPreview();
|
||||
if (m_currentRoom) {
|
||||
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||
}
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||
}
|
||||
|
||||
bool LinkPreviewer::loaded() const
|
||||
@@ -57,6 +62,9 @@ void LinkPreviewer::setUrl(QUrl url)
|
||||
|
||||
void LinkPreviewer::loadUrlPreview()
|
||||
{
|
||||
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (m_url.scheme() == QStringLiteral("https")) {
|
||||
m_loaded = false;
|
||||
Q_EMIT loadedChanged();
|
||||
|
||||
@@ -22,7 +22,7 @@ Login::Login(QObject *parent)
|
||||
void Login::init()
|
||||
{
|
||||
m_homeserverReachable = false;
|
||||
m_connection = new Connection();
|
||||
m_connection = new NeoChatConnection();
|
||||
m_matrixId = QString();
|
||||
m_password = QString();
|
||||
m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
|
||||
@@ -51,7 +51,7 @@ void Login::init()
|
||||
m_testing = true;
|
||||
Q_EMIT testingChanged();
|
||||
if (!m_connection) {
|
||||
m_connection = new Connection();
|
||||
m_connection = new NeoChatConnection();
|
||||
}
|
||||
m_connection->resolveServer(m_matrixId);
|
||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
|
||||
@@ -87,7 +87,11 @@ void Login::init()
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
connect(m_connection, &Connection::loginError, this, [this](QString error, const QString &) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
if (error == QStringLiteral("Invalid username or password")) {
|
||||
setInvalidPassword(true);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
}
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
@@ -133,6 +137,7 @@ QString Login::password() const
|
||||
|
||||
void Login::setPassword(const QString &password)
|
||||
{
|
||||
setInvalidPassword(false);
|
||||
m_password = password;
|
||||
Q_EMIT passwordChanged();
|
||||
}
|
||||
@@ -199,4 +204,15 @@ bool Login::isLoggedIn() const
|
||||
return m_isLoggedIn;
|
||||
}
|
||||
|
||||
void Login::setInvalidPassword(bool invalid)
|
||||
{
|
||||
m_invalidPassword = invalid;
|
||||
Q_EMIT isInvalidPasswordChanged();
|
||||
}
|
||||
|
||||
bool Login::isInvalidPassword() const
|
||||
{
|
||||
return m_invalidPassword;
|
||||
}
|
||||
|
||||
#include "moc_login.cpp"
|
||||
|
||||
17
src/login.h
17
src/login.h
@@ -6,10 +6,7 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class Login
|
||||
@@ -73,6 +70,11 @@ class Login : public QObject
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the password (or the username) is invalid.
|
||||
*/
|
||||
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
|
||||
|
||||
public:
|
||||
explicit Login(QObject *parent = nullptr);
|
||||
|
||||
@@ -100,6 +102,9 @@ public:
|
||||
|
||||
bool isLoggedIn() const;
|
||||
|
||||
bool isInvalidPassword() const;
|
||||
void setInvalidPassword(bool invalid);
|
||||
|
||||
Q_INVOKABLE void login();
|
||||
Q_INVOKABLE void loginWithSso();
|
||||
|
||||
@@ -116,6 +121,7 @@ Q_SIGNALS:
|
||||
void testingChanged();
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
void isInvalidPasswordChanged();
|
||||
|
||||
private:
|
||||
void setHomeserverReachable(bool reachable);
|
||||
@@ -126,9 +132,10 @@ private:
|
||||
QString m_deviceName;
|
||||
bool m_supportsSso = false;
|
||||
bool m_supportsPassword = false;
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
NeoChatConnection *m_connection = nullptr;
|
||||
QUrl m_ssoUrl;
|
||||
bool m_testing = false;
|
||||
bool m_isLoggingIn = false;
|
||||
bool m_isLoggedIn = false;
|
||||
bool m_invalidPassword = false;
|
||||
};
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -18,6 +18,10 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
#include <QtWebView>
|
||||
#endif
|
||||
|
||||
#include <KAboutData>
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
#include <KDBusService>
|
||||
@@ -50,7 +54,6 @@
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/accountemoticonmodel.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/devicesproxymodel.h"
|
||||
@@ -77,6 +80,7 @@
|
||||
#include "models/userlistmodel.h"
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "pollhandler.h"
|
||||
@@ -95,6 +99,7 @@
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
#endif
|
||||
#include "registration.h"
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include <Windows.h>
|
||||
@@ -139,6 +144,10 @@ int main(int argc, char *argv[])
|
||||
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
QtWebView::initialize();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QGuiApplication app(argc, argv);
|
||||
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
|
||||
@@ -233,6 +242,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Controller::instance().accounts());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Registration", &Registration::instance());
|
||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
||||
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
||||
@@ -240,7 +250,6 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||
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");
|
||||
@@ -273,12 +282,12 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<User>("org.kde.neochat", 1, 0, "User", {});
|
||||
qmlRegisterUncreatableType<NeoChatRoom>("org.kde.neochat", 1, 0, "NeoChatRoom", {});
|
||||
qmlRegisterUncreatableType<NeoChatConnection>("org.kde.neochat", 1, 0, "NeoChatConnection", {});
|
||||
|
||||
qRegisterMetaType<User *>("User*");
|
||||
qRegisterMetaType<User *>("const User*");
|
||||
qRegisterMetaType<User *>("const Quotient::User*");
|
||||
qRegisterMetaType<Room *>("Room*");
|
||||
qRegisterMetaType<Connection *>("Connection*");
|
||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||
qRegisterMetaType<NeoChatRoom *>("NeoChatRoom*");
|
||||
qRegisterMetaType<User *>("User*");
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| (source_row < sourceModel()->rowCount() - 1
|
||||
&& sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State) // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||
}
|
||||
|
||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
} else if (role == StateEventsRole) {
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_collapsestateproxymodel.cpp"
|
||||
@@ -1,80 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class CollapseStateProxyModel
|
||||
*
|
||||
* This model aggregates multiple sequential state events into a single entry.
|
||||
*
|
||||
* Events are only aggregated if they happened on the same day.
|
||||
*
|
||||
* @sa MessageEventModel
|
||||
*/
|
||||
class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) 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;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
@@ -145,7 +145,7 @@ void CompletionModel::updateCompletion()
|
||||
m_filterModel->setFullText(m_fullText);
|
||||
m_filterModel->setFilterText(m_text);
|
||||
m_filterModel->invalidate();
|
||||
} else if (text().startsWith(QLatin1Char(':')) && !text()[1].isUpper()
|
||||
} else if (text().startsWith(QLatin1Char(':')) && text().size() > 1 && !text()[1].isUpper()
|
||||
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|
||||
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
|
||||
m_filterModel->setSourceModel(m_emojiModel);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
|
||||
/**
|
||||
* @class MediaMessageFilterModel
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
SourceRole = CollapseStateProxyModel::LastRole + 1, /**< The mxc source URL for the item. */
|
||||
SourceRole = MessageFilterModel::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. */
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "messagefiltermodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
@@ -32,22 +34,192 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
// Don't show redacted (i.e. deleted) messages.
|
||||
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show hidden or replaced messages.
|
||||
const int specialMarks = index.data(MessageEventModel::SpecialMarksRole).toInt();
|
||||
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show events with an unknown type.
|
||||
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show state events that are not the first in a consecutive group on the
|
||||
// same day as they will be grouped as a single delegate.
|
||||
const bool notLastRow = sourceRow < sourceModel()->rowCount() - 1;
|
||||
const bool previousEventIsState =
|
||||
sourceModel()->data(sourceModel()->index(sourceRow + 1, 0), MessageEventModel::DelegateTypeRole) == MessageEventModel::DelegateType::State;
|
||||
const bool newDay = sourceModel()->data(sourceModel()->index(sourceRow, 0), MessageEventModel::ShowSectionRole).toBool();
|
||||
if (eventType == MessageEventModel::State && notLastRow && previousEventIsState && !newDay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant MessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
} else if (role == StateEventsRole) {
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MessageFilterModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::authorList(int sourceRow) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString MessageFilterModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_messagefiltermodel.cpp"
|
||||
|
||||
@@ -5,21 +5,79 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
/**
|
||||
* @class MessageFilterModel
|
||||
*
|
||||
* This model filters out any messages that should be hidden.
|
||||
*
|
||||
* Deleted messages are only hidden if the user hasn't set them to be shown.
|
||||
*
|
||||
* The model also contains the roles and functions to support aggregating multiple
|
||||
* consecutive state events into a single delegate. The state events must all happen
|
||||
* on the same day to be aggregated.
|
||||
*/
|
||||
class MessageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
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
|
||||
};
|
||||
|
||||
explicit MessageFilterModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter function to remove hidden 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;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == TextRole) {
|
||||
if (reaction.authors.count() > 1) {
|
||||
return QStringLiteral("%1 %2").arg(reaction.reaction, reaction.authors.count());
|
||||
return QStringLiteral("%1 %2").arg(reaction.reaction, QString::number(reaction.authors.count()));
|
||||
} else {
|
||||
return reaction.reaction;
|
||||
}
|
||||
|
||||
155
src/neochatconnection.cpp
Normal file
155
src/neochatconnection.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <QImageReader>
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <qt5keychain/keychain.h>
|
||||
#else
|
||||
#include <qt6keychain/keychain.h>
|
||||
#endif
|
||||
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/settings.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::logout(bool serverSideLogout)
|
||||
{
|
||||
SettingsGroup(QStringLiteral("Accounts")).remove(userId());
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
job.setKey(userId());
|
||||
QEventLoop loop;
|
||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
if (Controller::instance().accounts().count() > 1) {
|
||||
// Only set the connection if the the account being logged out is currently active
|
||||
if (this == Controller::instance().activeConnection()) {
|
||||
Controller::instance().setActiveConnection(dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().accounts()[0]));
|
||||
}
|
||||
} else {
|
||||
Controller::instance().setActiveConnection(nullptr);
|
||||
}
|
||||
if (!serverSideLogout) {
|
||||
return;
|
||||
}
|
||||
Connection::logout();
|
||||
}
|
||||
|
||||
bool NeoChatConnection::setAvatar(const QUrl &avatarSource)
|
||||
{
|
||||
QString decoded = avatarSource.path();
|
||||
if (decoded.isEmpty()) {
|
||||
callApi<SetAvatarUrlJob>(user()->id(), avatarSource);
|
||||
return true;
|
||||
}
|
||||
if (QImageReader(decoded).read().isNull()) {
|
||||
return false;
|
||||
} else {
|
||||
return user()->setAvatar(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList NeoChatConnection::getSupportedRoomVersions() const
|
||||
{
|
||||
const auto &roomVersions = availableRoomVersions();
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const auto &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id"_ls, v.id);
|
||||
roomVersionMap.insert("status"_ls, v.status);
|
||||
roomVersionMap.insert("isStable"_ls, v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
auto job = callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = currentPassword;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
NeochatChangePasswordJob *innerJob = callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, []() {
|
||||
Q_EMIT Controller::instance().passwordStatus(Controller::PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob]() {
|
||||
Q_EMIT Controller::instance().passwordStatus(innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls ? Controller::PasswordStatus::Wrong
|
||||
: Controller::PasswordStatus::Other);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::setLabel(const QString &label)
|
||||
{
|
||||
QJsonObject json{
|
||||
{"account_label"_ls, label},
|
||||
};
|
||||
setAccountData("org.kde.neochat.account_label"_ls, json);
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
|
||||
QString NeoChatConnection::label() const
|
||||
{
|
||||
return accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString();
|
||||
}
|
||||
|
||||
void NeoChatConnection::deactivateAccount(const QString &password)
|
||||
{
|
||||
auto job = callApi<NeoChatDeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = password;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto innerJob = callApi<NeoChatDeactivateAccountJob>(authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
logout(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
53
src/neochatconnection.h
Normal file
53
src/neochatconnection.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
class NeoChatConnection : public Quotient::Connection
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The account label for this account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
|
||||
public:
|
||||
NeoChatConnection(QObject *parent = nullptr);
|
||||
NeoChatConnection(const QUrl &server, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void logout(bool serverSideLogout);
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(const QUrl &avatarSource);
|
||||
|
||||
[[nodiscard]] QString label() const;
|
||||
void setLabel(const QString &label);
|
||||
|
||||
Q_INVOKABLE void deactivateAccount(const QString &password);
|
||||
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
};
|
||||
@@ -111,7 +111,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = avatar(128);
|
||||
}
|
||||
NotificationsManager::instance().postInviteNotification(this, htmlSafeDisplayName(), htmlSafeMemberName(senderId), avatar_image);
|
||||
NotificationsManager::instance().postInviteNotification(this, displayNameForHtml(), htmlSafeMemberName(senderId), avatar_image);
|
||||
});
|
||||
connect(this, &Room::changed, this, [this] {
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
@@ -978,11 +978,6 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
|
||||
return roomMemberEvent->membership() == Membership::Ban;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::htmlSafeDisplayName() const
|
||||
{
|
||||
return displayName().toHtmlEscaped();
|
||||
}
|
||||
|
||||
void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reason)
|
||||
{
|
||||
doDeleteMessagesByUser(user, reason);
|
||||
|
||||
@@ -111,11 +111,6 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||
|
||||
/**
|
||||
* @brief Display name with any html special characters escaped.
|
||||
*/
|
||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
||||
|
||||
/**
|
||||
* @brief The avatar image to be used for the room.
|
||||
*/
|
||||
@@ -591,8 +586,6 @@ public:
|
||||
|
||||
[[nodiscard]] bool readMarkerLoaded() const;
|
||||
|
||||
QString htmlSafeDisplayName() const;
|
||||
|
||||
/**
|
||||
* @brief Get subtitle text for room
|
||||
*
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
|
||||
#include <QPainter>
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/pushrules.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "texthandler.h"
|
||||
@@ -37,7 +37,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
void NotificationsManager::handleNotifications(QPointer<Connection> connection)
|
||||
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
|
||||
{
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
@@ -49,7 +49,7 @@ void NotificationsManager::handleNotifications(QPointer<Connection> connection)
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization)
|
||||
void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization)
|
||||
{
|
||||
if (job == nullptr) {
|
||||
return;
|
||||
@@ -145,7 +145,7 @@ void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection>
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationsManager::shouldPostNotification(QPointer<Quotient::Connection> connection, const QJsonValue ¬ification)
|
||||
bool NotificationsManager::shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification)
|
||||
{
|
||||
if (connection == nullptr) {
|
||||
return false;
|
||||
@@ -211,7 +211,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
return;
|
||||
}
|
||||
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
||||
Controller::instance().setActiveConnection(Controller::instance().accounts().get(room->localUser()->id()));
|
||||
Controller::instance().setActiveConnection(dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id())));
|
||||
}
|
||||
RoomManager::instance().enterRoom(room);
|
||||
});
|
||||
|
||||
@@ -12,11 +12,7 @@
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
class NeoChatConnection;
|
||||
class KNotification;
|
||||
class NeoChatRoom;
|
||||
|
||||
@@ -80,7 +76,7 @@ public:
|
||||
/**
|
||||
* @brief Handle the notifications for the given connection.
|
||||
*/
|
||||
void handleNotifications(QPointer<Quotient::Connection> connection);
|
||||
void handleNotifications(QPointer<NeoChatConnection> connection);
|
||||
|
||||
private:
|
||||
explicit NotificationsManager(QObject *parent = nullptr);
|
||||
@@ -90,13 +86,13 @@ private:
|
||||
|
||||
QStringList m_connActiveJob;
|
||||
|
||||
bool shouldPostNotification(QPointer<Quotient::Connection> connection, const QJsonValue ¬ification);
|
||||
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification);
|
||||
|
||||
QHash<QString, KNotification *> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
|
||||
private Q_SLOTS:
|
||||
void processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
|
||||
private:
|
||||
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
|
||||
|
||||
49
src/qml/Component/Login/Captcha.qml
Normal file
49
src/qml/Component/Login/Captcha.qml
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtWebView 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
FormCard.AbstractFormDelegate {
|
||||
background: null
|
||||
contentItem: WebView {
|
||||
id: webview
|
||||
url: "http://localhost:20847"
|
||||
implicitHeight: 500
|
||||
onLoadingChanged: {
|
||||
webview.runJavaScript("document.body.style.background = '" + Kirigami.Theme.backgroundColor + "'")
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
repeat: true
|
||||
running: true
|
||||
interval: 300
|
||||
onTriggered: {
|
||||
if(!webview.visible) {
|
||||
return
|
||||
}
|
||||
webview.runJavaScript("!!grecaptcha ? grecaptcha.getResponse() : \"\"", function(response){
|
||||
if(!webview.visible || !response)
|
||||
return
|
||||
timer.running = false;
|
||||
Registration.recaptchaResponse = response;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
59
src/qml/Component/Login/Email.qml
Normal file
59
src/qml/Component/Login/Email.qml
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) emailField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: emailField
|
||||
label: i18n("Add an e-mail address:")
|
||||
placeholderText: "user@example.com"
|
||||
onTextChanged: Registration.email = text
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
id: confirmMessage
|
||||
text: i18n("Confirm e-mail address")
|
||||
visible: false
|
||||
description: i18n("A confirmation e-mail has been sent to your address. Please continue here <b>after</b> clicking on the confirmation link in the e-mail")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: resendButton
|
||||
text: i18nc("@button", "Re-send confirmation e-mail")
|
||||
onClicked: Registration.registerEmail()
|
||||
visible: false
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
enabled: emailField.text.length > 0
|
||||
onTriggered: {
|
||||
if (confirmMessage.visible) {
|
||||
Registration.registerAccount()
|
||||
} else {
|
||||
Registration.registerEmail()
|
||||
confirmMessage.visible = true
|
||||
resendButton.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
@@ -6,56 +6,42 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
|
||||
property bool loading: false
|
||||
onActiveFocusChanged: if (activeFocus) urlField.forceActiveFocus()
|
||||
|
||||
title: i18nc("@title", "Select a Homeserver")
|
||||
|
||||
action: Kirigami.Action {
|
||||
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput
|
||||
onTriggered: {
|
||||
// TODO
|
||||
console.log("register todo")
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: urlField
|
||||
label: i18n("Server Url:")
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||
}
|
||||
onTextChanged: timer.restart()
|
||||
statusMessage: Registration.status === Registration.ServerNoRegistration ? i18n("Registration is disabled on this server.") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onHomeserverChanged: {
|
||||
LoginHelper.testHomeserver("@user:" + homeserver)
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 500
|
||||
onTriggered: Registration.homeserver = urlField.text
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Component.onCompleted: Controller.testHomeserver(homeserver)
|
||||
|
||||
QQC2.ComboBox {
|
||||
id: serverCombo
|
||||
|
||||
Kirigami.FormData.label: i18n("Homeserver:")
|
||||
model: ["matrix.org", "kde.org", "tchncs.de", i18n("Other...")]
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
id: customHomeserver
|
||||
|
||||
Kirigami.FormData.label: i18n("Url:")
|
||||
visible: serverCombo.currentIndex === 3
|
||||
onTextChanged: {
|
||||
Controller.testHomeserver(text)
|
||||
}
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: continueButton
|
||||
text: i18nc("@action:button", "Continue")
|
||||
action: root.action
|
||||
}
|
||||
nextAction: Kirigami.Action {
|
||||
text: Registration.testing ? i18n("Loading") : null
|
||||
enabled: Registration.status > Registration.ServerNoRegistration
|
||||
onTriggered: root.processed("qrc:/Username.qml");
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/LoginRegister.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,19 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
property var showContinueButton: false
|
||||
property var showBackButton: false
|
||||
|
||||
QQC2.Label {
|
||||
LoginStep {
|
||||
id: root
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Please wait. This might take a little while.")
|
||||
}
|
||||
FormCard.AbstractFormDelegate {
|
||||
contentItem: QQC2.BusyIndicator {}
|
||||
background: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
|
||||
@@ -7,43 +7,34 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: login
|
||||
id: root
|
||||
|
||||
showContinueButton: true
|
||||
showBackButton: false
|
||||
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Enter your Matrix ID")
|
||||
onActiveFocusChanged: if (activeFocus) matrixIdField.forceActiveFocus()
|
||||
|
||||
Component.onCompleted: {
|
||||
LoginHelper.matrixId = ""
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
QQC2.TextField {
|
||||
id: matrixIdField
|
||||
Kirigami.FormData.label: i18n("Matrix ID:")
|
||||
placeholderText: "@user:matrix.org"
|
||||
Accessible.name: i18n("Matrix ID")
|
||||
onTextChanged: {
|
||||
LoginHelper.matrixId = text
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: matrixIdField
|
||||
label: i18n("Matrix ID:")
|
||||
placeholderText: "@user:example.org"
|
||||
Accessible.name: i18n("Matrix ID")
|
||||
onTextChanged: {
|
||||
LoginHelper.matrixId = text
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
matrixIdField.forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
login.action.trigger()
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
action: Kirigami.Action {
|
||||
nextAction: Kirigami.Action {
|
||||
text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue")
|
||||
onTriggered: {
|
||||
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
|
||||
@@ -56,4 +47,9 @@ LoginStep {
|
||||
}
|
||||
enabled: LoginHelper.homeserverReachable
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
root.processed("qrc:/LoginRegister.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,26 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: loginMethod
|
||||
id: root
|
||||
|
||||
title: i18n("Login Methods")
|
||||
onActiveFocusChanged: if (activeFocus) loginPasswordButton.forceActiveFocus()
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with password")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginPasswordButton
|
||||
text: i18nc("@action:button", "Login with password")
|
||||
onClicked: processed("qrc:/Password.qml")
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with single sign-on")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginSsoButton
|
||||
text: i18nc("@action:button", "Login with single sign-on")
|
||||
onClicked: processed("qrc:/Sso.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,25 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: loginRegister
|
||||
id: root
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onActiveFocusChanged: if (activeFocus) loginButton.forceActiveFocus()
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Login.qml")
|
||||
Layout.fillWidth: true
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginButton
|
||||
text: i18nc("@action:button", "Login")
|
||||
onClicked: root.processed("qrc:/Login.qml")
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Register")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Homeserver.qml")
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Register")
|
||||
onClicked: root.processed("qrc:/Homeserver.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,25 @@ import QtQuick.Layouts 1.14
|
||||
|
||||
/// Step for the login/registration flow
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string title: i18n("Welcome")
|
||||
property string message: i18n("Welcome")
|
||||
property bool showContinueButton: false
|
||||
property bool showBackButton: false
|
||||
property bool acceptable: false
|
||||
property string previousUrl: ""
|
||||
/// Set to true if the login step does not have any controls. This will ensure that the focus remains on the "continue" button
|
||||
property bool noControls: false
|
||||
|
||||
/// Process this module, this is called by the continue button.
|
||||
/// Should call \sa processed when it finish successfully.
|
||||
property QQC2.Action action: null
|
||||
property QQC2.Action nextAction: null
|
||||
|
||||
/// Go to the previous module. This is called by the "go back" button.
|
||||
/// If no "go back" button should be shown, this should be null.
|
||||
property QQC2.Action previousAction: null
|
||||
|
||||
/// Called when switching to the next step.
|
||||
signal processed(url nextUrl)
|
||||
|
||||
/// Show a message in a banner at the top of the page.
|
||||
signal showMessage(string message)
|
||||
|
||||
/// Clears any error messages currently being shown
|
||||
signal clearError()
|
||||
}
|
||||
|
||||
@@ -6,25 +6,12 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: password
|
||||
|
||||
title: i18nc("@title", "Password")
|
||||
message: i18n("Enter your password")
|
||||
showContinueButton: true
|
||||
showBackButton: true
|
||||
previousUrl: LoginHelper.isLoggingIn ? "" : LoginHelper.supportsSso ? "qrc:/LoginMethod.qml" : "qrc:/Login.qml"
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Login")
|
||||
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||
onTriggered: {
|
||||
LoginHelper.login();
|
||||
}
|
||||
}
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
@@ -33,20 +20,32 @@ LoginStep {
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Kirigami.PasswordField {
|
||||
id: passwordField
|
||||
onTextChanged: LoginHelper.password = text
|
||||
enabled: !LoginHelper.isLoggingIn
|
||||
Accessible.name: i18n("Password")
|
||||
onActiveFocusChanged: if(activeFocus) passwordField.forceActiveFocus()
|
||||
|
||||
Component.onCompleted: {
|
||||
passwordField.forceActiveFocus()
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
password.action.trigger()
|
||||
}
|
||||
label: i18n("Password:")
|
||||
onTextChanged: LoginHelper.password = text
|
||||
enabled: !LoginHelper.isLoggingIn
|
||||
echoMode: TextInput.Password
|
||||
Accessible.name: i18n("Password")
|
||||
statusMessage: LoginHelper.isInvalidPassword ? i18n("Invalid username or password") : ""
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Login")
|
||||
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||
onTriggered: {
|
||||
root.clearError()
|
||||
LoginHelper.login();
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: processed("qrc:/Login.qml")
|
||||
}
|
||||
}
|
||||
|
||||
51
src/qml/Component/Login/RegisterPassword.qml
Normal file
51
src/qml/Component/Login/RegisterPassword.qml
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) passwordField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
label: i18n("Password:")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: Registration.password = text
|
||||
Keys.onReturnPressed: {
|
||||
confirmPasswordField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: confirmPasswordField
|
||||
label: i18n("Confirm Password:")
|
||||
enabled: passwordField.enabled
|
||||
echoMode: TextInput.Password
|
||||
statusMessage: passwordField.text.length === confirmPasswordField.text.length && passwordField.text !== confirmPasswordField.text ? i18n("The passwords do not match.") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
passwordField.enabled = false
|
||||
Registration.registerAccount()
|
||||
}
|
||||
enabled: passwordField.text === confirmPasswordField.text
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -6,47 +6,38 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Login with single sign-on")
|
||||
noControls: true
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
||||
loginButton.enabled = true
|
||||
loginButton.text = i18n("Login")
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/Loading.qml")
|
||||
}
|
||||
Component.onCompleted: LoginHelper.loginWithSso()
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
RowLayout {
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
|
||||
onClicked: {
|
||||
module.source = "qrc:/Login.qml"
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: loginButton
|
||||
text: i18n("Login")
|
||||
onClicked: {
|
||||
LoginHelper.loginWithSso()
|
||||
loginButton.enabled = false
|
||||
loginButton.text = i18n("Loading…")
|
||||
}
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/Loading.qml")
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Continue the login process in your browser.")
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: processed("qrc:/Login.qml")
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Re-open SSO URL")
|
||||
onTriggered: UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/qml/Component/Login/Terms.qml
Normal file
39
src/qml/Component/Login/Terms.qml
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
noControls: true
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Terms & Conditions")
|
||||
description: i18n("By continuing with the registration, you agree to the following terms and conditions:")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Registration.terms
|
||||
delegate: FormCard.FormTextDelegate {
|
||||
text: "<a href=\"" + modelData.url + "\">" + modelData.title + "</a>"
|
||||
onLinkActivated: Qt.openUrlExternally(modelData.url)
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
Registration.registerAccount()
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
46
src/qml/Component/Login/Username.qml
Normal file
46
src/qml/Component/Login/Username.qml
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) usernameField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: usernameField
|
||||
label: i18n("Username:")
|
||||
placeholderText: "user"
|
||||
onTextChanged: timer.restart()
|
||||
statusMessage: Registration.status === Registration.UsernameTaken ? i18n("Username unavailable") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 500
|
||||
onTriggered: Registration.username = usernameField.text
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: Registration.status === Registration.TestingUsername ? i18n("Loading") : null
|
||||
|
||||
onTriggered: root.processed("qrc:/RegisterPassword.qml")
|
||||
enabled: Registration.status === Registration.Ready
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Homeserver.qml")
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
readonly property var currentJsonSource: model.data(model.index(content.currentIndex, 0), MessageEventModel.SourceRole)
|
||||
|
||||
autoLoad: false
|
||||
|
||||
downloadAction: Components.DownloadAction {
|
||||
id: downloadAction
|
||||
onTriggered: {
|
||||
@@ -91,7 +89,7 @@ Components.AlbumMaximizeComponent {
|
||||
file: parent,
|
||||
mimeType: root.currentMimeType,
|
||||
progressInfo: root.currentProgressInfo,
|
||||
plainMessage: root.currentPlainText
|
||||
plainText: root.currentPlainText
|
||||
});
|
||||
contextMenu.closeFullscreen.connect(root.close)
|
||||
contextMenu.open();
|
||||
|
||||
@@ -375,10 +375,7 @@ ColumnLayout {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: root.author
|
||||
}).open();
|
||||
RoomManager.visitUser(root.author.object, "mention")
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
@@ -459,10 +456,7 @@ ColumnLayout {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: root.author
|
||||
}).open();
|
||||
RoomManager.visitUser(root.author.object, "mention")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -605,6 +599,7 @@ ColumnLayout {
|
||||
source: root.jsonSource,
|
||||
eventType: root.delegateType,
|
||||
plainText: root.plainText,
|
||||
htmlText: root.display,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
@@ -177,13 +177,13 @@ TimelineContainer {
|
||||
|
||||
onDurationChanged: {
|
||||
if (!duration) {
|
||||
vid.supportStreaming = false;
|
||||
root.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
onErrorChanged: {
|
||||
if (error != MediaPlayer.NoError) {
|
||||
vid.supportStreaming = false;
|
||||
root.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ TimelineContainer {
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: if (vid.supportStreaming || root.progressInfo.completed) {
|
||||
onTapped: if (root.supportStreaming || root.progressInfo.completed) {
|
||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||
vid.pause()
|
||||
} else {
|
||||
|
||||
@@ -17,10 +17,10 @@ QQC2.ScrollView {
|
||||
required property NeoChatRoom currentRoom
|
||||
onCurrentRoomChanged: {
|
||||
roomChanging = true;
|
||||
roomChangingTimer.restart()
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
messageListView.positionViewAtBeginning();
|
||||
hasScrolledUpBefore = false;
|
||||
roomChanging = true;
|
||||
}
|
||||
property bool roomChanging: false
|
||||
readonly property bool atYEnd: messageListView.atYEnd
|
||||
@@ -47,19 +47,16 @@ QQC2.ScrollView {
|
||||
interactive: Kirigami.Settings.isMobile
|
||||
bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2)
|
||||
|
||||
model: collapseStateProxyModel
|
||||
model: sortedMessageEventModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
room: root.currentRoom
|
||||
}
|
||||
|
||||
CollapseStateProxyModel {
|
||||
id: collapseStateProxyModel
|
||||
sourceModel: MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
sourceModel: messageEventModel
|
||||
}
|
||||
MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
sourceModel: messageEventModel
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -79,6 +76,13 @@ QQC2.ScrollView {
|
||||
messageEventModel.fetchMore(messageEventModel.index(0, 0));
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: roomChangingTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
root.roomChanging = false
|
||||
}
|
||||
}
|
||||
onAtYEndChanged: if (!root.roomChanging) {
|
||||
if (atYEnd && root.hasScrolledUpBefore) {
|
||||
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
|
||||
@@ -215,12 +219,6 @@ QQC2.ScrollView {
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
TypingPane {
|
||||
id: typingPane
|
||||
visible: root.currentRoom && root.currentRoom.usersTyping.length > 0
|
||||
@@ -348,7 +346,7 @@ QQC2.ScrollView {
|
||||
|
||||
MediaMessageFilterModel {
|
||||
id: mediaMessageFilterModel
|
||||
sourceModel: collapseStateProxyModel
|
||||
sourceModel: sortedMessageEventModel
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -438,11 +436,4 @@ QQC2.ScrollView {
|
||||
function positionViewAtBeginning() {
|
||||
messageListView.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: root.currentRoom,
|
||||
user: root.currentRoom.getUser(user.id),
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
46
src/qml/Dialog/ConfirmDeactivateAccountDialog.qml
Normal file
46
src/qml/Dialog/ConfirmDeactivateAccountDialog.qml
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
property var connection
|
||||
|
||||
title: i18nc("@title", "Deactivate Account")
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Deactivate Account")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18nc("@title", "Warning")
|
||||
description: i18n("Your account will be permanently disabled.\nThis cannot be undone.\nYour Matrix ID will not be available for new accounts.\nYour messages will stay available.")
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
label: i18n("Password")
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Deactivate account")
|
||||
icon.name: "emblem-warning"
|
||||
enabled: passwordField.text.length > 0
|
||||
onClicked: {
|
||||
root.connection.deactivateAccount(passwordField.text)
|
||||
root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ QQC2.Dialog {
|
||||
text: i18n("Sign out")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
Controller.logout(root.connection, true);
|
||||
root.connection.logout(true);
|
||||
root.close();
|
||||
root.accepted();
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ Kirigami.Dialog {
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
text: user.displayName
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
|
||||
@@ -24,7 +24,9 @@ Labs.MenuBar {
|
||||
text: i18nc("menu", "Configure NeoChat...")
|
||||
|
||||
shortcut: StandardKey.Preferences
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {}, {
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {
|
||||
connection: Controller.activeConnection
|
||||
}, {
|
||||
title: i18n("Configure")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: banSheet
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ Loader {
|
||||
required property string source
|
||||
property string selectedText: ""
|
||||
required property string plainText
|
||||
property string htmlText: undefined
|
||||
|
||||
property list<Kirigami.Action> nestedActions
|
||||
|
||||
@@ -40,6 +41,20 @@ Loader {
|
||||
currentRoom.chatBoxEditId = "";
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward")
|
||||
icon.name: "mail-forward-symbolic"
|
||||
onTriggered: {
|
||||
let page = applicationWindow().pageStack.pushDialogLayer("qrc:/ChooseRoomDialog.qml", {}, {
|
||||
title: i18nc("@title", "Forward Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
page.chosen.connect(function(targetRoomId) {
|
||||
Controller.activeConnection.room(targetRoomId).postHtmlMessage(loadRoot.plainText, loadRoot.htmlText ? loadRoot.htmlText : loadRoot.plainText)
|
||||
page.closeDialog()
|
||||
})
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
|
||||
@@ -7,6 +7,8 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: reportSheet
|
||||
|
||||
|
||||
39
src/qml/Page/ChooseRoomDialog.qml
Normal file
39
src/qml/Page/ChooseRoomDialog.qml
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import "./RoomList"
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title", "Choose a Room")
|
||||
|
||||
signal chosen(string roomId)
|
||||
|
||||
header: Kirigami.SearchField {
|
||||
onTextChanged: sortModel.filterText = text
|
||||
}
|
||||
|
||||
ListView {
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortModel
|
||||
sourceModel: RoomListModel {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
delegate: RoomDelegate {
|
||||
id: roomDelegate
|
||||
filterText: ""
|
||||
onClicked: {
|
||||
root.chosen(roomDelegate.currentRoom.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,15 @@ Kirigami.Page {
|
||||
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
|
||||
}
|
||||
|
||||
actions {
|
||||
left: Kirigami.Action {
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
id: undoAction
|
||||
text: i18nc("@action:button Undo modification", "Undo")
|
||||
icon.name: "edit-undo"
|
||||
onTriggered: imageDoc.undo();
|
||||
visible: imageDoc.edited
|
||||
}
|
||||
main: Kirigami.Action {
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: okAction
|
||||
text: i18nc("@action:button Accept image modification", "Accept")
|
||||
icon.name: "dialog-ok"
|
||||
@@ -54,7 +54,7 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,10 +122,10 @@ Kirigami.ScrollablePage {
|
||||
|
||||
title: i18nc("@title:window", "Add server")
|
||||
|
||||
onSheetOpenChanged: if (!serverUrlField.isValidServer && !sheetOpen) {
|
||||
onOpened: if (!serverUrlField.isValidServer && !opened) {
|
||||
serverField.currentIndex = 0
|
||||
server = serverField.currentValue
|
||||
} else if (sheetOpen) {
|
||||
} else if (opened) {
|
||||
serverUrlField.forceActiveFocus()
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ RowLayout {
|
||||
text: i18n("Create a Space")
|
||||
icon.name: "list-add"
|
||||
onTriggered: {
|
||||
let dialog = createSpaceDialog.createObject(root.overlay);
|
||||
let dialog = createSpaceDialog.createObject(applicationWindow().overlay);
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
|
||||
@@ -72,7 +72,12 @@ QQC2.Control {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
onCountChanged: root.enabled = count > 0
|
||||
onCountChanged: {
|
||||
root.enabled = count > 0
|
||||
if (!Controller.activeConnection.room(root.selectedSpaceId)) {
|
||||
root.selectedSpaceId = ""
|
||||
}
|
||||
}
|
||||
Component.onCompleted: root.enabled = count > 0
|
||||
|
||||
delegate: AvatarTabButton {
|
||||
|
||||
@@ -193,7 +193,7 @@ QQC2.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: (Controller.activeAccountLabel.length > 0 ? (Controller.activeAccountLabel + " ") : "") + Controller.activeConnection.localUser.id
|
||||
text: (Controller.activeConnection.label.length > 0 ? (Controller.activeConnection.label + " ") : "") + Controller.activeConnection.localUser.id
|
||||
font.pointSize: displayNameLabel.font.pointSize * 0.8
|
||||
opacity: 0.7
|
||||
textFormat: Text.PlainText
|
||||
|
||||
@@ -27,6 +27,14 @@ Kirigami.Page {
|
||||
focus: true
|
||||
padding: 0
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
visible: Kirigami.Settings.isMobile || !applicationWindow().pageStack.wideMode
|
||||
icon.name: "view-right-new"
|
||||
onTriggered: applicationWindow().openRoomDrawer()
|
||||
}
|
||||
]
|
||||
|
||||
KeyNavigation.left: pageStack.get(0)
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
@@ -38,7 +46,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: Controller
|
||||
function onIsOnlineChanged() {
|
||||
if (true || !Controller.isOnline) {
|
||||
if (!Controller.isOnline) {
|
||||
banner.text = i18n("NeoChat is offline. Please check your network connection.");
|
||||
banner.visible = true;
|
||||
banner.type = Kirigami.MessageType.Error;
|
||||
@@ -181,7 +189,22 @@ Kirigami.Page {
|
||||
banner.visible = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onShowUserDetail(user) {
|
||||
root.showUserDetail(user)
|
||||
}
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
timelineViewLoader.item.showUserDetail(user)
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: root.currentRoom,
|
||||
user: root.currentRoom.getUser(user.id),
|
||||
}).open();
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
UserDetailDialog {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,16 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: welcomePage
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
property alias currentStep: module.item
|
||||
|
||||
title: module.item.title ?? i18n("Welcome")
|
||||
title: i18n("Welcome")
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
@@ -25,79 +26,111 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: LoginHelper.init()
|
||||
FormCard.FormCard {
|
||||
id: contentCard
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
FormCard.AbstractFormDelegate {
|
||||
contentItem: Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||
}
|
||||
background: Item {}
|
||||
onActiveFocusChanged: if (activeFocus) module.item.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onInitiated() {
|
||||
pageStack.layers.pop();
|
||||
FormCard.FormTextDelegate {
|
||||
id: welcomeMessage
|
||||
text: AccountRegistry.accountCount > 0 ? i18n("Log in to a different account or create a new account.") : i18n("Welcome to NeoChat! Continue by logging in or creating a new account.")
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 25
|
||||
text: module.item.message ?? module.item.title ?? i18n("Welcome to Matrix")
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: welcomeMessage
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: module
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/Login.qml"
|
||||
onSourceChanged: {
|
||||
headerMessage.visible = false
|
||||
headerMessage.text = ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
source: "qrc:/LoginRegister.qml"
|
||||
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
|
||||
enabled: welcomePage.currentStep.previousUrl !== ""
|
||||
visible: welcomePage.currentStep.showBackButton
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
module.source = welcomePage.currentStep.previousUrl
|
||||
function onProcessed(nextUrl) {
|
||||
module.source = nextUrl;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus()
|
||||
} else {
|
||||
continueButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
function onShowMessage(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
function onClearError() {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: continueButton
|
||||
enabled: welcomePage.currentStep.acceptable
|
||||
visible: welcomePage.currentStep.showContinueButton
|
||||
action: welcomePage.currentStep.action
|
||||
Connections {
|
||||
target: Registration
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("qrc:/Captcha.qml")
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("qrc:/Terms.qml")
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("qrc:/Email.qml")
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("qrc:/Loading.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: currentStep
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
}
|
||||
|
||||
function onProcessed(nextUrl) {
|
||||
module.source = nextUrl;
|
||||
}
|
||||
function onShowMessage(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
LoginHelper.init()
|
||||
module.item.forceActiveFocus()
|
||||
Registration.username = ""
|
||||
Registration.password = ""
|
||||
Registration.email = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates 1.0 as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components 1.0 as KirigamiComponents
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: roomDrawer
|
||||
readonly property NeoChatRoom room: RoomManager.currentRoom
|
||||
|
||||
width: actualWidth
|
||||
|
||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
||||
property int actualWidth: {
|
||||
if (Config.roomDrawerWidth === -1) {
|
||||
return Kirigami.Units.gridUnit * 20;
|
||||
} else {
|
||||
return Config.roomDrawerWidth
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: undefined
|
||||
width: 2
|
||||
z: 500
|
||||
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
||||
enabled: true
|
||||
visible: true
|
||||
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
|
||||
onReleased: {
|
||||
Config.roomDrawerWidth = roomDrawer.actualWidth;
|
||||
Config.save();
|
||||
}
|
||||
property real _lastX: -1
|
||||
|
||||
onPositionChanged: {
|
||||
if (_lastX === -1) {
|
||||
return;
|
||||
}
|
||||
if (Qt.application.layoutDirection === Qt.RightToLeft) {
|
||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
|
||||
} else {
|
||||
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
|
||||
}
|
||||
}
|
||||
}
|
||||
enabled: true
|
||||
|
||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||
|
||||
// If modal has been changed and the drawer is closed automatically then dim on popup open will have been switched off in main.qml so switch it back on after the animation completes.
|
||||
// This is to avoid dim being active for a split second when the drawer is switched to modal which looks terrible.
|
||||
onAnimatingChanged: if (dim === false) dim = undefined
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
contentItem: Loader {
|
||||
id: loader
|
||||
active: roomDrawer.drawerOpen
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
readonly property string userSearchText: userListView.headerItem ? userListView.headerItem.userListSearchField.text : ''
|
||||
property alias highlightedUser: userListView.currentIndex
|
||||
|
||||
spacing: 0
|
||||
|
||||
function clearSearch() {
|
||||
userListView.headerItem.userListSearchField.text = ""
|
||||
}
|
||||
|
||||
QQC2.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredHeight: pageStack.globalToolBar.preferredHeight
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Room information")
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: settingsButton
|
||||
|
||||
icon.name: "settings-configure"
|
||||
text: i18n("Room settings")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
|
||||
header: ColumnLayout {
|
||||
id: columnLayout
|
||||
|
||||
property alias userListSearchField: userListSearchField
|
||||
|
||||
spacing: 0
|
||||
width: userListView.width
|
||||
|
||||
Loader {
|
||||
active: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
sourceComponent: room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
||||
onItemChanged: if (item) {
|
||||
userListView.positionViewAtBeginning();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Options")
|
||||
activeFocusOnTab: false
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: devtoolsButton
|
||||
|
||||
icon.name: "tools"
|
||||
text: i18n("Open developer tools")
|
||||
visible: Config.developerTools
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.pushDialogLayer("qrc:/DevtoolsPage.qml", {room: room}, {title: i18n("Developer Tools")})
|
||||
}
|
||||
}
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: searchButton
|
||||
|
||||
icon.name: "search"
|
||||
text: i18n("Search in this room")
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
|
||||
currentRoom: room
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: favouriteButton
|
||||
|
||||
icon.name: 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)
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
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")
|
||||
})
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
spacing: 0
|
||||
visible: !room.isDirectChat()
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
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.pushDialogLayer("qrc:/InviteUserPage.qml", {room: roomDrawer.room}, {title: i18nc("@title", "Invite a User")})
|
||||
}
|
||||
|
||||
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 {
|
||||
room: roomDrawer.room
|
||||
}
|
||||
|
||||
sortRole: "powerLevel"
|
||||
sortOrder: Qt.DescendingOrder
|
||||
filterRole: "name"
|
||||
filterCaseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
|
||||
model: room.isDirectChat() ? 0 : sortedMessageEventModel
|
||||
|
||||
clip: true
|
||||
activeFocusOnTab: true
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: userDelegate
|
||||
|
||||
required property string name
|
||||
required property string userId
|
||||
required property string avatar
|
||||
required property int powerLevel
|
||||
required property string powerLevelString
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
text: name
|
||||
|
||||
onClicked: {
|
||||
userDelegate.highlighted = true;
|
||||
const popup = userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: room,
|
||||
user: room.getUser(userDelegate.userId)
|
||||
});
|
||||
popup.closed.connect(() => {
|
||||
userDelegate.highlighted = false;
|
||||
});
|
||||
if (roomDrawer.modal) {
|
||||
roomDrawer.close();
|
||||
}
|
||||
popup.open();
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
implicitWidth: height
|
||||
sourceSize {
|
||||
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
}
|
||||
source: userDelegate.avatar
|
||||
name: userDelegate.userId
|
||||
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
text: userDelegate.name
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
visible: userDelegate.powerLevel > 0
|
||||
|
||||
text: userDelegate.powerLevelString
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRoomChanged: {
|
||||
if (loader.active) {
|
||||
loader.item.clearSearch()
|
||||
loader.item.highlightedUser = -1
|
||||
}
|
||||
if (room == null) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: groupChatDrawerHeader
|
||||
GroupChatDrawerHeader {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: directChatDrawerHeader
|
||||
DirectChatDrawerHeader {}
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,7 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
onClicked: {
|
||||
const popup = userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: room,
|
||||
user: room.getUser(room.directChatRemoteUser.id),
|
||||
})
|
||||
popup.closed.connect(() => {
|
||||
userListItem.highlighted = false
|
||||
})
|
||||
if (roomDrawer.modal) {
|
||||
roomDrawer.close()
|
||||
}
|
||||
popup.open()
|
||||
RoomManager.visitUser(room.getUser(room.directChatRemoteUser.id).object, "mention")
|
||||
}
|
||||
|
||||
contentItem: KirigamiComponents.Avatar {
|
||||
@@ -68,7 +68,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: TextEdit.PlainText
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
|
||||
visible: room && room.canonicalAlias
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
121
src/qml/RoomDrawer/RoomDrawer.qml
Normal file
121
src/qml/RoomDrawer/RoomDrawer.qml
Normal file
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates 1.0 as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components 1.0 as KirigamiComponents
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: root
|
||||
readonly property NeoChatRoom room: RoomManager.currentRoom
|
||||
|
||||
width: actualWidth
|
||||
|
||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
||||
property int actualWidth: {
|
||||
if (Config.roomDrawerWidth === -1) {
|
||||
return Kirigami.Units.gridUnit * 20;
|
||||
} else {
|
||||
return Config.roomDrawerWidth
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: undefined
|
||||
width: 2
|
||||
z: 500
|
||||
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
||||
enabled: true
|
||||
visible: true
|
||||
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
|
||||
onReleased: {
|
||||
Config.roomDrawerWidth = root.actualWidth;
|
||||
Config.save();
|
||||
}
|
||||
property real _lastX: -1
|
||||
|
||||
onPositionChanged: {
|
||||
if (_lastX === -1) {
|
||||
return;
|
||||
}
|
||||
if (Qt.application.layoutDirection === Qt.RightToLeft) {
|
||||
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
|
||||
} else {
|
||||
root.actualWidth = Math.min(root.maxWidth, Math.max(root.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
|
||||
}
|
||||
}
|
||||
}
|
||||
enabled: true
|
||||
|
||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||
|
||||
// If modal has been changed and the drawer is closed automatically then dim on popup open will have been switched off in main.qml so switch it back on after the animation completes.
|
||||
// This is to avoid dim being active for a split second when the drawer is switched to modal which looks terrible.
|
||||
onAnimatingChanged: if (dim === false) dim = undefined
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
contentItem: Loader {
|
||||
id: loader
|
||||
active: root.drawerOpen
|
||||
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
QQC2.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredHeight: pageStack.globalToolBar.preferredHeight
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Room information")
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: settingsButton
|
||||
|
||||
icon.name: "settings-configure"
|
||||
text: i18n("Room settings")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
RoomInformation {
|
||||
id: roomInformation
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/qml/RoomDrawer/RoomDrawerPage.qml
Normal file
60
src/qml/RoomDrawer/RoomDrawerPage.qml
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates 1.0 as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components 1.0 as KirigamiComponents
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
/**
|
||||
* @brief Page for holding a room drawer component.
|
||||
*
|
||||
* This the companion component to RoomDrawer and is designed to be used on mobile
|
||||
* where we want the room drawer to be pushed as a page as thin drawer doesn't
|
||||
* look good.
|
||||
*
|
||||
* @sa RoomDrawer
|
||||
*/
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The current room that user is viewing.
|
||||
*/
|
||||
readonly property NeoChatRoom room: RoomManager.currentRoom
|
||||
|
||||
title: roomInformation.title
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "settings-configure"
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer('qrc:/Categories.qml', {room: root.room}, { title: i18n("Room Settings") })
|
||||
}
|
||||
]
|
||||
|
||||
RoomInformation {
|
||||
id: roomInformation
|
||||
room: root.room
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: applicationWindow().pageStack
|
||||
onWideModeChanged: {
|
||||
if (applicationWindow().pageStack.wideMode) {
|
||||
console.log("widemode pop")
|
||||
applicationWindow().pageStack.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBackRequested: event => {
|
||||
event.accepted = true;
|
||||
applicationWindow().pageStack.pop()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user