Compare commits

..

1 Commits

Author SHA1 Message Date
Thiago Sueto
edee51a08a Make all QML modules required so their absence makes compilation fail 2024-11-21 12:35:35 -03:00
122 changed files with 20952 additions and 33495 deletions

55
.reuse/dep5 Normal file
View File

@@ -0,0 +1,55 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: NeoChat
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.svg android/res/drawable/neochat.png
Copyright: 2020 Carson Black <uhhadd@gmail.com>
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
Files: android/res/drawable/splash.xml
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: .gitignore
Copyright: None
License: CC0-1.0
Files: .gitlab/issue_templates/bug.md
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: CC0-1.0
Files: src/res.qrc src/res_android.qrc src/res_desktop.qrc
Copyright: None
License: CC0-1.0
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/qml/confetti.png src/qml/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0
Files: src/purpose/purposeplugin.json
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause

View File

@@ -94,7 +94,7 @@ else()
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0 REQUIRED)
find_package(ICU 61.0 COMPONENTS uc)
set_package_properties(ICU PROPERTIES
@@ -123,13 +123,12 @@ set_package_properties(cmark PROPERTIES
PURPOSE "Convert markdown to html"
)
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
ecm_find_qmlmodule(QtLocation)
ecm_find_qmlmodule(org.kde.prison)
ecm_find_qmlmodule(QtLocation REQUIRED)
ecm_find_qmlmodule(org.kde.kitemmodels 1.0 REQUIRED)
ecm_find_qmlmodule(org.kde.quickcharts 1.0 REQUIRED)
ecm_find_qmlmodule(org.kde.prison REQUIRED)
find_package(KQuickImageEditor COMPONENTS)
find_package(KQuickImageEditor REQUIRED COMPONENTS)
set_package_properties(KQuickImageEditor PROPERTIES
TYPE REQUIRED
DESCRIPTION "Simple image editor for QtQuick applications"

View File

@@ -1,84 +0,0 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
version = 1
SPDX-PackageName = "NeoChat"
SPDX-PackageSupplier = "Carl Schwan <carlschwan@kde.org>"
[[annotations]]
path = ["128-logo.png", "icons/**", "logo.png", "org.kde.neochat.svg", "org.kde.neochat.tray.svg", "android/res/drawable/neochat.png", "android/neochat-playstore.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Carson Black <uhhadd@gmail.com>"
SPDX-License-Identifier = "LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL"
[[annotations]]
path = "android/res/drawable/splash.xml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ".gitignore"
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".gitlab/issue_templates/bug.md"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ["src/res.qrc", "src/res_android.qrc", "src/res_desktop.qrc"]
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "cmake/Flatpak/99-noto-mono-color-emoji.conf"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/neochatconfig.kcfg"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/neochat.notifyrc"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ["src/qml/confetti.png", "src/qml/glowdot.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Alexey Andreyev <aa13q@ya.ru>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".flatpak-manifest.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2022 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "autotests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "appiumtests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "src/purpose/purposeplugin.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -83,15 +83,6 @@ def create_room():
next_sync_payload = "sync_response_new_room"
return response
@app.route("/_matrix/client/v3/publicRooms", methods=["POST"])
def public_rooms():
if request.get_json()["filter"]["generic_search_term"] == "forbidden":
data = dict()
data["errcode"] = "M_FORBIDDEN"
data["error"] = "You are not allowed to search for this. Go to https://wikipedia.org for more information"
return data, 403
return dict()
if __name__ == "__main__":

View File

@@ -32,6 +32,8 @@ private:
private Q_SLOTS:
void initTestCase();
void eventId();
void nullEventId();
void authorDisplayName();
void nullAuthorDisplayName();
void singleLineSidplayName();
@@ -54,8 +56,14 @@ private Q_SLOTS:
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void hasReply();
void nullHasReply();
void replyId();
void nullReplyId();
void replyAuthor();
void nullReplyAuthor();
void thread();
void nullThread();
void location();
void nullLocation();
};
@@ -66,6 +74,17 @@ void EventHandlerTest::initTestCase()
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
}
void EventHandlerTest::eventId()
{
QCOMPARE(EventHandler::id(room->messageEvents().at(0).get()), QStringLiteral("$153456789:example.org"));
}
void EventHandlerTest::nullEventId()
{
QTest::ignoreMessage(QtWarningMsg, "id called with event set to nullptr.");
QCOMPARE(EventHandler::id(nullptr), QString());
}
void EventHandlerTest::authorDisplayName()
{
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
@@ -99,9 +118,8 @@ void EventHandlerTest::time()
{
const auto event = room->messageEvents().at(0).get();
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC))),
QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC)));
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
}
void EventHandlerTest::nullTime()
@@ -120,19 +138,19 @@ void EventHandlerTest::timeString()
KFormat format;
QCOMPARE(EventHandler::timeString(event, false),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::LongFormat));
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(QStringLiteral("hh:mm")));
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
}
void EventHandlerTest::highlighted()
@@ -277,6 +295,30 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
}
void EventHandlerTest::hasReply()
{
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(5).get()), true);
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(0).get()), false);
}
void EventHandlerTest::nullHasReply()
{
QTest::ignoreMessage(QtWarningMsg, "hasReply called with event set to nullptr.");
QCOMPARE(EventHandler::hasReply(nullptr), false);
}
void EventHandlerTest::replyId()
{
QCOMPARE(EventHandler::replyId(room->messageEvents().at(5).get()), QStringLiteral("$153456789:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(0).get()), QStringLiteral(""));
}
void EventHandlerTest::nullReplyId()
{
QTest::ignoreMessage(QtWarningMsg, "replyId called with event set to nullptr.");
QCOMPARE(EventHandler::replyId(nullptr), QString());
}
void EventHandlerTest::replyAuthor()
{
auto replyEvent = room->messageEvents().at(0).get();
@@ -302,6 +344,29 @@ void EventHandlerTest::nullReplyAuthor()
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember());
}
void EventHandlerTest::thread()
{
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(0).get()), false);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(0).get()), QString());
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(9).get()), true);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(10).get()), true);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(10).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(10).get()), QStringLiteral("$threadmessage1:example.org"));
}
void EventHandlerTest::nullThread()
{
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with event set to nullptr.");
QCOMPARE(EventHandler::isThreaded(nullptr), false);
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with event set to nullptr.");
QCOMPARE(EventHandler::threadRoot(nullptr), QString());
}
void EventHandlerTest::location()
{
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());

