Compare commits
1 Commits
release/25
...
work/carl/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
586054bc0b |
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.8",
|
||||
"runtime-version": "6.7",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
@@ -25,23 +25,13 @@
|
||||
"modules": [
|
||||
{
|
||||
"name": "kirigamiaddons",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTING=OFF"
|
||||
],
|
||||
"config-opts": [ "-DBUILD_TESTING=OFF" ],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
|
||||
}
|
||||
]
|
||||
"sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "commit": "34d311219e8b7209746a98b3a29b91ded05ff936" } ]
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"config-opts": [
|
||||
"-DBUILD_WITH_QT6=ON",
|
||||
"-DBUILD_TESTING=OFF"
|
||||
],
|
||||
"config-opts": [ "-DBUILD_WITH_QT6=ON" ],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
@@ -53,19 +43,17 @@
|
||||
{
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DOLM_TESTS=OFF"
|
||||
],
|
||||
"config-opts": [ "-DOLM_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.matrix.org/matrix-org/olm.git",
|
||||
"tag": "3.2.16",
|
||||
"tag": "3.2.10",
|
||||
"x-checker-data": {
|
||||
"type": "git",
|
||||
"tag-pattern": "^([\\d.]+)$"
|
||||
},
|
||||
"commit": "7e0c8277032e40308987257b711b38af8d77cc69"
|
||||
"commit": "9908862979147a71dc6abaecd521be526ae77be1"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -77,13 +65,13 @@
|
||||
"-Dvapi=false",
|
||||
"-Dgtk_doc=false",
|
||||
"-Dintrospection=false",
|
||||
"-Dcrypto=disabled"
|
||||
"-Dgcrypt=false"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
|
||||
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
|
||||
"sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
|
||||
"x-checker-data": {
|
||||
"type": "gnome",
|
||||
"name": "libsecret",
|
||||
@@ -98,13 +86,13 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.15.0.tar.gz",
|
||||
"sha256": "f4254dc8f0933b06d90672d683eab08ef770acd8336e44dfa030ce041dc2ca22",
|
||||
"url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
|
||||
"sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 4138,
|
||||
"stable-only": true,
|
||||
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/$version.tar.gz"
|
||||
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -112,8 +100,7 @@
|
||||
"-DBUILD_WITH_QT6=ON",
|
||||
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
|
||||
"-DLIB_INSTALL_DIR=/app/lib",
|
||||
"-DBUILD_TRANSLATIONS=NO",
|
||||
"-DBUILD_TESTING=OFF"
|
||||
"-DBUILD_TRANSLATIONS=NO"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -136,31 +123,29 @@
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DCMARK_TESTS=OFF",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INSTALL_PREFIX=/app"
|
||||
],
|
||||
"config-opts": [ "-DCMARK_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/commonmark/cmark.git"
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DCMARK_TESTS=OFF",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INSTALL_PREFIX=/app"
|
||||
],
|
||||
"builddir": true
|
||||
},
|
||||
{
|
||||
"name": "qcoro",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DQCORO_BUILD_EXAMPLES=OFF",
|
||||
"-DBUILD_TESTING=OFF"
|
||||
],
|
||||
"config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
|
||||
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
|
||||
"sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 236236,
|
||||
@@ -173,15 +158,14 @@
|
||||
{
|
||||
"name": "neochat",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTING=OFF",
|
||||
"-DNEOCHAT_FLATPAK=ON"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "dir",
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"config-opts": [
|
||||
"-DNEOCHAT_FLATPAK=ON"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,14 +5,10 @@ include:
|
||||
- project: sysadmin/ci-utilities
|
||||
file:
|
||||
- /gitlab-templates/reuse-lint.yml
|
||||
- /gitlab-templates/json-validation.yml
|
||||
- /gitlab-templates/xml-lint.yml
|
||||
- /gitlab-templates/yaml-lint.yml
|
||||
- /gitlab-templates/android-qt6.yml
|
||||
- /gitlab-templates/linux-qt6.yml
|
||||
# - /gitlab-templates/linux-qt6-next.yml
|
||||
- /gitlab-templates/windows-qt6.yml
|
||||
# - /gitlab-templates/freebsd-qt6.yml
|
||||
# - /gitlab-templates/freebsd-qt6.yml
|
||||
- /gitlab-templates/flatpak.yml
|
||||
- /gitlab-templates/snap-snapcraft-lxd.yml
|
||||
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||
|
||||
71
.kde-ci.yml
71
.kde-ci.yml
@@ -2,43 +2,42 @@
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
Dependencies:
|
||||
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
'require':
|
||||
'frameworks/extra-cmake-modules': '@latest-kf6'
|
||||
'frameworks/kcoreaddons': '@latest-kf6'
|
||||
'frameworks/kirigami': '@latest-kf6'
|
||||
'frameworks/ki18n': '@latest-kf6'
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'frameworks/kcolorscheme': '@latest-kf6'
|
||||
'frameworks/kiconthemes': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
'frameworks/prison': '@latest-kf6'
|
||||
'libraries/kirigami-addons': '@latest-kf6'
|
||||
'third-party/libquotient': '@latest'
|
||||
'third-party/qtkeychain': '@latest'
|
||||
'third-party/cmark': '@latest'
|
||||
'third-party/qcoro': '@latest'
|
||||
- 'on': ['Windows', 'Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/qqc2-desktop-style': '@latest-kf6'
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
'frameworks/kcrash': '@latest-kf6'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/purpose': '@latest-kf6'
|
||||
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
'require':
|
||||
'frameworks/extra-cmake-modules': '@latest-kf6'
|
||||
'frameworks/kcoreaddons': '@latest-kf6'
|
||||
'frameworks/kirigami': '@latest-kf6'
|
||||
'frameworks/ki18n': '@latest-kf6'
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'frameworks/kcolorscheme': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
'frameworks/prison': '@latest-kf6'
|
||||
'libraries/kirigami-addons': '@latest-kf6'
|
||||
'third-party/libquotient': '@latest'
|
||||
'third-party/qtkeychain': '@latest'
|
||||
'third-party/cmark': '@latest'
|
||||
'third-party/qcoro': '@latest'
|
||||
- 'on': ['Windows', 'Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/qqc2-desktop-style': '@latest-kf6'
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
'frameworks/kcrash': '@latest-kf6'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/purpose': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux']
|
||||
'require':
|
||||
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
|
||||
- 'on': ['Linux']
|
||||
'require':
|
||||
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
|
||||
|
||||
Options:
|
||||
per-test-timeout: 90
|
||||
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD', 'Windows' ]
|
||||
|
||||
@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "04")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "3")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -66,7 +66,7 @@ if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
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
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
|
||||
@@ -20,11 +20,11 @@ class ReactionModelTest : public QObject
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
MessageContentModel *parentModel;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void nullModel();
|
||||
void basicReaction();
|
||||
void newReaction();
|
||||
};
|
||||
@@ -33,13 +33,20 @@ void ReactionModelTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
|
||||
parentModel = new MessageContentModel(room, "123456"_L1);
|
||||
}
|
||||
|
||||
void ReactionModelTest::nullModel()
|
||||
{
|
||||
auto model = ReactionModel(nullptr, nullptr);
|
||||
|
||||
QCOMPARE(model.rowCount(), 0);
|
||||
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
|
||||
}
|
||||
|
||||
void ReactionModelTest::basicReaction()
|
||||
{
|
||||
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
|
||||
auto model = ReactionModel(parentModel, event->id(), room);
|
||||
auto model = ReactionModel(event, room);
|
||||
|
||||
QCOMPARE(model.rowCount(), 1);
|
||||
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), u"<span style=\"font-family: 'emoji';\">👍</span>"_s);
|
||||
@@ -51,7 +58,7 @@ void ReactionModelTest::basicReaction()
|
||||
void ReactionModelTest::newReaction()
|
||||
{
|
||||
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
|
||||
auto model = new ReactionModel(parentModel, event->id(), room);
|
||||
auto model = new ReactionModel(event, room);
|
||||
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s);
|
||||
|
||||
@@ -530,9 +530,6 @@ void TextHandlerTest::componentOutput_data()
|
||||
QTest::newRow("quote") << u"<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>"_s
|
||||
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
|
||||
MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}};
|
||||
QTest::newRow("multiple paragraph quote") << u"<blockquote>\n<p>blockquote</p>\n<p>next paragraph</p>\n</blockquote>"_s
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Quote, u"<p>“blockquote</p>\n<p>next paragraph”</p>"_s, {}}};
|
||||
QTest::newRow("no tag first paragraph") << u"Text\n<p>Text</p>"_s
|
||||
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
|
||||
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};
|
||||
|
||||
@@ -137,11 +137,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
model->setRoom(room);
|
||||
QCOMPARE(model->rowCount(), 0);
|
||||
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
auto txnId = room->postText("New plain message"_L1);
|
||||
#else
|
||||
auto txnId = room->postPlainText("New plain message"_L1);
|
||||
#endif
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(spyInsert.count(), 1);
|
||||
|
||||
@@ -149,11 +145,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
QCOMPARE(model->rowCount(), 0);
|
||||
QCOMPARE(spyRemove.count(), 1);
|
||||
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
txnId = room->postText("New plain message"_L1);
|
||||
#else
|
||||
txnId = room->postPlainText("New plain message"_L1);
|
||||
#endif
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(spyInsert.count(), 2);
|
||||
|
||||
|
||||
@@ -70,12 +70,10 @@
|
||||
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
||||
<summary xml:lang="it">Chat su Matrix</summary>
|
||||
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix에서 대화하기</summary>
|
||||
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary>
|
||||
<summary xml:lang="nl">Chat op Matrix</summary>
|
||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||
<summary xml:lang="ru">Общение в Matrix</summary>
|
||||
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
|
||||
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta på Matrix</summary>
|
||||
@@ -104,7 +102,6 @@
|
||||
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
|
||||
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
|
||||
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 네트워크를 사용하는 채팅 앱입니다. 텍스트 메시지, 동영상, 오디오 파일을 가족, 친구, 동료와 안전하게 공유할 수 있습니다.</p>
|
||||
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
||||
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
||||
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
||||
@@ -375,7 +372,6 @@
|
||||
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
|
||||
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
|
||||
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
||||
<caption xml:lang="ko">Matrix 스페이스에서 새로운 커뮤니티 탐험</caption>
|
||||
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
||||
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
||||
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
||||
@@ -477,12 +473,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="25.04.3" date="2025-07-03"/>
|
||||
<release version="25.04.2" date="2025-06-05"/>
|
||||
<release version="25.04.1" date="2025-05-08"/>
|
||||
<release version="25.04.0" date="2025-04-17"/>
|
||||
<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"/>
|
||||
@@ -659,9 +649,6 @@
|
||||
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
|
||||
</release>
|
||||
</releases>
|
||||
<requires>
|
||||
<display_length compare="ge">360</display_length>
|
||||
</requires>
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#a6e4f3</color>
|
||||
<color type="primary" scheme_preference="dark">#235670</color>
|
||||
|
||||
@@ -109,12 +109,9 @@ Comment[hu]=Csevegés Matrixon
|
||||
Comment[ia]=Conversation en ditecto sur Matrix
|
||||
Comment[it]= su Matrix
|
||||
Comment[ka]=ჩატი Matrix-ზე
|
||||
Comment[ko]=Matrix에서 대화하기
|
||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||
Comment[nl]=Chat op Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
Comment[pt_BR]=Bate papo na Matrix
|
||||
Comment[ru]=Общение в Matrix
|
||||
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
|
||||
Comment[sl]=Klepet na Matrixu
|
||||
Comment[sv]=Chatta på Matrix
|
||||
@@ -122,7 +119,6 @@ Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவு
|
||||
Comment[tr]=Matrix üzerinde sohbet edin
|
||||
Comment[uk]=Спілкування у Matrix
|
||||
Comment[x-test]=xxChat on Matrixxx
|
||||
Comment[zh_CN]=在 Matrix 上聊天
|
||||
Comment[zh_TW]=在 Matrix 上聊天
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
Exec=neochat %u
|
||||
|
||||
1192
po/ar/neochat.po
1192
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1110
po/ast/neochat.po
1110
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1258
po/az/neochat.po
1258
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1156
po/ca/neochat.po
1156
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1498
po/cs/neochat.po
1498
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1199
po/da/neochat.po
1199
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1482
po/de/neochat.po
1482
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1305
po/el/neochat.po
1305
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1248
po/en_GB/neochat.po
1248
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1149
po/eo/neochat.po
1149
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1931
po/es/neochat.po
1931
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1204
po/eu/neochat.po
1204
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1289
po/fi/neochat.po
1289
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1340
po/fr/neochat.po
1340
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1235
po/gl/neochat.po
1235
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
6167
po/he/neochat.po
6167
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
1239
po/hi/neochat.po
1239
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
1531
po/hu/neochat.po
1531
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1198
po/ia/neochat.po
1198
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1266
po/id/neochat.po
1266
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1231
po/ie/neochat.po
1231
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1196
po/it/neochat.po
1196
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1109
po/ja/neochat.po
1109
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1189
po/ka/neochat.po
1189
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
2604
po/ko/neochat.po
2604
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1112
po/lt/neochat.po
1112
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1489
po/lv/neochat.po
1489
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1195
po/nl/neochat.po
1195
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1165
po/nn/neochat.po
1165
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1256
po/pa/neochat.po
1256
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1201
po/pl/neochat.po
1201
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1266
po/pt/neochat.po
1266
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1257
po/pt_BR/neochat.po
1257
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
2028
po/ru/neochat.po
2028
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1239
po/sa/neochat.po
1239
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
1250
po/sk/neochat.po
1250
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1189
po/sl/neochat.po
1189
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1308
po/sv/neochat.po
1308
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1240
po/ta/neochat.po
1240
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1192
po/tok/neochat.po
1192
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1225
po/tr/neochat.po
1225
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1194
po/uk/neochat.po
1194
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1149
po/zh_CN/neochat.po
1149
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1189
po/zh_TW/neochat.po
1189
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Scarlett Moore <sgmoore@kde.org>
|
||||
# SPDX-FileCopyrightText: 2024 Scarlett Moore <sgmoore@kde.org>
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
name: neochat
|
||||
base: core24
|
||||
base: core22
|
||||
adopt-info: neochat
|
||||
grade: stable
|
||||
confinement: strict
|
||||
@@ -24,15 +24,12 @@ apps:
|
||||
- network-manager-observe
|
||||
- password-manager-service
|
||||
- accounts-service
|
||||
environment:
|
||||
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
|
||||
QML_IMPORT_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml"
|
||||
|
||||
compression: lzo
|
||||
|
||||
package-repositories:
|
||||
- type: apt
|
||||
ppa: ubuntu-toolchain-r/test
|
||||
- type: apt
|
||||
ppa: ubuntu-toolchain-r/test
|
||||
|
||||
slots:
|
||||
session-dbus-interface:
|
||||
@@ -44,12 +41,11 @@ parts:
|
||||
olm:
|
||||
source: https://gitlab.matrix.org/matrix-org/olm.git
|
||||
source-depth: 1
|
||||
source-tag: '3.2.16'
|
||||
source-tag: '3.2.12'
|
||||
plugin: cmake
|
||||
cmake-parameters:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
@@ -70,7 +66,7 @@ parts:
|
||||
- -Dcrypto=disabled
|
||||
- -Dgtk_doc=false
|
||||
build-packages:
|
||||
- meson
|
||||
- meson
|
||||
- libglib2.0-dev
|
||||
- libgcrypt20-dev
|
||||
prime:
|
||||
@@ -85,7 +81,7 @@ parts:
|
||||
plugin: cmake
|
||||
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:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
@@ -109,6 +105,8 @@ parts:
|
||||
build-snaps:
|
||||
- cmake
|
||||
build-packages:
|
||||
- gcc-13
|
||||
- g++-13
|
||||
- libssl-dev
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
@@ -116,6 +114,9 @@ parts:
|
||||
- -DBUILD_TESTING=OFF
|
||||
- -DQuotient_ENABLE_E2EE=ON
|
||||
- -DBUILD_WITH_QT6=ON
|
||||
override-build: |
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13
|
||||
craftctl default
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
@@ -128,8 +129,6 @@ parts:
|
||||
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:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
@@ -140,32 +139,17 @@ parts:
|
||||
- -usr/lib/*/pkgconfig
|
||||
- -usr/lib/*/cmake
|
||||
|
||||
kunifiedpush:
|
||||
source: https://invent.kde.org/libraries/kunifiedpush.git
|
||||
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:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_TESTING=OFF
|
||||
|
||||
neochat:
|
||||
after:
|
||||
- qtkeychain
|
||||
- libquotient
|
||||
- kquickimageeditor
|
||||
- kunifiedpush
|
||||
parse-info:
|
||||
- usr/share/metainfo/org.kde.neochat.appdata.xml
|
||||
source: .
|
||||
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:
|
||||
- cmark
|
||||
- libcmark-dev
|
||||
@@ -189,12 +173,3 @@ parts:
|
||||
prime:
|
||||
- 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
|
||||
|
||||
@@ -107,6 +107,8 @@ add_library(neochat STATIC
|
||||
models/imagepacksmodel.h
|
||||
events/imagepackevent.cpp
|
||||
events/imagepackevent.h
|
||||
events/joinrulesevent.cpp
|
||||
events/joinrulesevent.h
|
||||
models/reactionmodel.cpp
|
||||
models/reactionmodel.h
|
||||
delegatesizehelper.cpp
|
||||
@@ -124,6 +126,8 @@ add_library(neochat STATIC
|
||||
registration.cpp
|
||||
neochatconnection.cpp
|
||||
neochatconnection.h
|
||||
jobs/neochatdeactivateaccountjob.cpp
|
||||
jobs/neochatdeactivateaccountjob.h
|
||||
jobs/neochatgetcommonroomsjob.cpp
|
||||
jobs/neochatgetcommonroomsjob.h
|
||||
mediasizehelper.cpp
|
||||
@@ -190,12 +194,6 @@ add_library(neochat STATIC
|
||||
models/roomsortparametermodel.h
|
||||
models/messagemodel.cpp
|
||||
models/messagemodel.h
|
||||
models/messagecontentfiltermodel.cpp
|
||||
models/messagecontentfiltermodel.h
|
||||
models/pinnedmessagemodel.cpp
|
||||
models/pinnedmessagemodel.h
|
||||
models/commonroomsmodel.cpp
|
||||
models/commonroomsmodel.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
@@ -206,9 +204,6 @@ if(ANDROID OR WIN32)
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
QT_QML_SOURCE_TYPENAME ShareAction
|
||||
)
|
||||
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
||||
QT_QML_SOURCE_TYPENAME GlobalMenu
|
||||
)
|
||||
endif()
|
||||
|
||||
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
@@ -218,7 +213,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/AccountMenu.qml
|
||||
qml/ExploreComponent.qml
|
||||
qml/ExploreComponentMobile.qml
|
||||
qml/RoomContextMenu.qml
|
||||
qml/ContextMenu.qml
|
||||
qml/CollapsedRoomDelegate.qml
|
||||
qml/RoomDelegate.qml
|
||||
qml/RoomListPage.qml
|
||||
@@ -254,7 +249,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/RoomPinnedMessagesPage.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
qml/InvitationView.qml
|
||||
@@ -323,10 +317,7 @@ if(NOT ANDROID AND NOT WIN32)
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
else()
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
qml/ShareActionStub.qml
|
||||
qml/GlobalMenuStub.qml
|
||||
)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
|
||||
endif()
|
||||
|
||||
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
|
||||
@@ -427,7 +418,6 @@ target_link_libraries(neochat PUBLIC
|
||||
KF6::ConfigGui
|
||||
KF6::CoreAddons
|
||||
KF6::SonnetCore
|
||||
KF6::IconThemes
|
||||
KF6::ColorScheme
|
||||
KF6::ItemModels
|
||||
QuotientQt6
|
||||
@@ -501,7 +491,6 @@ if(ANDROID)
|
||||
"network-connect"
|
||||
"list-remove-user"
|
||||
"org.kde.neochat"
|
||||
"org.kde.neochat.tray"
|
||||
"preferences-system-users"
|
||||
"preferences-desktop-theme-global"
|
||||
"notifications"
|
||||
@@ -539,16 +528,12 @@ if(ANDROID)
|
||||
"object-rotate-left"
|
||||
"object-rotate-right"
|
||||
"add-subtitle"
|
||||
"security-high"
|
||||
"security-low"
|
||||
"security-low-symbolic"
|
||||
"kde"
|
||||
"list-remove-symbolic"
|
||||
"edit-delete"
|
||||
"user-home-symbolic"
|
||||
"pin-symbolic"
|
||||
"kt-restore-defaults-symbolic"
|
||||
"user-symbolic"
|
||||
)
|
||||
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
|
||||
else()
|
||||
|
||||
@@ -283,12 +283,12 @@ QQC2.Control {
|
||||
if (quickFormatBar.visible && selectedText.length > 0) {
|
||||
quickFormatBar.close();
|
||||
}
|
||||
} else if (event.key === Qt.Key_Escape && completionMenu.visible) {
|
||||
completionMenu.close();
|
||||
}
|
||||
}
|
||||
Keys.onShortcutOverride: event => {
|
||||
if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.close();
|
||||
} else if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
|
||||
_private.chatBarCache.attachmentPath = "";
|
||||
_private.chatBarCache.replyId = "";
|
||||
_private.chatBarCache.threadId = "";
|
||||
@@ -315,13 +315,11 @@ QQC2.Control {
|
||||
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
||||
onClicked: modelData.trigger()
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: modelData.tooltip
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
contentItem: PieProgressBar {
|
||||
PieProgressBar {
|
||||
visible: modelData.isBusy
|
||||
progress: root.currentRoom.fileUploadingProgress
|
||||
}
|
||||
|
||||
@@ -117,10 +117,6 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
});
|
||||
});
|
||||
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
|
||||
if (!m_document) {
|
||||
m_highlighter->setDocument(nullptr);
|
||||
return;
|
||||
}
|
||||
m_highlighter->setDocument(m_document->textDocument());
|
||||
});
|
||||
connect(this, &ChatDocumentHandler::cursorPositionChanged, this, [this]() {
|
||||
@@ -226,14 +222,11 @@ void ChatDocumentHandler::complete(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we only search for the beginning of the current completion identifier
|
||||
const auto fromIndex = qMax(completionStartIndex(), 0);
|
||||
|
||||
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
|
||||
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString();
|
||||
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
|
||||
auto text = getText();
|
||||
auto at = text.indexOf(QLatin1Char('@'), fromIndex);
|
||||
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
@@ -246,7 +239,7 @@ void ChatDocumentHandler::complete(int index)
|
||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
|
||||
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
|
||||
auto text = getText();
|
||||
auto at = text.indexOf(QLatin1Char('/'), fromIndex);
|
||||
auto at = text.lastIndexOf(QLatin1Char('/'));
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
@@ -254,7 +247,7 @@ void ChatDocumentHandler::complete(int index)
|
||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
|
||||
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
|
||||
auto text = getText();
|
||||
auto at = text.indexOf(QLatin1Char('#'), fromIndex);
|
||||
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
@@ -267,7 +260,7 @@ void ChatDocumentHandler::complete(int index)
|
||||
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
|
||||
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
|
||||
auto text = getText();
|
||||
auto at = text.indexOf(QLatin1Char(':'), fromIndex);
|
||||
auto at = text.lastIndexOf(QLatin1Char(':'));
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
|
||||
@@ -168,6 +168,7 @@ void Controller::addConnection(NeoChatConnection *c)
|
||||
connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
|
||||
m_notificationsManager.handleNotifications(c);
|
||||
});
|
||||
connect(c, &NeoChatConnection::showInviteNotification, &m_notificationsManager, &NotificationsManager::postInviteNotification);
|
||||
|
||||
c->sync();
|
||||
|
||||
@@ -294,7 +295,7 @@ bool Controller::supportSystemTray() const
|
||||
void Controller::setQuitOnLastWindowClosed()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -38,7 +37,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
function openEventSource(stateKey: string): void {
|
||||
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateKeysModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
@@ -50,17 +50,12 @@ public:
|
||||
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
|
||||
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||
Reply, /**< A component to show a replied-to message. */
|
||||
Reaction, /**< A component to show the reactions to this message. */
|
||||
LinkPreview, /**< A preview of a URL in the message. */
|
||||
LinkPreviewLoad, /**< A loading dialog for a link preview. */
|
||||
ChatBar, /**< A text edit for editing a message. */
|
||||
ThreadRoot, /**< The root message of the thread. */
|
||||
ThreadBody, /**< The other messages in the thread. */
|
||||
ReplyButton, /**< A button to reply in the current thread. */
|
||||
FetchButton, /**< A button to fetch more messages in the current thread. */
|
||||
Verification, /**< A user verification session start message. */
|
||||
Loading, /**< The component is loading. */
|
||||
Separator, /**< A horizontal separator. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Quotient
|
||||
{
|
||||
namespace EventContent
|
||||
{
|
||||
class FileInfo;
|
||||
struct FileInfo;
|
||||
}
|
||||
class RoomEvent;
|
||||
class RoomMember;
|
||||
|
||||
16
src/events/joinrulesevent.cpp
Normal file
16
src/events/joinrulesevent.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "joinrulesevent.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
QString JoinRulesEvent::joinRule() const
|
||||
{
|
||||
return fromJson<QString>(contentJson()["join_rule"_L1]);
|
||||
}
|
||||
|
||||
QJsonArray JoinRulesEvent::allow() const
|
||||
{
|
||||
return contentJson()["allow"_L1].toArray();
|
||||
}
|
||||
43
src/events/joinrulesevent.h
Normal file
43
src/events/joinrulesevent.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/events/stateevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class JoinRulesEvent
|
||||
*
|
||||
* Class to define a join rule state event.
|
||||
*
|
||||
* @sa Quotient::StateEvent
|
||||
*/
|
||||
class JoinRulesEvent : public StateEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
|
||||
|
||||
explicit JoinRulesEvent(const QJsonObject &obj)
|
||||
: StateEvent(obj)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The join rule for the room.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* the available join rules for a room.
|
||||
*/
|
||||
QString joinRule() const;
|
||||
|
||||
/**
|
||||
* @brief The allow rule for restricted rooms.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* full details on allow rules.
|
||||
*/
|
||||
QJsonArray allow() const;
|
||||
};
|
||||
}
|
||||
14
src/jobs/neochatdeactivateaccountjob.cpp
Normal file
14
src/jobs/neochatdeactivateaccountjob.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "neochatdeactivateaccountjob.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, u"DisableDeviceJob"_s, "_matrix/client/v3/account/deactivate")
|
||||
{
|
||||
QJsonObject data;
|
||||
addParam<IfNotEmpty>(data, u"auth"_s, auth);
|
||||
setRequestData(data);
|
||||
}
|
||||
12
src/jobs/neochatdeactivateaccountjob.h
Normal file
12
src/jobs/neochatdeactivateaccountjob.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -21,6 +22,7 @@ LinkPreviewer::LinkPreviewer(const QUrl &url, QObject *parent)
|
||||
Q_ASSERT(dynamic_cast<Connection *>(this->parent()));
|
||||
|
||||
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||
|
||||
loadUrlPreview();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ Kirigami.Page {
|
||||
|
||||
property bool showExisting: false
|
||||
property bool _showExisting: showExisting && root.currentStepString === root.initialStep
|
||||
property bool showSettings: true
|
||||
property alias currentStep: module.item
|
||||
property string currentStepString: initialStep
|
||||
property string initialStep: "LoginRegister"
|
||||
@@ -266,7 +265,6 @@ Kirigami.Page {
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing * 2
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
visible: root.showSettings
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Settings")
|
||||
icon.name: "settings-configure"
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include <KCrash>
|
||||
#endif
|
||||
|
||||
#include <KIconTheme>
|
||||
#include <KLocalizedContext>
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -102,7 +101,6 @@ Q_DECL_EXPORT
|
||||
#endif
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
KIconTheme::initTheme();
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
@@ -239,7 +237,6 @@ int main(int argc, char *argv[])
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
|
||||
|
||||
qml_register_types_org_kde_neochat();
|
||||
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
|
||||
@@ -136,11 +136,7 @@ QList<ActionsModel::Action> actions{
|
||||
Action{
|
||||
u"plain"_s,
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
room->postText(text.toHtmlEscaped());
|
||||
#else
|
||||
room->postPlainText(text.toHtmlEscaped());
|
||||
#endif
|
||||
return QString();
|
||||
},
|
||||
std::nullopt,
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "commonroomsmodel.h"
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
CommonRoomsModel::CommonRoomsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NeoChatConnection *CommonRoomsModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void CommonRoomsModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
reload();
|
||||
}
|
||||
|
||||
QString CommonRoomsModel::userId() const
|
||||
{
|
||||
return m_userId;
|
||||
}
|
||||
|
||||
void CommonRoomsModel::setUserId(const QString &userId)
|
||||
{
|
||||
m_userId = userId;
|
||||
Q_EMIT userIdChanged();
|
||||
reload();
|
||||
}
|
||||
|
||||
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
||||
{
|
||||
Q_UNUSED(index)
|
||||
Q_UNUSED(roleName)
|
||||
return {};
|
||||
}
|
||||
|
||||
int CommonRoomsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_commonRooms.size();
|
||||
}
|
||||
|
||||
void CommonRoomsModel::reload()
|
||||
{
|
||||
if (!m_connection || m_userId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_connection->canCheckMutualRooms()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checking if you have mutual rooms with yourself doesn't make sense and servers reject it too
|
||||
if (m_connection->userId() == m_userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
|
||||
const auto &replyData = job->jsonData();
|
||||
beginResetModel();
|
||||
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
|
||||
m_commonRooms.push_back(roomId.toString());
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_commonroomsmodel.cpp"
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
/**
|
||||
* @brief Model to show the common or mutual rooms between you and another user.
|
||||
*/
|
||||
class CommonRoomsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED)
|
||||
Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
TextRole = Qt::DisplayRole,
|
||||
LongitudeRole,
|
||||
LatitudeRole,
|
||||
AssetRole,
|
||||
AuthorRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit CommonRoomsModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
|
||||
[[nodiscard]] QString userId() const;
|
||||
void setUserId(const QString &userId);
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void userIdChanged();
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
void reload();
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QString m_userId;
|
||||
QList<QString> m_commonRooms;
|
||||
};
|
||||
@@ -39,6 +39,14 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date();
|
||||
return day != previousEventDay;
|
||||
}
|
||||
// Catch and force the author to be shown for all rows
|
||||
if (role == TimelineMessageModel::ContentModelRole) {
|
||||
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(TimelineMessageModel::ContentModelRole));
|
||||
if (model != nullptr) {
|
||||
model->setShowAuthor(true);
|
||||
}
|
||||
return QVariant::fromValue<MessageContentModel *>(model);
|
||||
}
|
||||
|
||||
QVariantMap mediaInfo = mapToSource(index).data(TimelineMessageModel::MediaInfoRole).toMap();
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "messagecontentfiltermodel.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
MessageContentFilterModel::MessageContentFilterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool MessageContentFilterModel::showAuthor() const
|
||||
{
|
||||
return m_showAuthor;
|
||||
}
|
||||
|
||||
void MessageContentFilterModel::setShowAuthor(bool showAuthor)
|
||||
{
|
||||
if (showAuthor == m_showAuthor) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_showAuthor = showAuthor;
|
||||
Q_EMIT showAuthorChanged();
|
||||
}
|
||||
|
||||
bool MessageContentFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
if (m_showAuthor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto index = sourceModel()->index(source_row, 0, source_parent);
|
||||
auto contentType = static_cast<MessageComponentType::Type>(index.data(MessageContentModel::ComponentTypeRole).toInt());
|
||||
return contentType != MessageComponentType::Author;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class MessageContentFilterModel
|
||||
*
|
||||
* This model filters a message's contents.
|
||||
*/
|
||||
class MessageContentFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief Whether the author component should be shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
||||
|
||||
public:
|
||||
explicit MessageContentFilterModel(QObject *parent = nullptr);
|
||||
|
||||
bool showAuthor() const;
|
||||
void setShowAuthor(bool showAuthor);
|
||||
|
||||
Q_SIGNALS:
|
||||
void showAuthorChanged();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
bool m_showAuthor = true;
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "messagecontentmodel.h"
|
||||
#include "eventhandler.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
#include <QImageReader>
|
||||
@@ -28,7 +27,6 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "filetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "texthandler.h"
|
||||
@@ -132,6 +130,9 @@ void MessageContentModel::initializeModel()
|
||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||
if (m_room != nullptr) {
|
||||
if (senderId().isEmpty() || senderId() == member.id()) {
|
||||
@@ -151,18 +152,12 @@ void MessageContentModel::initializeModel()
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
});
|
||||
connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (eventId == m_eventId) {
|
||||
updateReactionModel();
|
||||
}
|
||||
});
|
||||
|
||||
initializeEvent();
|
||||
if (m_currentState == Available || m_currentState == Pending) {
|
||||
updateReplyModel();
|
||||
}
|
||||
resetModel();
|
||||
updateReactionModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::initializeEvent()
|
||||
@@ -238,6 +233,32 @@ NeochatRoomMember *MessageContentModel::senderObject() const
|
||||
return m_room->qmlSafeMember(eventResult.first->senderId());
|
||||
}
|
||||
|
||||
bool MessageContentModel::showAuthor() const
|
||||
{
|
||||
return m_showAuthor;
|
||||
}
|
||||
|
||||
void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
{
|
||||
if (showAuthor == m_showAuthor) {
|
||||
return;
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_room->connection()->isIgnored(senderId())) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
endInsertRows();
|
||||
} else {
|
||||
beginRemoveRows({}, 0, 0);
|
||||
m_components.remove(0, 1);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
Q_EMIT showAuthorChanged();
|
||||
}
|
||||
|
||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||
|
||||
QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
@@ -345,21 +366,6 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
}
|
||||
if (role == ReactionModelRole) {
|
||||
return QVariant::fromValue<ReactionModel *>(m_reactionModel);
|
||||
;
|
||||
}
|
||||
if (role == ThreadRootRole) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first);
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))) {
|
||||
#else
|
||||
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
|
||||
#endif
|
||||
return roomMessageEvent->threadRootEventId();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
if (role == LinkPreviewerRole) {
|
||||
if (component.type == MessageComponentType::LinkPreview) {
|
||||
return QVariant::fromValue<LinkPreviewer *>(
|
||||
@@ -386,33 +392,27 @@ int MessageContentModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
{
|
||||
return roleNamesStatic();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[MessageContentModel::DisplayRole] = "display";
|
||||
roles[MessageContentModel::ComponentTypeRole] = "componentType";
|
||||
roles[MessageContentModel::ComponentAttributesRole] = "componentAttributes";
|
||||
roles[MessageContentModel::EventIdRole] = "eventId";
|
||||
roles[MessageContentModel::TimeRole] = "time";
|
||||
roles[MessageContentModel::TimeStringRole] = "timeString";
|
||||
roles[MessageContentModel::AuthorRole] = "author";
|
||||
roles[MessageContentModel::MediaInfoRole] = "mediaInfo";
|
||||
roles[MessageContentModel::FileTransferInfoRole] = "fileTransferInfo";
|
||||
roles[MessageContentModel::ItineraryModelRole] = "itineraryModel";
|
||||
roles[MessageContentModel::LatitudeRole] = "latitude";
|
||||
roles[MessageContentModel::LongitudeRole] = "longitude";
|
||||
roles[MessageContentModel::AssetRole] = "asset";
|
||||
roles[MessageContentModel::PollHandlerRole] = "pollHandler";
|
||||
roles[MessageContentModel::ReplyEventIdRole] = "replyEventId";
|
||||
roles[MessageContentModel::ReplyAuthorRole] = "replyAuthor";
|
||||
roles[MessageContentModel::ReplyContentModelRole] = "replyContentModel";
|
||||
roles[MessageContentModel::ReactionModelRole] = "reactionModel";
|
||||
roles[MessageContentModel::ThreadRootRole] = "threadRoot";
|
||||
roles[MessageContentModel::LinkPreviewerRole] = "linkPreviewer";
|
||||
roles[MessageContentModel::ChatBarCacheRole] = "chatBarCache";
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[DisplayRole] = "display";
|
||||
roles[ComponentTypeRole] = "componentType";
|
||||
roles[ComponentAttributesRole] = "componentAttributes";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
roles[TimeStringRole] = "timeString";
|
||||
roles[AuthorRole] = "author";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[FileTransferInfoRole] = "fileTransferInfo";
|
||||
roles[ItineraryModelRole] = "itineraryModel";
|
||||
roles[LatitudeRole] = "latitude";
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
roles[PollHandlerRole] = "pollHandler";
|
||||
roles[ReplyEventIdRole] = "replyEventId";
|
||||
roles[ReplyAuthorRole] = "replyAuthor";
|
||||
roles[ReplyContentModelRole] = "replyContentModel";
|
||||
roles[ThreadRootRole] = "threadRoot";
|
||||
roles[LinkPreviewerRole] = "linkPreviewer";
|
||||
roles[ChatBarCacheRole] = "chatBarCache";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -434,7 +434,9 @@ void MessageContentModel::resetModel()
|
||||
return;
|
||||
}
|
||||
|
||||
m_components += MessageComponent{MessageComponentType::Author, QString(), {}};
|
||||
if (m_showAuthor) {
|
||||
m_components += MessageComponent{MessageComponentType::Author, QString(), {}};
|
||||
}
|
||||
|
||||
m_components += messageContentComponents();
|
||||
endResetModel();
|
||||
@@ -490,21 +492,6 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
newComponents = addLinkPreviews(newComponents);
|
||||
}
|
||||
|
||||
if ((m_reactionModel && m_reactionModel->rowCount() > 0)) {
|
||||
newComponents += MessageComponent{MessageComponentType::Reaction, QString(), {}};
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
if (NeoChatConfig::self()->threads() && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
|
||||
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
|
||||
#else
|
||||
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()
|
||||
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
|
||||
#endif
|
||||
newComponents += MessageComponent{MessageComponentType::Separator, {}, {}};
|
||||
newComponents += MessageComponent{MessageComponentType::ThreadBody, u"Thread Body"_s, {}};
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
if (isThreading && roomMessageEvent && !(roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))) {
|
||||
@@ -528,7 +515,7 @@ void MessageContentModel::updateReplyModel()
|
||||
if (roomMessageEvent == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!roomMessageEvent->isReply(!NeoChatConfig::self()->threads()) || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
|
||||
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
@@ -539,7 +526,7 @@ void MessageContentModel::updateReplyModel()
|
||||
return;
|
||||
}
|
||||
|
||||
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(!NeoChatConfig::self()->threads()), true, false, this);
|
||||
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
||||
@@ -746,25 +733,4 @@ void MessageContentModel::updateItineraryModel()
|
||||
}
|
||||
}
|
||||
|
||||
void MessageContentModel::updateReactionModel()
|
||||
{
|
||||
if (m_reactionModel != nullptr && m_reactionModel->rowCount() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_reactionModel == nullptr) {
|
||||
m_reactionModel = new ReactionModel(this, m_eventId, m_room);
|
||||
connect(m_reactionModel, &ReactionModel::reactionsUpdated, this, &MessageContentModel::updateReactionModel);
|
||||
}
|
||||
|
||||
if (m_reactionModel->rowCount() <= 0) {
|
||||
m_reactionModel->disconnect(this);
|
||||
delete m_reactionModel;
|
||||
m_reactionModel = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
resetContent();
|
||||
}
|
||||
|
||||
#include "moc_messagecontentmodel.cpp"
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "messagecomponent.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
/**
|
||||
@@ -25,6 +25,11 @@ class MessageContentModel : public QAbstractListModel
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief Whether the author component is being shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
||||
|
||||
public:
|
||||
enum MessageState {
|
||||
Unknown, /**< The message state is unknown. */
|
||||
@@ -57,8 +62,6 @@ public:
|
||||
ReplyAuthorRole, /**< The author of the event that was replied to. */
|
||||
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
|
||||
|
||||
ReactionModelRole, /**< Reaction model for this event. */
|
||||
|
||||
ThreadRootRole, /**< The thread root event ID for the event. */
|
||||
|
||||
LinkPreviewerRole, /**< The link preview details. */
|
||||
@@ -72,6 +75,9 @@ public:
|
||||
bool isPending = false,
|
||||
MessageContentModel *parent = nullptr);
|
||||
|
||||
bool showAuthor() const;
|
||||
void setShowAuthor(bool showAuthor);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
@@ -93,8 +99,6 @@ public:
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
static QHash<int, QByteArray> roleNamesStatic();
|
||||
|
||||
/**
|
||||
* @brief Close the link preview at the given index.
|
||||
*
|
||||
@@ -113,6 +117,7 @@ private:
|
||||
NeochatRoomMember *senderObject() const;
|
||||
|
||||
MessageState m_currentState = Unknown;
|
||||
bool m_showAuthor = true;
|
||||
bool m_isReply;
|
||||
|
||||
void initializeModel();
|
||||
@@ -127,7 +132,6 @@ private:
|
||||
QPointer<MessageContentModel> m_replyModel;
|
||||
void updateReplyModel();
|
||||
|
||||
ReactionModel *m_reactionModel = nullptr;
|
||||
ItineraryModel *m_itineraryModel = nullptr;
|
||||
|
||||
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
||||
@@ -138,6 +142,4 @@ private:
|
||||
|
||||
void updateItineraryModel();
|
||||
bool m_emptyItinerary = false;
|
||||
|
||||
void updateReactionModel();
|
||||
};
|
||||
|
||||
@@ -92,8 +92,12 @@ QVariant MessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
} else if (role == MessageModel::ShowAuthorRole) {
|
||||
return showAuthor(index);
|
||||
} else if (role == TimelineMessageModel::ContentModelRole) {
|
||||
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(TimelineMessageModel::ContentModelRole));
|
||||
if (model != nullptr && !showAuthor(index)) {
|
||||
model->setShowAuthor(false);
|
||||
}
|
||||
return QVariant::fromValue<MessageContentModel *>(model);
|
||||
}
|
||||
return QSortFilterProxyModel::data(index, role);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
#include "neochatconfig.h"
|
||||
|
||||
#include <Quotient/events/encryptedevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
#include <Quotient/thread.h>
|
||||
#endif
|
||||
|
||||
@@ -121,15 +120,16 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == ContentModelRole) {
|
||||
if (event->get().is<EncryptedEvent>() || event->get().is<StickerEvent>()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(event->get().id()));
|
||||
QString modelId;
|
||||
if (!event->get().id().isEmpty() && m_contentModels.contains(event->get().id())) {
|
||||
modelId = event.value().get().id();
|
||||
} else if (!event.value().get().transactionId().isEmpty() && m_contentModels.contains(event.value().get().transactionId())) {
|
||||
modelId = event.value().get().transactionId();
|
||||
}
|
||||
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get());
|
||||
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(roomMessageEvent->threadRootEventId()));
|
||||
if (!modelId.isEmpty()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_contentModels.at(modelId).get());
|
||||
}
|
||||
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(&event->get()));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
@@ -178,7 +178,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get());
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(event.value().get().id()))) {
|
||||
const auto &thread = m_room->threads().value(roomMessageEvent->isThreaded() ? roomMessageEvent->threadRootEventId() : event.value().get().id());
|
||||
if (thread.latestEventId != event.value().get().id()) {
|
||||
@@ -201,15 +201,13 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&event.value().get())) {
|
||||
if (e->has<EventContent::FileContent>() || e->has<EventContent::ImageContent>() || e->has<EventContent::VideoContent>()
|
||||
|| e->has<EventContent::AudioContent>()) {
|
||||
if (e->has<EventContent::FileContent>()) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get()));
|
||||
}
|
||||
}
|
||||
if (eventCast<const StickerEvent>(&event.value().get())) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == TimeRole) {
|
||||
@@ -221,9 +219,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == IsThreadedRole) {
|
||||
if (!NeoChatConfig::self()->threads()) {
|
||||
return false;
|
||||
}
|
||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get())) {
|
||||
return roomMessageEvent->isThreaded();
|
||||
}
|
||||
@@ -266,6 +261,18 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
return m_readMarkerModels.contains(event.value().get().id());
|
||||
}
|
||||
|
||||
if (role == ReactionRole) {
|
||||
if (m_reactionModels.contains(event.value().get().id())) {
|
||||
return QVariant::fromValue<ReactionModel *>(m_reactionModels[event.value().get().id()].data());
|
||||
} else {
|
||||
return QVariantList();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ShowReactionsRole) {
|
||||
return m_reactionModels.contains(event.value().get().id());
|
||||
}
|
||||
|
||||
if (role == VerifiedRole) {
|
||||
if (event.value().get().originalEvent()) {
|
||||
auto encrypted = dynamic_cast<const EncryptedEvent *>(event.value().get().originalEvent());
|
||||
@@ -296,10 +303,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
|
||||
&& event.value().get().senderId() == m_room->localMember().id();
|
||||
}
|
||||
|
||||
if (role == ShowAuthorRole) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -319,6 +322,8 @@ QHash<int, QByteArray> MessageModel::roleNames() const
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
roles[ReadMarkersRole] = "readMarkers";
|
||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||
roles[ReactionRole] = "reaction";
|
||||
roles[ShowReactionsRole] = "showReactions";
|
||||
roles[VerifiedRole] = "verified";
|
||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||
roles[IsRedactedRole] = "isRedacted";
|
||||
@@ -327,7 +332,6 @@ QHash<int, QByteArray> MessageModel::roleNames() const
|
||||
roles[ContentModelRole] = "contentModel";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[IsEditableRole] = "isEditable";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -424,6 +428,17 @@ void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isP
|
||||
senderId = m_room->localMember().id();
|
||||
}
|
||||
|
||||
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
||||
if (!event->isStateEvent() || event->matrixType() == u"org.matrix.msc3672.beacon_info"_s) {
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_room, eventId, false, isPending));
|
||||
}
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
if (roomMessageEvent && roomMessageEvent->isThreaded() && !m_threadModels.contains(roomMessageEvent->threadRootEventId())) {
|
||||
m_threadModels[roomMessageEvent->threadRootEventId()] = QSharedPointer<ThreadModel>(new ThreadModel(roomMessageEvent->threadRootEventId(), m_room));
|
||||
}
|
||||
|
||||
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
||||
// handle adding and removing whole models here.
|
||||
if (m_readMarkerModels.contains(eventId)) {
|
||||
@@ -448,6 +463,31 @@ void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto roomEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
// ReactionModel handles updates to add and remove reactions, we only need to
|
||||
// handle adding and removing whole models here.
|
||||
if (m_reactionModels.contains(eventId)) {
|
||||
// If a model already exists but now has no reactions remove it
|
||||
if (m_reactionModels[eventId]->rowCount() <= 0) {
|
||||
m_reactionModels.remove(eventId);
|
||||
if (!resetting) {
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_room->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
|
||||
// If a model doesn't exist and there are reactions add it.
|
||||
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(roomEvent, m_room));
|
||||
if (reactionModel->rowCount() > 0) {
|
||||
m_reactionModels[eventId] = reactionModel;
|
||||
if (!resetting) {
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageModel::clearModel()
|
||||
@@ -473,6 +513,8 @@ void MessageModel::clearModel()
|
||||
|
||||
void MessageModel::clearEventObjects()
|
||||
{
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
}
|
||||
|
||||
@@ -486,7 +528,7 @@ bool MessageModel::event(QEvent *event)
|
||||
|
||||
ThreadModel *MessageModel::threadModelForRootId(const QString &threadRootId) const
|
||||
{
|
||||
return m_room->modelForThread(threadRootId);
|
||||
return m_threadModels[threadRootId].data();
|
||||
}
|
||||
|
||||
#include "moc_messagemodel.cpp"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QQmlEngine>
|
||||
#include <functional>
|
||||
|
||||
#include "messagecontentmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "pollhandler.h"
|
||||
#include "readmarkermodel.h"
|
||||
@@ -45,7 +46,6 @@ class MessageModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting its messages from.
|
||||
@@ -77,13 +77,14 @@ public:
|
||||
|
||||
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||
ReactionRole, /**< List model for this event. */
|
||||
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
||||
|
||||
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||
IsEditableRole, /**< Whether the event can be edited by the user. */
|
||||
ShowAuthorRole, /**< Whether the author of a message should be shown. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
@@ -152,7 +153,10 @@ private:
|
||||
bool resetting = false;
|
||||
bool movingEvent = false;
|
||||
|
||||
std::map<QString, std::unique_ptr<MessageContentModel>> m_contentModels;
|
||||
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
|
||||
QMap<QString, QSharedPointer<ThreadModel>> m_threadModels;
|
||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||
|
||||
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
|
||||
};
|
||||
|
||||
@@ -121,10 +121,6 @@ void NotificationsModel::loadData()
|
||||
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl();
|
||||
|
||||
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
|
||||
if (!roomEvent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
beginInsertRows({}, m_notifications.length(), m_notifications.length());
|
||||
m_notifications += Notification{
|
||||
.roomId = notification.roomId,
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "pinnedmessagemodel.h"
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
PinnedMessageModel::PinnedMessageModel(QObject *parent)
|
||||
: MessageModel(parent)
|
||||
{
|
||||
connect(this, &MessageModel::roomChanged, this, &PinnedMessageModel::fill);
|
||||
}
|
||||
|
||||
bool PinnedMessageModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
int PinnedMessageModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_pinnedEvents.size();
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> PinnedMessageModel::getEventForIndex(const QModelIndex index) const
|
||||
{
|
||||
if (static_cast<size_t>(index.row()) >= m_pinnedEvents.size() || index.row() < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::reference_wrapper{*m_pinnedEvents[index.row()].get()};
|
||||
}
|
||||
|
||||
void PinnedMessageModel::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
void PinnedMessageModel::fill()
|
||||
{
|
||||
if (!m_room) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto events = m_room->pinnedEventIds();
|
||||
|
||||
for (const auto &event : std::as_const(events)) {
|
||||
auto job = m_room->connection()->callApi<GetOneRoomEventJob>(m_room->id(), event);
|
||||
connect(job, &BaseJob::success, this, [this, job] {
|
||||
beginInsertRows({}, m_pinnedEvents.size(), m_pinnedEvents.size());
|
||||
m_pinnedEvents.push_back(std::move(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData())));
|
||||
Q_EMIT newEventAdded(m_pinnedEvents.back().get(), false);
|
||||
endInsertRows();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_pinnedmessagemodel.cpp"
|
||||
@@ -1,62 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
#include <QString>
|
||||
|
||||
#include <Quotient/csapi/rooms.h>
|
||||
|
||||
#include "messagemodel.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class PinnedMessageModel
|
||||
*
|
||||
* This class defines the model for visualising a room's pinned messages.
|
||||
*/
|
||||
class PinnedMessageModel : public MessageModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief Whether the model is currently loading.
|
||||
*/
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
explicit PinnedMessageModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
bool loading() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void loadingChanged();
|
||||
|
||||
protected:
|
||||
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> getEventForIndex(QModelIndex index) const override;
|
||||
|
||||
private:
|
||||
void setLoading(bool loading);
|
||||
void fill();
|
||||
|
||||
bool m_loading = false;
|
||||
|
||||
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_pinnedEvents;
|
||||
};
|
||||
@@ -9,27 +9,22 @@
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
ReactionModel::ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room)
|
||||
: QAbstractListModel(parent)
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_eventId(eventId)
|
||||
, m_event(event)
|
||||
{
|
||||
Q_ASSERT(parent);
|
||||
Q_ASSERT(parent != nullptr);
|
||||
Q_ASSERT(!eventId.isEmpty());
|
||||
Q_ASSERT(room != nullptr);
|
||||
if (m_event != nullptr && m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (m_event && m_event->id() == eventId) {
|
||||
updateReactions();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (m_eventId == eventId) {
|
||||
updateReactions();
|
||||
}
|
||||
});
|
||||
|
||||
updateReactions();
|
||||
updateReactions();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
@@ -104,16 +99,12 @@ int ReactionModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
void ReactionModel::updateReactions()
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_reactions.clear();
|
||||
m_shortcodes.clear();
|
||||
|
||||
const auto &annotations = m_room->relatedEvents(m_eventId, Quotient::EventRelation::AnnotationType);
|
||||
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
|
||||
if (annotations.isEmpty()) {
|
||||
endResetModel();
|
||||
return;
|
||||
|
||||
@@ -3,20 +3,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "neochatroom.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/reactionevent.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class RoomMessageEvent;
|
||||
}
|
||||
|
||||
class MessageContentModel;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ReactionModel
|
||||
*
|
||||
@@ -47,7 +38,7 @@ public:
|
||||
HasLocalMember, /**< Whether the local member is in the list of authors. */
|
||||
};
|
||||
|
||||
explicit ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room);
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
@@ -70,15 +61,9 @@ public:
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* @brief The reactions in the model have been updated.
|
||||
*/
|
||||
void reactionsUpdated();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QString m_eventId;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
QMap<QString, QString> m_shortcodes;
|
||||
|
||||
|
||||
@@ -348,12 +348,6 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(room);
|
||||
}
|
||||
if (role == SubtitleTextRole) {
|
||||
if (room->isInvite()) {
|
||||
if (room->isDirectChat()) {
|
||||
return i18nc("@info:label", "Invited you to chat");
|
||||
}
|
||||
return i18nc("@info:label", "%1 invited you", room->member(room->invitingUserId()).displayName());
|
||||
}
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "eventhandler.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "messagecontentmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
: QConcatenateTablesProxyModel(room)
|
||||
, m_threadRootId(threadRootId)
|
||||
, m_threadFetchModel(new ThreadFetchModel(this))
|
||||
, m_threadChatBarModel(new ThreadChatBarModel(this, room))
|
||||
{
|
||||
Q_ASSERT(!m_threadRootId.isEmpty());
|
||||
Q_ASSERT(room);
|
||||
|
||||
m_threadRootContentModel = std::unique_ptr<MessageContentModel>(new MessageContentModel(room, threadRootId));
|
||||
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
|
||||
connect(room, &Quotient::Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
|
||||
#else
|
||||
@@ -49,7 +49,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
// If the thread was created by the local user fetchMore() won't find the current
|
||||
// pending event.
|
||||
checkPending();
|
||||
fetchMoreEvents(3);
|
||||
fetchMore({});
|
||||
addModels();
|
||||
}
|
||||
|
||||
@@ -73,28 +73,34 @@ QString ThreadModel::threadRootId() const
|
||||
return m_threadRootId;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ThreadModel::roleNames() const
|
||||
MessageContentModel *ThreadModel::threadRootContentModel() const
|
||||
{
|
||||
return MessageContentModel::roleNamesStatic();
|
||||
return m_threadRootContentModel.get();
|
||||
}
|
||||
|
||||
bool ThreadModel::moreEventsAvailable(const QModelIndex &parent) const
|
||||
QHash<int, QByteArray> ThreadModel::roleNames() const
|
||||
{
|
||||
return m_threadRootContentModel->roleNames();
|
||||
}
|
||||
|
||||
bool ThreadModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return !m_currentJob && m_nextBatch.has_value();
|
||||
}
|
||||
|
||||
void ThreadModel::fetchMoreEvents(int max)
|
||||
void ThreadModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (!m_currentJob && m_nextBatch.has_value()) {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
const auto connection = room->connection();
|
||||
m_currentJob = connection->callApi<Quotient::GetRelatingEventsWithRelTypeJob>(room->id(), m_threadRootId, u"m.thread"_s, *m_nextBatch, QString(), max);
|
||||
Q_EMIT moreEventsAvailableChanged();
|
||||
m_currentJob = connection->callApi<Quotient::GetRelatingEventsWithRelTypeJob>(room->id(), m_threadRootId, u"m.thread"_s, *m_nextBatch, QString(), 5);
|
||||
connect(m_currentJob, &Quotient::BaseJob::success, this, [this]() {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
auto newEvents = m_currentJob->chunk();
|
||||
for (auto &event : newEvents) {
|
||||
m_events.push_back(event->id());
|
||||
m_contentModels.push_back(new MessageContentModel(room, event->id()));
|
||||
}
|
||||
|
||||
addModels();
|
||||
@@ -110,18 +116,18 @@ void ThreadModel::fetchMoreEvents(int max)
|
||||
}
|
||||
|
||||
m_currentJob.clear();
|
||||
Q_EMIT moreEventsAvailableChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
auto eventId = event->id();
|
||||
if (eventId.isEmpty()) {
|
||||
eventId = event->transactionId();
|
||||
}
|
||||
m_events.push_front(eventId);
|
||||
m_contentModels.push_front(new MessageContentModel(room, eventId));
|
||||
}
|
||||
|
||||
void ThreadModel::addModels()
|
||||
@@ -130,16 +136,9 @@ void ThreadModel::addModels()
|
||||
clearModels();
|
||||
}
|
||||
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
addSourceModel(m_threadFetchModel);
|
||||
for (auto it = m_events.crbegin(); it != m_events.crend(); ++it) {
|
||||
const auto contentModel = room->contentModelForEvent(*it);
|
||||
if (contentModel != nullptr) {
|
||||
addSourceModel(room->contentModelForEvent(*it));
|
||||
}
|
||||
addSourceModel(m_threadRootContentModel.get());
|
||||
for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) {
|
||||
addSourceModel(*it);
|
||||
}
|
||||
addSourceModel(m_threadChatBarModel);
|
||||
|
||||
@@ -149,90 +148,15 @@ void ThreadModel::addModels()
|
||||
|
||||
void ThreadModel::clearModels()
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
removeSourceModel(m_threadFetchModel);
|
||||
for (const auto &model : m_events) {
|
||||
const auto contentModel = room->contentModelForEvent(model);
|
||||
if (sourceModels().contains(contentModel)) {
|
||||
removeSourceModel(contentModel);
|
||||
removeSourceModel(m_threadRootContentModel.get());
|
||||
for (const auto &model : m_contentModels) {
|
||||
if (sourceModels().contains(model)) {
|
||||
removeSourceModel(model);
|
||||
}
|
||||
}
|
||||
removeSourceModel(m_threadChatBarModel);
|
||||
}
|
||||
|
||||
void ThreadModel::closeLinkPreview(int row)
|
||||
{
|
||||
if (row < 0 || row >= rowCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = this->index(row, 0);
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sourceIndex = mapToSource(index);
|
||||
const auto sourceModel = sourceIndex.model();
|
||||
if (sourceModel == nullptr) {
|
||||
return;
|
||||
}
|
||||
// This is a bit silly but we can only get a const reference to the model from the
|
||||
// index so we need to search the source models.
|
||||
for (const auto &model : sourceModels()) {
|
||||
if (model == sourceModel) {
|
||||
const auto sourceContentModel = dynamic_cast<MessageContentModel *>(model);
|
||||
if (sourceContentModel == nullptr) {
|
||||
return;
|
||||
}
|
||||
sourceContentModel->closeLinkPreview(sourceIndex.row());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadFetchModel::ThreadFetchModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(parent);
|
||||
Q_ASSERT(threadModel != nullptr);
|
||||
connect(threadModel, &ThreadModel::moreEventsAvailableChanged, this, [this]() {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
|
||||
QVariant ThreadFetchModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() > 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == ComponentTypeRole) {
|
||||
return MessageComponentType::FetchButton;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int ThreadFetchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
|
||||
if (threadModel == nullptr) {
|
||||
qWarning() << "ThreadFetchModel created with incorrect parent, a ThreadModel must be set as the parent on creation.";
|
||||
return {};
|
||||
}
|
||||
return threadModel->moreEventsAvailable({}) ? 1 : 0;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ThreadFetchModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{ComponentTypeRole, "componentType"},
|
||||
};
|
||||
}
|
||||
|
||||
ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
|
||||
@@ -19,52 +19,7 @@
|
||||
#include "messagecontentmodel.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ThreadFetchModel
|
||||
*
|
||||
* A model to provide a fetch more historical messages button in a thread.
|
||||
*/
|
||||
class ThreadFetchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*
|
||||
* The role values need to match MessageContentModel not to blow up.
|
||||
*
|
||||
* @sa MessageContentModel
|
||||
*/
|
||||
enum Roles {
|
||||
ComponentTypeRole = MessageContentModel::ComponentTypeRole, /**< The type of component to visualise the message. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit ThreadFetchModel(QObject *parent);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief 1 or 0, depending on whether there are more messages to download.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a map with ComponentTypeRole it's the only one.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
};
|
||||
class ReactionModel;
|
||||
|
||||
/**
|
||||
* @class ThreadChatBarModel
|
||||
@@ -136,6 +91,11 @@ public:
|
||||
|
||||
QString threadRootId() const;
|
||||
|
||||
/**
|
||||
* @brief The content model for the thread root event.
|
||||
*/
|
||||
MessageContentModel *threadRootContentModel() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
@@ -144,33 +104,34 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Whether there are more events for the model to fetch.
|
||||
*/
|
||||
bool moreEventsAvailable(const QModelIndex &parent) const;
|
||||
|
||||
/**
|
||||
* @brief Fetches the next batch of events if any is available.
|
||||
*/
|
||||
Q_INVOKABLE void fetchMoreEvents(int max = 5);
|
||||
|
||||
/**
|
||||
* @brief Close the link preview at the given index.
|
||||
* @brief Whether there is more data available for the model to fetch.
|
||||
*
|
||||
* If the given index is not a link preview component, nothing happens.
|
||||
* @sa QAbstractItemModel::canFetchMore()
|
||||
*/
|
||||
Q_INVOKABLE void closeLinkPreview(int row);
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void moreEventsAvailableChanged();
|
||||
/**
|
||||
* @brief Fetches the next batch of model data if any is available.
|
||||
*
|
||||
* @sa QAbstractItemModel::fetchMore()
|
||||
*/
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
private:
|
||||
QString m_threadRootId;
|
||||
QPointer<MessageContentModel> m_threadRootContentModel;
|
||||
|
||||
std::deque<QString> m_events;
|
||||
ThreadFetchModel *m_threadFetchModel;
|
||||
std::unique_ptr<MessageContentModel> m_threadRootContentModel;
|
||||
|
||||
std::deque<MessageContentModel *> m_contentModels;
|
||||
ThreadChatBarModel *m_threadChatBarModel;
|
||||
|
||||
QList<QString> m_events;
|
||||
QList<QString> m_pendingEvents;
|
||||
|
||||
std::unordered_map<QString, std::unique_ptr<Quotient::RoomEvent>> m_unloadedEvents;
|
||||
|
||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||
|
||||
QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr;
|
||||
std::optional<QString> m_nextBatch = QString();
|
||||
bool m_addingPending = false;
|
||||
|
||||
@@ -27,20 +27,24 @@ void TimelineMessageModel::connectNewRoom()
|
||||
}
|
||||
|
||||
connect(m_room, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &&event : events) {
|
||||
Q_EMIT newEventAdded(event.get());
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, timelineServerIndex(), timelineServerIndex() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_room, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &event : events) {
|
||||
Q_EMIT newEventAdded(event.get());
|
||||
}
|
||||
if (rowCount() > 0) {
|
||||
rowBelowInserted = rowCount() - 1; // See #312
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) {
|
||||
if (m_initialized) {
|
||||
for (int i = lowest; i == biggest; ++i) {
|
||||
const auto event = m_room->findInTimeline(i)->event();
|
||||
Q_EMIT newEventAdded(event);
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
if (!m_lastReadEventIndex.isValid()) {
|
||||
|
||||
@@ -32,6 +32,42 @@ class TimelineMessageModel : public MessageModel
|
||||
QML_ELEMENT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
|
||||
SectionRole, /**< The date of the event as a string. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
HighlightRole, /**< Whether the event should be highlighted. */
|
||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||
ProgressInfoRole, /**< Progress info when downloading files. */
|
||||
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
|
||||
ContentModelRole, /**< The MessageContentModel for the event. */
|
||||
|
||||
IsThreadedRole, /**< Whether the message is in a thread. */
|
||||
ThreadRootRole, /**< The Matrix ID of the thread root message, if any . */
|
||||
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||
ReactionRole, /**< List model for this event. */
|
||||
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
||||
|
||||
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||
IsEditableRole, /**< Whether the event can be edited by the user. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
explicit TimelineMessageModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
|
||||
@@ -174,8 +174,8 @@ void UserListModel::refreshAllMembers()
|
||||
m_members = m_currentRoom->joinedMemberIds();
|
||||
MemberSorter sorter;
|
||||
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
|
||||
const auto leftPl = m_currentRoom->memberEffectivePowerLevel(left);
|
||||
const auto rightPl = m_currentRoom->memberEffectivePowerLevel(right);
|
||||
const auto leftPl = m_currentRoom->getUserPowerLevel(left);
|
||||
const auto rightPl = m_currentRoom->getUserPowerLevel(right);
|
||||
if (leftPl > rightPl) {
|
||||
return true;
|
||||
} else if (rightPl > leftPl) {
|
||||
@@ -184,6 +184,7 @@ void UserListModel::refreshAllMembers()
|
||||
|
||||
return sorter(m_currentRoom->member(left), m_currentRoom->member(right));
|
||||
});
|
||||
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT usersRefreshed();
|
||||
|
||||
@@ -39,7 +39,7 @@ struct WebShortcutModelPrivate;
|
||||
* }
|
||||
* QQC2.MenuSeparator {}
|
||||
* QQC2.MenuItem {
|
||||
* text: i18n("Configure Web Shortcuts…")
|
||||
* text: i18n("Configure Web Shortcuts...")
|
||||
* icon.name: "configure"
|
||||
* onTriggered: webshortcutmodel.configureWebShortcuts()
|
||||
* }
|
||||
|
||||
@@ -290,7 +290,6 @@ Name[hu]=Megosztás
|
||||
Name[ia]=Comparti
|
||||
Name[it]=Condivisione
|
||||
Name[ka]=გაზიარება
|
||||
Name[ko]=공유
|
||||
Name[lv]=Kopīgot
|
||||
Name[nl]=Gedeelde
|
||||
Name[nn]=Del
|
||||
@@ -304,7 +303,6 @@ Name[ta]=பகிர்
|
||||
Name[tr]=Paylaş
|
||||
Name[uk]=Оприлюднення
|
||||
Name[x-test]=xxSharexx
|
||||
Name[zh_CN]=分享
|
||||
Name[zh_TW]=分享
|
||||
Comment=The result of sharing a piece of content
|
||||
Comment[ar]=نتيجة مشاركة محتوى
|
||||
@@ -325,7 +323,6 @@ Comment[hu]=Tartalom megosztásának eredménye
|
||||
Comment[ia]=Le exito de compartir un pecietta de contento
|
||||
Comment[it]=Il risultato della condivisione di un contenuto
|
||||
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
||||
Comment[ko]=콘텐츠 공유 결과
|
||||
Comment[lv]=Satura kopīgošanas rezultāts
|
||||
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[nn]=Resultatet av deling av innhald
|
||||
@@ -339,6 +336,5 @@ Comment[ta]=எதையோ பகிர்ந்ததன் விளைவ
|
||||
Comment[tr]=Bir parça içerik paylaşımının sonucu
|
||||
Comment[uk]=Результат оприлюднення даних
|
||||
Comment[x-test]=xxThe result of sharing a piece of contentxx
|
||||
Comment[zh_CN]=分享一个内容得到的结果
|
||||
Comment[zh_TW]=分享一份內容之後的結果
|
||||
Action=Popup
|
||||
|
||||
@@ -104,7 +104,6 @@
|
||||
</entry>
|
||||
<entry name="ShowLinkPreview" type="bool">
|
||||
<label>Show preview of the links in the chat messages</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="SystemTray" type="bool">
|
||||
<label>Close NeoChat to system tray</label>
|
||||
@@ -224,10 +223,6 @@
|
||||
<label>Reject unknown invites</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="PreferUsingEncryption" type="bool">
|
||||
<label>Prefer encrypting chats</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QImageReader>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "spacehierarchycache.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/csapi/registration.h>
|
||||
#include <Quotient/csapi/versions.h>
|
||||
#include <Quotient/database.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/room.h>
|
||||
@@ -107,6 +109,10 @@ void NeoChatConnection::connectSignals()
|
||||
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) {
|
||||
Q_UNUSED(room)
|
||||
if (prev && prev->isDirectChat()) {
|
||||
@@ -136,19 +142,9 @@ void NeoChatConnection::connectSignals()
|
||||
connect(job, &GetVersionsJob::success, this, [this, job] {
|
||||
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
|
||||
Q_EMIT canCheckMutualRoomsChanged();
|
||||
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
|
||||
Q_EMIT canEraseDataChanged();
|
||||
});
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] {
|
||||
setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
|
||||
});
|
||||
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
|
||||
});
|
||||
}
|
||||
|
||||
int NeoChatConnection::badgeNotificationCount() const
|
||||
@@ -171,25 +167,6 @@ void NeoChatConnection::refreshBadgeNotificationCount()
|
||||
}
|
||||
}
|
||||
|
||||
bool NeoChatConnection::globalUrlPreviewEnabled()
|
||||
{
|
||||
return m_globalUrlPreviewEnabled;
|
||||
}
|
||||
|
||||
void NeoChatConnection::setGlobalUrlPreviewEnabled(bool newState)
|
||||
{
|
||||
if (m_globalUrlPreviewEnabled == newState) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_globalUrlPreviewEnabled = newState;
|
||||
if (!m_globalUrlPreviewEnabled) {
|
||||
m_linkPreviewers.clear();
|
||||
}
|
||||
NeoChatConfig::setShowLinkPreview(m_globalUrlPreviewEnabled);
|
||||
Q_EMIT globalUrlPreviewEnabledChanged();
|
||||
}
|
||||
|
||||
void NeoChatConnection::logout(bool serverSideLogout)
|
||||
{
|
||||
SettingsGroup(u"Accounts"_s).remove(userId());
|
||||
@@ -278,20 +255,20 @@ QString NeoChatConnection::label() const
|
||||
return accountDataJson("org.kde.neochat.account_label"_L1)["account_label"_L1].toString();
|
||||
}
|
||||
|
||||
void NeoChatConnection::deactivateAccount(const QString &password, const bool erase)
|
||||
void NeoChatConnection::deactivateAccount(const QString &password)
|
||||
{
|
||||
auto job = callApi<DeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password, erase] {
|
||||
auto job = callApi<NeoChatDeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.authInfo["password"_L1] = password;
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
QJsonObject authData;
|
||||
authData["session"_L1] = replyData["session"_L1];
|
||||
authData["password"_L1] = password;
|
||||
authData["type"_L1] = "m.login.password"_L1;
|
||||
authData["user"_L1] = user()->id();
|
||||
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
authData.authInfo["identifier"_L1] = identifier;
|
||||
auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase);
|
||||
authData["identifier"_L1] = identifier;
|
||||
auto innerJob = callApi<NeoChatDeactivateAccountJob>(authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
logout(false);
|
||||
});
|
||||
@@ -495,18 +472,32 @@ QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
||||
false);
|
||||
|
||||
qInfo() << "Registered for push notifications";
|
||||
m_pushNotificationsEnabled = true;
|
||||
} else {
|
||||
qWarning() << "There's no gateway, not setting up push notifications.";
|
||||
m_pushNotificationsEnabled = false;
|
||||
}
|
||||
Q_EMIT enablePushNotificationsChanged();
|
||||
#else
|
||||
Q_UNUSED(endpoint)
|
||||
co_return;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeoChatConnection::deviceKey() const
|
||||
{
|
||||
return edKeyForUserDevice(userId(), deviceId());
|
||||
}
|
||||
|
||||
QString NeoChatConnection::encryptionKey() const
|
||||
{
|
||||
auto query = database()->prepareQuery(u"SELECT curveKey FROM tracked_devices WHERE matrixId=:matrixId AND deviceid=:deviceId LIMIT 1;"_s);
|
||||
query.bindValue(u":matrixId"_s, userId());
|
||||
query.bindValue(u":deviceId"_s, deviceId());
|
||||
database()->execute(query);
|
||||
if (!query.next()) {
|
||||
return {};
|
||||
}
|
||||
return query.value(0).toString();
|
||||
}
|
||||
|
||||
bool NeoChatConnection::isOnline() const
|
||||
{
|
||||
return m_isOnline;
|
||||
@@ -528,7 +519,7 @@ QString NeoChatConnection::accountDataJsonString(const QString &type) const
|
||||
|
||||
LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
||||
{
|
||||
if (!m_globalUrlPreviewEnabled) {
|
||||
if (!NeoChatConfig::showLinkPreview()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -557,23 +548,4 @@ KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphra
|
||||
return KeyImport::Success;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::canEraseData() const
|
||||
{
|
||||
return m_canEraseData;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::pushNotificationsAvailable() const
|
||||
{
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NeoChatConnection::enablePushNotifications() const
|
||||
{
|
||||
return m_pushNotificationsEnabled;
|
||||
}
|
||||
|
||||
#include "moc_neochatconnection.cpp"
|
||||
|
||||
@@ -31,11 +31,8 @@ class NeoChatConnection : public Quotient::Connection
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether URL previews are enabled globally.
|
||||
*/
|
||||
Q_PROPERTY(bool globalUrlPreviewEnabled READ globalUrlPreviewEnabled WRITE setGlobalUrlPreviewEnabled NOTIFY globalUrlPreviewEnabledChanged)
|
||||
Q_PROPERTY(QString deviceKey READ deviceKey CONSTANT)
|
||||
Q_PROPERTY(QString encryptionKey READ encryptionKey CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The model with the account's 3PIDs.
|
||||
@@ -90,21 +87,6 @@ class NeoChatConnection : public Quotient::Connection
|
||||
*/
|
||||
Q_PROPERTY(bool canCheckMutualRooms READ canCheckMutualRooms NOTIFY canCheckMutualRoomsChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the server supports erasing user data when deactivating the account. This checks for MSC4025.
|
||||
*/
|
||||
Q_PROPERTY(bool canEraseData READ canEraseData NOTIFY canEraseDataChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether this build of NeoChat supports push notifications via KUnifiedPush.
|
||||
*/
|
||||
Q_PROPERTY(bool pushNotificationsAvailable READ pushNotificationsAvailable CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether we successfully set up push notifications with the server.
|
||||
*/
|
||||
Q_PROPERTY(bool enablePushNotifications READ enablePushNotifications NOTIFY enablePushNotificationsChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
@@ -122,7 +104,6 @@ public:
|
||||
Q_INVOKABLE void logout(bool serverSideLogout);
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
|
||||
bool canCheckMutualRooms() const;
|
||||
bool canEraseData() const;
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
@@ -142,7 +123,7 @@ public:
|
||||
[[nodiscard]] QString label() const;
|
||||
void setLabel(const QString &label);
|
||||
|
||||
Q_INVOKABLE void deactivateAccount(const QString &password, bool erase);
|
||||
Q_INVOKABLE void deactivateAccount(const QString &password);
|
||||
|
||||
ThreePIdModel *threePIdModel() const;
|
||||
|
||||
@@ -187,24 +168,20 @@ public:
|
||||
int badgeNotificationCount() const;
|
||||
void refreshBadgeNotificationCount();
|
||||
|
||||
bool globalUrlPreviewEnabled();
|
||||
void setGlobalUrlPreviewEnabled(bool newState);
|
||||
|
||||
bool directChatInvites() const;
|
||||
|
||||
// note: this is intentionally a copied QString because
|
||||
// the reference could be destroyed before the task is finished
|
||||
QCoro::Task<void> setupPushNotifications(QString endpoint);
|
||||
|
||||
bool pushNotificationsAvailable() const;
|
||||
bool enablePushNotifications() const;
|
||||
QString deviceKey() const;
|
||||
QString encryptionKey() const;
|
||||
|
||||
bool isOnline() const;
|
||||
|
||||
LinkPreviewer *previewerForLink(const QUrl &link);
|
||||
|
||||
Q_SIGNALS:
|
||||
void globalUrlPreviewEnabledChanged();
|
||||
void labelChanged();
|
||||
void identityServerChanged();
|
||||
void directChatNotificationsChanged();
|
||||
@@ -217,8 +194,6 @@ Q_SIGNALS:
|
||||
void userConsentRequired(QUrl url);
|
||||
void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
|
||||
void canCheckMutualRoomsChanged();
|
||||
void canEraseDataChanged();
|
||||
void enablePushNotificationsChanged();
|
||||
|
||||
/**
|
||||
* @brief Request a message be shown to the user of the given type.
|
||||
@@ -230,6 +205,11 @@ Q_SIGNALS:
|
||||
*/
|
||||
void errorOccured(const QString &error);
|
||||
|
||||
/**
|
||||
* @brief Request a notification be shown for an invite to this room.
|
||||
*/
|
||||
void showInviteNotification(NeoChatRoom *room);
|
||||
|
||||
private:
|
||||
bool m_isOnline = true;
|
||||
void setIsOnline(bool isOnline);
|
||||
@@ -239,11 +219,8 @@ private:
|
||||
void connectSignals();
|
||||
|
||||
int m_badgeNotificationCount = 0;
|
||||
bool m_globalUrlPreviewEnabled = true;
|
||||
|
||||
QCache<QUrl, LinkPreviewer> m_linkPreviewers;
|
||||
|
||||
bool m_canCheckMutualRooms = false;
|
||||
bool m_canEraseData = false;
|
||||
bool m_pushNotificationsEnabled = false;
|
||||
};
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "clipboard.h"
|
||||
#include "eventhandler.h"
|
||||
#include "events/joinrulesevent.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "filetransferpseudojob.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroommember.h"
|
||||
#include "roomlastmessageprovider.h"
|
||||
#include "spacehierarchycache.h"
|
||||
@@ -127,12 +127,16 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
updatePushNotificationState(u"m.push_rules"_s);
|
||||
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
if (this->joinState() == JoinState::Invite) {
|
||||
Q_EMIT showInviteNotification(this);
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
connect(this, &Room::changed, this, [this] {
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
Q_EMIT parentIdsChanged();
|
||||
Q_EMIT canonicalParentChanged();
|
||||
Q_EMIT joinRuleChanged();
|
||||
Q_EMIT readOnlyChanged();
|
||||
});
|
||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||
@@ -156,10 +160,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
Q_EMIT childrenHaveHighlightNotificationsChanged();
|
||||
}
|
||||
});
|
||||
|
||||
const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
|
||||
Q_ASSERT(neochatconnection);
|
||||
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
|
||||
}
|
||||
|
||||
bool NeoChatRoom::visible() const
|
||||
@@ -173,8 +173,6 @@ void NeoChatRoom::setVisible(bool visible)
|
||||
|
||||
if (!visible) {
|
||||
m_memberObjects.clear();
|
||||
m_eventContentModels.clear();
|
||||
m_threadModels.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,12 +606,63 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
|
||||
doDeleteMessagesByUser(user, reason);
|
||||
}
|
||||
|
||||
QString NeoChatRoom::joinRule() const
|
||||
{
|
||||
auto joinRulesEvent = currentState().get<JoinRulesEvent>();
|
||||
if (!joinRulesEvent) {
|
||||
return {};
|
||||
}
|
||||
return joinRulesEvent->joinRule();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces)
|
||||
{
|
||||
if (!canSendState("m.room.join_rules"_L1)) {
|
||||
qWarning() << "Power level too low to set join rules";
|
||||
return;
|
||||
}
|
||||
auto actualRule = joinRule;
|
||||
if (joinRule == "restricted"_L1 && allowedSpaces.isEmpty()) {
|
||||
actualRule = "private"_L1;
|
||||
}
|
||||
|
||||
QJsonArray allowConditions;
|
||||
if (actualRule == "restricted"_L1) {
|
||||
for (auto allowedSpace : allowedSpaces) {
|
||||
allowConditions += QJsonObject{{"type"_L1, "m.room_membership"_L1}, {"room_id"_L1, allowedSpace}};
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject content;
|
||||
content.insert("join_rule"_L1, joinRule);
|
||||
if (!allowConditions.isEmpty()) {
|
||||
content.insert("allow"_L1, allowConditions);
|
||||
}
|
||||
qWarning() << content;
|
||||
setState("m.room.join_rules"_L1, {}, content);
|
||||
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||
}
|
||||
|
||||
QList<QString> NeoChatRoom::restrictedIds() const
|
||||
{
|
||||
auto joinRulesEvent = currentState().get<JoinRulesEvent>();
|
||||
if (!joinRulesEvent) {
|
||||
return {};
|
||||
}
|
||||
if (joinRulesEvent->joinRule() != "restricted"_L1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QString> roomIds;
|
||||
for (auto allow : joinRulesEvent->allow()) {
|
||||
roomIds += allow.toObject().value("room_id"_L1).toString();
|
||||
}
|
||||
return roomIds;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
if (auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
|
||||
return stateEvent->contentJson()["history_visibility"_L1].toString();
|
||||
}
|
||||
return {};
|
||||
return currentState().get("m.room.history_visibility"_L1)->contentJson()["history_visibility"_L1].toString();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
||||
@@ -680,9 +729,6 @@ void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
|
||||
|
||||
bool NeoChatRoom::urlPreviewEnabled() const
|
||||
{
|
||||
if (!static_cast<NeoChatConnection *>(connection())->globalUrlPreviewEnabled()) {
|
||||
return false;
|
||||
}
|
||||
if (hasAccountData("org.matrix.room.preview_urls"_L1)) {
|
||||
return !accountData("org.matrix.room.preview_urls"_L1)->contentJson()["disable"_L1].toBool();
|
||||
} else {
|
||||
@@ -736,6 +782,22 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
||||
}
|
||||
}
|
||||
|
||||
int NeoChatRoom::getUserPowerLevel(const QString &userId) const
|
||||
{
|
||||
if (!successorId().isEmpty()) {
|
||||
return 0; // No one can upgrade a room that's already upgraded
|
||||
}
|
||||
|
||||
const auto &mId = userId.isEmpty() ? connection()->userId() : userId;
|
||||
if (const auto *plEvent = currentState().get<RoomPowerLevelsEvent>()) {
|
||||
return plEvent->powerLevelForUser(mId);
|
||||
}
|
||||
if (const auto *createEvent = creation()) {
|
||||
return createEvent->senderId() == mId ? 100 : 0;
|
||||
}
|
||||
return 0; // That's rather weird but may happen, according to rvdh
|
||||
}
|
||||
|
||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
||||
{
|
||||
QStringList events;
|
||||
@@ -1671,11 +1733,7 @@ void NeoChatRoom::cleanupExtraEvent(const QString &eventId)
|
||||
}
|
||||
QString NeoChatRoom::invitingUserId() const
|
||||
{
|
||||
auto event = currentState().get<RoomMemberEvent>(connection()->userId());
|
||||
if (!event) {
|
||||
return {};
|
||||
}
|
||||
return event->senderId();
|
||||
return currentState().get<RoomMemberEvent>(connection()->userId())->senderId();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content)
|
||||
@@ -1685,6 +1743,10 @@ void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, con
|
||||
|
||||
NeochatRoomMember *NeoChatRoom::qmlSafeMember(const QString &memberId)
|
||||
{
|
||||
if (!isMember(memberId)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_memberObjects.contains(memberId)) {
|
||||
return m_memberObjects.emplace(memberId, std::make_unique<NeochatRoomMember>(this, memberId)).first->second.get();
|
||||
}
|
||||
@@ -1692,94 +1754,4 @@ NeochatRoomMember *NeoChatRoom::qmlSafeMember(const QString &memberId)
|
||||
return m_memberObjects[memberId].get();
|
||||
}
|
||||
|
||||
MessageContentModel *NeoChatRoom::contentModelForEvent(const QString &eventId)
|
||||
{
|
||||
if (eventId.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_eventContentModels.contains(eventId)) {
|
||||
return m_eventContentModels.emplace(eventId, std::make_unique<MessageContentModel>(this, eventId)).first->second.get();
|
||||
}
|
||||
|
||||
return m_eventContentModels[eventId].get();
|
||||
}
|
||||
|
||||
MessageContentModel *NeoChatRoom::contentModelForEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
if (roomMessageEvent == nullptr) {
|
||||
// If for some reason a model is there remove.
|
||||
if (m_eventContentModels.contains(event->id())) {
|
||||
m_eventContentModels.erase(event->id());
|
||||
}
|
||||
if (m_eventContentModels.contains(event->transactionId())) {
|
||||
m_eventContentModels.erase(event->transactionId());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (event->isStateEvent() || event->matrixType() == u"org.matrix.msc3672.beacon_info"_s) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto eventId = event->id();
|
||||
const auto txnId = event->transactionId();
|
||||
if (!m_eventContentModels.contains(eventId) && !m_eventContentModels.contains(txnId)) {
|
||||
return m_eventContentModels
|
||||
.emplace(eventId.isEmpty() ? txnId : eventId,
|
||||
std::make_unique<MessageContentModel>(this, eventId.isEmpty() ? txnId : eventId, false, eventId.isEmpty()))
|
||||
.first->second.get();
|
||||
}
|
||||
|
||||
if (!eventId.isEmpty() && m_eventContentModels.contains(eventId)) {
|
||||
return m_eventContentModels[eventId].get();
|
||||
}
|
||||
|
||||
if (!txnId.isEmpty() && m_eventContentModels.contains(txnId)) {
|
||||
if (eventId.isEmpty()) {
|
||||
return m_eventContentModels[txnId].get();
|
||||
}
|
||||
|
||||
// If we now have an event ID use that as the map key instead of transaction ID.
|
||||
auto txnModel = std::move(m_eventContentModels[txnId]);
|
||||
m_eventContentModels.erase(txnId);
|
||||
return m_eventContentModels.emplace(eventId, std::move(txnModel)).first->second.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ThreadModel *NeoChatRoom::modelForThread(const QString &threadRootId)
|
||||
{
|
||||
if (threadRootId.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_threadModels.contains(threadRootId)) {
|
||||
return m_threadModels.emplace(threadRootId, std::make_unique<ThreadModel>(threadRootId, this)).first->second.get();
|
||||
}
|
||||
|
||||
return m_threadModels[threadRootId].get();
|
||||
}
|
||||
|
||||
void NeoChatRoom::pinEvent(const QString &eventId)
|
||||
{
|
||||
auto eventIds = pinnedEventIds();
|
||||
eventIds.push_back(eventId);
|
||||
setPinnedEvents(eventIds);
|
||||
}
|
||||
|
||||
void NeoChatRoom::unpinEvent(const QString &eventId)
|
||||
{
|
||||
auto eventIds = pinnedEventIds();
|
||||
eventIds.removeAll(eventId);
|
||||
setPinnedEvents(eventIds);
|
||||
}
|
||||
|
||||
bool NeoChatRoom::isEventPinned(const QString &eventId) const
|
||||
{
|
||||
return pinnedEventIds().contains(eventId);
|
||||
}
|
||||
|
||||
#include "moc_neochatroom.cpp"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include <QCache>
|
||||
@@ -15,8 +14,6 @@
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "enums/pushrule.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "models/threadmodel.h"
|
||||
#include "neochatroommember.h"
|
||||
#include "pollhandler.h"
|
||||
|
||||
@@ -134,6 +131,22 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(bool readOnly READ readOnly NOTIFY readOnlyChanged)
|
||||
|
||||
/**
|
||||
* @brief The current join rule for the room as a QString.
|
||||
*
|
||||
* Possible values are [public, knock, invite, private, restricted].
|
||||
*
|
||||
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomjoin_rules
|
||||
*/
|
||||
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||
|
||||
/**
|
||||
* @brief The space IDs that members of can join this room.
|
||||
*
|
||||
* Empty if the join rule is not restricted.
|
||||
*/
|
||||
Q_PROPERTY(QList<QString> restrictedIds READ restrictedIds NOTIFY joinRuleChanged)
|
||||
|
||||
/**
|
||||
* @brief Get the maximum room version that the server supports.
|
||||
*
|
||||
@@ -404,6 +417,25 @@ public:
|
||||
|
||||
bool readOnly() const;
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
|
||||
/**
|
||||
* @brief Set the join rule for the room.
|
||||
*
|
||||
* Will fail if the user doesn't have the required privileges.
|
||||
*
|
||||
* @param joinRule the join rule [public, knock, invite, private, restricted].
|
||||
* @param allowedSpaces only used when the join rule is restricted. This is a
|
||||
* list of space Matrix IDs that members of can join without an invite.
|
||||
* If the rule is restricted and this list is empty it is treated as a join
|
||||
* rule of private instead.
|
||||
*
|
||||
* @sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
|
||||
*/
|
||||
Q_INVOKABLE void setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces = {});
|
||||
|
||||
QList<QString> restrictedIds() const;
|
||||
|
||||
int maxRoomVersion() const;
|
||||
|
||||
/**
|
||||
@@ -469,6 +501,14 @@ public:
|
||||
|
||||
bool canEncryptRoom() const;
|
||||
|
||||
/**
|
||||
* @brief Get the power level for the given user ID in the room.
|
||||
*
|
||||
* Returns the default value for a user in the room if they have no escalated
|
||||
* privileges or if they are not a member so membership should be known before using.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int getUserPowerLevel(const QString &userId) const;
|
||||
|
||||
Q_INVOKABLE void setUserPowerLevel(const QString &userID, const int &powerLevel);
|
||||
|
||||
ChatBarCache *mainCache() const;
|
||||
@@ -553,71 +593,8 @@ public:
|
||||
*/
|
||||
Quotient::FileTransferInfo cachedFileTransferInfo(const Quotient::RoomEvent *event) const;
|
||||
|
||||
/**
|
||||
* @brief Return a NeochatRoomMember object for the given user ID.
|
||||
*
|
||||
* @warning Because we can't guarantee that a member state event is downloaded
|
||||
* before a message they sent arrives this will create the object unconditionally
|
||||
* assuming that the state event will turn up later. It is therefor the
|
||||
* responsibility of the caller to ensure that they only ask for objects
|
||||
* for real senders.
|
||||
*/
|
||||
NeochatRoomMember *qmlSafeMember(const QString &memberId);
|
||||
|
||||
/**
|
||||
* @brief Returns the content model for the given event ID.
|
||||
*
|
||||
* A model is created is one doesn't exist. Will return nullptr if evtOrTxnId
|
||||
* is empty.
|
||||
*
|
||||
* @warning If a non-empty ID is given it is assumed to be a valid Quotient::RoomMessageEvent
|
||||
* event ID. The caller must ensure that the ID is a real event. A model will be
|
||||
* returned unconditionally.
|
||||
*
|
||||
* @warning Do NOT use for pending events as this function has no way to differentiate.
|
||||
*/
|
||||
MessageContentModel *contentModelForEvent(const QString &evtOrTxnId);
|
||||
|
||||
/**
|
||||
* @brief Returns the content model for the given event.
|
||||
*
|
||||
* A model is created is one doesn't exist. Will return nullptr if event is:
|
||||
* - nullptr
|
||||
* - not a Quotient::RoomMessageEvent (e.g a state event)
|
||||
*
|
||||
* @note This method is preferred to the version using just an event ID as it
|
||||
* can perform some basic checks. If a copy of the event is not available,
|
||||
* you may have to use the version that takes an event ID.
|
||||
*
|
||||
* @note This version must be used for pending events as it can differentiate.
|
||||
*/
|
||||
MessageContentModel *contentModelForEvent(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Returns the thread model for the given thread root event ID.
|
||||
*
|
||||
* A model is created is one doesn't exist. Will return nullptr if threadRootId
|
||||
* is empty.
|
||||
*/
|
||||
Q_INVOKABLE ThreadModel *modelForThread(const QString &threadRootId);
|
||||
|
||||
/**
|
||||
* @brief Pin a message in the room.
|
||||
* @param eventId The id of the event to pin.
|
||||
*/
|
||||
Q_INVOKABLE void pinEvent(const QString &eventId);
|
||||
|
||||
/**
|
||||
* @brief Unpin a message in the room.
|
||||
* @param eventId The id of the event to unpin.
|
||||
*/
|
||||
Q_INVOKABLE void unpinEvent(const QString &eventId);
|
||||
|
||||
/**
|
||||
* @return True if @p eventId is pinned in the room.
|
||||
*/
|
||||
Q_INVOKABLE bool isEventPinned(const QString &eventId) const;
|
||||
|
||||
private:
|
||||
bool m_visible = false;
|
||||
|
||||
@@ -650,8 +627,6 @@ private:
|
||||
void cleanupExtraEvent(const QString &eventId);
|
||||
|
||||
std::unordered_map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||
std::unordered_map<QString, std::unique_ptr<MessageContentModel>> m_eventContentModels;
|
||||
std::unordered_map<QString, std::unique_ptr<ThreadModel>> m_threadModels;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updatePushNotificationState(QString type);
|
||||
@@ -675,6 +650,7 @@ Q_SIGNALS:
|
||||
void displayNameChanged();
|
||||
void pushNotificationStateChanged(PushNotificationState::State state);
|
||||
void canEncryptRoomChanged();
|
||||
void joinRuleChanged();
|
||||
void historyVisibilityChanged();
|
||||
void defaultUrlPreviewStateChanged();
|
||||
void urlPreviewEnabledChanged();
|
||||
@@ -687,6 +663,14 @@ Q_SIGNALS:
|
||||
*/
|
||||
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:
|
||||
/**
|
||||
* @brief Upload a file to the matrix server and post the file to the room.
|
||||
|
||||
@@ -127,8 +127,9 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
|
||||
}
|
||||
auto sender = room->member(notification["event"_L1]["sender"_L1].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) {
|
||||
postInviteNotification(qobject_cast<NeoChatRoom *>(room));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -243,7 +244,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
|
||||
if (canReply) {
|
||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||
replyAction->setPlaceholderText(i18n("Reply…"));
|
||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(text);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"Name[ia]": "Tobias Fella",
|
||||
"Name[it]": "Tobias Fella",
|
||||
"Name[ka]": "Tobias Fella",
|
||||
"Name[ko]": "Tobias Fella",
|
||||
"Name[lv]": "Tobias Fella",
|
||||
"Name[nl]": "Tobias Fella",
|
||||
"Name[nn]": "Tobias Fella",
|
||||
@@ -36,7 +35,6 @@
|
||||
"Name[tr]": "Tobias Fella",
|
||||
"Name[uk]": "Tobias Fella",
|
||||
"Name[x-test]": "xxTobias Fellaxx",
|
||||
"Name[zh_CN]": "Tobias Fella",
|
||||
"Name[zh_TW]": "Tobias Fella"
|
||||
}
|
||||
],
|
||||
@@ -60,7 +58,6 @@
|
||||
"Description[ia]": "Comparti via NeoChat",
|
||||
"Description[it]": "Condividi tramite NeoChat",
|
||||
"Description[ka]": "გააზიარეთ NeoChat-ით",
|
||||
"Description[ko]": "NeoChat으로 공유",
|
||||
"Description[lv]": "Kopīgot ar „NeoChat“",
|
||||
"Description[nl]": "Delen via NeoChat",
|
||||
"Description[nn]": "Del via NeoChat",
|
||||
@@ -73,7 +70,6 @@
|
||||
"Description[tr]": "NeoChat ile Paylaş",
|
||||
"Description[uk]": "Оприлюднити за допомогою NeoChat",
|
||||
"Description[x-test]": "xxShare via NeoChatxx",
|
||||
"Description[zh_CN]": "通过 NeoChat 分享",
|
||||
"Description[zh_TW]": "透過 NeoChat 分享",
|
||||
"Icon": "org.kde.neochat.tray",
|
||||
"License": "GPL",
|
||||
@@ -98,7 +94,6 @@
|
||||
"Name[ia]": "Neochat",
|
||||
"Name[it]": "NeoChat",
|
||||
"Name[ka]": "NeoChat",
|
||||
"Name[ko]": "NeoChat",
|
||||
"Name[lv]": "NeoChat",
|
||||
"Name[nl]": "NeoChat",
|
||||
"Name[nn]": "NeoChat",
|
||||
@@ -112,7 +107,6 @@
|
||||
"Name[tr]": "NeoChat",
|
||||
"Name[uk]": "NeoChat",
|
||||
"Name[x-test]": "xxNeoChatxx",
|
||||
"Name[zh_CN]": "NeoChat",
|
||||
"Name[zh_TW]": "NeoChat",
|
||||
"X-Purpose-ActionDisplay": "NeoChat"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user