Compare commits

..

1 Commits

Author SHA1 Message Date
Carl Schwan
2897f9e9b8 Fix build with libquotient dev (version: 0.9.2) 2025-01-19 16:49:47 +01:00
132 changed files with 13846 additions and 24595 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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
@@ -206,9 +208,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
@@ -323,10 +322,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 +423,6 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ColorScheme
KF6::ItemModels
QuotientQt6
@@ -547,8 +542,6 @@ if(ANDROID)
"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()

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,17 +50,14 @@ 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);

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

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

View File

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

View File

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

View File

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

View File

@@ -136,7 +136,7 @@ QList<ActionsModel::Action> actions{
Action{
u"plain"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
#if Quotient_VERSION_MINOR > 9
#if Quotient_VERSION_MINOR >= 9
room->postText(text.toHtmlEscaped());
#else
room->postPlainText(text.toHtmlEscaped());

View File

@@ -60,11 +60,6 @@ void CommonRoomsModel::reload()
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();

View File

@@ -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()
@@ -345,10 +340,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)
@@ -409,7 +400,6 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
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";
@@ -490,18 +480,12 @@ 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()))
if (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()) {
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
#endif
newComponents += MessageComponent{MessageComponentType::Separator, {}, {}};
newComponents += MessageComponent{MessageComponentType::ThreadBody, u"Thread Body"_s, {}};
}
@@ -528,7 +512,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 +523,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 +730,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"

View File

@@ -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"
/**
@@ -57,8 +57,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. */
@@ -127,7 +125,6 @@ private:
QPointer<MessageContentModel> m_replyModel;
void updateReplyModel();
ReactionModel *m_reactionModel = nullptr;
ItineraryModel *m_itineraryModel = nullptr;
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
@@ -138,6 +135,4 @@ private:
void updateItineraryModel();
bool m_emptyItinerary = false;
void updateReactionModel();
};

View File

@@ -5,7 +5,6 @@
#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)
@@ -121,12 +120,8 @@ 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()));
}
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get());
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()) {
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(roomMessageEvent->threadRootEventId()));
}
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(&event->get()));
@@ -221,9 +216,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 +258,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());
@@ -319,6 +323,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";
@@ -448,6 +454,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 +504,7 @@ void MessageModel::clearModel()
void MessageModel::clearEventObjects()
{
m_reactionModels.clear();
m_readMarkerModels.clear();
}

View File

