Compare commits

..

33 Commits

Author SHA1 Message Date
Thiago Sueto
cfea50bc08 Make height of chatbar, userinfo bar and side tab bar the same 2024-11-15 13:32:52 -03:00
l10n daemon script
9acaaade45 GIT_SILENT Sync po/docbooks with svn 2024-11-13 01:30:31 +00:00
l10n daemon script
aaca28dbf6 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-13 01:24:06 +00:00
Paul Brown
d4ef5f9d4d Update org.kde.neochat.appdata.xml 2024-11-12 19:02:36 +00:00
James Graham
2095dea801 Add #if for patch to fix pendingEventAdded event ref 2024-11-12 16:25:23 +00:00
James Graham
a36f7ef10d Fix test 2024-11-12 16:25:23 +00:00
James Graham
9874962ee3 Make sure that the content model is loaded properly when a new event is set. This fixes seeing an unknown event message for all new events. Instead a loading symbol is briefly seen before switching to the actual content. 2024-11-12 16:25:23 +00:00
l10n daemon script
4b08022075 GIT_SILENT Sync po/docbooks with svn 2024-11-12 01:33:25 +00:00
l10n daemon script
dc3db3aec4 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-12 01:26:03 +00:00
l10n daemon script
0568c2a93d GIT_SILENT Sync po/docbooks with svn 2024-11-11 01:35:46 +00:00
l10n daemon script
7ab0a6fc9e SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-11 01:24:43 +00:00
Joshua Goins
d6b780762e PollHandler: Make sure it's not constructible from QML 2024-11-10 15:16:26 +00:00
Joshua Goins
5ef66b5cf6 PollHandler: Ensure that m_pollStartEvent is always initialized to null
Otherwise it may be undefined, and we DO create default-constructed
PollHandler. For example, one is used as a fallback poll object
in NeoChatRoom::poll.

This is blind fix for a pretty nasty poll-related crash we saw a few
months ago.

BUG: 493649
2024-11-10 15:16:26 +00:00
l10n daemon script
19e8cd5e48 GIT_SILENT Sync po/docbooks with svn 2024-11-10 01:35:11 +00:00
l10n daemon script
df5117892f SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-10 01:24:25 +00:00
l10n daemon script
aaa4216f55 GIT_SILENT made messages (after extraction) 2024-11-10 00:40:38 +00:00
Joshua Goins
85ee5084b6 Add m.room.create state events to sync_response
In case we need to access the creation state in an appium test in the
future.
2024-11-09 23:21:37 +00:00
Joshua Goins
bb9ce117de Hide rooms that have a defined room type
I have a room with a custom type that's only for holding data, and
doesn't need to be shown in the room list. Currently the spec is a bit
vague about what clients should do, but hiding them is probably fine
for now.
2024-11-09 23:21:37 +00:00
Carl Schwan
00c5aa26bb RoomGeneralPage: Add missing separator
And some other minor fixes
2024-11-09 23:11:19 +00:00
Joshua Goins
bae4de227c Make closing link previews instant, as it should be
We were missing a endResetModel() call, now with it added the removal
happens instantly.
2024-11-09 23:10:54 +00:00
Joshua Goins
253f891c5a Stop being able to crash NeoChat by pressing a button repeatedly
If you spam click the "Close link preview" button, it's possible to
crash NeoChat. This is because the index check is wrong for the array
size.

It's possible to even do this due to a bug causing the removal to be
reflected visually too slowly, that's fixed in the next commit.
2024-11-09 23:10:54 +00:00
Joshua Goins
6966159062 Improve clicking link previews
First of all, clicking on them actually works - because we were missing
an import for RoomManager. Secondly, we use a dedicated TapHandler
since onLinkActivated sucks. We want to be able to click anywhere on the
preview to go to the website/room anyway.
2024-11-09 23:10:40 +00:00
Joshua Goins
07d3b80c3e Don't set isThread on the message and file delegate context menus
It doesn't have a property called isThread, and I don't know where it
went - if it ever existed?
2024-11-09 23:10:31 +00:00
Joshua Goins
a41d0f3214 Make fullscreen images focused when they're opened
Otherwise keyboard shortcuts don't work until you tap the image, which
makes no sense.

BUG: 484322
2024-11-09 23:10:21 +00:00
Joshua Goins
1ee15de78b Fix viewing any kind of data in developer tools
Fix pageStack being undefined, so we're able to view event data again.
2024-11-09 23:10:08 +00:00
Carl Schwan
b044358970 Update checkbox of PollComponent
Use FormCheckDelegate instead of a CheckBox inside a RowLayout. This
increase the click area particularly on mobile.
2024-11-09 23:09:51 +00:00
Oliver Beard
d2e11bb3bb timeline: Round separators for replies and link previews 2024-11-09 23:09:33 +00:00
Joshua Goins
a55bac899c README: Change snap store badge to the one from apps.kde.org
It seems CORS is blocking access to the badge, but we have rehosted on
apps.kde.org.
2024-11-09 21:32:45 +00:00
Joshua Goins
c2380fb8df Update network proxy page with the improved version from Tokodon
This functions the same, but looks a bit nicer.
2024-11-09 17:11:11 +00:00
Joshua Goins
f31c644b13 Update desktop file and app description to match AppStream data
This was updated to "Chat on Matrix" but in other places it was never
switched from "Matrix client" and the like. Now it should be more
consistent.
2024-11-09 17:11:00 +00:00
Joshua Goins
26cd621d0e Clarify that sorting rooms by activity isn't the only thing it does
Recently, it also sorts rooms based on unread notification count and
importance. This adds a clarification to the setting so users (like me)
aren't confused why it isn't sorting only by activity.
2024-11-09 16:46:54 +00:00
l10n daemon script
4c58512c54 GIT_SILENT Sync po/docbooks with svn 2024-11-09 01:30:21 +00:00
Albert Astals Cid
04c1b47660 GIT_SILENT Upgrade release service version to 25.03.70. 2024-11-08 19:38:32 +01:00
124 changed files with 9352 additions and 8856 deletions

View File

@@ -110,7 +110,7 @@
{ {
"type": "git", "type": "git",
"url": "https://github.com/quotient-im/libQuotient.git", "url": "https://github.com/quotient-im/libQuotient.git",
"tag": "0.9.2", "branch": "dev",
"disable-submodules": true "disable-submodules": true
} }
], ],

View File

@@ -14,7 +14,6 @@ Dependencies:
'frameworks/kquickcharts': '@latest-kf6' 'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6' 'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6' 'frameworks/kcolorscheme': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6' 'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6' 'frameworks/sonnet': '@latest-kf6'
'frameworks/prison': '@latest-kf6' 'frameworks/prison': '@latest-kf6'

View File

@@ -7,9 +7,9 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script. # KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24") set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "12") set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "3") set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION}) project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
@@ -66,7 +66,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW) qt_policy(SET QTP0004 NEW)
endif () endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme) find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
set_package_properties(KF6 PROPERTIES set_package_properties(KF6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"

View File

@@ -12,7 +12,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.6.0' classpath 'com.android.tools.build:gradle:7.4.1'
} }
} }

View File