View File

@@ -54,24 +54,19 @@
<summary xml:lang="ar">دردش على ماتركس</summary>
<summary xml:lang="ca">Xat a Matrix</summary>
<summary xml:lang="ca-valencia">Xat a Matrix</summary>
<summary xml:lang="de">Über Matrix unterhalten</summary>
<summary xml:lang="en-GB">Chat on Matrix</summary>
<summary xml:lang="es">Charle en Matrix</summary>
<summary xml:lang="eu">Berriketa Matrix-en</summary>
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
<summary xml:lang="fr">Discuter sur Matrix</summary>
<summary xml:lang="gl">Charlar en Matrix</summary>
<summary xml:lang="he">התכתבות דרך Matrix</summary>
<summary xml:lang="hu">Csevegés Matrixon</summary>
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ 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="sl">Klepet na Matrixu</summary>
<summary xml:lang="sv">Chatta på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary>
@@ -293,7 +288,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
<value key="KDE::supporters">Tanguy Fardet</value>
</custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots>
@@ -448,7 +443,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/>
<release version="24.08.1" date="2024-09-12"/>

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -92,7 +92,7 @@ parts:
- olm
- qtkeychain
source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.1
source-tag: 0.9.0
source-depth: 1
plugin: cmake
build-packages:

View File

@@ -194,20 +194,12 @@ add_library(neochat STATIC
models/threadmodel.h
enums/messagetype.h
messagecomponent.h
enums/roomsortparameter.cpp
enums/roomsortparameter.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
@@ -319,9 +311,13 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml
)
else()
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_RESOURCE_ALIAS qml/ShareAction.qml
)
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32)
@@ -535,7 +531,6 @@ if(ANDROID)
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()

View File

@@ -176,14 +176,13 @@ QQC2.Control {
RowLayout {
QQC2.ScrollView {
id: chatBarScrollView
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
@@ -321,11 +320,12 @@ QQC2.Control {
id: actionsRow
spacing: 0
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: Kirigami.Units.smallSpacing * 4
Layout.bottomMargin: Kirigami.Units.smallSpacing * 1.5
Repeater {
model: root.actions
delegate: QQC2.ToolButton {
Layout.alignment: Qt.AlignVCenter
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: modelData.trigger()
@@ -342,6 +342,7 @@ QQC2.Control {
}
}
}
DelegateSizeHelper {
id: chatBarSizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 46

View File

@@ -11,8 +11,6 @@
#include "neochatroom.h"
#include "texthandler.h"
using namespace Qt::StringLiterals;
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
{
@@ -321,25 +319,7 @@ void ChatBarCache::postMessage()
return;
}
bool isReply = !replyId().isEmpty();
const auto replyIt = room->findInTimeline(replyId());
if (replyIt == room->historyEdge()) {
isReply = false;
}
auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s);
std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
if (!threadId().isEmpty()) {
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
} else if (!editId().isEmpty()) {
relatesTo = Quotient::EventRelation::replace(editId());
} else if (isReply) {
relatesTo = Quotient::EventRelation::replyTo(replyId());
}
const auto type = std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result);
room->post<Quotient::RoomMessageEvent>(text(), type ? *type : Quotient::RoomMessageEvent::MsgType::Text, std::move(content), relatesTo);
room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
clearCache();
}

View File

@@ -8,6 +8,7 @@
ColorSchemer::ColorSchemer(QObject *parent)
: QObject(parent)
, c(new KColorSchemeManager(this))
{
}
@@ -17,17 +18,17 @@ ColorSchemer::~ColorSchemer()
QAbstractItemModel *ColorSchemer::model() const
{
return KColorSchemeManager::instance()->model();
return c->model();
}
void ColorSchemer::apply(int idx)
{
KColorSchemeManager::instance()->activateScheme(KColorSchemeManager::instance()->model()->index(idx, 0));
c->activateScheme(c->model()->index(idx, 0));
}
int ColorSchemer::indexForCurrentScheme()
{
return KColorSchemeManager::instance()->indexForSchemeId(KColorSchemeManager::instance()->activeSchemeId()).row();
return c->indexForSchemeId(c->activeSchemeId()).row();
}
#include "moc_colorschemer.cpp"

View File

@@ -49,4 +49,7 @@ public:
* @sa KColorScheme
*/
Q_INVOKABLE int indexForCurrentScheme();
private:
KColorSchemeManager *c;
};

View File

@@ -423,14 +423,10 @@ void Controller::setTestMode(bool test)
void Controller::removeConnection(const QString &userId)
{
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
// entry for it so we need to check both separately.
if (m_accountsLoading.contains(userId)) {
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
}
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
SettingsGroup("Accounts"_ls).remove(userId);
}
}

View File

@@ -33,28 +33,4 @@ ColumnLayout {
}
}
}
FormCard.FormCard {
FormCard.FormSwitchDelegate {
id: showAccessTokenCheckbox
text: i18nc("@info", "Show Access Token")
description: i18n("This should not be shared with anyone, even other users. This token gives full access to your account.")
}
FormCard.FormTextDelegate {
text: i18nc("@info", "Access Token")
description: root.connection.accessToken
visible: showAccessTokenCheckbox.checked
contentItem.children: QQC2.Button {
text: i18nc("@action:button", "Copy access token to clipboard")
icon.name: "edit-copy"
display: QQC2.AbstractButton.IconOnly
onClicked: Clipboard.saveText(root.connection.accessToken)
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
}
}
}

View File

@@ -21,22 +21,19 @@ ColumnLayout {
title: i18nc("@title", "Choose Room")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
text: root.room?.displayNameForHtml ?? i18nc("@info", "No room selected")
description: i18nc("@info", "Click to choose a room");
onClicked: {
let dialog = root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
connection: root.connection,
}, {
title: i18nc("@title:dialog", "Choose Room"),
width: Kirigami.Units.gridUnit * 24
});
dialog.chosen.connect(id => root.room = root.connection.room(id))
}
FormCard.FormComboBoxDelegate {
id: roomComboBox
text: i18n("Room")
textRole: "escapedDisplayName"
valueRole: "roomId"
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.EscapedDisplayNameRole)
model: RoomManager.roomListModel
currentIndex: 0
displayMode: FormCard.FormComboBoxDelegate.Page
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
}
FormCard.FormTextDelegate {
visible: root.room
text: i18n("Room Id: %1", root.room.id)
}
}