@@ -45,7 +45,6 @@ class MessageModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
/**
* @brief The current room that the model is getting its messages from.
@@ -77,6 +76,8 @@ 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. */
@@ -153,6 +154,7 @@ private:
bool movingEvent = false;
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,6 @@
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());
@@ -49,7 +48,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();
}
@@ -78,19 +77,19 @@ QHash<int, QByteArray> ThreadModel::roleNames() const
return MessageContentModel::roleNamesStatic();
}
bool ThreadModel::moreEventsAvailable(const QModelIndex &parent) const
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]() {
auto newEvents = m_currentJob->chunk();
for (auto &event : newEvents) {
@@ -110,7 +109,6 @@ void ThreadModel::fetchMoreEvents(int max)
}
m_currentJob.clear();
Q_EMIT moreEventsAvailableChanged();
});
}
}
@@ -134,7 +132,6 @@ void ThreadModel::addModels()
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) {
@@ -153,7 +150,6 @@ void ThreadModel::clearModels()
if (room == nullptr) {
return;
}
removeSourceModel(m_threadFetchModel);
for (const auto &model : m_events) {
const auto contentModel = room->contentModelForEvent(model);
if (sourceModels().contains(contentModel)) {
@@ -192,47 +188,6 @@ void ThreadModel::closeLinkPreview(int 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)

View File

@@ -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
@@ -144,14 +99,18 @@ public:
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/**
* @brief Whether there are more events for the model to fetch.
* @brief Whether there is more data available for the model to fetch.
*
* @sa QAbstractItemModel::canFetchMore()
*/
bool moreEventsAvailable(const QModelIndex &parent) const;
bool canFetchMore(const QModelIndex &parent) const override;
/**
* @brief Fetches the next batch of events if any is available.
* @brief Fetches the next batch of model data if any is available.
*
* @sa QAbstractItemModel::fetchMore()
*/
Q_INVOKABLE void fetchMoreEvents(int max = 5);
void fetchMore(const QModelIndex &parent) override;
/**
* @brief Close the link preview at the given index.
@@ -160,17 +119,15 @@ public:
*/
Q_INVOKABLE void closeLinkPreview(int row);
Q_SIGNALS:
void moreEventsAvailableChanged();
private:
QString m_threadRootId;
QPointer<MessageContentModel> m_threadRootContentModel;
std::deque<QString> m_events;
ThreadFetchModel *m_threadFetchModel;
ThreadChatBarModel *m_threadChatBarModel;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr;
std::optional<QString> m_nextBatch = QString();
bool m_addingPending = false;

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,10 +145,6 @@ void NeoChatConnection::connectSignals()
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());
@@ -495,12 +472,9 @@ 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;
@@ -528,7 +502,7 @@ QString NeoChatConnection::accountDataJsonString(const QString &type) const
LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
{
if (!m_globalUrlPreviewEnabled) {
if (!NeoChatConfig::showLinkPreview()) {
return nullptr;
}
@@ -562,18 +536,4 @@ 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"

View File

@@ -32,11 +32,6 @@ class NeoChatConnection : public Quotient::Connection
*/
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)
/**
* @brief The model with the account's 3PIDs.
*/
@@ -95,16 +90,6 @@ class NeoChatConnection : public Quotient::Connection
*/
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.
@@ -187,24 +172,17 @@ 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;
bool isOnline() const;
LinkPreviewer *previewerForLink(const QUrl &link);
Q_SIGNALS:
void globalUrlPreviewEnabledChanged();
void labelChanged();
void identityServerChanged();
void directChatNotificationsChanged();
@@ -218,7 +196,6 @@ Q_SIGNALS:
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.
@@ -239,11 +216,9 @@ 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;
};

View File

@@ -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"
@@ -133,6 +133,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
Q_EMIT canEncryptRoomChanged();
Q_EMIT parentIdsChanged();
Q_EMIT canonicalParentChanged();
Q_EMIT joinRuleChanged();
Q_EMIT readOnlyChanged();
});
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
@@ -156,10 +157,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
@@ -608,12 +605,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 +728,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 {
@@ -1671,11 +1716,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)
@@ -1726,9 +1767,7 @@ MessageContentModel *NeoChatRoom::contentModelForEvent(const Quotient::RoomEvent
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()))
return m_eventContentModels.emplace(eventId, std::make_unique<MessageContentModel>(this, eventId.isEmpty() ? txnId : eventId, false, eventId.isEmpty()))
.first->second.get();
}
@@ -1763,23 +1802,4 @@ ThreadModel *NeoChatRoom::modelForThread(const QString &threadRootId)
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"

View File

@@ -134,6 +134,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 +420,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;
/**
@@ -601,23 +636,6 @@ public:
*/
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;
@@ -675,6 +693,7 @@ Q_SIGNALS:
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void canEncryptRoomChanged();
void joinRuleChanged();
void historyVisibilityChanged();
void defaultUrlPreviewStateChanged();
void urlPreviewEnabledChanged();

View File

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

View File

@@ -87,18 +87,7 @@ KirigamiComponents.ConvergentContextMenu {
QQC2.Action {
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
onTriggered: {
root.connection.startSelfVerification();
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
title: i18nc("@title", "Verification Request Sent"),
subtitle: i18nc("@info:label", "To proceed, accept the verification request on another device."),
standardButtons: Kirigami.Dialog.Ok
})
dialog.open();
root.connection.onNewKeyVerificationSession.connect(() => {
dialog.close();
});
}
onTriggered: root.connection.startSelfVerification()
enabled: Controller.csSupported
}

View File