@@ -11,11 +11,11 @@ ecm_add_test(
TEST_NAME neochatroomtest TEST_NAME neochatroomtest
) )
# ecm_add_test( ecm_add_test(
# texthandlertest.cpp texthandlertest.cpp
# LINK_LIBRARIES neochat Qt::Test LINK_LIBRARIES neochat Qt::Test
# TEST_NAME texthandlertest TEST_NAME texthandlertest
# ) )
ecm_add_test( ecm_add_test(
delegatesizehelpertest.cpp delegatesizehelpertest.cpp

View File

@@ -63,7 +63,6 @@ private Q_SLOTS:
void receiveRichEdited(); void receiveRichEdited();
void receiveLineSeparator(); void receiveLineSeparator();
void receiveRichCodeUrl(); void receiveRichCodeUrl();
void receiveRichColor();
void componentOutput_data(); void componentOutput_data();
void componentOutput(); void componentOutput();
@@ -521,25 +520,6 @@ void TextHandlerTest::receiveRichCodeUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(), input); QCOMPARE(testTextHandler.handleRecieveRichText(), input);
} }
void TextHandlerTest::receiveRichColor()
{
const QString testInputString = QStringLiteral(
"<span data-mx-color=\"#ff00be\">¯</span><span data-mx-color=\"#ff3b1d\">\\</span><span data-mx-color=\"#ffa600\">_</span><span "
"data-mx-color=\"#64d200\">(</span><span data-mx-color=\"#00e261\">ツ</span><span data-mx-color=\"#00e7ff\">)</span><span "
"data-mx-color=\"#00e1ff\">_</span><span data-mx-color=\"#00bdff\">/</span><span data-mx-color=\"#ff60ff\">¯</span>");
const QString testOutputString = QStringLiteral(
"<span style=\"color: #ff00be;\">¯</span><span style=\"color: #ff3b1d;\">\\</span><span style=\"color: #ffa600;\">_</span><span style=\"color: "
"#64d200;\">(</span><span style=\"color: #00e261;\">ツ</span><span style=\"color: #00e7ff;\">)</span><span style=\"color: #00e1ff;\">_</span><span "
"style=\"color: #00bdff;\">/</span><span style=\"color: #ff60ff;\">¯</span>");
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
qInfo() << testTextHandler.handleRecieveRichText();
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
void TextHandlerTest::componentOutput_data() void TextHandlerTest::componentOutput_data()
{ {
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
@@ -555,7 +535,7 @@ void TextHandlerTest::componentOutput_data()
QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}}; QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}};
QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>") QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Quote, QStringLiteral("blockquote"), {}}}; MessageComponent{MessageComponentType::Quote, QStringLiteral("\"blockquote\""), {}}};
QTest::newRow("no tag first paragraph") << QStringLiteral("Text\n<p>Text</p>") QTest::newRow("no tag first paragraph") << QStringLiteral("Text\n<p>Text</p>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}}; MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};

View File

@@ -54,16 +54,11 @@
<summary xml:lang="ar">دردش على ماتركس</summary> <summary xml:lang="ar">دردش على ماتركس</summary>
<summary xml:lang="ca">Xat a Matrix</summary> <summary xml:lang="ca">Xat a Matrix</summary>
<summary xml:lang="ca-valencia">Xat a Matrix</summary> <summary xml:lang="ca-valencia">Xat a Matrix</summary>
<summary xml:lang="de">Über Matrix unterhalten</summary>
<summary xml:lang="en-GB">Chat on Matrix</summary> <summary xml:lang="en-GB">Chat on Matrix</summary>
<summary xml:lang="eo">Babilo en Matrix</summary>
<summary xml:lang="es">Charle en Matrix</summary> <summary xml:lang="es">Charle en Matrix</summary>
<summary xml:lang="eu">Berriketa Matrix-en</summary> <summary xml:lang="eu">Berriketa Matrix-en</summary>
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
<summary xml:lang="fr">Discuter sur Matrix</summary> <summary xml:lang="fr">Discuter sur Matrix</summary>
<summary xml:lang="gl">Charlar en Matrix</summary> <summary xml:lang="gl">Charlar en Matrix</summary>
<summary xml:lang="he">התכתבות דרך Matrix</summary>
<summary xml:lang="hu">Csevegés Matrixon</summary>
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary> <summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary> <summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary> <summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
@@ -71,7 +66,6 @@
<summary xml:lang="nn">Prat med via Matrix</summary> <summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary> <summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary> <summary xml:lang="sl">Klepet na Matrixu</summary>
<summary xml:lang="sv">Chatta på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary> <summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary> <summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary> <summary xml:lang="uk">Спілкування у Matrix</summary>
@@ -119,7 +113,7 @@
<p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p> <p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p>
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p> <p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p> <p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é fornecer compatibilidade coa especificación completa.</p> <p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="he">NeoChat מתיימר להיות יישום עתיר יכולות לפי מפרט Matrix. כיוון שזה ייעודו, כל מה שבמפרט היציב עם חריגות משמעותיות כגון VoIP, שרשורים ועוד מגוון היבטים של הצפנה מקצה לקצה נתמכים גם הם. יש מספר השמטות קטן עקב העובדה שהמפרט של Matrix ממשיך להתפתח אך המטרה היא להמשיך לספק תמיכה בסופו של דבר לכל המפרט.</p> <p xml:lang="he">NeoChat מתיימר להיות יישום עתיר יכולות לפי מפרט Matrix. כיוון שזה ייעודו, כל מה שבמפרט היציב עם חריגות משמעותיות כגון VoIP, שרשורים ועוד מגוון היבטים של הצפנה מקצה לקצה נתמכים גם הם. יש מספר השמטות קטן עקב העובדה שהמפרט של Matrix ממשיך להתפתח אך המטרה היא להמשיך לספק תמיכה בסופו של דבר לכל המפרט.</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p> <p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p> <p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
@@ -293,6 +287,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value> <value key="KDE::windows_store::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::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::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet</value>
</custom> </custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable> <launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots> <screenshots>
@@ -447,10 +442,6 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/> <release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/> <release version="24.08.2" date="2024-10-10"/>
<release version="24.08.1" date="2024-09-12"/> <release version="24.08.1" date="2024-09-12"/>

View File

@@ -88,32 +88,20 @@ GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端 GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端 GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix Comment=Chat on Matrix
Comment[ar]=دردش على ماتركس
Comment[ca]=Xat a Matrix Comment[ca]=Xat a Matrix
Comment[ca@valencia]=Xat a Matrix
Comment[de]=Über Matrix unterhalten
Comment[en_GB]=Chat on Matrix
Comment[eo]=Babilo en Matrix
Comment[es]=Chat en Matrix Comment[es]=Chat en Matrix
Comment[eu]=Berriketa Matrix-en Comment[eu]=Berriketa Matrix-en
Comment[fi]=Keskustele Matrixissä
Comment[fr]=Clavarder sur Matrix Comment[fr]=Clavarder sur Matrix
Comment[gl]=Charle en Matrix Comment[gl]=Charle en Matrix
Comment[he]=התכתבות דרך Matrix
Comment[hu]=Csevegés Matrixon
Comment[ia]=Conversation en ditecto sur Matrix Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix Comment[it]= su Matrix
Comment[ka]=საუბარი Matrix-ზე Comment[ka]=ჩატი Matrix-ზე
Comment[nl]=Chat op Matrix Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie Comment[pl]=Rozmawiaj na Matriksie
Comment[pt_BR]=Bate papo na Matrix
Comment[sl]=Klepet na Matrixu Comment[sl]=Klepet na Matrixu
Comment[sv]=Chatta på Matrix Comment[tr]=Matrix Üzerinde Sohbet Et
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix Comment[uk]=Спілкування у Matrix
Comment[zh_CN]= Matrix 上聊天 Comment[x-test]=xxChat on Matrixxx
Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix; MimeType=x-scheme-handler/matrix;
Exec=neochat %u Exec=neochat %u
Terminal=false Terminal=false

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
--- ---
name: neochat name: neochat
base: core24 base: core22
adopt-info: neochat adopt-info: neochat
grade: stable grade: stable
confinement: strict confinement: strict
@@ -27,10 +27,6 @@ apps:
compression: lzo compression: lzo
package-repositories:
- type: apt
ppa: ubuntu-toolchain-r/test
slots: slots:
session-dbus-interface: session-dbus-interface:
interface: dbus interface: dbus
@@ -80,7 +76,6 @@ parts:
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment: build-environment:
- PATH: /snap/bin:${PATH}
- PKG_CONFIG_PATH: $CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH - PKG_CONFIG_PATH: $CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
@@ -97,13 +92,9 @@ parts:
- olm - olm
- qtkeychain - qtkeychain
source: https://github.com/quotient-im/libQuotient.git source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.1 source-tag: 0.9.0
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
build-snaps:
- cmake
build-packages: build-packages:
- libssl-dev - libssl-dev
cmake-parameters: cmake-parameters:
@@ -122,10 +113,6 @@ parts:
source-tag: 'v0.3.0' source-tag: 'v0.3.0'
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
@@ -143,12 +130,9 @@ parts:
- kquickimageeditor - kquickimageeditor
parse-info: parse-info:
- usr/share/metainfo/org.kde.neochat.appdata.xml - usr/share/metainfo/org.kde.neochat.appdata.xml
source: . source: https://invent.kde.org/network/neochat.git
source-tag: 'v24.08.1'
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
build-packages: build-packages:
- cmark - cmark
- libcmark-dev - libcmark-dev
@@ -172,12 +156,3 @@ parts:
prime: prime:
- usr/lib/*/libcmark.so* - usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

@@ -200,12 +200,6 @@ set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE QT_QML_SINGLETON_TYPE TRUE
) )
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES QML_FILES
@@ -317,9 +311,13 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml qml/EditMenu.qml
) )
else() else()
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_RESOURCE_ALIAS qml/ShareAction.qml
)
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml) qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif() endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h) configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32) if(WIN32)
@@ -418,7 +416,6 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui KF6::ConfigGui
KF6::CoreAddons KF6::CoreAddons
KF6::SonnetCore KF6::SonnetCore
KF6::IconThemes
KF6::ColorScheme KF6::ColorScheme
KF6::ItemModels KF6::ItemModels
QuotientQt6 QuotientQt6
@@ -492,7 +489,6 @@ if(ANDROID)
"network-connect" "network-connect"
"list-remove-user" "list-remove-user"
"org.kde.neochat" "org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users" "preferences-system-users"
"preferences-desktop-theme-global" "preferences-desktop-theme-global"
"notifications" "notifications"
@@ -530,13 +526,11 @@ if(ANDROID)
"object-rotate-left" "object-rotate-left"
"object-rotate-right" "object-rotate-right"
"add-subtitle" "add-subtitle"
"security-high"
"security-low" "security-low"
"security-low-symbolic" "security-low-symbolic"
"kde" "kde"
"list-remove-symbolic" "list-remove-symbolic"
"edit-delete" "edit-delete"
"user-home-symbolic"
) )
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android) ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else() else()

View File

@@ -43,9 +43,6 @@ QQC2.ItemDelegate {
anchors.fill: parent anchors.fill: parent
visible: root.emoji.startsWith("mxc") || root.isImage visible: root.emoji.startsWith("mxc") || root.isImage
source: visible ? root.emoji : "" source: visible ? root.emoji : ""
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
} }
} }

View File

@@ -84,7 +84,6 @@ QQC2.ScrollView {
Kirigami.PlaceholderMessage { Kirigami.PlaceholderMessage {
anchors.centerIn: parent anchors.centerIn: parent
icon.name: root.stickers ? "stickers" : "preferences-desktop-emoticons"
text: root.stickers ? i18n("No stickers") : i18n("No emojis") text: root.stickers ? i18n("No stickers") : i18n("No emojis")
visible: emojis.count === 0 visible: emojis.count === 0
} }

View File

@@ -66,7 +66,6 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0 QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
visible: categories.count !== 0
ListView { ListView {
id: categories id: categories
@@ -202,13 +201,8 @@ ColumnLayout {
width: root.categoryIconSize width: root.categoryIconSize
height: width height: width
checked: stickerModel.packIndex === model.index checked: stickerModel.packIndex === model.index
padding: Kirigami.Units.largeSpacing
contentItem: Image { contentItem: Image {
source: model.avatarUrl source: model.avatarUrl
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
} }
QQC2.ToolTip.text: model.name QQC2.ToolTip.text: model.name
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay

View File

@@ -319,8 +319,7 @@ void ChatBarCache::postMessage()
return; return;
} }
auto type = std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result); room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
room->postMessage(text(), sendText, type ? *type : Quotient::RoomMessageEvent::MsgType::Text, replyId(), editId(), threadId());
clearCache(); clearCache();
} }

View File

@@ -168,6 +168,7 @@ void Controller::addConnection(NeoChatConnection *c)
connect(c, &NeoChatConnection::syncDone, this, [this, c]() { connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
m_notificationsManager.handleNotifications(c); m_notificationsManager.handleNotifications(c);
}); });
connect(c, &NeoChatConnection::showInviteNotification, &m_notificationsManager, &NotificationsManager::postInviteNotification);
c->sync(); c->sync();
@@ -294,7 +295,7 @@ bool Controller::supportSystemTray() const
void Controller::setQuitOnLastWindowClosed() void Controller::setQuitOnLastWindowClosed()
{ {
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) { if (NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this); m_trayIcon = new TrayIcon(this);
m_trayIcon->show(); m_trayIcon->show();
} else { } else {
@@ -422,14 +423,10 @@ void Controller::setTestMode(bool test)
void Controller::removeConnection(const QString &userId) void Controller::removeConnection(const QString &userId)
{ {
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
// entry for it so we need to check both separately.
if (m_accountsLoading.contains(userId)) {
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
}
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) { if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId]; auto connection = m_connectionsLoading[userId];
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
SettingsGroup("Accounts"_ls).remove(userId); SettingsGroup("Accounts"_ls).remove(userId);
} }
} }

View File

@@ -3,7 +3,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
@@ -38,7 +37,7 @@ FormCard.FormCardPage {
} }
function openEventSource(stateKey: string): void { function openEventSource(stateKey: string): void {
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), { applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel, model: stateKeysModel,
allowEdit: true, allowEdit: true,
room: root.room, room: root.room,

View File

@@ -25,7 +25,8 @@ void LoginHelper::init()
m_connection = new NeoChatConnection(); m_connection = new NeoChatConnection();
m_matrixId = QString(); m_matrixId = QString();
m_password = QString(); m_password = QString();
m_deviceName = QStringLiteral("NeoChat"); m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
.arg(QSysInfo::machineHostName(), QSysInfo::productType(), QSysInfo::productVersion(), QSysInfo::currentCpuArchitecture());
m_supportsSso = false; m_supportsSso = false;
m_supportsPassword = false; m_supportsPassword = false;
m_ssoUrl = QUrl(); m_ssoUrl = QUrl();

View File

@@ -13,7 +13,6 @@ LoginStep {
id: root id: root
FormCard.FormTextDelegate { FormCard.FormTextDelegate {
textItem.wrapMode: Text.Wrap
text: i18n("Please wait while your messages are loaded from the server. This might take a little while.") text: i18n("Please wait while your messages are loaded from the server. This might take a little while.")
} }
FormCard.AbstractFormDelegate { FormCard.AbstractFormDelegate {

View File

@@ -7,7 +7,6 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat import org.kde.neochat
import org.kde.neochat.settings import org.kde.neochat.settings
@@ -17,7 +16,6 @@ Kirigami.Page {
property bool showExisting: false property bool showExisting: false
property bool _showExisting: showExisting && root.currentStepString === root.initialStep property bool _showExisting: showExisting && root.currentStepString === root.initialStep
property bool showSettings: true
property alias currentStep: module.item property alias currentStep: module.item
property string currentStepString: initialStep property string currentStepString: initialStep
property string initialStep: "LoginRegister" property string initialStep: "LoginRegister"
@@ -92,27 +90,11 @@ Kirigami.Page {
id: loadedAccounts id: loadedAccounts
model: AccountRegistry model: AccountRegistry
delegate: FormCard.FormButtonDelegate { delegate: FormCard.FormButtonDelegate {
id: delegate text: model.userId
required property string userId
required property NeoChatConnection connection
text: QmlUtils.escapeString(connection.localUser.displayName)
description: connection.localUser.id
leadingPadding: Kirigami.Units.largeSpacing
onClicked: { onClicked: {
Controller.activeConnection = delegate.connection; Controller.activeConnection = model.connection;
root.connectionChosen(); root.connectionChosen();
} }
leading: KirigamiComponents.Avatar {
id: avatar
name: delegate.text
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : ""
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
}
} }
} }
Repeater { Repeater {
@@ -266,7 +248,6 @@ Kirigami.Page {
FormCard.FormCard { FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing * 2 Layout.topMargin: Kirigami.Units.largeSpacing * 2
maximumWidth: Kirigami.Units.gridUnit * 20 maximumWidth: Kirigami.Units.gridUnit * 20
visible: root.showSettings
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Settings") text: i18nc("@action:button", "Settings")
icon.name: "settings-configure" icon.name: "settings-configure"

View File

@@ -37,7 +37,6 @@
#include <KCrash> #include <KCrash>
#endif #endif
#include <KIconTheme>
#include <KLocalizedContext> #include <KLocalizedContext>
#include <KLocalizedString> #include <KLocalizedString>
@@ -102,7 +101,6 @@ Q_DECL_EXPORT
#endif #endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
KIconTheme::initTheme();
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
#ifdef HAVE_WEBVIEW #ifdef HAVE_WEBVIEW

View File

@@ -600,19 +600,14 @@ bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messa
} else { } else {
originalString = event->plainBody(); originalString = event->plainBody();
} }
QString replaceId = event->id();
const auto eventRelation = event->relatesTo();
if (eventRelation && eventRelation->type == "m.replace"_L1) {
replaceId = eventRelation->eventId;
}
if (flags == "/g"_L1) { if (flags == "/g"_L1) {
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, replaceId); room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
} else { } else {
room->postHtmlMessage(messageText, room->postHtmlMessage(messageText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement), originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(), event->msgtype(),
{}, {},
replaceId); event->id());
} }
return true; return true;
} }

View File

@@ -85,7 +85,13 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole); return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
} }
if (role == IconNameRole) { if (role == IconNameRole) {
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString(); auto mediaId = m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
if (mediaId.isEmpty()) {
return QVariant();
}
if (m_room) {
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
}
} }
} }
if (m_autoCompletionType == Emoji) { if (m_autoCompletionType == Emoji) {

View File

@@ -34,7 +34,7 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_room(room) , m_room(room)
, m_eventId(eventId) , m_eventId(eventId)
, m_isPending(isPending) , m_currentState(isPending ? Pending : Unknown)
, m_isReply(isReply) , m_isReply(isReply)
{ {
initializeModel(); initializeModel();
@@ -45,19 +45,27 @@ void MessageContentModel::initializeModel()
Q_ASSERT(m_room != nullptr); Q_ASSERT(m_room != nullptr);
Q_ASSERT(!m_eventId.isEmpty()); Q_ASSERT(!m_eventId.isEmpty());
connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent); connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() {
if (m_room != nullptr && m_currentState == Unknown) {
initializeEvent();
updateReplyModel();
resetModel();
}
});
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) { connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
if (m_room != nullptr) { if (m_room != nullptr) {
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) { if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
beginResetModel();
m_isPending = false;
m_eventId = serverEvent->id(); m_eventId = serverEvent->id();
initializeEvent();
endResetModel();
} }
} }
}); });
connect(m_room, &NeoChatRoom::pendingEventMerged, this, [this]() {
if (m_room != nullptr && m_currentState == Pending) {
initializeEvent();
updateReplyModel();
resetModel();
}
});
connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) { connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) {
if (m_room != nullptr) { if (m_room != nullptr) {
for (int i = fromIndex; i <= toIndex; i++) { for (int i = fromIndex; i <= toIndex; i++) {
@@ -143,20 +151,33 @@ void MessageContentModel::initializeModel()
}); });
initializeEvent(); initializeEvent();
updateReplyModel(); if (m_currentState == Available || m_currentState == Pending) {
updateReplyModel();
}
resetModel(); resetModel();
} }
void MessageContentModel::initializeEvent() void MessageContentModel::initializeEvent()
{ {
const auto event = m_room->getEvent(m_eventId); if (m_currentState == UnAvailable) {
if (event == nullptr) {
Q_EMIT eventUnavailable();
return; return;
} }
const auto eventResult = m_room->getEvent(m_eventId);
if (eventResult.first == nullptr) {
if (m_currentState != Pending) {
getEvent();
}
return;
}
if (eventResult.second) {
m_currentState = Pending;
} else {
m_currentState = Available;
}
if (m_eventSenderObject == nullptr) { if (m_eventSenderObject == nullptr) {
auto senderId = event->senderId(); auto senderId = eventResult.first->senderId();
// A pending event might not have a sender ID set yet but in that case it must // A pending event might not have a sender ID set yet but in that case it must
// be the local member. // be the local member.
if (senderId.isEmpty()) { if (senderId.isEmpty()) {
@@ -172,7 +193,6 @@ void MessageContentModel::getEvent()
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) { Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
if (m_room != nullptr) { if (m_room != nullptr) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
m_notFound = false;
initializeEvent(); initializeEvent();
updateReplyModel(); updateReplyModel();
resetModel(); resetModel();
@@ -184,7 +204,7 @@ void MessageContentModel::getEvent()
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) { Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
if (m_room != nullptr) { if (m_room != nullptr) {
if (eventId == m_eventId) { if (eventId == m_eventId) {
m_notFound = true; m_currentState = UnAvailable;
resetModel(); resetModel();
return true; return true;
} }
@@ -237,7 +257,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
const auto component = m_components[index.row()]; const auto component = m_components[index.row()];
const auto event = m_room->getEvent(m_eventId); const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) { if (event.first == nullptr) {
if (role == DisplayRole) { if (role == DisplayRole) {
if (m_isReply) { if (m_isReply) {
return i18n("Loading reply"); return i18n("Loading reply");
@@ -252,7 +272,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
} }
if (role == DisplayRole) { if (role == DisplayRole) {
if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) { if (m_currentState == UnAvailable || m_room->connection()->isIgnored(m_eventSenderId)) {
Kirigami::Platform::PlatformTheme *theme = Kirigami::Platform::PlatformTheme *theme =
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true)); static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
@@ -276,7 +296,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
if (!component.content.isEmpty()) { if (!component.content.isEmpty()) {
return component.content; return component.content;
} }
return EventHandler::richBody(m_room, event); return EventHandler::richBody(m_room, event.first);
} }
if (role == ComponentTypeRole) { if (role == ComponentTypeRole) {
return component.type; return component.type;
@@ -285,53 +305,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return component.attributes; return component.attributes;
} }
if (role == EventIdRole) { if (role == EventIdRole) {
return EventHandler::id(event); return EventHandler::id(event.first);
} }
if (role == TimeRole) { if (role == TimeRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) { const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
return event->transactionId() == pendingEvent->transactionId(); return event.first->transactionId() == pendingEvent->transactionId();
}); });
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
return EventHandler::time(event, m_isPending, lastUpdated); return EventHandler::time(event.first, m_currentState == Pending, lastUpdated);
} }
if (role == TimeStringRole) { if (role == TimeStringRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) { const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
return event->transactionId() == pendingEvent->transactionId(); return event.first->transactionId() == pendingEvent->transactionId();
}); });
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated); return EventHandler::timeString(event.first, QStringLiteral("hh:mm"), m_currentState == Pending, lastUpdated);
} }
if (role == AuthorRole) { if (role == AuthorRole) {
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get()); return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
} }
if (role == MediaInfoRole) { if (role == MediaInfoRole) {
return EventHandler::mediaInfo(m_room, event); return EventHandler::mediaInfo(m_room, event.first);
} }
if (role == FileTransferInfoRole) { if (role == FileTransferInfoRole) {
return QVariant::fromValue(m_room->cachedFileTransferInfo(event)); return QVariant::fromValue(m_room->cachedFileTransferInfo(event.first));
} }
if (role == ItineraryModelRole) { if (role == ItineraryModelRole) {
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel); return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
} }
if (role == LatitudeRole) { if (role == LatitudeRole) {
return EventHandler::latitude(event); return EventHandler::latitude(event.first);
} }
if (role == LongitudeRole) { if (role == LongitudeRole) {
return EventHandler::longitude(event); return EventHandler::longitude(event.first);
} }
if (role == AssetRole) { if (role == AssetRole) {
return EventHandler::locationAssetType(event); return EventHandler::locationAssetType(event.first);
} }
if (role == PollHandlerRole) { if (role == PollHandlerRole) {
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId)); return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
} }
if (role == ReplyEventIdRole) { if (role == ReplyEventIdRole) {
return EventHandler::replyId(event); return EventHandler::replyId(event.first);
} }
if (role == ReplyAuthorRole) { if (role == ReplyAuthorRole) {
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event)); return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
} }
if (role == ReplyContentModelRole) { if (role == ReplyContentModelRole) {
return QVariant::fromValue<MessageContentModel *>(m_replyModel); return QVariant::fromValue<MessageContentModel *>(m_replyModel);
@@ -387,18 +407,17 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
void MessageContentModel::resetModel() void MessageContentModel::resetModel()
{ {
const auto event = m_room->getEvent(m_eventId);
beginResetModel(); beginResetModel();
m_components.clear(); m_components.clear();
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) { if (m_room->connection()->isIgnored(m_eventSenderId) || m_currentState == UnAvailable) {
m_components += MessageComponent{MessageComponentType::Text, QString(), {}}; m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
endResetModel(); endResetModel();
return; return;
} }
if (event == nullptr) { const auto event = m_room->getEvent(m_eventId);
if (event.first == nullptr) {
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}}; m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
endResetModel(); endResetModel();
return; return;
@@ -431,19 +450,19 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading) QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
{ {
const auto event = m_room->getEvent(m_eventId); const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) { if (event.first == nullptr) {
return {}; return {};
} }
QList<MessageComponent> newComponents; QList<MessageComponent> newComponents;
if (eventCast<const Quotient::RoomMessageEvent>(event) if (eventCast<const Quotient::RoomMessageEvent>(event.first)
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) { && eventCast<const Quotient::RoomMessageEvent>(event.first)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}}; newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
return newComponents; return newComponents;
} }
if (event->isRedacted()) { if (event.first->isRedacted()) {
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}}; newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
return newComponents; return newComponents;
} }
@@ -455,7 +474,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
if (isEditing) { if (isEditing) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
} else { } else {
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event))); newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
} }
if (m_room->urlPreviewEnabled()) { if (m_room->urlPreviewEnabled()) {
@@ -463,7 +482,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
} }
// If the event is already threaded the ThreadModel will handle displaying a chat bar. // If the event is already threaded the ThreadModel will handle displaying a chat bar.
if (isThreading && !EventHandler::isThreaded(event)) { if (isThreading && !EventHandler::isThreaded(event.first)) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
} }
@@ -473,11 +492,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
void MessageContentModel::updateReplyModel() void MessageContentModel::updateReplyModel()
{ {
const auto event = m_room->getEvent(m_eventId); const auto event = m_room->getEvent(m_eventId);
if (event == nullptr || m_isReply) { if (event.first == nullptr || m_isReply) {
return; return;
} }
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) { if (!EventHandler::hasReply(event.first) || (EventHandler::isThreaded(event.first) && NeoChatConfig::self()->threads())) {
if (m_replyModel) { if (m_replyModel) {
delete m_replyModel; delete m_replyModel;
} }
@@ -488,7 +507,7 @@ void MessageContentModel::updateReplyModel()
return; return;
} }
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this); m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event.first), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() { connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole}); Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -498,13 +517,13 @@ void MessageContentModel::updateReplyModel()
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type) QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
{ {
const auto event = m_room->getEvent(m_eventId); const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) { if (event.first == nullptr) {
return {}; return {};
} }
switch (type) { switch (type) {
case MessageComponentType::Text: { case MessageComponentType::Text: {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event); const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
auto body = EventHandler::rawMessageBody(*roomMessageEvent); auto body = EventHandler::rawMessageBody(*roomMessageEvent);
return TextHandler().textComponents(body, return TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent), EventHandler::messageBodyInputFormat(*roomMessageEvent),
@@ -515,11 +534,11 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::File: { case MessageComponentType::File: {
QList<MessageComponent> components; QList<MessageComponent> components;
components += MessageComponent{MessageComponentType::File, QString(), {}}; components += MessageComponent{MessageComponentType::File, QString(), {}};
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event); const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (m_emptyItinerary) { if (m_emptyItinerary) {
if (!m_isReply) { if (!m_isReply) {
auto fileTransferInfo = m_room->cachedFileTransferInfo(event); auto fileTransferInfo = m_room->cachedFileTransferInfo(event.first);
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>()); Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
@@ -567,24 +586,17 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::Image: case MessageComponentType::Image:
case MessageComponentType::Audio: case MessageComponentType::Audio:
case MessageComponentType::Video: { case MessageComponentType::Video: {
if (!event->is<StickerEvent>()) { if (!event.first->is<StickerEvent>()) {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event); const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
const auto fileContent = roomMessageEvent->get<EventContent::FileContentBase>(); QList<MessageComponent> components;
if (fileContent != nullptr) { components += MessageComponent{type, QString(), {}};
const auto fileInfo = fileContent->commonInfo(); auto body = EventHandler::rawMessageBody(*roomMessageEvent);
const auto body = EventHandler::rawMessageBody(*roomMessageEvent); components += TextHandler().textComponents(body,
// Do not attach the description to the image, if it's the same as the original filename. EventHandler::messageBodyInputFormat(*roomMessageEvent),
if (fileInfo.originalName != body) { m_room,
QList<MessageComponent> components; roomMessageEvent,
components += MessageComponent{type, QString(), {}}; roomMessageEvent->isReplaced());
components += TextHandler().textComponents(body, return components;
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
return components;
}
}
} }
} }
default: default:
@@ -660,13 +672,13 @@ void MessageContentModel::closeLinkPreview(int row)
void MessageContentModel::updateItineraryModel() void MessageContentModel::updateItineraryModel()
{ {
const auto event = m_room->getEvent(m_eventId); const auto event = m_room->getEvent(m_eventId);
if (m_room == nullptr || event == nullptr) { if (m_room == nullptr || event.first == nullptr) {
return; return;
} }
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) { if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
if (roomMessageEvent->has<EventContent::FileContent>()) { if (roomMessageEvent->has<EventContent::FileContent>()) {
auto filePath = m_room->cachedFileTransferInfo(event).localPath; auto filePath = m_room->cachedFileTransferInfo(event.first).localPath;
if (filePath.isEmpty() && m_itineraryModel != nullptr) { if (filePath.isEmpty() && m_itineraryModel != nullptr) {
delete m_itineraryModel; delete m_itineraryModel;
m_itineraryModel = nullptr; m_itineraryModel = nullptr;

View File

@@ -31,6 +31,14 @@ class MessageContentModel : public QAbstractListModel
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged) Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
public: public:
enum MessageState {
Unknown, /**< The message state is unknown. */
Pending, /**< The message is a new pending message which the server has not yet acknowledged. */
Available, /**< The message is available and acknowledged by the server. */
UnAvailable, /**< The message can't be retrieved either because it doesn't exist or is blocked. */
};
Q_ENUM(MessageState)
/** /**
* @brief Defines the model roles. * @brief Defines the model roles.
*/ */
@@ -98,7 +106,6 @@ public:
Q_SIGNALS: Q_SIGNALS:
void showAuthorChanged(); void showAuthorChanged();
void eventUnavailable();
void eventUpdated(); void eventUpdated();
private: private:
@@ -107,10 +114,9 @@ private:
QString m_eventSenderId; QString m_eventSenderId;
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr; std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
bool m_isPending; MessageState m_currentState = Unknown;
bool m_showAuthor = true; bool m_showAuthor = true;
bool m_isReply; bool m_isReply;
bool m_notFound = false;
void initializeModel(); void initializeModel();
void initializeEvent(); void initializeEvent();