View File

@@ -37,7 +37,7 @@ FormCard.FormCardPage {
}
function openEventSource(stateKey: string): void {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel,
allowEdit: true,
room: root.room,

View File

@@ -1,85 +0,0 @@
// SPDX-FileCopyrightText: 2024 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 "roomsortparameter.h"
namespace
{
template<typename T>
int typeCompare(T left, T right)
{
return left == right ? 0 : left > right ? 1 : -1;
}
template<>
int typeCompare<QString>(QString left, QString right)
{
return left.localeAwareCompare(right);
}
}
int RoomSortParameter::compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
switch (parameter) {
case AlphabeticalAscending:
return compareParameter<AlphabeticalAscending>(leftRoom, rightRoom);
case AlphabeticalDescending:
return compareParameter<AlphabeticalDescending>(leftRoom, rightRoom);
case HasUnread:
return compareParameter<HasUnread>(leftRoom, rightRoom);
case MostUnread:
return compareParameter<MostUnread>(leftRoom, rightRoom);
case HasHighlight:
return compareParameter<HasHighlight>(leftRoom, rightRoom);
case MostHighlights:
return compareParameter<MostHighlights>(leftRoom, rightRoom);
case LastActive:
return compareParameter<LastActive>(leftRoom, rightRoom);
default:
return false;
}
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return -typeCompare(leftRoom->displayName(), rightRoom->displayName());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->displayName(), rightRoom->displayName());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->contextAwareNotificationCount() > 0, rightRoom->contextAwareNotificationCount() > 0);
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->contextAwareNotificationCount(), rightRoom->contextAwareNotificationCount());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
const auto leftHighlight = leftRoom->highlightCount() > 0 && leftRoom->contextAwareNotificationCount() > 0;
const auto rightHighlight = rightRoom->highlightCount() > 0 && rightRoom->contextAwareNotificationCount() > 0;
return typeCompare(leftHighlight, rightHighlight);
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(int(leftRoom->highlightCount()), int(rightRoom->highlightCount()));
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->lastActiveTime(), rightRoom->lastActiveTime());
}

View File

@@ -1,122 +0,0 @@
// SPDX-FileCopyrightText: 2024 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 "neochatroom.h"
#include <QObject>
#include <QQmlEngine>
#include <KLocalizedString>
/**
* @class RoomSortParameter
*
* A class with the Parameter enum for room sort parameters.
*/
class RoomSortParameter : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Defines the available sort parameters.
*/
enum Parameter {
AlphabeticalAscending,
AlphabeticalDescending,
HasUnread,
MostUnread,
HasHighlight,
MostHighlights,
LastActive,
};
Q_ENUM(Parameter)
/**
* @brief Translate the Parameter enum value to a human readable name string.
*
* @sa Parameter
*/
static QString parameterName(Parameter parameter)
{
switch (parameter) {
case Parameter::AlphabeticalAscending:
return i18nc("As in sorting alphabetically with A first and Z last", "Alphabetical Ascending");
case Parameter::AlphabeticalDescending:
return i18nc("As in sorting alphabetically with Z first and A last", "Alphabetical Descending");
case Parameter::HasUnread:
return i18nc("As in sorting rooms with unread message above those without", "Has Unread Messages");
case Parameter::MostUnread:
return i18nc("As in sorting rooms with the most unread messages higher", "Most Unread Messages");
case Parameter::HasHighlight:
return i18nc("As in sorting rooms with highlighted message above those without", "Has Highlighted Messages");
case Parameter::MostHighlights:
return i18nc("As in sorting rooms with the most highlighted messages higher", "Most Highlighted Messages");
case Parameter::LastActive:
return i18nc("As in sorting the chat room with the newest meassage first", "Last Active");
default:
return {};
}
};
/**
* @brief Translate the Parameter enum value to a human readable description string.
*
* @sa Parameter
*/
static QString parameterDescription(Parameter parameter)
{
switch (parameter) {
case Parameter::AlphabeticalAscending:
return i18nc("@info", "Room names closer to A alphabetically are higher");
case Parameter::AlphabeticalDescending:
return i18nc("@info", "Room names closer to Z alphabetically are higher");
case Parameter::HasUnread:
return i18nc("@info", "Rooms with unread messages are higher");
case Parameter::MostUnread:
return i18nc("@info", "Rooms with the most unread message are higher");
case Parameter::HasHighlight:
return i18nc("@info", "Rooms with highlighted messages are higher");
case Parameter::MostHighlights:
return i18nc("@info", "Rooms with the most highlighted messages are higher");
case Parameter::LastActive:
return i18nc("@info", "Rooms with the newer messages are higher");
default:
return {};
}
};
/**
* @brief Compare the given parameter of the two given rooms.
*
* @return 0 if they are equal, 1 if the left is greater and -1 if the right is greater.
*
* @sa Parameter
*/
static int compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
private:
template<Parameter parameter>
static int compareParameter(NeoChatRoom *, NeoChatRoom *)
{
return false;
}
};
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);

View File