@@ -34,8 +34,9 @@ Delegates.RoundedItemDelegate {
Keys.onSpacePressed: selected()
Keys.onEnterPressed: selected()
onPressAndHold: root.contextMenuRequested()
TapHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton | Qt.LeftButton
onTapped: (eventPoint, button) => {
if (button === Qt.RightButton) {
@@ -46,12 +47,6 @@ Delegates.RoundedItemDelegate {
}
}
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onTapped: root.selected()
onLongPressed: root.contextMenuRequested()
}
contentItem: AvatarNotification {
id: avatarNotification
source: root.source

View File

@@ -75,7 +75,7 @@ KirigamiComponents.ConvergentContextMenu {
component RemoveMessageAction: Kirigami.Action {
visible: author.isLocalMember || currentRoom.canSendState("redact")
text: i18nc("@action:button", "Remove")
text: i18nc("@action:button", "Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: {
@@ -132,15 +132,6 @@ KirigamiComponents.ConvergentContextMenu {
}
}
component PinMessageAction: Kirigami.Action {
readonly property bool pinned: currentRoom.isEventPinned(root.eventId)
visible: currentRoom.canSendState("m.room.pinned_events")
text: pinned ? i18nc("@action:button 'Unpin' as in 'Unpin this message'", "Unpin") : i18nc("@action:button 'Pin' as in 'Pin the message in the room'", "Pin")
icon.name: pinned ? "window-unpin-symbolic" : "pin-symbolic"
onTriggered: pinned ? currentRoom.unpinEvent(root.eventId) : currentRoom.pinEvent(root.eventId)
}
headerContentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing

View File

@@ -24,6 +24,10 @@ RowLayout {
*/
signal textChanged(string newText)
Item {
Layout.preferredWidth: Kirigami.Units.largeSpacing
}
Kirigami.Heading {
Layout.fillWidth: true
visible: !root.collapsed
@@ -74,6 +78,21 @@ RowLayout {
Component {
id: menu
QQC2.Menu {
QQC2.MenuItem {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});
}
}
QQC2.MenuItem {
text: i18n("Find your friends")
icon.name: "list-add-user"

View File

@@ -71,7 +71,7 @@ DelegateContextMenu {
Kirigami.Action {
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
text: i18n("Remove")
text: i18n("Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: {
@@ -90,8 +90,6 @@ DelegateContextMenu {
}
}
DelegateContextMenu.PinMessageAction {}
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}

View File

@@ -15,7 +15,7 @@ import org.kde.neochat.settings
Labs.MenuBar {
id: root
required property NeoChatConnection connection
property NeoChatConnection connection
Labs.Menu {
title: i18nc("menu", "NeoChat")
@@ -40,7 +40,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "Find your friends")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
onTriggered: pushReplaceLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
@@ -51,11 +51,8 @@ Labs.MenuBar {
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
shortcut: StandardKey.New
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
const dialog = createRoomDialog.createObject(root.overlay);
dialog.open();
}
}
Labs.MenuItem {

View File

@@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import org.kde.neochat
Item {
required property NeoChatConnection connection
}

View File

@@ -19,11 +19,6 @@ ColumnLayout {
*/
required property NeoChatRoom room
/**
* @brief The canonical alias of the room, if it exists. Otherwise falls back to the first available alias.
*/
readonly property var roomAlias: room.aliases[0]
Layout.fillWidth: true
RowLayout {
@@ -78,8 +73,8 @@ ColumnLayout {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.room && root.roomAlias
text: root.room && root.roomAlias ? root.roomAlias : ""
visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
}
}

View File

@@ -21,7 +21,6 @@ QQC2.Control {
contentItem: QQC2.Label {
text: root.text.startsWith("https://matrix.to/") ? "" : root.text
elide: Text.ElideRight
Accessible.description: i18nc("@info screenreader", "The currently selected link")
}

View File

@@ -1,5 +1,4 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
@@ -7,89 +6,22 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
ColumnLayout {
Kirigami.PlaceholderMessage {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId())
spacing: Kirigami.Units.smallSpacing
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
color: root.invitingMember.color
}
Loader {
active: !root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
text: i18nc("@info:label", "%1 has invited you to join", root.invitingMember.displayName)
Layout.alignment: Qt.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
}
}
Loader {
active: root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
QQC2.Label {
text: i18nc("@info:label", "This user is inviting you to chat.")
Layout.alignment: Qt.AlignHCenter
}
}
}
QQC2.Label {
color: Kirigami.Theme.disabledTextColor
text: i18n("You can reject invitations from unknown users under Security settings.")
visible: root.currentRoom.connection.canCheckMutualRooms
}
text: i18n("Accept this invitation?")
explanation: root.currentRoom.connection.canCheckMutualRooms ? i18n("You can reject invitations from unknown users under Security settings.") : ""
RowLayout {
spacing: Kirigami.Units.smallSpacing
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.mediumSpacing
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
text: i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and Ignore User")
icon.name: "list-remove-symbolic"
text: i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and ignore user")
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
@@ -98,18 +30,18 @@ ColumnLayout {
}
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
icon.name: "cards-block-symbolic"
text: i18nc("@action:button", "Reject")
text: i18n("Reject")
onClicked: RoomManager.leaveRoom(root.currentRoom)
}
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
icon.name: "dialog-ok-symbolic"
text: i18nc("@action:button", "Accept")
text: i18n("Accept")
onClicked: root.currentRoom.acceptInvitation()
onClicked: {
root.currentRoom.acceptInvitation();
}
}
}
}

View File

@@ -63,7 +63,7 @@ Kirigami.Page {
when: root.session.state === KeyVerificationSession.READY
PropertyChanges {
target: stateLoader
sourceComponent: chooseVerificationComponent
sourceComponent: emojiVerificationComponent
}
},
State {
@@ -152,22 +152,15 @@ Kirigami.Page {
}
Component {
id: chooseVerificationComponent
Item {
ColumnLayout {
anchors.centerIn: parent
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
text: i18nc("@info", "Choose a verification method to continue")
}
QQC2.Button {
id: emojiVerification
text: i18nc("@action:button", "Emoji Verification")
icon.name: "smiley"
onClicked: root.session.sendStartSas()
Layout.alignment: Qt.AlignHCenter
}
id: emojiVerificationComponent
Delegates.RoundedItemDelegate {
id: emojiVerification
text: i18n("Emoji Verification")
contentItem: Delegates.SubtitleContentItem {
subtitle: i18n("Compare a set of emoji on both devices")
itemDelegate: emojiVerification
}
onClicked: root.session.sendStartSas()
}
}
}

View File

@@ -73,8 +73,9 @@ Kirigami.ApplicationWindow {
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
sourceComponent: GlobalMenu {
connection: root.connection
sourceComponent: Qt.createComponent("org.kde.neochat", "GlobalMenu")
onActiveChanged: if (active) {
item.connection = root.connection;
}
}
@@ -291,12 +292,8 @@ Kirigami.ApplicationWindow {
HoverLinkIndicator {
id: linkIndicator
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
rightMargin: Kirigami.Units.largeSpacing
}
anchors.bottom: parent.bottom
anchors.left: parent.left
}
Shortcut {

View File

@@ -78,7 +78,7 @@ DelegateContextMenu {
}
QQC2.Action {
text: i18nc("@action:inmenu", "Copy Message Link")
icon.name: "link-symbolic"
icon.name: "edit-copy"
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
}
@@ -86,7 +86,6 @@ DelegateContextMenu {
Kirigami.Action {
separator: true
}
DelegateContextMenu.PinMessageAction {}
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}
Kirigami.Action {
@@ -105,20 +104,20 @@ DelegateContextMenu {
Kirigami.Action {
id: webShortcutModelAction
text: i18n("Search for '%1'", webShortcutModel.trunkatedSearchText)
text: i18n("Search for '%1'", webshortcutModel.trunkatedSearchText)
icon.name: "search-symbolic"
visible: webShortcutModel.enabled
visible: webshortcutModel.enabled
readonly property Instantiator instantiator: Instantiator {
model: WebShortcutModel {
id: webShortcutModel
id: webshortcutModel
selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText
onOpenUrl: url => RoomManager.resolveResource(url.toString())
}
delegate: QQC2.Action {
text: model.display
icon.name: model.decoration
onTriggered: webShortcutModel.trigger(model.edit)
onTriggered: webshortcutModel.trigger(model.edit)
}
onObjectAdded: (index, object) => webShortcutModelAction.children.push(object)
}
@@ -127,7 +126,7 @@ DelegateContextMenu {
Kirigami.Action {
text: i18n("Configure Web Shortcuts…")
icon.name: "configure"
visible: !Controller.isFlatpak && webShortcutModel.enabled
onTriggered: webShortcutModel.configureWebShortcuts()
visible: !Controller.isFlatpak && webshortcutModel.enabled
onTriggered: webshortcutmodel.configureWebShortcuts()
}
}

View File

@@ -125,17 +125,12 @@ KirigamiComponents.ConvergentContextMenu {
QQC2.Action {
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy"
onTriggered: {
// The canonical alias (if it exists) otherwise the first available alias
const firstAlias = room.aliases[0];
if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (!firstAlias) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(firstAlias);
}
onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(room.canonicalAlias);
}
}

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