View File

@@ -160,12 +160,21 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
refreshLastUserEvents(i); refreshLastUserEvents(i);
} }
}); });
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
connect(m_currentRoom, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
m_initialized = true;
createEventObjects(event, true);
beginInsertRows({}, 0, 0);
endInsertRows();
});
#else
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) { connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
m_initialized = true; m_initialized = true;
createEventObjects(event); createEventObjects(event, true);
beginInsertRows({}, 0, 0); beginInsertRows({}, 0, 0);
}); });
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
#endif
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) { connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole}); Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
if (i == 0) { if (i == 0) {
@@ -505,8 +514,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
if (role == ProgressInfoRole) { if (role == ProgressInfoRole) {
if (auto e = eventCast<const RoomMessageEvent>(&evt)) { if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
if (e->has<EventContent::FileContent>() || e->has<EventContent::ImageContent>() || e->has<EventContent::VideoContent>() if (e->has<EventContent::FileContent>()) {
|| e->has<EventContent::AudioContent>()) {
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt)); return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
} }
} }
@@ -619,7 +627,7 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(); return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
} }
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event) void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, bool isPending)
{ {
if (event == nullptr) { if (event == nullptr) {
return; return;
@@ -642,7 +650,7 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) { if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) { if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId)); m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId, false, isPending));
} }
} }