@@ -49,6 +49,16 @@ Q_DECLARE_FLAGS(MemberChanges, MemberChange)
Q_DECLARE_OPERATORS_FOR_FLAGS(MemberChanges)
};
QString EventHandler::id(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "id called with event set to nullptr.";
return {};
}
return !event->id().isEmpty() ? event->id() : event->transactionId();
}
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
{
if (room == nullptr) {
@@ -60,7 +70,7 @@ QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient:
return {};
}
if (is<RoomMemberEvent>(*event) && event->unsignedJson()[QStringLiteral("prev_content")].toObject().contains("displayname"_L1)
if (is<RoomMemberEvent>(*event) && !event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
&& event->stateKey() == event->senderId()) {
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
if (previousDisplayName.isEmpty()) {
@@ -281,7 +291,7 @@ QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
{
if (event->isRedacted() && !event->isStateEvent()) {
if (event->isRedacted()) {
auto reason = event->redactedBecause()->reason();
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason.toHtmlEscaped());
}
@@ -498,7 +508,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
return {};
}
if (event->isRedacted() && !event->isStateEvent()) {
if (event->isRedacted()) {
return i18n("<i>[This message was deleted]</i>");
}
@@ -824,6 +834,31 @@ QVariantMap EventHandler::getMediaInfoFromTumbnail(const NeoChatRoom *room, cons
return thumbnailInfo;
}
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
{
if (event == nullptr) {
qCWarning(EventHandling) << "hasReply called with event set to nullptr.";
return false;
}
const auto relations = event->contentPart<QJsonObject>("m.relates_to"_ls);
if (!relations.isEmpty()) {
const bool hasReplyRelation = relations.contains("m.in_reply_to"_ls);
bool isFallingBack = relations["is_falling_back"_ls].toBool();
return hasReplyRelation && (showFallbacks ? true : !isFallingBack);
}
return false;
}
QString EventHandler::replyId(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "replyId called with event set to nullptr.";
return {};
}
return event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
}
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
{
if (room == nullptr) {
@@ -842,6 +877,38 @@ Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Qu
}
}
bool EventHandler::isThreaded(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "isThreaded called with event set to nullptr.";
return false;
}
return (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|| (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
}
QString EventHandler::threadRoot(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "threadRoot called with event set to nullptr.";
return {};
}
// Get the thread root ID from m.relates_to if it exists.
if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
return event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
}
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
// If so return the event ID as it is the root.
if (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
return id(event);
}
return {};
}
float EventHandler::latitude(const Quotient::RoomEvent *event)
{
if (event == nullptr) {

View File

@@ -37,6 +37,14 @@ class NeoChatRoom;
class EventHandler
{
public:
/**
* @brief Return the ID of the event.
*
* Returns the transaction ID if the Matrix ID is empty, which may be the case
* for a pending event.
*/
static QString id(const Quotient::RoomEvent *event);
/**
* @brief Get the display name of the event author.
*
@@ -212,6 +220,20 @@ public:
*/
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
/**
* @brief Whether the event is a reply to another in the timeline.
*
* @param showFallbacks whether message that have is_falling_back set true should
* show the fallback reply. Leave true for non-threaded
* timelines.
*/
static bool hasReply(const Quotient::RoomEvent *event, bool showFallbacks = true);
/**
* @brief Return the Matrix ID of the event replied to.
*/
static QString replyId(const Quotient::RoomEvent *event);
/**
* @brief Get the author of the event replied to in context of the room.
*
@@ -227,6 +249,20 @@ public:
*/
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
/**
* @brief Whether the message is part of a thread.
*
* i.e. There is a rel_type of m.thread.
*/
static bool isThreaded(const Quotient::RoomEvent *event);
/**
* @brief Return the Matrix ID of the thread's root message.
*
* Empty if this not part of a thread.
*/
static QString threadRoot(const Quotient::RoomEvent *event);
/**
* @brief Return the latitude for the event.
*

View File

@@ -4,12 +4,11 @@
#include "linkpreviewer.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/authed-content-repo.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "utils.h"
using namespace Quotient;
@@ -62,13 +61,7 @@ void LinkPreviewer::loadUrlPreview()
if (conn == nullptr) {
return;
}
BaseJob *job = nullptr;
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
job = conn->callApi<GetUrlPreviewAuthedJob>(m_url);
} else {
QT_IGNORE_DEPRECATIONS(job = conn->callApi<GetUrlPreviewJob>(m_url);)
}
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url);
connect(job, &BaseJob::success, this, [this, job, conn]() {
const auto json = job->jsonData();

View File

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

View File

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

View File

@@ -96,7 +96,7 @@ Kirigami.Page {
required property string userId
required property NeoChatConnection connection
text: QmlUtils.escapeString(connection.localUser.displayName)
text: connection.localUser.displayName
description: connection.localUser.id
leadingPadding: Kirigami.Units.largeSpacing
@@ -106,6 +106,8 @@ Kirigami.Page {
}
leading: KirigamiComponents.Avatar {
id: avatar
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.gridUnit
name: delegate.text
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : ""

View File

@@ -271,6 +271,7 @@ int main(int argc, char *argv[])
#endif
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
if (parser.isSet("ignore-ssl-errors"_ls)) {

View File

@@ -109,10 +109,11 @@ QList<ActionsModel::Action> actions{
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
}
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>("/rainbow %1"_L1.arg(text), MessageEventType::Text, std::move(content), relatesTo);
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -128,10 +129,11 @@ QList<ActionsModel::Action> actions{
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
}
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>(u"/rainbow %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Emote,
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -142,7 +144,7 @@ QList<ActionsModel::Action> actions{
Action{
QStringLiteral("plain"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
room->postPlainText(text.toHtmlEscaped());
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
return QString();
},
false,
@@ -154,10 +156,11 @@ QList<ActionsModel::Action> actions{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
auto content = std::make_unique<Quotient::EventContent::TextContent>(u"<span data-mx-spoiler>%1</span>"_s.arg(text), u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>(u"/spoiler %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
return QString();
},
false,
@@ -597,20 +600,15 @@ bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messa
} else {
originalString = event->plainBody();
}
QString replaceId = event->id();
const auto eventRelation = event->relatesTo();
if (eventRelation && eventRelation->type == "m.replace"_L1) {
replaceId = eventRelation->eventId;
}
std::unique_ptr<EventContent::TextContent> content = nullptr;
if (flags == "/g"_L1) {
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
} else {
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
room->postHtmlMessage(messageText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
event->id());
}
Quotient::EventRelation relatesTo = Quotient::EventRelation::replace(replaceId);
room->post<Quotient::RoomMessageEvent>(messageText, event->msgtype(), std::move(content), relatesTo);
return true;
}
}

View File

@@ -305,7 +305,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return component.attributes;
}
if (role == EventIdRole) {
return event.first->displayId();
return EventHandler::id(event.first);
}
if (role == TimeRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
@@ -348,9 +348,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
}
if (role == ReplyEventIdRole) {
if (const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first)) {
return roomMessageEvent->replyEventId();
}
return EventHandler::replyId(event.first);
}
if (role == ReplyAuthorRole) {
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
@@ -458,8 +456,8 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
QList<MessageComponent> newComponents;
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
if (eventCast<const Quotient::RoomMessageEvent>(event.first)
&& eventCast<const Quotient::RoomMessageEvent>(event.first)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
return newComponents;
}
@@ -484,7 +482,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
}
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) {
if (isThreading && !EventHandler::isThreaded(event.first)) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
}
@@ -498,11 +496,7 @@ void MessageContentModel::updateReplyModel()
return;
}
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (roomMessageEvent == nullptr) {
return;
}
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
if (!EventHandler::hasReply(event.first) || (EventHandler::isThreaded(event.first) && NeoChatConfig::self()->threads())) {
if (m_replyModel) {
delete m_replyModel;
}
@@ -513,7 +507,7 @@ void MessageContentModel::updateReplyModel()
return;
}
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event.first), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -612,7 +606,6 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
}
}
}
[[fallthrough]];
default:
return {MessageComponent{type, QString(), {}}};
}

View File

@@ -501,8 +501,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return EventStatus::Hidden;
}
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
if (roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->threadRootEventId() != evt.id() && NeoChatConfig::threads()) {
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
return EventStatus::Hidden;
}
@@ -510,7 +509,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == EventIdRole) {
return evt.displayId();
return EventHandler::id(&evt);
}
if (role == ProgressInfoRole) {
@@ -535,18 +534,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == IsThreadedRole) {
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt)) {
return roomMessageEvent->isThreaded();
}
return {};
return EventHandler::isThreaded(&evt);
}
if (role == ThreadRootRole) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
return roomMessageEvent->threadRootEventId();
}
return {};
return EventHandler::threadRoot(&evt);
}
if (role == ShowSectionRole) {
@@ -662,10 +654,8 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, boo
}
}
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_currentRoom));
if (EventHandler::isThreaded(event) && !m_threadModels.contains(EventHandler::threadRoot(event))) {
m_threadModels[EventHandler::threadRoot(event)] = QSharedPointer<ThreadModel>(new ThreadModel(EventHandler::threadRoot(event), m_currentRoom));
}
// ReadMarkerModel handles updates to add and remove markers, we only need to

View File

@@ -8,23 +8,6 @@
using namespace Quotient;
class NeoChatQueryPublicRoomsJob : public QueryPublicRoomsJob
{
public:
explicit NeoChatQueryPublicRoomsJob(const QString &server = {},
std::optional<int> limit = std::nullopt,
const QString &since = {},
const std::optional<Filter> &filter = std::nullopt,
std::optional<bool> includeAllNetworks = std::nullopt,
const QString &thirdPartyInstanceId = {})
: QueryPublicRoomsJob(server, limit, since, filter, includeAllNetworks, thirdPartyInstanceId)
{
// TODO Remove once we can use libQuotient's job directly
// This is to make libQuotient happy about results not having the "chunk" field
setExpectedKeys({});
}
};
PublicRoomListModel::PublicRoomListModel(QObject *parent)
: QAbstractListModel(parent)
{
@@ -170,8 +153,6 @@ void PublicRoomListModel::next(int limit)
if (m_connection == nullptr || limit < 1) {
return;
}
m_redirectedText.clear();
Q_EMIT redirectedChanged();
if (job) {
qCDebug(PublicRoomList) << "Other job running, ignore";
@@ -182,7 +163,7 @@ void PublicRoomListModel::next(int limit)
if (m_showOnlySpaces) {
roomTypes += QLatin1String("m.space");
}
job = m_connection->callApi<NeoChatQueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] {
@@ -200,9 +181,6 @@ void PublicRoomListModel::next(int limit)
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
rooms.append(job->chunk());
this->endInsertRows();
} else if (job->error() == BaseJob::ContentAccessError) {
m_redirectedText = job->jsonData()[u"error"_s].toString();
Q_EMIT redirectedChanged();
}
this->job = nullptr;
@@ -324,9 +302,4 @@ bool PublicRoomListModel::searching() const
return job != nullptr;
}
QString PublicRoomListModel::redirectedText() const
{
return m_redirectedText;
}
#include "moc_publicroomlistmodel.cpp"

View File

@@ -52,11 +52,6 @@ class PublicRoomListModel : public QAbstractListModel
*/
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
/**
* @brief The text returned by the server after redirection
*/
Q_PROPERTY(QString redirectedText READ redirectedText NOTIFY redirectedChanged)
public:
/**
* @brief Defines the model roles.
@@ -118,8 +113,6 @@ public:
*/
Q_INVOKABLE void search(int limit = 50);
QString redirectedText() const;
private:
QPointer<NeoChatConnection> m_connection = nullptr;
QString m_server;
@@ -142,7 +135,6 @@ private:
QList<Quotient::PublicRoomsChunk> rooms;
Quotient::QueryPublicRoomsJob *job = nullptr;
QString m_redirectedText;
Q_SIGNALS:
void connectionChanged();
@@ -150,5 +142,4 @@ Q_SIGNALS:
void searchTextChanged();
void showOnlySpacesChanged();
void searchingChanged();
void redirectedChanged();
};

View File

@@ -129,7 +129,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
refresh(room);
});
connect(room, &Room::addedMessages, this, [this, room] {
refresh(room, {SubtitleTextRole});
refresh(room, {SubtitleTextRole, LastActiveTimeRole});
});
connect(room, &Room::pendingEventMerged, this, [this, room] {
refresh(room, {SubtitleTextRole});
@@ -229,6 +229,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (role == HasHighlightNotificationsRole) {
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
}
if (role == LastActiveTimeRole) {
return room->lastActiveTime();
}
if (role == JoinStateRole) {
if (!room->successorId().isEmpty()) {
return QStringLiteral("upgraded");
@@ -288,6 +291,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
roles[CategoryRole] = "category";
roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom";
roles[SubtitleTextRole] = "subtitleText";

View File

@@ -43,6 +43,7 @@ public:
CategoryRole, /**< The room category, e.g favourite. */
ContextNotificationCountRole, /**< The context aware notification count for the room. */
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */
SubtitleTextRole, /**< The text to show as the room subtitle. */

View File

@@ -176,7 +176,7 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
refreshRoomRoles(room);
});
connect(room, &Room::addedMessages, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole});
refreshRoomRoles(room, {SubtitleTextRole, LastActiveTimeRole});
});
connect(room, &Room::pendingEventMerged, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole});
@@ -274,6 +274,7 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[CategoryRole] = "category";
roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom";
roles[SubtitleTextRole] = "subtitleText";
@@ -283,6 +284,8 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[IsDirectChat] = "isDirectChat";
roles[DelegateTypeRole] = "delegateType";
roles[IconRole] = "icon";
roles[AttentionRole] = "attention";
roles[FavouriteRole] = "favourite";
roles[RoomTypeRole] = "roomType";
return roles;
}
@@ -338,6 +341,9 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == HasHighlightNotificationsRole) {
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
}
if (role == LastActiveTimeRole) {
return room->lastActiveTime();
}
if (role == JoinStateRole) {
if (!room->successorId().isEmpty()) {
return QStringLiteral("upgraded");
@@ -374,6 +380,12 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == DelegateTypeRole) {
return QStringLiteral("normal");
}
if (role == AttentionRole) {
return room->notificationCount() + room->highlightCount() > 0;
}
if (role == FavouriteRole) {
return room->isFavourite();
}
if (role == RoomTypeRole) {
if (room->creation()) {
return room->creation()->contentPart<QString>("type"_L1);

View File

@@ -36,6 +36,7 @@ public:
CategoryRole, /**< The room category, e.g favourite. */
ContextNotificationCountRole, /**< The context aware notification count for the room. */
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */
SubtitleTextRole, /**< The text to show as the room subtitle. */
@@ -47,6 +48,8 @@ public:
IsDirectChat, /**< Whether this room is a direct chat. */
DelegateTypeRole,
IconRole,
AttentionRole, /**< Whether there are any notifications. */
FavouriteRole, /**< Whether the room is favourited. */
RoomTypeRole, /**< The room's type. */
};
Q_ENUM(EventRoles)

View File

@@ -108,17 +108,11 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
case HighlightRole:
return EventHandler::isHighlighted(m_room, &event);
case EventIdRole:
return event.displayId();
return EventHandler::id(&event);
case IsThreadedRole:
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event)) {
return roomMessageEvent->isThreaded();
}
return {};
return EventHandler::isThreaded(&event);
case ThreadRootRole:
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event); roomMessageEvent->isThreaded()) {
return roomMessageEvent->threadRootEventId();
}
return {};
return EventHandler::threadRoot(&event);
case ContentModelRole: {
if (!event.isStateEvent()) {
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));