View File

@@ -136,7 +136,7 @@ private:
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {}); int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
void moveReadMarker(const QString &toEventId); void moveReadMarker(const QString &toEventId);
void createEventObjects(const Quotient::RoomEvent *event); void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows // Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
bool m_initialized = false; bool m_initialized = false;

View File

@@ -212,7 +212,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return room->displayName().toHtmlEscaped(); return room->displayName().toHtmlEscaped();
} }
if (role == AvatarRole) { if (role == AvatarRole) {
return room->avatarMediaUrl(); return room->avatarMediaId();
} }
if (role == CanonicalAliasRole) { if (role == CanonicalAliasRole) {
return room->canonicalAlias(); return room->canonicalAlias();

View File

@@ -324,7 +324,7 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return room->displayName(); return room->displayName();
} }
if (role == AvatarRole) { if (role == AvatarRole) {
return room->avatarMediaUrl(); return room->avatarMediaId();
} }
if (role == CanonicalAliasRole) { if (role == CanonicalAliasRole) {
return room->canonicalAlias(); return room->canonicalAlias();

View File

@@ -261,7 +261,7 @@ Action=Popup
Name=Share Name=Share
Name[ar]=شارك Name[ar]=شارك
Name[ca]=Compartició Name[ca]=Compartició
Name[ca@valencia]=Compartiu Name[ca@valencia]=Compartició
Name[cs]=Sdílet Name[cs]=Sdílet
Name[de]=Teilen Name[de]=Teilen
Name[el]=Κοινοποίηση Name[el]=Κοινοποίηση
@@ -289,7 +289,6 @@ Name[ta]=பகிர்
Name[tr]=Paylaş Name[tr]=Paylaş
Name[uk]=Оприлюднення Name[uk]=Оприлюднення
Name[x-test]=xxSharexx Name[x-test]=xxSharexx
Name[zh_CN]=分享
Name[zh_TW]=分享 Name[zh_TW]=分享
Comment=The result of sharing a piece of content Comment=The result of sharing a piece of content
Comment[ar]=نتيجة مشاركة محتوى Comment[ar]=نتيجة مشاركة محتوى
@@ -321,6 +320,5 @@ Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவ
Comment[tr]=Bir parça içerik paylaşımının sonucu Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果 Comment[zh_TW]=分享一份內容之後的結果
Action=Popup Action=Popup

View File

@@ -109,6 +109,10 @@ void NeoChatConnection::connectSignals()
Q_EMIT homeHaveHighlightNotificationsChanged(); Q_EMIT homeHaveHighlightNotificationsChanged();
}); });
}); });
connect(this, &NeoChatConnection::invitedRoom, this, [this](Quotient::Room *room) {
auto r = dynamic_cast<NeoChatRoom *>(room);
connect(r, &NeoChatRoom::showInviteNotification, this, &NeoChatConnection::showInviteNotification);
});
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) { connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
Q_UNUSED(room) Q_UNUSED(room)
if (prev && prev->isDirectChat()) { if (prev && prev->isDirectChat()) {

View File

@@ -205,6 +205,11 @@ Q_SIGNALS:
*/ */
void errorOccured(const QString &error); void errorOccured(const QString &error);
/**
* @brief Request a notification be shown for an invite to this room.
*/
void showInviteNotification(NeoChatRoom *room);
private: private:
bool m_isOnline = true; bool m_isOnline = true;
void setIsOnline(bool isOnline); void setIsOnline(bool isOnline);

View File

@@ -124,6 +124,9 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
updatePushNotificationState(QStringLiteral("m.push_rules")); updatePushNotificationState(QStringLiteral("m.push_rules"));
Q_EMIT canEncryptRoomChanged(); Q_EMIT canEncryptRoomChanged();
if (this->joinState() == JoinState::Invite) {
Q_EMIT showInviteNotification(this);
}
}, },
Qt::SingleShotConnection); Qt::SingleShotConnection);
connect(this, &Room::changed, this, [this] { connect(this, &Room::changed, this, [this] {
@@ -428,9 +431,9 @@ QDateTime NeoChatRoom::lastActiveTime()
return messageEvents().rbegin()->get()->originTimestamp(); return messageEvents().rbegin()->get()->originTimestamp();
} }
QUrl NeoChatRoom::avatarMediaUrl() const QString NeoChatRoom::avatarMediaId() const
{ {
if (const auto avatar = Room::avatarUrl(); !avatar.isEmpty()) { if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
return avatar; return avatar;
} }
@@ -438,7 +441,7 @@ QUrl NeoChatRoom::avatarMediaUrl() const
const auto directChatMembers = this->directChatMembers(); const auto directChatMembers = this->directChatMembers();
for (const auto member : directChatMembers) { for (const auto member : directChatMembers) {
if (member != localMember()) { if (member != localMember()) {
return member.avatarUrl(); return member.avatarMediaId();
} }
} }
@@ -747,10 +750,7 @@ QList<QString> NeoChatRoom::restrictedIds() const
QString NeoChatRoom::historyVisibility() const QString NeoChatRoom::historyVisibility() const
{ {
if (auto stateEvent = currentState().get("m.room.history_visibility"_ls)) { return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
return stateEvent->contentJson()["history_visibility"_ls].toString();
}
return {};
} }
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule) void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
@@ -1749,25 +1749,31 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
}); });
} }
const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString &eventId) const
{ {
if (eventId.isEmpty()) { if (eventId.isEmpty()) {
return nullptr; return {};
} }
const auto timelineIt = findInTimeline(eventId); const auto timelineIt = findInTimeline(eventId);
if (timelineIt != historyEdge()) { if (timelineIt != historyEdge()) {
return timelineIt->get(); return std::make_pair(timelineIt->get(), false);
} }
const auto pendingIt = findPendingEvent(eventId); auto pendingIt = findPendingEvent(eventId);
if (pendingIt != pendingEvents().end()) { if (pendingIt != pendingEvents().end()) {
return pendingIt->event(); return std::make_pair(pendingIt->event(), true);
}
// findPendingEvent() searches by transaction ID, we also need to check event ID.
for (const auto &event : pendingEvents()) {
if (event->id() == eventId || event->transactionId() == eventId) {
return std::make_pair(event.event(), true);
}
} }
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) { auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
return event->id() == eventId; return event->id() == eventId;
}); });
return extraIt != m_extraEvents.end() ? extraIt->get() : nullptr; return std::make_pair(extraIt != m_extraEvents.end() ? extraIt->get() : nullptr, false);
} }
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const