View File

@@ -6,7 +6,6 @@
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "neochatroomtype.h"
#include "roommanager.h"
#include "roomtreemodel.h"
@@ -45,49 +44,58 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QOb
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
{
m_sortOrder = sortOrder;
if (sortOrder == SortFilterRoomTreeModel::Alphabetical) {
setSortRole(RoomTreeModel::DisplayNameRole);
} else if (sortOrder == SortFilterRoomTreeModel::Activity) {
setSortRole(RoomTreeModel::LastActiveTimeRole);
}
invalidate();
}
static const QVector<RoomSortParameter::Parameter> alphabeticalSortPriorities{
static const QVector<RoomTreeModel::EventRoles> alphabeticalSortPriorities{
// Does exactly what it says on the tin.
RoomSortParameter::AlphabeticalAscending,
RoomTreeModel::DisplayNameRole,
};
static const QVector<RoomSortParameter::Parameter> activitySortPriorities{
RoomSortParameter::HasHighlight,
RoomSortParameter::MostHighlights,
RoomSortParameter::HasUnread,
RoomSortParameter::MostUnread,
RoomSortParameter::LastActive,
static const QVector<RoomTreeModel::EventRoles> activitySortPriorities{
// Anything useful at the top, quiet rooms at the bottom
RoomTreeModel::AttentionRole,
// Organize by highlights, notifications, unread favorites, all other unread, in that order
RoomTreeModel::HasHighlightNotificationsRole,
RoomTreeModel::ContextNotificationCountRole,
RoomTreeModel::FavouriteRole,
// Finally sort by last activity time
RoomTreeModel::LastActiveTimeRole,
};
static const QVector<RoomSortParameter::Parameter> lastMessageSortPriorities{
RoomSortParameter::LastActive,
};
bool SortFilterRoomTreeModel::roleCmp(const QVariant &sortLeft, const QVariant &sortRight) const
{
switch (sortLeft.typeId()) {
case QMetaType::Bool:
return (sortLeft == sortRight) ? false : sortLeft.toBool();
case QMetaType::QString:
return sortLeft.toString() < sortRight.toString();
case QMetaType::Int:
return sortLeft.toInt() > sortRight.toInt();
case QMetaType::QDateTime:
return sortLeft.toDateTime() > sortRight.toDateTime();
default:
return false;
}
}
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities,
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities,
const QModelIndex &source_left,
const QModelIndex &source_right) const
{
const auto treeModel = dynamic_cast<RoomTreeModel *>(sourceModel());
if (treeModel == nullptr) {
return false;
}
const auto leftRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_left.data(RoomTreeModel::RoomIdRole).toString()));
const auto rightRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_right.data(RoomTreeModel::RoomIdRole).toString()));
if (leftRoom == nullptr || rightRoom == nullptr) {
return false;
}
for (auto sortRole : priorities) {
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom, rightRoom);
if (result != 0) {
return result > 0;
for (RoomTreeModel::EventRoles sortRole : priorities) {
const auto sortLeft = sourceModel()->data(source_left, sortRole);
const auto sortRight = sourceModel()->data(source_right, sortRole);
if (sortLeft != sortRight) {
return roleCmp(sortLeft, sortRight);
}
}
return false;
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
@@ -102,9 +110,8 @@ bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QMo
return prioritiesCmp(alphabeticalSortPriorities, source_left, source_right);
case SortFilterRoomTreeModel::Activity:
return prioritiesCmp(activitySortPriorities, source_left, source_right);
case SortFilterRoomTreeModel::LastMessage:
return prioritiesCmp(lastMessageSortPriorities, source_left, source_right);
}
return QSortFilterProxyModel::lessThan(source_left, source_right);
}

View File

@@ -7,7 +7,6 @@
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include "enums/roomsortparameter.h"
#include "models/roomtreemodel.h"
/**
@@ -54,7 +53,6 @@ public:
enum RoomSortOrder {
Alphabetical,
Activity,
LastMessage,
};
Q_ENUM(RoomSortOrder)
@@ -106,5 +104,6 @@ private:
QString m_filterText;
QString m_activeSpaceId;
bool prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities, const QModelIndex &left, const QModelIndex &right) const;
bool roleCmp(const QVariant &left, const QVariant &right) const;
bool prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities, const QModelIndex &left, const QModelIndex &right) const;
};

View File

@@ -25,7 +25,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
addNewEvent(event);
addModels();
}
@@ -34,7 +34,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
for (const auto &event : events) {
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
addNewEvent(roomEvent);
}
}

View File

@@ -10,8 +10,6 @@
#include <QTemporaryFile>
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/eventrelation.h>
#include <Quotient/events/roommessageevent.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <qcoro/qcorosignal.h>
@@ -484,6 +482,119 @@ QString msgTypeToString(MessageEventType msgType)
}
}
void NeoChatRoom::postMessage(const QString &rawText,
const QString &text,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId,
const QString &fallbackId)
{
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId, fallbackId);
}
void NeoChatRoom::postHtmlMessage(const QString &text,
const QString &html,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId,
const QString &fallbackId)
{
bool isReply = !replyEventId.isEmpty();
bool isEdit = !relateToEventId.isEmpty();
bool isThread = !threadRootId.isEmpty();
const auto replyIt = findInTimeline(replyEventId);
if (replyIt == historyEdge()) {
isReply = false;
}
if (isThread) {
bool isFallingBack = !fallbackId.isEmpty();
QString replyEventId = isFallingBack ? fallbackId : QString();
if (isReply) {
isFallingBack = false;
replyEventId = EventHandler::id(replyIt->get());
}
// If we are not replying and there is no fallback ID it means a new thread
// is being created.
if (!isFallingBack && !isReply) {
isFallingBack = true;
replyEventId = threadRootId;
}
// clang-format off
QJsonObject json{
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, text},
{"format"_ls, "org.matrix.custom.html"_ls},
{"m.relates_to"_ls,
QJsonObject {
{"rel_type"_ls, "m.thread"_ls},
{"event_id"_ls, threadRootId},
{"is_falling_back"_ls, isFallingBack},
{"m.in_reply_to"_ls,
QJsonObject {
{"event_id"_ls, replyEventId}
}
}
}
},
{"formatted_body"_ls, html}
};
// clang-format on
postJson("m.room.message"_ls, json);
return;
}
if (isEdit) {
QJsonObject json{
{"type"_ls, "m.room.message"_ls},
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, "* %1"_ls.arg(text)},
{"format"_ls, "org.matrix.custom.html"_ls},
{"formatted_body"_ls, html},
{"m.new_content"_ls,
QJsonObject{{"body"_ls, text}, {"msgtype"_ls, msgTypeToString(type)}, {"format"_ls, "org.matrix.custom.html"_ls}, {"formatted_body"_ls, html}}},
{"m.relates_to"_ls, QJsonObject{{"rel_type"_ls, "m.replace"_ls}, {"event_id"_ls, relateToEventId}}}};
postJson("m.room.message"_ls, json);
return;
}
if (isReply) {
const auto &replyEvt = **replyIt;
// clang-format off
QJsonObject json{
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), EventHandler::plainBody(this, &replyEvt), text)},
{"format"_ls, "org.matrix.custom.html"_ls},
{"m.relates_to"_ls,
QJsonObject {
{"m.in_reply_to"_ls,
QJsonObject {
{"event_id"_ls, replyEventId}
}
}
}
},
{"formatted_body"_ls,
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), EventHandler::richBody(this, &replyEvt), html)
}
};
// clang-format on
postJson("m.room.message"_ls, json);
return;
}
Room::postHtmlMessage(text, html, type);
}
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
{
if (eventId.isEmpty() || reaction.isEmpty()) {

View File

@@ -691,6 +691,40 @@ public Q_SLOTS:
*/
void sendTypingNotification(bool isTyping);
/**
* @brief Send a message to the room.
*
* @param rawText the text as it was typed.
* @param cleanedText the text marked up as html.
* @param type the type of message being sent.
* @param replyEventId the id of the message being replied to if a reply.
* @param relateToEventId the id of the message being edited if an edit.
*/
void postMessage(const QString &rawText,
const QString &cleanedText,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString(),
const QString &fallbackId = QString());
/**
* @brief Send an html message to the room.
*
* @param text the text as it was typed.
* @param html the text marked up as html.
* @param type the type of message being sent.
* @param replyEventId the id of the message being replied to if a reply.
* @param relateToEventId the id of the message being edited if an edit.
*/
void postHtmlMessage(const QString &text,
const QString &html,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString(),
const QString &fallbackId = QString());
/**
* @brief Set the room avatar.
*/