View File

@@ -69,9 +69,9 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged) Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
/** /**
* @brief The avatar image to be used for the room, as a mxc:// URL. * @brief The avatar image to be used for the room.
*/ */
Q_PROPERTY(QUrl avatarMediaUrl READ avatarMediaUrl NOTIFY avatarChanged STORED false) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
/** /**
* @brief Get a RoomMember object for the other person in a direct chat. * @brief Get a RoomMember object for the other person in a direct chat.
@@ -320,7 +320,7 @@ public:
[[nodiscard]] bool readMarkerLoaded() const; [[nodiscard]] bool readMarkerLoaded() const;
[[nodiscard]] QUrl avatarMediaUrl() const; [[nodiscard]] QString avatarMediaId() const;
NeochatRoomMember *directChatRemoteMember(); NeochatRoomMember *directChatRemoteMember();
@@ -570,7 +570,7 @@ public:
* *
* The result will be nullptr if not found so needs to be managed. * The result will be nullptr if not found so needs to be managed.
*/ */
const Quotient::RoomEvent *getEvent(const QString &eventId) const; std::pair<const Quotient::RoomEvent *, bool> getEvent(const QString &eventId) const;
/** /**
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply. * @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.
@@ -654,6 +654,14 @@ Q_SIGNALS:
*/ */
void showMessage(MessageType::Type messageType, const QString &message); void showMessage(MessageType::Type messageType, const QString &message);
/**
* @brief Request a notification be shown for an invite to this room.
*
* @note This may later be blocked if there are any rules on where invites can
* come from, but this is not NeoChatRoom's responsibility.
*/
void showInviteNotification(NeoChatRoom *room);
public Q_SLOTS: public Q_SLOTS:
/** /**
* @brief Upload a file to the matrix server and post the file to the room. * @brief Upload a file to the matrix server and post the file to the room.

View File

@@ -153,6 +153,15 @@ QColor NeochatRoomMember::color() const
return m_room->member(m_memberId).color(); return m_room->member(m_memberId).color();
} }
QString NeochatRoomMember::avatarMediaId() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).avatarMediaId();
}
QUrl NeochatRoomMember::avatarUrl() const QUrl NeochatRoomMember::avatarUrl() const
{ {
if (m_room == nullptr || m_memberId.isEmpty()) { if (m_room == nullptr || m_memberId.isEmpty()) {

View File

@@ -70,6 +70,7 @@ public:
int hue() const; int hue() const;
qreal hueF() const; qreal hueF() const;
QColor color() const; QColor color() const;
QString avatarMediaId() const;
QUrl avatarUrl() const; QUrl avatarUrl() const;
Q_SIGNALS: Q_SIGNALS:

View File

@@ -127,8 +127,9 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
} }
auto sender = room->member(notification["event"_ls]["sender"_ls].toString()); auto sender = room->member(notification["event"_ls]["sender"_ls].toString());
// Don't display notifications for events in invited rooms
// This should prevent empty notifications from appearing when they shouldn't
if (room->joinState() == JoinState::Invite) { if (room->joinState() == JoinState::Invite) {
postInviteNotification(qobject_cast<NeoChatRoom *>(room));
continue; continue;
} }

View File

@@ -77,7 +77,7 @@ Comment[ru]=Поиск комнат NeoChat
Comment[sl]=Najdi sobe v NeoChatu Comment[sl]=Najdi sobe v NeoChatu
Comment[sv]=Sök efter rum i NeoChat Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும் Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
Comment[tr]=NeoChatte odalar bulun Comment[tr]=NeoChat'te odalar bulun
Comment[uk]=Пошук кімнат у NeoChat Comment[uk]=Пошук кімнат у NeoChat
Comment[x-test]=xxFind rooms in NeoChatxx Comment[x-test]=xxFind rooms in NeoChatxx
Comment[zh_CN]=在 NeoChat 查找聊天室 Comment[zh_CN]=在 NeoChat 查找聊天室

View File

@@ -25,7 +25,6 @@
"Name[nl]": "Tobias Fella", "Name[nl]": "Tobias Fella",
"Name[nn]": "Tobias Fella", "Name[nn]": "Tobias Fella",
"Name[pl]": "Tobias Fella", "Name[pl]": "Tobias Fella",
"Name[pt_BR]": "Tobias Fella",
"Name[ru]": "Tobias Fella", "Name[ru]": "Tobias Fella",
"Name[sk]": "Tobias Fella", "Name[sk]": "Tobias Fella",
"Name[sl]": "Tobias Fella", "Name[sl]": "Tobias Fella",
@@ -34,7 +33,6 @@
"Name[tr]": "Tobias Fella", "Name[tr]": "Tobias Fella",
"Name[uk]": "Tobias Fella", "Name[uk]": "Tobias Fella",
"Name[x-test]": "xxTobias Fellaxx", "Name[x-test]": "xxTobias Fellaxx",
"Name[zh_CN]": "Tobias Fella",
"Name[zh_TW]": "Tobias Fella" "Name[zh_TW]": "Tobias Fella"
} }
], ],
@@ -61,7 +59,6 @@
"Description[nl]": "Delen via NeoChat", "Description[nl]": "Delen via NeoChat",
"Description[nn]": "Del via NeoChat", "Description[nn]": "Del via NeoChat",
"Description[pl]": "Udostępnij przez NeoChat", "Description[pl]": "Udostępnij przez NeoChat",
"Description[pt_BR]": "Compartilhar via NeoChat",
"Description[ru]": "Опубликовать в NeoChat", "Description[ru]": "Опубликовать в NeoChat",
"Description[sl]": "Deli prek NeoChat", "Description[sl]": "Deli prek NeoChat",
"Description[sv]": "Dela via NeoChat", "Description[sv]": "Dela via NeoChat",
@@ -69,9 +66,8 @@
"Description[tr]": "NeoChat ile Paylaş", "Description[tr]": "NeoChat ile Paylaş",
"Description[uk]": "Оприлюднити за допомогою NeoChat", "Description[uk]": "Оприлюднити за допомогою NeoChat",
"Description[x-test]": "xxShare via NeoChatxx", "Description[x-test]": "xxShare via NeoChatxx",
"Description[zh_CN]": "通过 NeoChat 分享",
"Description[zh_TW]": "透過 NeoChat 分享", "Description[zh_TW]": "透過 NeoChat 分享",
"Icon": "org.kde.neochat.tray", "Icon": "org.kde.neochat",
"License": "GPL", "License": "GPL",
"Name": "NeoChat", "Name": "NeoChat",
"Name[ar]": "نيوتشات", "Name[ar]": "نيوتشات",
@@ -97,7 +93,6 @@
"Name[nl]": "NeoChat", "Name[nl]": "NeoChat",
"Name[nn]": "NeoChat", "Name[nn]": "NeoChat",
"Name[pl]": "NeoChat", "Name[pl]": "NeoChat",
"Name[pt_BR]": "NeoChat",
"Name[ru]": "NeoChat", "Name[ru]": "NeoChat",
"Name[sk]": "NeoChat", "Name[sk]": "NeoChat",
"Name[sl]": "NeoChat", "Name[sl]": "NeoChat",
@@ -106,7 +101,6 @@
"Name[tr]": "NeoChat", "Name[tr]": "NeoChat",
"Name[uk]": "NeoChat", "Name[uk]": "NeoChat",
"Name[x-test]": "xxNeoChatxx", "Name[x-test]": "xxNeoChatxx",
"Name[zh_CN]": "NeoChat",
"Name[zh_TW]": "NeoChat", "Name[zh_TW]": "NeoChat",
"X-Purpose-ActionDisplay": "NeoChat" "X-Purpose-ActionDisplay": "NeoChat"
}, },

View File

@@ -27,8 +27,7 @@ QQC2.Menu {
text: "https://matrix.to/#/" + root.connection.localUser.id, text: "https://matrix.to/#/" + root.connection.localUser.id,
title: root.connection.localUser.displayName, title: root.connection.localUser.displayName,
subtitle: root.connection.localUser.id, subtitle: root.connection.localUser.id,
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl. avatarSource: root.connection.makeMediaUrl(root.connection.localUser.avatarUrl)
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
}); });
if (typeof root.closeDialog === "function") { if (typeof root.closeDialog === "function") {
root.closeDialog(); root.closeDialog();

View File

@@ -125,7 +125,7 @@ Kirigami.Dialog {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
} }
source: userDelegate.connection.localUser.avatarUrl.toString().length > 0 ? userDelegate.connection.makeMediaUrl(userDelegate.connection.localUser.avatarUrl) : "" source: userDelegate.connection.localUser.avatarMediaId ? userDelegate.connection.makeMediaUrl("mxc://" + userDelegate.connection.localUser.avatarMediaId) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
} }

View File

@@ -18,7 +18,7 @@ QQC2.ItemDelegate {
required property NeoChatRoom currentRoom required property NeoChatRoom currentRoom
required property bool categoryVisible required property bool categoryVisible
required property string filterText required property string filterText
required property url avatar required property string avatar
required property string displayName required property string displayName
topPadding: Kirigami.Units.largeSpacing topPadding: Kirigami.Units.largeSpacing
@@ -32,7 +32,7 @@ QQC2.ItemDelegate {
visible: root.categoryVisible || filterText.length > 0 visible: root.categoryVisible || filterText.length > 0
contentItem: KirigamiComponents.Avatar { contentItem: KirigamiComponents.Avatar {
source: root.avatar source: root.avatar ? root.currentRoom.connection.makeMediaUrl("mxc://" + root.avatar) : ""
name: root.displayName name: root.displayName
sourceSize { sourceSize {

View File

@@ -163,7 +163,7 @@ Loader {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar { KirigamiComponents.Avatar {
id: avatar id: avatar
source: room.avatarMediaUrl source: room.avatarMediaId ? root.connection.makeMediaUrl("mxc://" + room.avatarMediaId) : ""
name: room.displayName name: room.displayName
Layout.preferredWidth: Kirigami.Units.gridUnit * 3 Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3 Layout.preferredHeight: Kirigami.Units.gridUnit * 3

View File

@@ -5,7 +5,6 @@
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
@@ -178,25 +177,10 @@ Loader {
Repeater { Repeater {
model: root.actions model: root.actions
DelegateChooser { QQC2.MenuItem {
role: "separator" visible: modelData.visible
DelegateChoice { action: modelData
roleValue: true onClicked: root.item.close()
QQC2.MenuSeparator {
visible: modelData.visible
}
}
DelegateChoice {
roleValue: false
QQC2.MenuItem {
visible: modelData.visible
action: modelData
onClicked: root.item.close()
}
}
} }
} }
QQC2.Menu { QQC2.Menu {
@@ -212,7 +196,7 @@ Loader {
model: WebShortcutModel { model: WebShortcutModel {
id: webshortcutmodel id: webshortcutmodel
selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText
onOpenUrl: url => RoomManager.resolveResource(url.toString()) onOpenUrl: RoomManager.resolveResource(url)
} }
delegate: QQC2.MenuItem { delegate: QQC2.MenuItem {
text: model.display text: model.display
@@ -357,30 +341,15 @@ Loader {
id: listViewAction id: listViewAction
model: root.actions model: root.actions
DelegateChooser { FormCard.FormButtonDelegate {
role: "separator" icon.name: modelData.icon.name
DelegateChoice { icon.color: modelData.icon.color ?? undefined
roleValue: true enabled: modelData.enabled
visible: modelData.visible
FormCard.FormDelegateSeparator { text: modelData.text
visible: modelData.visible onClicked: {
} modelData.triggered();
} root.item.close();
DelegateChoice {
roleValue: false
FormCard.FormButtonDelegate {
icon.name: modelData.icon.name
icon.color: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
modelData.triggered();
root.item.close();
}
}
} }
} }
} }

View File

@@ -38,7 +38,7 @@ ColumnLayout {
contentItem: KirigamiComponents.Avatar { contentItem: KirigamiComponents.Avatar {
name: root.room ? root.room.displayName : "" name: root.room ? root.room.displayName : ""
source: root.room ? root.room.avatarMediaUrl : "" source: root.room ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
Rectangle { Rectangle {
visible: root.room.usesEncryption visible: root.room.usesEncryption

View File

@@ -54,8 +54,8 @@ DelegateContextMenu {
icon.name: "document-save" icon.name: "document-save"
onTriggered: { onTriggered: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay); var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
dialog.selectedFile = currentRoom.fileNameToDownload(eventId);
dialog.open(); dialog.open();
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId);
} }
}, },
DelegateContextMenu.ReplyMessageAction {}, DelegateContextMenu.ReplyMessageAction {},

View File

@@ -102,7 +102,7 @@ Labs.MenuBar {
} }
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "About KDE") text: i18nc("menu", "About KDE")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage")) onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDE"))
} }
} }
} }

View File

@@ -34,7 +34,7 @@ ColumnLayout {
Layout.preferredHeight: Kirigami.Units.iconSizes.large Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : "" name: root.room ? root.room.displayName : ""
source: root.room ? root.room.avatarMediaUrl : "" source: root.room ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
Rectangle { Rectangle {
visible: room.usesEncryption visible: room.usesEncryption
@@ -75,7 +75,6 @@ ColumnLayout {
textFormat: TextEdit.PlainText textFormat: TextEdit.PlainText
visible: root.room && root.room.canonicalAlias visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : "" text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
} }
} }
QQC2.AbstractButton { QQC2.AbstractButton {
@@ -89,11 +88,11 @@ ColumnLayout {
} }
onClicked: { onClicked: {
let map = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, { let map = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(parent, {
text: barcode.content, text: barcode.content,
title: root.room ? root.room.displayName : "", title: root.room ? root.room.displayName : "",
subtitle: root.room ? root.room.id : "", subtitle: root.room ? root.room.id : "",
avatarSource: root.room ? root.room.avatarMediaUrl : "" avatarSource: root.room && root.room.avatarMediaId ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
}); });
map.open(); map.open();
} }

View File

@@ -7,6 +7,7 @@ import QtQuick.Layouts
import QtCore as Core import QtCore as Core
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kquickimageeditor as KQuickImageEditor import org.kde.kquickimageeditor as KQuickImageEditor
Kirigami.Page { Kirigami.Page {
@@ -167,11 +168,10 @@ Kirigami.Page {
} }
} }
footer: Kirigami.InlineMessage { footer: KirigamiComponents.Banner {
id: msg id: msg
type: Kirigami.MessageType.Error type: Kirigami.MessageType.Error
showCloseButton: true showCloseButton: true
visible: false visible: false
position: Kirigami.InlineMessage.Position.Header
} }
} }

View File

@@ -318,13 +318,11 @@ Kirigami.ApplicationWindow {
}) })
} }
function showUserDetail(user, room) { function showUserDetail(user, room) {
const dialog = Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root, { Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root.QQC2.ApplicationWindow.window, {
room: room, room: room,
user: user, user: user,
connection: root.connection, connection: root.connection
}); }).open();
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
dialog.open();
} }
function load() { function load() {

View File

@@ -29,20 +29,13 @@ Components.AlbumMaximizeComponent {
readonly property var currentProgressInfo: model.data(model.index(content.currentIndex, 0), MessageEventModel.ProgressInfoRole) readonly property var currentProgressInfo: model.data(model.index(content.currentIndex, 0), MessageEventModel.ProgressInfoRole)
onCurrentProgressInfoChanged: () => {
if (root.currentProgressInfo) {
root.downloadAction.progress = root.currentProgressInfo.progress / root.currentProgressInfo.total * 100.0;
} else {
root.downloadAction.progress = 0;
}
}
/** /**
* @brief Whether the delegate is part of a thread timeline. * @brief Whether the delegate is part of a thread timeline.
*/ */
property bool isThread: false property bool isThread: false
downloadAction: Components.DownloadAction { downloadAction: Components.DownloadAction {
id: downloadAction
onTriggered: { onTriggered: {
currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId)); currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId));
} }
@@ -69,11 +62,19 @@ Components.AlbumMaximizeComponent {
function onFileTransferProgress(id, progress, total) { function onFileTransferProgress(id, progress, total) {
if (id == root.currentEventId) { if (id == root.currentEventId) {
root.downloadAction.progress = progress / total * 100.0; downloadAction.progress = progress / total * 100.0;
} }
} }
} }
Connections {
target: content
function onCurrentIndexChanged() {
downloadAction.progress = currentProgressInfo.progress / currentProgressInfo.total * 100.0;
}
}
leading: RowLayout { leading: RowLayout {
Components.Avatar { Components.Avatar {
id: userAvatar id: userAvatar
@@ -105,12 +106,12 @@ Components.AlbumMaximizeComponent {
onOpened: forceActiveFocus() onOpened: forceActiveFocus()
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom, root.currentAuthor) onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
onSaveItem: { onSaveItem: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay); var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
dialog.selectedFile = currentRoom.fileNameToDownload(root.currentEventId);
dialog.open(); dialog.open();
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId);
} }
Connections { Connections {

View File

@@ -12,7 +12,7 @@ Components.AbstractMaximizeComponent {
required property string text required property string text
property color avatarColor property color avatarColor
required property url avatarSource required property string avatarSource
onOpened: forceActiveFocus() onOpened: forceActiveFocus()

View File

@@ -27,7 +27,6 @@ Kirigami.SearchDialog {
onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text
model: RoomManager.sortFilterRoomListModel model: RoomManager.sortFilterRoomListModel
emptyText: i18nc("Placeholder message", "No room found") emptyText: i18nc("Placeholder message", "No room found")
parent: QQC2.Overlay.overlay
delegate: RoomDelegate { delegate: RoomDelegate {
connection: root.connection connection: root.connection

View File

@@ -30,9 +30,9 @@ Kirigami.Dialog {
FormCard.AbstractFormDelegate { FormCard.AbstractFormDelegate {
background: null background: null
contentItem: RowLayout { contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing * 4
Avatar { Avatar {
source: SpaceHierarchyCache.recommendedSpaceAvatar.toString().length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : 0 source: root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar)
name: SpaceHierarchyCache.recommendedSpaceDisplayName name: SpaceHierarchyCache.recommendedSpaceDisplayName
} }
ColumnLayout { ColumnLayout {
@@ -51,7 +51,6 @@ Kirigami.Dialog {
FormCard.FormDelegateSeparator {} FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Join") text: i18nc("@action:button", "Join")
icon.name: "list-add-symbolic"
onClicked: { onClicked: {
SpaceHierarchyCache.recommendedSpaceHidden = true; SpaceHierarchyCache.recommendedSpaceHidden = true;
RoomManager.resolveResource(SpaceHierarchyCache.recommendedSpaceId, "join"); RoomManager.resolveResource(SpaceHierarchyCache.recommendedSpaceId, "join");
@@ -59,7 +58,6 @@ Kirigami.Dialog {
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
icon.name: "mail-thread-ignored-symbolic"
text: i18nc("@action:button", "Ignore") text: i18nc("@action:button", "Ignore")
onClicked: { onClicked: {
SpaceHierarchyCache.recommendedSpaceHidden = true; SpaceHierarchyCache.recommendedSpaceHidden = true;

View File

@@ -21,7 +21,7 @@ Delegates.RoundedItemDelegate {
required property bool hasHighlightNotifications required property bool hasHighlightNotifications
required property NeoChatRoom currentRoom required property NeoChatRoom currentRoom
required property NeoChatConnection connection required property NeoChatConnection connection
required property url avatar required property string avatar
required property string subtitleText required property string subtitleText
required property string displayName required property string displayName
@@ -55,7 +55,7 @@ Delegates.RoundedItemDelegate {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
AvatarNotification { AvatarNotification {
source: root.avatar source: root.avatar ? root.connection.makeMediaUrl("mxc://" + root.avatar) : ""
name: root.displayName name: root.displayName
visible: NeoChatConfig.showAvatarInRoomDrawer visible: NeoChatConfig.showAvatarInRoomDrawer
implicitHeight: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) implicitHeight: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2)

View File

@@ -99,11 +99,8 @@ Kirigami.OverlayDrawer {
Layout.preferredHeight: pageStack.globalToolBar.preferredHeight Layout.preferredHeight: pageStack.globalToolBar.preferredHeight
contentItem: RowLayout { contentItem: RowLayout {
spacing: 0
Kirigami.Heading { Kirigami.Heading {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
text: drawerItemLoader.item ? drawerItemLoader.item.title : "" text: drawerItemLoader.item ? drawerItemLoader.item.title : ""
} }

View File

@@ -219,7 +219,7 @@ QQC2.ScrollView {
required property int index required property int index
required property string name required property string name
required property string userId required property string userId
required property url avatar required property string avatar
required property int powerLevel required property int powerLevel
required property string powerLevelString required property string powerLevelString

View File

@@ -7,6 +7,7 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kitemmodels import org.kde.kitemmodels
@@ -98,12 +99,11 @@ Kirigami.Page {
} }
} }
header: Kirigami.InlineMessage { header: KirigamiComponents.Banner {
id: banner id: banner
showCloseButton: true showCloseButton: true
visible: false visible: false
position: Kirigami.InlineMessage.Position.Header
} }
Loader { Loader {
@@ -198,7 +198,7 @@ Kirigami.Page {
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: { onActivated: {
if (!timelineViewLoader.item.atYEnd || !root.currentRoom.partiallyReadStats.empty()) { if (!timelineViewLoader.item.atYEnd || root.currentRoom.hasUnreadMessages) {
timelineViewLoader.item.goToLastMessage(); timelineViewLoader.item.goToLastMessage();
root.currentRoom.markAllMessagesAsRead(); root.currentRoom.markAllMessagesAsRead();
} else { } else {
@@ -308,7 +308,6 @@ Kirigami.Page {
NeochatMaximizeComponent { NeochatMaximizeComponent {
currentRoom: root.currentRoom currentRoom: root.currentRoom
model: root.mediaMessageFilterModel model: root.mediaMessageFilterModel
parent: root.QQC2.Overlay.overlay
} }
} }
} }

View File

@@ -131,7 +131,7 @@ QQC2.Control {
text: i18nc("@button View all one-on-one chats with your friends.", "Friends") text: i18nc("@button View all one-on-one chats with your friends.", "Friends")
contentItem: Kirigami.Icon { contentItem: Kirigami.Icon {
source: "system-users-symbolic" source: "system-users"
QQC2.Label { QQC2.Label {
id: directChatNotificationCountLabel id: directChatNotificationCountLabel
@@ -182,7 +182,7 @@ QQC2.Control {
id: spaceDelegate id: spaceDelegate
required property string displayName required property string displayName
required property url avatar required property string avatar
required property string roomId required property string roomId
required property var currentRoom required property var currentRoom
@@ -191,7 +191,7 @@ QQC2.Control {
Layout.maximumHeight: width - Kirigami.Units.smallSpacing Layout.maximumHeight: width - Kirigami.Units.smallSpacing
text: displayName text: displayName
source: avatar source: avatar ? root.connection.makeMediaUrl("mxc://" + avatar) : ""
notificationCount: spaceDelegate.currentRoom.childrenNotificationCount notificationCount: spaceDelegate.currentRoom.childrenNotificationCount
notificationHighlight: spaceDelegate.currentRoom.childrenHaveHighlightNotifications notificationHighlight: spaceDelegate.currentRoom.childrenHaveHighlightNotifications
@@ -219,7 +219,7 @@ QQC2.Control {
visible: SpaceHierarchyCache.recommendedSpaceId.length > 0 && !root.connection.room(SpaceHierarchyCache.recommendedSpaceId) && !SpaceHierarchyCache.recommendedSpaceHidden visible: SpaceHierarchyCache.recommendedSpaceId.length > 0 && !root.connection.room(SpaceHierarchyCache.recommendedSpaceId) && !SpaceHierarchyCache.recommendedSpaceHidden
text: i18nc("Join <name of a space>", "Join %1", SpaceHierarchyCache.recommendedSpaceDisplayName) text: i18nc("Join <name of a space>", "Join %1", SpaceHierarchyCache.recommendedSpaceDisplayName)
source: SpaceHierarchyCache.recommendedSpaceAvatar.toString().length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : "" source: SpaceHierarchyCache.recommendedSpaceAvatar.length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : ""
onSelected: { onSelected: {
recommendedSpaceDialogComponent.createObject(QQC2.Overlay.overlay, { recommendedSpaceDialogComponent.createObject(QQC2.Overlay.overlay, {
connection: root.connection connection: root.connection

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