View File

@@ -161,5 +161,3 @@ QUrl NeochatRoomMember::avatarUrl() const
return m_room->member(m_memberId).avatarUrl();
}
#include "moc_neochatroommember.cpp"

View File

@@ -248,9 +248,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
TextHandler textHandler;
textHandler.setData(text);
auto content = std::make_unique<Quotient::EventContent::TextContent>(textHandler.handleSendText(), u"text/html"_s);
EventRelation relatesTo = EventRelation::replyTo(replyEventId);
room->post<Quotient::RoomMessageEvent>(text, MessageEventType::Text, std::move(content), relatesTo);
room->postMessage(text, textHandler.handleSendText(), RoomMessageEvent::MsgType::Text, replyEventId, QString());
});
notification->setReplyAction(std::move(replyAction));
}

View File

@@ -168,8 +168,9 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
ownAnswers.insert(0, answerId);
}
const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
auto response = new PollResponseEvent(eventId, ownAnswers);
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
room->postEvent(response);
}
bool PollHandler::hasEnded() const

View File

@@ -20,7 +20,7 @@ QQC2.Menu {
margins: Kirigami.Units.smallSpacing
QQC2.MenuItem {
text: i18nc("@action:button", "Show QR Code")
text: i18nc("@action:button", "Show QR code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
@@ -37,7 +37,7 @@ QQC2.Menu {
}
}
QQC2.MenuItem {
text: i18n("Edit This Account")
text: i18n("Edit this account")
icon.name: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
connection: root.connection
@@ -46,7 +46,7 @@ QQC2.Menu {
})
}
QQC2.MenuItem {
text: i18n("Notification Settings")
text: i18n("Notification settings")
icon.name: "notifications"
onTriggered: {
NeoChatSettingsView.open('notifications');
@@ -60,7 +60,7 @@ QQC2.Menu {
}
}
QQC2.MenuItem {
text: i18n("Open Developer Tools")
text: i18n("Open developer tools")
icon.name: "tools"
visible: NeoChatConfig.developerTools
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
@@ -80,7 +80,7 @@ QQC2.Menu {
})
}
QQC2.MenuItem {
text: i18nc("@action:inmenu", "Verify This Device")
text: i18nc("@action:inmenu", "Verify this Device")
icon.name: "security-low"
onTriggered: root.connection.startSelfVerification()
enabled: Controller.csSupported

View File

@@ -23,11 +23,7 @@ SearchPage {
model: RoomManager.sortFilterRoomListModel
modelDelegate: RoomDelegate {
onClicked: {
root.chosen(currentRoom.id);
root.closeDialog();
}
onClicked: root.chosen(currentRoom.id)
connection: root.connection
openOnClick: false
}
}

View File

@@ -57,11 +57,6 @@ Loader {
*/
property string selectedText: ""
/**
* @brief The link the user has currently hovered.
*/
property string hoveredLink: ""
/**
* @brief The list of menu item actions that have sub-actions.
*

View File

@@ -50,8 +50,6 @@ SearchPage {
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
title: i18nc("@action:title", "Explore Rooms")
customPlaceholderText: publicRoomListModel.redirectedText
customPlaceholderIcon: "data-warning"
Component.onCompleted: focusSearch()
@@ -95,7 +93,6 @@ SearchPage {
activeFocusOnTab: false // We handle moving to this item via up/down arrows, otherwise the tab order is wacky
text: i18n("Enter a Room Manually")
visible: publicRoomListModel.redirectedText.length === 0
icon.name: "compass"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2

View File

@@ -20,15 +20,8 @@ Kirigami.ApplicationWindow {
property bool initialized: false
title: {
if (NeoChatConfig.windowTitleFocus) {
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
} else if (RoomManager.currentRoom) {
return RoomManager.currentRoom.displayName;
} else {
return Application.displayName;
}
}
title: NeoChatConfig.windowTitleFocus ? activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "") : "NeoChat"
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 15
@@ -92,7 +85,7 @@ Kirigami.ApplicationWindow {
target: RoomManager
function onCurrentRoomChanged() {
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
if (RoomManager.currentRoom && pageStack.depth <= 1 && initialized && Kirigami.Settings.isMobile) {
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
connection: root.connection
});
@@ -307,7 +300,7 @@ Kirigami.ApplicationWindow {
target: ShareHandler
function onTextChanged(): void {
if (root.connection && ShareHandler.text.length > 0) {
root.handleShare();
handleShare();
}
}
}

View File

@@ -63,12 +63,6 @@ DelegateContextMenu {
separator: true
},
DelegateContextMenu.RemoveMessageAction {},
Kirigami.Action {
text: i18nc("@action:inmenu", "Copy Link Address")
icon.name: "edit-copy"
visible: root.hoveredLink.length > 0
onTriggered: Clipboard.saveText(root.hoveredLink)
},
Kirigami.Action {
text: i18nc("@action:inmenu", "Copy Text")
icon.name: "edit-copy"

View File

@@ -25,7 +25,6 @@ Delegates.RoundedItemDelegate {
required property string subtitleText
required property string displayName
property bool openOnClick: true
property bool showConfigure: true
property bool collapsed: false
@@ -36,10 +35,8 @@ Delegates.RoundedItemDelegate {
Accessible.onPressAction: clicked()
onClicked: {
if (root.openOnClick) {
RoomManager.resolveResource(currentRoom.id);
pageStack.currentIndex = 1;
}
RoomManager.resolveResource(currentRoom.id);
pageStack.currentIndex = 1;
}
onPressAndHold: createRoomListContextMenu()

View File

@@ -19,7 +19,6 @@ Kirigami.OverlayDrawer {
required property NeoChatConnection connection
width: actualWidth
interactive: modal
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25

View File

@@ -325,7 +325,7 @@ Kirigami.Page {
QtObject {
id: _private
property int currentWidth: NeoChatConfig.collapsed ? collapsedSize : defaultWidth
readonly property int defaultWidth: Kirigami.Units.gridUnit * 15
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
readonly property int collapsedSize: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) + Kirigami.Units.largeSpacing * 2 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
}

View File

@@ -163,23 +163,6 @@ Kirigami.Page {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
Image {
visible: !NeoChatConfig.compactLayout && !NeoChatConfig.blur
source: "qrc:/qt/qml/org/kde/neochat/timeline/images/chat-page-background.svg"
anchors.fill: parent
fillMode: Image.Tile
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignTop
function isDarkColor(background: color): bool {
const temp = Qt.darker(background, 1);
const darkness = 1 - (0.299 * temp.r + 0.587 * temp.g + 0.114 * temp.b);
return temp.a > 0 && darkness >= 0.4;
}
opacity: isDarkColor(Kirigami.Theme.backgroundColor) ? 0.2 : 1
}
}
footer: Loader {
@@ -262,10 +245,9 @@ Kirigami.Page {
});
}
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) {
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, isThread) {
const contextMenu = messageDelegateContextMenu.createObject(root, {
selectedText: selectedText,
hoveredLink: hoveredLink,
author: author,
eventId: eventId,
messageComponentType: messageComponentType,

View File

@@ -80,17 +80,6 @@ Kirigami.ScrollablePage {
*/
property bool showSearchButton: true
/**
* @brief Message to be shown in a custom placeholder.
* The custom placeholder will be shown if the text is not empty
*/
property alias customPlaceholderText: customPlaceholder.text
/**
* @brief icon for the custom placeholder
*/
property string customPlaceholderIcon: ""
/**
* @brief Force the search field to be focussed.
*/
@@ -178,25 +167,18 @@ Kirigami.ScrollablePage {
Kirigami.PlaceholderMessage {
id: noSearchMessage
anchors.centerIn: parent
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
visible: searchField.text.length === 0 && listView.count === 0
}
Kirigami.PlaceholderMessage {
id: noResultMessage
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
}
Kirigami.PlaceholderMessage {
id: customPlaceholder
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && text.length > 0
icon.name: root.customPlaceholderIcon
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching
}
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching
}
Keys.onUpPressed: {

View File

@@ -131,7 +131,7 @@ QQC2.Control {
text: i18nc("@button View all one-on-one chats with your friends.", "Friends")
contentItem: Kirigami.Icon {
source: "system-users-symbolic"
source: "system-users"
QQC2.Label {
id: directChatNotificationCountLabel
@@ -200,6 +200,7 @@ QQC2.Control {
activeFocusOnTab: true
onSelected: {
RoomManager.resolveResource(spaceDelegate.roomId);
RoomManager.currentSpace = spaceDelegate.roomId;
}
checked: RoomManager.currentSpace === roomId

View File

@@ -27,7 +27,7 @@ RowLayout {
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 3 : -1
Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 2 : -1
onVisibleChanged: {
if (!visible) {

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