Compare commits
47 Commits
work/tobia
...
v24.01.75
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
819d88e18c | ||
|
|
dbbad2cf13 | ||
|
|
08b84c6592 | ||
|
|
b8abf0540d | ||
|
|
7f9e709559 | ||
|
|
c4f6abee9d | ||
|
|
1a4947b98a | ||
|
|
6ba2b715c3 | ||
|
|
e9e1e223f7 | ||
|
|
006da1fb16 | ||
|
|
3aff1795c8 | ||
|
|
576b1f928f | ||
|
|
fc3ab50701 | ||
|
|
e7fa3ad524 | ||
|
|
5adffddbd8 | ||
|
|
1d95d5aa15 | ||
|
|
dabd6291a5 | ||
|
|
0e55c3b38f | ||
|
|
ff4cf86ea5 | ||
|
|
f4d5ccbf12 | ||
|
|
192601d358 | ||
|
|
d7c432119e | ||
|
|
eddb2b73c2 | ||
|
|
0c60bfdb83 | ||
|
|
2bfb2fa1f9 | ||
|
|
d1aac971bf | ||
|
|
284cadf305 | ||
|
|
4697b7fcf1 | ||
|
|
9f356912c9 | ||
|
|
1cf891f845 | ||
|
|
52e2d636b9 | ||
|
|
dc6b539ddf | ||
|
|
e3cf85aa8c | ||
|
|
2065eb6684 | ||
|
|
9cac2a8abd | ||
|
|
feb87e6f70 | ||
|
|
a0057b8a49 | ||
|
|
dbb0269354 | ||
|
|
7fdb617b33 | ||
|
|
d798b0dec9 | ||
|
|
95cf23eb5b | ||
|
|
7e3db20229 | ||
|
|
12689babfb | ||
|
|
211407da44 | ||
|
|
400a84e48d | ||
|
|
7a45640e5e | ||
|
|
8af20885ab |
@@ -16,6 +16,7 @@ Dependencies:
|
||||
'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'
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "01")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "75")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -37,6 +37,7 @@ include(ECMAddAppIcon)
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -119,6 +120,7 @@ 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)
|
||||
|
||||
find_package(KQuickImageEditor COMPONENTS)
|
||||
set_package_properties(KQuickImageEditor PROPERTIES
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
android:versionName="${versionName}"
|
||||
android:versionCode="${versionCode}"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<application android:name="org.qtproject.qt.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop"
|
||||
|
||||
@@ -12,7 +12,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ android {
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qt5AndroidDir - holds the path to qt android files
|
||||
* - qtAndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
@@ -44,17 +44,20 @@ android {
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
// Extract native libraries from the APK
|
||||
packagingOptions.jniLibs.useLegacyPackaging true
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
||||
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qtAndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
|
||||
@@ -40,3 +40,9 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatbarcachetest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
chatdocumenthandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatdocumenthandlertest
|
||||
)
|
||||
|
||||
36
autotests/chatdocumenthandlertest.cpp
Normal file
36
autotests/chatdocumenthandlertest.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
class ChatDocumentHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
ChatDocumentHandler emptyHandler;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void nullComplete();
|
||||
};
|
||||
|
||||
void ChatDocumentHandlerTest::initTestCase()
|
||||
{
|
||||
// HACK: this is to stop KStatusNotifierItem SEGFAULTING on cleanup.
|
||||
NeoChatConfig::self()->setSystemTray(false);
|
||||
}
|
||||
|
||||
void ChatDocumentHandlerTest::nullComplete()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "complete called with m_document set to nullptr.");
|
||||
emptyHandler.complete(0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(ChatDocumentHandlerTest)
|
||||
#include "chatdocumenthandlertest.moc"
|
||||
@@ -40,6 +40,7 @@ private Q_SLOTS:
|
||||
void stripDisallowedTags();
|
||||
void stripDisallowedAttributes();
|
||||
void emptyCodeTags();
|
||||
void formatBlockQuote();
|
||||
|
||||
void sendSimpleStringCase();
|
||||
void sendSingleParaMarkup();
|
||||
@@ -146,6 +147,16 @@ void TextHandlerTest::emptyCodeTags()
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::formatBlockQuote()
|
||||
{
|
||||
auto input = QStringLiteral("<blockquote>\n<p>Lorem Ispum</p>\n</blockquote>");
|
||||
auto expectedOutput = QStringLiteral("<blockquote><table><tr><td>\u201CLorem Ispum\u201D</td></tr></table></blockquote>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(input);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), expectedOutput);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
|
||||
@@ -458,8 +469,9 @@ void TextHandlerTest::receiveRichEdited_data()
|
||||
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
|
||||
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
|
||||
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
|
||||
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
|
||||
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||
QTest::newRow("blockquote")
|
||||
<< QStringLiteral("<blockquote>Edited</blockquote>")
|
||||
<< QStringLiteral("<blockquote><table><tr><td>\u201CEdited\u201D</td></tr></table></blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited()
|
||||
|
||||
@@ -343,6 +343,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.08.3" date="2023-11-09"/>
|
||||
<release version="23.08.2" date="2023-10-12"/>
|
||||
<release version="23.08.0" date="2023-08-24">
|
||||
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
|
||||
|
||||
442
po/ar/neochat.po
442
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
425
po/az/neochat.po
425
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
442
po/ca/neochat.po
442
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
423
po/cs/neochat.po
423
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
417
po/da/neochat.po
417
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
446
po/de/neochat.po
446
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
446
po/el/neochat.po
446
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
433
po/eo/neochat.po
433
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
449
po/es/neochat.po
449
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
441
po/eu/neochat.po
441
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/fi/neochat.po
680
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
452
po/fr/neochat.po
452
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
446
po/hu/neochat.po
446
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
439
po/ia/neochat.po
439
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
442
po/id/neochat.po
442
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
424
po/ie/neochat.po
424
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
453
po/it/neochat.po
453
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
407
po/ja/neochat.po
407
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
443
po/ka/neochat.po
443
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
453
po/ko/neochat.po
453
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
407
po/lt/neochat.po
407
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
443
po/nl/neochat.po
443
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
436
po/nn/neochat.po
436
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
424
po/pa/neochat.po
424
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
440
po/pl/neochat.po
440
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
442
po/pt/neochat.po
442
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
446
po/ru/neochat.po
446
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
422
po/sk/neochat.po
422
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
439
po/sl/neochat.po
439
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
452
po/sv/neochat.po
452
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
452
po/ta/neochat.po
452
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
464
po/tr/neochat.po
464
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
444
po/uk/neochat.po
444
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -146,13 +146,14 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/main.qml
|
||||
qml/AccountMenu.qml
|
||||
qml/ExploreComponent.qml
|
||||
qml/ExploreComponentMobile.qml
|
||||
qml/ContextMenu.qml
|
||||
qml/CollapsedRoomDelegate.qml
|
||||
qml/RoomDelegate.qml
|
||||
qml/RoomListPage.qml
|
||||
qml/SpaceListContextMenu.qml
|
||||
qml/UserInfo.qml
|
||||
qml/LoadingPage.qml
|
||||
qml/UserInfoDesktop.qml
|
||||
qml/RoomPage.qml
|
||||
qml/RoomWindow.qml
|
||||
qml/JoinRoomPage.qml
|
||||
@@ -286,6 +287,9 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/SpaceHierarchyDelegate.qml
|
||||
qml/RemoveChildDialog.qml
|
||||
qml/SelectParentDialog.qml
|
||||
qml/Security.qml
|
||||
qml/QrCodeMaximizeComponent.qml
|
||||
qml/SelectSpacesDialog.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
@@ -307,6 +311,13 @@ ecm_qt_declare_logging_category(neochat
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
HEADER "chatdocumenthandler_logging.h"
|
||||
IDENTIFIER "ChatDocumentHandling"
|
||||
CATEGORY_NAME "org.kde.neochat.chatdocumenthandler"
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
main.cpp
|
||||
)
|
||||
@@ -453,6 +464,7 @@ if(ANDROID)
|
||||
"gps"
|
||||
"system-users-symbolic"
|
||||
)
|
||||
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR/android})
|
||||
else()
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
|
||||
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
|
||||
|
||||
@@ -75,7 +75,7 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
|
||||
}
|
||||
handledText = handledText.replace(mention.cursor.anchor(),
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
mentions->clear();
|
||||
|
||||
|
||||
@@ -173,3 +173,5 @@ void ChatBarCache::setSavedText(const QString &savedText)
|
||||
{
|
||||
m_savedText = savedText;
|
||||
}
|
||||
|
||||
#include "moc_chatbarcache.cpp"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
|
||||
#include "chatdocumenthandler_logging.h"
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
public:
|
||||
@@ -98,7 +100,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
, m_document(nullptr)
|
||||
, m_cursorPosition(-1)
|
||||
, m_highlighter(new SyntaxHighlighter(this))
|
||||
, m_completionModel(new CompletionModel())
|
||||
, m_completionModel(new CompletionModel(this))
|
||||
{
|
||||
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
|
||||
m_completionModel->setRoom(m_room);
|
||||
@@ -148,20 +150,6 @@ int ChatDocumentHandler::completionStartIndex() const
|
||||
return start;
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::isEdit() const
|
||||
{
|
||||
return m_isEdit;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setIsEdit(bool edit)
|
||||
{
|
||||
if (edit == m_isEdit) {
|
||||
return;
|
||||
}
|
||||
m_isEdit = edit;
|
||||
Q_EMIT isEditChanged();
|
||||
}
|
||||
|
||||
QQuickTextDocument *ChatDocumentHandler::document() const
|
||||
{
|
||||
return m_document;
|
||||
@@ -227,6 +215,15 @@ void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
|
||||
|
||||
void ChatDocumentHandler::complete(int index)
|
||||
{
|
||||
if (m_document == nullptr) {
|
||||
qCWarning(ChatDocumentHandling) << "complete called with m_document set to nullptr.";
|
||||
return;
|
||||
}
|
||||
if (m_completionModel->autoCompletionType() == CompletionModel::None) {
|
||||
qCWarning(ChatDocumentHandling) << "complete called with m_completionModel->autoCompletionType() == CompletionModel::None.";
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -310,15 +307,17 @@ void ChatDocumentHandler::setSelectionEnd(int position)
|
||||
|
||||
QString ChatDocumentHandler::getText() const
|
||||
{
|
||||
if (!m_room) {
|
||||
return QString();
|
||||
if (!m_chatBarCache) {
|
||||
qCWarning(ChatDocumentHandling) << "getText called with m_chatBarCache set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return m_chatBarCache->text();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
{
|
||||
if (!m_room) {
|
||||
if (!m_chatBarCache) {
|
||||
qCWarning(ChatDocumentHandling) << "pushMention called with m_chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
m_chatBarCache->mentions()->push_back(mention);
|
||||
|
||||
@@ -62,14 +62,6 @@ class ChatDocumentHandler : public QObject
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief Is the instance being used to handle an edit message.
|
||||
*
|
||||
* This is needed to ensure that the text and mentions are saved and retrieved
|
||||
* from the correct parameters in the assigned room.
|
||||
*/
|
||||
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
|
||||
|
||||
/**
|
||||
* @brief The QQuickTextDocument that is being handled.
|
||||
*/
|
||||
@@ -96,7 +88,7 @@ class ChatDocumentHandler : public QObject
|
||||
* This is typically provided to a qml component to visualise the current
|
||||
* completion results.
|
||||
*/
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The current room that the the text document is being handled for.
|
||||
@@ -121,9 +113,6 @@ class ChatDocumentHandler : public QObject
|
||||
public:
|
||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isEdit() const;
|
||||
void setIsEdit(bool edit);
|
||||
|
||||
[[nodiscard]] QQuickTextDocument *document() const;
|
||||
void setDocument(QQuickTextDocument *document);
|
||||
|
||||
@@ -144,7 +133,6 @@ public:
|
||||
|
||||
Q_INVOKABLE void complete(int index);
|
||||
|
||||
void updateCompletions();
|
||||
CompletionModel *completionModel() const;
|
||||
|
||||
[[nodiscard]] QColor mentionColor() const;
|
||||
@@ -154,12 +142,10 @@ public:
|
||||
void setErrorColor(const QColor &color);
|
||||
|
||||
Q_SIGNALS:
|
||||
void isEditChanged();
|
||||
void documentChanged();
|
||||
void cursorPositionChanged();
|
||||
void roomChanged();
|
||||
void chatBarCacheChanged();
|
||||
void completionModelChanged();
|
||||
void selectionStartChanged();
|
||||
void selectionEndChanged();
|
||||
void errorColorChanged();
|
||||
@@ -168,8 +154,6 @@ Q_SIGNALS:
|
||||
private:
|
||||
int completionStartIndex() const;
|
||||
|
||||
bool m_isEdit = false;
|
||||
|
||||
QPointer<QQuickTextDocument> m_document;
|
||||
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
|
||||
@@ -60,7 +60,8 @@ Controller::Controller(QObject *parent)
|
||||
invokeLogin();
|
||||
});
|
||||
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
delete m_trayIcon;
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -98,10 +99,6 @@ Controller::Controller(QObject *parent)
|
||||
}
|
||||
oldAccountCount = m_accountRegistry.size();
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
m_pushRuleModel = new PushRuleModel;
|
||||
});
|
||||
}
|
||||
|
||||
Controller &Controller::instance()
|
||||
@@ -170,6 +167,8 @@ void Controller::invokeLogin()
|
||||
QString id = NeoChatConfig::self()->activeConnection();
|
||||
for (const auto &accountId : accounts) {
|
||||
AccountSettings account{accountId};
|
||||
m_accountsLoading += accountId;
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (id.isEmpty()) {
|
||||
// handle case where the account config is empty
|
||||
id = accountId;
|
||||
@@ -189,6 +188,8 @@ void Controller::invokeLogin()
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (connection->userId() == id) {
|
||||
setActiveConnection(connection);
|
||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
|
||||
@@ -349,12 +350,6 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionIndexChanged();
|
||||
}
|
||||
|
||||
PushRuleModel *Controller::pushRuleModel() const
|
||||
{
|
||||
return m_pushRuleModel;
|
||||
}
|
||||
|
||||
void Controller::saveWindowGeometry()
|
||||
@@ -415,14 +410,6 @@ void Controller::setApplicationProxy()
|
||||
QNetworkProxy::setApplicationProxy(proxy);
|
||||
}
|
||||
|
||||
int Controller::activeConnectionIndex() const
|
||||
{
|
||||
auto result = std::find_if(m_accountRegistry.accounts().begin(), m_accountRegistry.accounts().end(), [this](const auto &it) {
|
||||
return it == m_connection;
|
||||
});
|
||||
return result - m_accountRegistry.accounts().begin();
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
{
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "models/pushrulemodel.h"
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickItem>
|
||||
@@ -47,16 +46,6 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(NeoChatConnection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The PushRuleModel that has the active connection's push rules.
|
||||
*/
|
||||
Q_PROPERTY(PushRuleModel *pushRuleModel READ pushRuleModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The row number in the accounts directory of the active connection.
|
||||
*/
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
|
||||
*/
|
||||
@@ -74,6 +63,8 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||
|
||||
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
@@ -95,8 +86,6 @@ public:
|
||||
void setActiveConnection(NeoChatConnection *connection);
|
||||
[[nodiscard]] NeoChatConnection *activeConnection() const;
|
||||
|
||||
[[nodiscard]] PushRuleModel *pushRuleModel() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
@@ -107,8 +96,6 @@ public:
|
||||
*/
|
||||
void dropConnection(NeoChatConnection *c);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
@@ -154,8 +141,8 @@ private:
|
||||
bool m_isOnline = true;
|
||||
QMap<Quotient::Room *, int> m_notificationCounts;
|
||||
|
||||
QPointer<PushRuleModel> m_pushRuleModel;
|
||||
Quotient::AccountRegistry m_accountRegistry;
|
||||
QStringList m_accountsLoading;
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
@@ -178,7 +165,7 @@ Q_SIGNALS:
|
||||
void passwordStatus(Controller::PasswordStatus status);
|
||||
void userConsentRequired(QUrl url);
|
||||
void isOnlineChanged(bool isOnline);
|
||||
void activeConnectionIndexChanged();
|
||||
void accountsLoadingChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void saveWindowGeometry();
|
||||
|
||||
@@ -100,7 +100,8 @@ void LoginHelper::init()
|
||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||
});
|
||||
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, []() {
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
||||
Q_EMIT loaded();
|
||||
Q_EMIT Controller::instance().initiated();
|
||||
});
|
||||
}
|
||||
|
||||
14
src/login.h
14
src/login.h
@@ -79,7 +79,17 @@ class LoginHelper : public QObject
|
||||
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
|
||||
|
||||
public:
|
||||
explicit LoginHelper(QObject *parent = nullptr);
|
||||
static LoginHelper &instance()
|
||||
{
|
||||
static LoginHelper _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static LoginHelper *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||
return &instance();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void init();
|
||||
|
||||
@@ -125,6 +135,7 @@ Q_SIGNALS:
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
void isInvalidPasswordChanged();
|
||||
void loaded();
|
||||
|
||||
private:
|
||||
void setHomeserverReachable(bool reachable);
|
||||
@@ -141,4 +152,5 @@ private:
|
||||
bool m_isLoggingIn = false;
|
||||
bool m_isLoggedIn = false;
|
||||
bool m_invalidPassword = false;
|
||||
explicit LoginHelper(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
14
src/main.cpp
14
src/main.cpp
@@ -131,7 +131,11 @@ int main(int argc, char *argv[])
|
||||
i18n("Matrix client"),
|
||||
KAboutLicense::GPL_V3,
|
||||
i18n("© 2018-2020 Black Hat, 2020-2023 KDE Community"));
|
||||
about.addAuthor(i18n("Carl Schwan"), i18n("Maintainer"), QStringLiteral("carl@carlschwan.eu"), QStringLiteral("https://carlschwan.eu"));
|
||||
about.addAuthor(i18n("Carl Schwan"),
|
||||
i18n("Maintainer"),
|
||||
QStringLiteral("carl@carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu/avatar.png"));
|
||||
about.addAuthor(i18n("Tobias Fella"), i18n("Maintainer"), QStringLiteral("tobias.fella@kde.org"), QStringLiteral("https://tobiasfella.de"));
|
||||
about.addAuthor(i18n("James Graham"), i18n("Maintainer"), QStringLiteral("james.h.graham@protonmail.com"));
|
||||
about.addCredit(i18n("Black Hat"), i18n("Original author of Spectral"), QStringLiteral("bhat@encom.eu.org"));
|
||||
@@ -140,7 +144,7 @@ int main(int argc, char *argv[])
|
||||
about.setOrganizationDomain("kde.org");
|
||||
|
||||
about.addComponent(QStringLiteral("libQuotient"),
|
||||
i18n("A Qt5 library to write cross-platform clients for Matrix"),
|
||||
i18n("A Qt library to write cross-platform clients for Matrix"),
|
||||
i18nc("<version number> (built against <possibly different version number>)",
|
||||
"%1 (built against %2)",
|
||||
Quotient::versionString(),
|
||||
@@ -219,7 +223,7 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
}
|
||||
|
||||
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
||||
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
|
||||
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
||||
|
||||
engine.load(QUrl(QStringLiteral("qrc:/org/kde/neochat/qml/main.qml")));
|
||||
@@ -232,8 +236,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
Runner runner;
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, &runner, QDBusConnection::ExportScriptableContents);
|
||||
auto runner = Runner::create(&engine, &engine);
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, runner, QDBusConnection::ExportScriptableContents);
|
||||
#endif
|
||||
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)
|
||||
: mediaId(std::move(id))
|
||||
, requestedSize(size)
|
||||
, localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
|
||||
@@ -25,6 +27,7 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
mediaId,
|
||||
QString::number(requestedSize.width()),
|
||||
QString::number(requestedSize.height())))
|
||||
, m_connection(connection)
|
||||
, errorStr("Image request hasn't started"_ls)
|
||||
{
|
||||
if (requestedSize.isEmpty()) {
|
||||
@@ -51,24 +54,24 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
qWarning() << "Current connection is null";
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute a request on the main thread asynchronously
|
||||
moveToThread(Controller::instance().activeConnection()->thread());
|
||||
moveToThread(m_connection->thread());
|
||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ThumbnailResponse::startRequest()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
Q_ASSERT(QThread::currentThread() == Controller::instance().activeConnection()->thread());
|
||||
job = Controller::instance().activeConnection()->getThumbnail(mediaId, requestedSize);
|
||||
Q_ASSERT(QThread::currentThread() == m_connection->thread());
|
||||
job = m_connection->getThumbnail(mediaId, requestedSize);
|
||||
// Connect to any possible outcome including abandonment
|
||||
// to make sure the QML thread is not left stuck forever.
|
||||
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
|
||||
@@ -118,7 +121,7 @@ QString ThumbnailResponse::errorString() const
|
||||
|
||||
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
return new ThumbnailResponse(id, requestedSize);
|
||||
return new ThumbnailResponse(id, requestedSize, m_connection);
|
||||
}
|
||||
|
||||
#include "moc_matriximageprovider.cpp"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <QReadWriteLock>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class ThumbnailResponse
|
||||
*
|
||||
@@ -21,7 +23,7 @@ class ThumbnailResponse : public QQuickImageResponse
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ThumbnailResponse(QString mediaId, QSize requestedSize);
|
||||
explicit ThumbnailResponse(QString mediaId, QSize requestedSize, NeoChatConnection *m_connection);
|
||||
~ThumbnailResponse() override = default;
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -33,6 +35,7 @@ private:
|
||||
QSize requestedSize;
|
||||
const QString localFile;
|
||||
Quotient::MediaThumbnailJob *job = nullptr;
|
||||
NeoChatConnection *m_connection;
|
||||
|
||||
QImage image;
|
||||
QString errorStr;
|
||||
@@ -51,11 +54,27 @@ private:
|
||||
*/
|
||||
class MatrixImageProvider : public QQuickAsyncImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection MEMBER m_connection)
|
||||
public:
|
||||
static MatrixImageProvider *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
static MatrixImageProvider instance;
|
||||
engine->setObjectOwnership(&instance, QQmlEngine::CppOwnership);
|
||||
return &instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a job to provide the image with the given ID.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider::requestImageResponse
|
||||
*/
|
||||
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||
|
||||
private:
|
||||
NeoChatConnection *m_connection = nullptr;
|
||||
MatrixImageProvider() = default;
|
||||
};
|
||||
|
||||
@@ -261,7 +261,7 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
auto connection = room->connection();
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
||||
if (parts.length() >= 2) {
|
||||
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
|
||||
@@ -285,7 +285,7 @@ QList<ActionsModel::Action> actions{
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
if (Controller::instance().activeConnection()->room(text) || Controller::instance().activeConnection()->roomByAlias(text)) {
|
||||
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -15,13 +15,28 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
void CustomEmojiModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (connection == m_connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
fetchEmojis();
|
||||
}
|
||||
|
||||
NeoChatConnection *CustomEmojiModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void CustomEmojiModel::fetchEmojis()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -53,10 +68,10 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||
auto job = m_connection->uploadFile(location.toLocalFile());
|
||||
|
||||
connect(job, &BaseJob::success, this, [name, location, job] {
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
connect(job, &BaseJob::success, this, [name, location, job, this] {
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"_ls].toObject();
|
||||
|
||||
@@ -78,7 +93,7 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
});
|
||||
|
||||
json["images"_ls] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,7 +101,7 @@ void CustomEmojiModel::removeEmoji(const QString &name)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
Q_ASSERT(data);
|
||||
auto json = data->contentJson();
|
||||
const QString _name = name.mid(1).chopped(1);
|
||||
@@ -109,18 +124,18 @@ void CustomEmojiModel::removeEmoji(const QString &name)
|
||||
emojiData.remove(_name);
|
||||
json["emoticons"_ls] = emojiData;
|
||||
}
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
}
|
||||
|
||||
CustomEmojiModel::CustomEmojiModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
CustomEmojiModel::fetchEmojis();
|
||||
connect(Controller::instance().activeConnection(), &Connection::accountDataChanged, this, [this](const QString &id) {
|
||||
connect(m_connection, &Connection::accountDataChanged, this, [this](const QString &id) {
|
||||
if (id != QStringLiteral("im.ponies.user_emotes")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <memory>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
struct CustomEmoji {
|
||||
QString name; // with :semicolons:
|
||||
QString url; // mxc://
|
||||
@@ -31,6 +33,8 @@ class CustomEmojiModel : public QAbstractListModel
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
@@ -98,9 +102,16 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void removeEmoji(const QString &name);
|
||||
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
NeoChatConnection *connection() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
explicit CustomEmojiModel(QObject *parent = nullptr);
|
||||
QList<CustomEmoji> m_emojis;
|
||||
NeoChatConnection *m_connection = nullptr;
|
||||
|
||||
void fetchEmojis();
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ DevicesModel::DevicesModel(QObject *parent)
|
||||
|
||||
void DevicesModel::fetchDevices()
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
auto job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
|
||||
if (m_connection) {
|
||||
auto job = m_connection->callApi<GetDevicesJob>();
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
beginResetModel();
|
||||
m_devices = job->devices();
|
||||
@@ -102,7 +102,7 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
||||
for (index = 0; m_devices[index].deviceId != deviceId; index++)
|
||||
;
|
||||
|
||||
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
||||
auto job = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
||||
|
||||
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
||||
auto onSuccess = [this, index]() {
|
||||
@@ -117,9 +117,9 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = password;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, Controller::instance().activeConnection()->user()->id()}};
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
auto *innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||
} else {
|
||||
onSuccess();
|
||||
@@ -132,7 +132,7 @@ void DevicesModel::setName(const QString &deviceId, const QString &name)
|
||||
int index;
|
||||
for (index = 0; m_devices[index].deviceId != deviceId; index++);
|
||||
|
||||
auto job = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
|
||||
auto job = m_connection->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
|
||||
QString oldName = m_devices[index].displayName;
|
||||
beginResetModel();
|
||||
m_devices[index].displayName = name;
|
||||
@@ -160,7 +160,7 @@ void DevicesModel::setConnection(Connection *connection)
|
||||
|
||||
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &userId, const QString &deviceId) {
|
||||
Q_UNUSED(deviceId);
|
||||
if (userId == Controller::instance().activeConnection()->userId()) {
|
||||
if (userId == m_connection->userId()) {
|
||||
fetchDevices();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 "keywordnotificationrulemodel.h"
|
||||
#include "controller.h"
|
||||
#include "notificationsmanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/converters.h>
|
||||
#include <Quotient/csapi/definitions/push_ruleset.h>
|
||||
#include <Quotient/csapi/pushrules.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &KeywordNotificationRuleModel::updateNotificationRules);
|
||||
updateNotificationRules("m.push_rules");
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
|
||||
const QList<Quotient::PushRule> contentRules = ruleData.content;
|
||||
|
||||
beginResetModel();
|
||||
m_notificationRules.clear();
|
||||
for (const auto &i : contentRules) {
|
||||
if (!m_notificationRules.contains(i.ruleId) && i.ruleId[0] != '.') {
|
||||
m_notificationRules.append(i.ruleId);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant KeywordNotificationRuleModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= m_notificationRules.count()) {
|
||||
qDebug() << "KeywordNotificationRuleModel, something's wrong: index.row() >= m_notificationRules.count()";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
return m_notificationRules.at(index.row());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int KeywordNotificationRuleModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return m_notificationRules.count();
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::addKeyword(const QString &keyword)
|
||||
{
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().initializeKeywordNotificationAction();
|
||||
}
|
||||
|
||||
const QList<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
|
||||
|
||||
auto job = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QList<Quotient::PushCondition>(), keyword);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, keyword]() {
|
||||
beginInsertRows(QModelIndex(), m_notificationRules.count(), m_notificationRules.count());
|
||||
m_notificationRules.append(keyword);
|
||||
endInsertRows();
|
||||
});
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::removeKeywordAtIndex(int index)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", "content", m_notificationRules[index]);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, index]() {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_notificationRules.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().deactivateKeywordNotificationAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> KeywordNotificationRuleModel::roleNames() const
|
||||
{
|
||||
return {{NameRole, QByteArrayLiteral("name")}};
|
||||
}
|
||||
|
||||
#include "moc_keywordnotificationrulemodel.cpp"
|
||||
@@ -1,66 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 <Quotient/csapi/definitions/push_rule.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
/**
|
||||
* @class KeywordNotificationRuleModel
|
||||
*
|
||||
* This class defines the model for managing notification push rule keywords.
|
||||
*/
|
||||
class KeywordNotificationRuleModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
|
||||
};
|
||||
|
||||
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Add a new keyword to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addKeyword(const QString &keyword);
|
||||
|
||||
/**
|
||||
* @brief Remove a keyword from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
||||
|
||||
private Q_SLOTS:
|
||||
void controllerConnectionChanged();
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
private:
|
||||
QList<QString> m_notificationRules;
|
||||
};
|
||||
@@ -66,19 +66,6 @@ PushRuleModel::PushRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
m_defaultKeywordAction = static_cast<PushNotificationAction::Action>(NeoChatConfig::self()->keywordPushRuleDefault());
|
||||
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &PushRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
void PushRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
|
||||
updateNotificationRules(QStringLiteral("m.push_rules"));
|
||||
}
|
||||
}
|
||||
|
||||
void PushRuleModel::updateNotificationRules(const QString &type)
|
||||
@@ -87,7 +74,7 @@ void PushRuleModel::updateNotificationRules(const QString &type)
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson(QStringLiteral("m.push_rules"));
|
||||
const QJsonObject ruleDataJson = m_connection->accountDataJson(QStringLiteral("m.push_rules"));
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson[QStringLiteral("global")].toObject());
|
||||
|
||||
beginResetModel();
|
||||
@@ -156,8 +143,7 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
|
||||
*
|
||||
* Rooms that the user hasn't joined shouldn't have a rule.
|
||||
*/
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (connection->room(ruleId) != nullptr) {
|
||||
if (m_connection->room(ruleId) != nullptr) {
|
||||
return PushNotificationSection::Undefined;
|
||||
}
|
||||
/**
|
||||
@@ -171,7 +157,7 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
|
||||
if (!testUserId.startsWith(u'@')) {
|
||||
testUserId.prepend(u'@');
|
||||
}
|
||||
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && connection->user(testUserId) != nullptr) {
|
||||
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && m_connection->user(testUserId) != nullptr) {
|
||||
return PushNotificationSection::Undefined;
|
||||
}
|
||||
// If the rule has push conditions and one is a room ID it is a room only keyword.
|
||||
@@ -325,14 +311,14 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||
pushConditions.append(keywordCondition);
|
||||
}
|
||||
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||
PushNotificationKind::kindString(kind),
|
||||
keyword,
|
||||
actions,
|
||||
QString(),
|
||||
QString(),
|
||||
pushConditions,
|
||||
roomId.isEmpty() ? keyword : QString());
|
||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||
PushNotificationKind::kindString(kind),
|
||||
keyword,
|
||||
actions,
|
||||
QString(),
|
||||
QString(),
|
||||
pushConditions,
|
||||
roomId.isEmpty() ? keyword : QString());
|
||||
connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() {
|
||||
qWarning() << QLatin1String("Unable to set push rule for keyword %1: ").arg(keyword) << job->errorString();
|
||||
});
|
||||
@@ -351,7 +337,7 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
}
|
||||
|
||||
auto kind = PushNotificationKind::kindString(m_rules[index].kind);
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||
});
|
||||
@@ -359,10 +345,10 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
|
||||
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::IsPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId);
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled]() {
|
||||
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId);
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled, this]() {
|
||||
if (job->enabled() != enabled) {
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId, enabled);
|
||||
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId, enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -376,7 +362,7 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
|
||||
actions = actionToVariant(action);
|
||||
}
|
||||
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||
}
|
||||
|
||||
PushNotificationAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
|
||||
@@ -453,4 +439,23 @@ QList<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action ac
|
||||
return actions;
|
||||
}
|
||||
|
||||
NeoChatConnection *PushRuleModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void PushRuleModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (connection == m_connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
|
||||
if (m_connection) {
|
||||
connect(m_connection, &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
|
||||
updateNotificationRules(QStringLiteral("m.push_rules"));
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_pushrulemodel.cpp"
|
||||
|
||||
@@ -157,6 +157,8 @@ class PushRuleModel : public QAbstractListModel
|
||||
*/
|
||||
Q_PROPERTY(bool globalNotificationsSet READ globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
struct Rule {
|
||||
QString id;
|
||||
@@ -225,18 +227,22 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void removeKeyword(const QString &keyword);
|
||||
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
NeoChatConnection *connection() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void defaultStateChanged();
|
||||
void globalNotificationsEnabledChanged();
|
||||
void globalNotificationsSetChanged();
|
||||
void connectionChanged();
|
||||
|
||||
private Q_SLOTS:
|
||||
void controllerConnectionChanged();
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
private:
|
||||
PushNotificationAction::Action m_defaultKeywordAction;
|
||||
QList<Rule> m_rules;
|
||||
NeoChatConnection *m_connection;
|
||||
|
||||
void setRules(QList<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ ServerListModel::ServerListModel(QObject *parent)
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
const KConfigGroup serverGroup = stateConfig->group(QStringLiteral("Servers"));
|
||||
|
||||
QString domain = Controller::instance().activeConnection()->domain();
|
||||
QString domain = m_connection->domain();
|
||||
|
||||
// Add the user's homeserver
|
||||
m_servers.append(Server{
|
||||
@@ -100,7 +100,7 @@ void ServerListModel::checkServer(const QString &url)
|
||||
m_checkServerJob->abandon();
|
||||
}
|
||||
|
||||
m_checkServerJob = Controller::instance().activeConnection()->callApi<Quotient::QueryPublicRoomsJob>(url, 1);
|
||||
m_checkServerJob = m_connection->callApi<Quotient::QueryPublicRoomsJob>(url, 1);
|
||||
connect(m_checkServerJob, &Quotient::BaseJob::success, this, [this, url] {
|
||||
Q_EMIT serverCheckComplete(url, true);
|
||||
});
|
||||
@@ -153,4 +153,18 @@ QHash<int, QByteArray> ServerListModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatConnection *ServerListModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void ServerListModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
#include "moc_serverlistmodel.cpp"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QUrl>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class ServerListModel
|
||||
*
|
||||
@@ -27,6 +29,8 @@ class ServerListModel : public QAbstractListModel
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Define the data required to represent a server.
|
||||
@@ -96,10 +100,15 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void removeServerAtIndex(int index);
|
||||
|
||||
NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
|
||||
Q_SIGNALS:
|
||||
void serverCheckComplete(QString url, bool valid);
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
QList<Server> m_servers;
|
||||
QPointer<Quotient::QueryPublicRoomsJob> m_checkServerJob = nullptr;
|
||||
NeoChatConnection *m_connection = nullptr;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
m_rootItem = new SpaceTreeItem();
|
||||
m_rootItem = new SpaceTreeItem(nullptr);
|
||||
}
|
||||
|
||||
SpaceChildrenModel::~SpaceChildrenModel()
|
||||
@@ -71,7 +71,8 @@ void SpaceChildrenModel::refreshModel()
|
||||
delete m_rootItem;
|
||||
m_loading = true;
|
||||
Q_EMIT loadingChanged();
|
||||
m_rootItem = new SpaceTreeItem(nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
|
||||
m_rootItem =
|
||||
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()), nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
|
||||
endResetModel();
|
||||
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), Quotient::none, Quotient::none, 1);
|
||||
m_currentJobs.append(job);
|
||||
@@ -112,7 +113,8 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
|
||||
}
|
||||
}
|
||||
parentItem->insertChild(insertRow,
|
||||
new SpaceTreeItem(parentItem,
|
||||
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
|
||||
parentItem,
|
||||
children[i].roomId,
|
||||
children[i].name,
|
||||
children[i].canonicalAlias,
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
SpaceTreeItem::SpaceTreeItem(SpaceTreeItem *parent,
|
||||
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
|
||||
SpaceTreeItem *parent,
|
||||
const QString &id,
|
||||
const QString &name,
|
||||
const QString &canonicalAlias,
|
||||
@@ -15,7 +16,8 @@ SpaceTreeItem::SpaceTreeItem(SpaceTreeItem *parent,
|
||||
bool allowGuests,
|
||||
bool worldReadable,
|
||||
bool isSpace)
|
||||
: m_parentItem(parent)
|
||||
: m_connection(connection)
|
||||
, m_parentItem(parent)
|
||||
, m_id(id)
|
||||
, m_name(name)
|
||||
, m_canonicalAlias(canonicalAlias)
|
||||
@@ -107,8 +109,7 @@ QUrl SpaceTreeItem::avatarUrl() const
|
||||
if (m_avatarUrl.isEmpty() || m_avatarUrl.scheme() != QLatin1String("mxc")) {
|
||||
return {};
|
||||
}
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
auto url = connection->makeMediaUrl(m_avatarUrl);
|
||||
auto url = m_connection->makeMediaUrl(m_avatarUrl);
|
||||
if (url.scheme() == QLatin1String("mxc")) {
|
||||
return url;
|
||||
}
|
||||
@@ -127,11 +128,10 @@ bool SpaceTreeItem::worldReadable() const
|
||||
|
||||
bool SpaceTreeItem::isJoined() const
|
||||
{
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (!connection) {
|
||||
if (!m_connection) {
|
||||
return false;
|
||||
}
|
||||
return connection->room(id(), Quotient::JoinState::Join) != nullptr;
|
||||
return m_connection->room(id(), Quotient::JoinState::Join) != nullptr;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::isSpace() const
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <Quotient/csapi/space_hierarchy.h>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class SpaceTreeItem
|
||||
*
|
||||
@@ -18,7 +20,8 @@
|
||||
class SpaceTreeItem
|
||||
{
|
||||
public:
|
||||
explicit SpaceTreeItem(SpaceTreeItem *parent = nullptr,
|
||||
explicit SpaceTreeItem(NeoChatConnection *connection,
|
||||
SpaceTreeItem *parent = nullptr,
|
||||
const QString &id = {},
|
||||
const QString &name = {},
|
||||
const QString &canonicalAlias = {},
|
||||
@@ -121,6 +124,7 @@ public:
|
||||
bool isSpace() const;
|
||||
|
||||
private:
|
||||
NeoChatConnection *m_connection;
|
||||
QList<SpaceTreeItem *> m_children;
|
||||
SpaceTreeItem *m_parentItem;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/database.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/settings.h>
|
||||
#include <Quotient/user.h>
|
||||
@@ -234,4 +235,21 @@ void NeoChatConnection::openOrCreateDirectChat(User *user)
|
||||
requestDirectChat(user);
|
||||
}
|
||||
|
||||
QString NeoChatConnection::deviceKey() const
|
||||
{
|
||||
return edKeyForUserDevice(userId(), deviceId());
|
||||
}
|
||||
|
||||
QString NeoChatConnection::encryptionKey() const
|
||||
{
|
||||
auto query = database()->prepareQuery(QStringLiteral("SELECT curveKey FROM tracked_devices WHERE matrixId=:matrixId AND deviceid=:deviceId LIMIT 1;"));
|
||||
query.bindValue(QStringLiteral(":matrixId"), userId());
|
||||
query.bindValue(QStringLiteral(":deviceId"), deviceId());
|
||||
database()->execute(query);
|
||||
if (!query.next()) {
|
||||
return {};
|
||||
}
|
||||
return query.value(0).toString();
|
||||
}
|
||||
|
||||
#include "moc_neochatconnection.cpp"
|
||||
|
||||
@@ -23,6 +23,8 @@ class NeoChatConnection : public Quotient::Connection
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
Q_PROPERTY(QString deviceKey READ deviceKey CONSTANT)
|
||||
Q_PROPERTY(QString encryptionKey READ encryptionKey CONSTANT)
|
||||
|
||||
public:
|
||||
NeoChatConnection(QObject *parent = nullptr);
|
||||
@@ -68,6 +70,9 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
|
||||
|
||||
QString deviceKey() const;
|
||||
QString encryptionKey() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
};
|
||||
|
||||
@@ -123,6 +123,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
Q_EMIT parentIdsChanged();
|
||||
Q_EMIT canonicalParentChanged();
|
||||
Q_EMIT joinRuleChanged();
|
||||
});
|
||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||
connect(this, &Room::changed, this, [this]() {
|
||||
@@ -712,16 +713,51 @@ QString NeoChatRoom::joinRule() const
|
||||
return joinRulesEvent->joinRule();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule)
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces)
|
||||
{
|
||||
if (!canSendState("m.room.join_rules"_ls)) {
|
||||
qWarning() << "Power level too low to set join rules";
|
||||
return;
|
||||
}
|
||||
setState("m.room.join_rules"_ls, {}, QJsonObject{{"join_rule"_ls, joinRule}});
|
||||
auto actualRule = joinRule;
|
||||
if (joinRule == "restricted"_ls && allowedSpaces.isEmpty()) {
|
||||
actualRule = "private"_ls;
|
||||
}
|
||||
|
||||
QJsonArray allowConditions;
|
||||
if (actualRule == "restricted"_ls) {
|
||||
for (auto allowedSpace : allowedSpaces) {
|
||||
allowConditions += QJsonObject{{"type"_ls, "m.room_membership"_ls}, {"room_id"_ls, allowedSpace}};
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject content;
|
||||
content.insert("join_rule"_ls, joinRule);
|
||||
if (!allowConditions.isEmpty()) {
|
||||
content.insert("allow"_ls, allowConditions);
|
||||
}
|
||||
qWarning() << content;
|
||||
setState("m.room.join_rules"_ls, {}, 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"_ls) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QString> roomIds;
|
||||
for (auto allow : joinRulesEvent->allow()) {
|
||||
roomIds += allow.toObject().value("room_id"_ls).toString();
|
||||
}
|
||||
return roomIds;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
|
||||
@@ -1141,6 +1177,21 @@ QList<QString> NeoChatRoom::parentIds() const
|
||||
return parentIds;
|
||||
}
|
||||
|
||||
QList<NeoChatRoom *> NeoChatRoom::parentObjects(bool multiLevel) const
|
||||
{
|
||||
QList<NeoChatRoom *> parentObjects;
|
||||
QList<QString> parentIds = this->parentIds();
|
||||
for (const auto &parentId : parentIds) {
|
||||
if (auto parentObject = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parentObjects += parentObject;
|
||||
if (multiLevel) {
|
||||
parentObjects += parentObject->parentObjects(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parentObjects;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::canonicalParent() const
|
||||
{
|
||||
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
||||
@@ -1338,7 +1389,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global"_ls, "room"_ls, id());
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "room"_ls, id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1349,7 +1400,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"_ls] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global"_ls, "override"_ls, id());
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "override"_ls, id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1385,11 +1436,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
auto job = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto job = connection()->callApi<SetPushRuleJob>("global"_ls, "override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "override"_ls, id(), true);
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "override"_ls, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1413,11 +1462,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
// No conditions for a room rule
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
auto setJob = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1446,11 +1493,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
auto setJob = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
|
||||
@@ -160,6 +160,13 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -505,6 +512,17 @@ public:
|
||||
|
||||
QList<QString> parentIds() const;
|
||||
|
||||
/**
|
||||
* @brief Get a list of parent space objects for this room.
|
||||
*
|
||||
* Will only return retrun spaces that are know, i.e. the user has joined and
|
||||
* a valid NeoChatRoom is available.
|
||||
*
|
||||
* @param multiLevel whether the function should recursively gather all levels
|
||||
* of parents
|
||||
*/
|
||||
Q_INVOKABLE QList<NeoChatRoom *> parentObjects(bool multiLevel = false) const;
|
||||
|
||||
QString canonicalParent() const;
|
||||
void setCanonicalParent(const QString &parentId);
|
||||
|
||||
@@ -559,7 +577,23 @@ public:
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
void setJoinRule(const QString &joinRule);
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
|
||||
@@ -204,15 +204,15 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
notification->setText(notification->text() + QLatin1Char('\n') + entry);
|
||||
notification->setPixmap(createNotificationImage(icon, room));
|
||||
|
||||
notification->setDefaultAction(i18n("Open NeoChat in this room"));
|
||||
connect(notification, &KNotification::defaultActivated, this, [notification, room]() {
|
||||
auto defaultAction = notification->addDefaultAction(i18n("Open NeoChat in this room"));
|
||||
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
||||
Controller::instance().setActiveConnection(dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id())));
|
||||
}
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id()));
|
||||
Controller::instance().setActiveConnection(connection);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
RoomManager::instance().enterRoom(room);
|
||||
});
|
||||
|
||||
@@ -240,28 +240,31 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
|
||||
notification->setTitle(title);
|
||||
notification->setPixmap(createNotificationImage(icon, nullptr));
|
||||
notification->setFlags(KNotification::Persistent);
|
||||
notification->setDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(notification, &KNotification::defaultActivated, this, [notification, room]() {
|
||||
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
notification->close();
|
||||
RoomManager::instance().enterRoom(room);
|
||||
});
|
||||
notification->setActions({i18nc("@action:button The thing being accepted is an invitation to chat", "Accept"), i18nc("@action:button The thing being rejected is an invitation to chat", "Reject"), i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and Ignore User")});
|
||||
connect(notification, &KNotification::action1Activated, this, [room, notification]() {
|
||||
|
||||
const auto acceptAction = notification->addAction(i18nc("@action:button The thing being accepted is an invitation to chat", "Accept"));
|
||||
const auto rejectAction = notification->addAction(i18nc("@action:button The thing being rejected is an invitation to chat", "Reject"));
|
||||
const auto rejectAndIgnoreAction = notification->addAction(i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and Ignore User"));
|
||||
connect(acceptAction, &KNotificationAction::activated, this, [room, notification]() {
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
room->acceptInvitation();
|
||||
notification->close();
|
||||
});
|
||||
connect(notification, &KNotification::action2Activated, this, [room, notification]() {
|
||||
connect(rejectAction, &KNotificationAction::activated, this, [room, notification]() {
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
RoomManager::instance().leaveRoom(room);
|
||||
notification->close();
|
||||
});
|
||||
connect(notification, &KNotification::action3Activated, this, [room, notification]() {
|
||||
connect(rejectAndIgnoreAction, &KNotificationAction::activated, this, [room, notification]() {
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ FormCard.FormCardPage {
|
||||
|
||||
Loader {
|
||||
id: colorSchemeDelegate
|
||||
visible: item !== null && Qt.platform.os !== "android"
|
||||
visible: item !== null
|
||||
source: "qrc:/org/kde/neochat/qml/ColorScheme.qml"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Kirigami.ScrollablePage {
|
||||
delegate: RoomDelegate {
|
||||
id: roomDelegate
|
||||
filterText: ""
|
||||
onClicked: {
|
||||
onSelected: {
|
||||
root.chosen(roomDelegate.currentRoom.id)
|
||||
}
|
||||
connection: root.connection
|
||||
|
||||
@@ -16,6 +16,11 @@ FormCard.FormCardPage {
|
||||
|
||||
title: i18n("Devices")
|
||||
|
||||
background: Kirigami.PlaceholderMessage {
|
||||
text: i18n("Loading…")
|
||||
visible: !thisDeviceCard.visible
|
||||
}
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
property DevicesModel devicesModel: DevicesModel {
|
||||
@@ -24,6 +29,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
DevicesCard {
|
||||
id: thisDeviceCard
|
||||
title: i18n("This Device")
|
||||
type: DevicesModel.This
|
||||
showVerifyButton: false
|
||||
@@ -59,26 +65,33 @@ FormCard.FormCardPage {
|
||||
visible: !root.connection
|
||||
}
|
||||
|
||||
property Kirigami.OverlaySheet passwordSheet: Kirigami.OverlaySheet {
|
||||
property Kirigami.Dialog passwordSheet: Kirigami.Dialog {
|
||||
id: passwordSheet
|
||||
|
||||
property string deviceId
|
||||
|
||||
preferredWidth: Kirigami.Units.gridUnit * 24
|
||||
|
||||
title: i18n("Remove device")
|
||||
Kirigami.FormLayout {
|
||||
QQC2.TextField {
|
||||
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
Kirigami.FormData.label: i18n("Password:")
|
||||
label: i18n("Password:")
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18n("Confirm")
|
||||
onClicked: {
|
||||
}
|
||||
customFooterActions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("As in 'Remove this device'", "Remove")
|
||||
icon.name: "delete"
|
||||
onTriggered: {
|
||||
devicesModel.logout(passwordSheet.deviceId, passwordField.text)
|
||||
passwordField.text = ""
|
||||
passwordSheet.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
import org.kde.neochat
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The current room that user is viewing.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 0
|
||||
@@ -26,15 +33,15 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
onClicked: {
|
||||
RoomManager.visitUser(room.getUser(room.directChatRemoteUser.id).object, "mention")
|
||||
RoomManager.visitUser(root.room.getUser(root.room.directChatRemoteUser.id).object, "mention")
|
||||
}
|
||||
|
||||
contentItem: KirigamiComponents.Avatar {
|
||||
name: room ? room.displayName : ""
|
||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
name: root.room ? root.room.displayName : ""
|
||||
source: root.room ? ("image://mxc/" + root.room.avatarMediaId) : ""
|
||||
|
||||
Rectangle {
|
||||
visible: room.usesEncryption
|
||||
visible: root.room.usesEncryption
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
|
||||
width: Kirigami.Units.gridUnit
|
||||
@@ -56,7 +63,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
type: Kirigami.Heading.Type.Primary
|
||||
wrapMode: QQC2.Label.Wrap
|
||||
text: room.displayName
|
||||
text: root.room.displayName
|
||||
textFormat: Text.PlainText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
159
src/qml/ExploreComponentMobile.qml
Normal file
159
src/qml/ExploreComponentMobile.qml
Normal file
@@ -0,0 +1,159 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: 0
|
||||
|
||||
/**
|
||||
* @brief The connection for the current user.
|
||||
*/
|
||||
required property NeoChatConnection connection
|
||||
|
||||
/**
|
||||
* @brief Emitted when the text is changed in the search field.
|
||||
*/
|
||||
signal textChanged(string newText)
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Kirigami.NavigationTabBar {
|
||||
id: exploreTabBar
|
||||
Layout.fillWidth: true
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
id: infoAction
|
||||
text: i18n("Search")
|
||||
icon.name: "search"
|
||||
onTriggered: {
|
||||
if (explorePopup.visible && explorePopupLoader.sourceComponent == search) {
|
||||
explorePopup.close();
|
||||
exploreTabBar.currentIndex = -1;
|
||||
} else if (explorePopup.visible && explorePopupLoader.sourceComponent != search) {
|
||||
explorePopup.close();
|
||||
explorePopup.open();
|
||||
} else {
|
||||
explorePopup.open();
|
||||
}
|
||||
explorePopupLoader.sourceComponent = search;
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Explore rooms")
|
||||
icon.name: "compass"
|
||||
onTriggered: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
if (isJoined) {
|
||||
RoomManager.enterRoom(root.connection.room(roomId));
|
||||
} else {
|
||||
Controller.joinRoom(roomId.length > 0 ? roomId : alias);
|
||||
}
|
||||
})
|
||||
exploreTabBar.currentIndex = -1;
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Start a Chat")
|
||||
icon.name: "list-add-user"
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")})
|
||||
exploreTabBar.currentIndex = -1;
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Create New")
|
||||
icon.name: "list-add"
|
||||
onTriggered: {
|
||||
if (explorePopup.visible && explorePopupLoader.sourceComponent == create) {
|
||||
explorePopup.close();
|
||||
exploreTabBar.currentIndex = -1;
|
||||
} else if (explorePopup.visible && explorePopupLoader.sourceComponent != create) {
|
||||
explorePopup.close();
|
||||
explorePopup.open();
|
||||
} else {
|
||||
explorePopup.open();
|
||||
}
|
||||
explorePopupLoader.sourceComponent = create;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
QQC2.Popup {
|
||||
id: explorePopup
|
||||
parent: root
|
||||
|
||||
y: -height + 1
|
||||
width: root.width
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: Kirigami.Units.largeSpacing
|
||||
topPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
closePolicy: QQC2.Popup.CloseOnEscape
|
||||
|
||||
contentItem: Loader {
|
||||
id: explorePopupLoader
|
||||
sourceComponent: search
|
||||
}
|
||||
|
||||
background: ColumnLayout {
|
||||
spacing: 0
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: search
|
||||
Kirigami.SearchField {
|
||||
onTextChanged: root.textChanged(text)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: create
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Delegates.RoundedItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Create a Room")
|
||||
icon.name: "system-users-symbolic"
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Create a Room")})
|
||||
explorePopup.close()
|
||||
}
|
||||
shortcut: StandardKey.New
|
||||
}
|
||||
}
|
||||
Delegates.RoundedItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Create a Space")
|
||||
icon.name: "list-add"
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection, isSpace: true, title: i18nc("@title", "Create a Space")}, {title: i18nc("@title", "Create a Space")})
|
||||
explorePopup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,23 @@ import org.kde.neochat
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18nc("@title:window", "Notifications")
|
||||
|
||||
property PushRuleModel pushRuleModel: PushRuleModel {
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18n("Enable notifications for this account")
|
||||
description: i18n("Whether push notifications are generated by your Matrix server")
|
||||
checked: Controller.pushRuleModel.globalNotificationsEnabled
|
||||
enabled: Controller.pushRuleModel.globalNotificationsSet
|
||||
checked: root.pushRuleModel.globalNotificationsEnabled
|
||||
enabled: root.pushRuleModel.globalNotificationsSet
|
||||
onToggled: {
|
||||
Controller.pushRuleModel.globalNotificationsEnabled = checked
|
||||
root.pushRuleModel.globalNotificationsEnabled = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +41,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Room;
|
||||
@@ -52,7 +58,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Mentions;
|
||||
@@ -69,7 +75,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
@@ -100,7 +106,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
root.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
}
|
||||
@@ -114,7 +120,7 @@ FormCard.FormCardPage {
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||
|
||||
onClicked: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
root.pushRuleModel.addKeyword(keywordAddField.text)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
|
||||
@@ -133,7 +139,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Invites;
|
||||
@@ -154,7 +160,7 @@ FormCard.FormCardPage {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
id: unknownModel
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
return sectionRole == PushNotificationSection.Unknown;
|
||||
@@ -169,9 +175,9 @@ FormCard.FormCardPage {
|
||||
id: ruleDelegate
|
||||
NotificationRuleItem {
|
||||
onDeleteRule: {
|
||||
Controller.pushRuleModel.removeKeyword(id)
|
||||
root.pushRuleModel.removeKeyword(id)
|
||||
}
|
||||
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
|
||||
onActionChanged: (action) => root.pushRuleModel.setPushRuleAction(id, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ Kirigami.ScrollablePage {
|
||||
|
||||
title: i18n("Invite a User")
|
||||
|
||||
actions {
|
||||
main: Kirigami.Action {
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: "dialog-close"
|
||||
text: i18nc("@action", "Cancel")
|
||||
onTriggered: applicationWindow().pageStack.layers.pop()
|
||||
onTriggered: root.closeDialog()
|
||||
}
|
||||
}
|
||||
]
|
||||
header: RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
@@ -69,6 +69,7 @@ Kirigami.ScrollablePage {
|
||||
valueRole: "url"
|
||||
model: ServerListModel {
|
||||
id: serverListModel
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
title: i18n("Loading…")
|
||||
Kirigami.LoadingPlaceholder {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ Components.AbstractMaximizeComponent {
|
||||
]
|
||||
|
||||
content: MapView {
|
||||
id: map
|
||||
id: mapView
|
||||
map.plugin: Plugin {
|
||||
name: "osm"
|
||||
PluginParameter {
|
||||
@@ -50,7 +50,7 @@ Components.AbstractMaximizeComponent {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.location = map.toCoordinate(Qt.point(mouseX, mouseY), false)
|
||||
root.location = mapView.map.toCoordinate(Qt.point(mouseX, mouseY), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,6 @@ QQC2.TextArea {
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
isEdit: true
|
||||
document: root.textDocument
|
||||
cursorPosition: root.cursorPosition
|
||||
selectionStart: root.selectionStart
|
||||
|
||||
@@ -16,6 +16,10 @@ FormCard.FormCardPage {
|
||||
|
||||
property NeoChatRoom room
|
||||
|
||||
property PushRuleModel pushRuleModel: PushRuleModel {
|
||||
connection: root.room.connection
|
||||
}
|
||||
|
||||
title: i18nc('@title:window', 'Notifications')
|
||||
|
||||
FormCard.FormHeader {
|
||||
@@ -63,7 +67,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: KSortFilterProxyModel {
|
||||
sourceModel: Controller.pushRuleModel
|
||||
sourceModel: root.pushRuleModel
|
||||
|
||||
filterRowCallback: function(source_row, source_parent) {
|
||||
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
|
||||
@@ -78,9 +82,9 @@ FormCard.FormCardPage {
|
||||
id: ruleDelegate
|
||||
NotificationRuleItem {
|
||||
onDeleteRule: {
|
||||
Controller.pushRuleModel.removeKeyword(id)
|
||||
root.pushRuleModel.removeKeyword(id)
|
||||
}
|
||||
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
|
||||
onActionChanged: (action) => root.pushRuleModel.setPushRuleAction(id, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +109,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
root.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
}
|
||||
@@ -119,7 +123,7 @@ FormCard.FormCardPage {
|
||||
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown && keywordAddField.text.length > 0
|
||||
|
||||
onClicked: {
|
||||
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
root.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
|
||||
keywordAddField.text = ""
|
||||
}
|
||||
|
||||
|
||||
50
src/qml/QrCodeMaximizeComponent.qml
Normal file
50
src/qml/QrCodeMaximizeComponent.qml
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.prison
|
||||
|
||||
Components.AbstractMaximizeComponent {
|
||||
id: root
|
||||
|
||||
required property string text
|
||||
required property color avatarColor
|
||||
required property string avatarSource
|
||||
|
||||
Shortcut {
|
||||
sequences: [StandardKey.Cancel]
|
||||
onActivated: root.close()
|
||||
}
|
||||
|
||||
leading: Components.Avatar {
|
||||
id: userAvatar
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: root.title
|
||||
source: root.avatarSource
|
||||
color: root.avatarColor
|
||||
}
|
||||
|
||||
content: Item {
|
||||
Keys.onEscapePressed: root.close()
|
||||
Barcode {
|
||||
barcodeType: Barcode.QRCode
|
||||
content: root.text
|
||||
height: Math.min(parent.height, Kirigami.Units.gridUnit * 20)
|
||||
width: height
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ QQC2.Dialog {
|
||||
|
||||
connection: root.connection
|
||||
|
||||
onClicked: {
|
||||
onSelected: {
|
||||
RoomManager.enterRoom(currentRoom);
|
||||
root.close()
|
||||
}
|
||||
|
||||
@@ -78,15 +78,10 @@ Item {
|
||||
implicitWidth: mainLayout.implicitWidth
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
|
||||
GridLayout {
|
||||
RowLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
implicitHeight: Math.max(replyAvatar.implicitHeight, replyName.implicitHeight) + loader.height
|
||||
|
||||
rows: 2
|
||||
columns: 3
|
||||
rowSpacing: Kirigami.Units.smallSpacing
|
||||
columnSpacing: Kirigami.Units.largeSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Rectangle {
|
||||
id: verticalBorder
|
||||
@@ -97,47 +92,55 @@ Item {
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
color: root.author.color
|
||||
}
|
||||
KirigamiComponents.Avatar {
|
||||
id: replyAvatar
|
||||
ColumnLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
source: root.author.avatarSource
|
||||
name: root.author.displayName
|
||||
color: root.author.color
|
||||
}
|
||||
QQC2.Label {
|
||||
id: replyName
|
||||
Layout.fillWidth: true
|
||||
KirigamiComponents.Avatar {
|
||||
id: replyAvatar
|
||||
|
||||
color: root.author.color
|
||||
text: root.author.displayName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Loader {
|
||||
id: loader
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: loader.item && (root.type == DelegateType.Image || root.type == DelegateType.Sticker) ? loader.item.height : loader.item.implicitHeight
|
||||
Layout.columnSpan: 2
|
||||
source: root.author.avatarSource
|
||||
name: root.author.displayName
|
||||
color: root.author.color
|
||||
}
|
||||
QQC2.Label {
|
||||
id: replyName
|
||||
Layout.fillWidth: true
|
||||
|
||||
sourceComponent: {
|
||||
switch (root.type) {
|
||||
case DelegateType.Image:
|
||||
case DelegateType.Sticker:
|
||||
return imageComponent;
|
||||
case DelegateType.Message:
|
||||
case DelegateType.Notice:
|
||||
return textComponent;
|
||||
case DelegateType.File:
|
||||
case DelegateType.Video:
|
||||
case DelegateType.Audio:
|
||||
return mimeComponent;
|
||||
case DelegateType.Encrypted:
|
||||
return encryptedComponent;
|
||||
default:
|
||||
return textComponent;
|
||||
color: root.author.color
|
||||
text: root.author.displayName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: loader.item && (root.type == DelegateType.Image || root.type == DelegateType.Sticker) ? loader.item.height : loader.item.implicitHeight
|
||||
Layout.columnSpan: 2
|
||||
|
||||
sourceComponent: {
|
||||
switch (root.type) {
|
||||
case DelegateType.Image:
|
||||
case DelegateType.Sticker:
|
||||
return imageComponent;
|
||||
case DelegateType.Message:
|
||||
case DelegateType.Notice:
|
||||
return textComponent;
|
||||
case DelegateType.File:
|
||||
case DelegateType.Video:
|
||||
case DelegateType.Audio:
|
||||
return mimeComponent;
|
||||
case DelegateType.Encrypted:
|
||||
return encryptedComponent;
|
||||
default:
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,18 @@ table td {
|
||||
border: 1px solid black;
|
||||
padding: 3px;
|
||||
}
|
||||
blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
blockquote table {
|
||||
width: 100%;
|
||||
border-width: 0;
|
||||
background-color:" + Kirigami.Theme.alternateBackgroundColor + ";
|
||||
}
|
||||
blockquote td {
|
||||
width: 100%;
|
||||
padding: " + Kirigami.Units.largeSpacing + ";
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap
|
||||
}
|
||||
|
||||
@@ -31,13 +31,24 @@ Delegates.RoundedItemDelegate {
|
||||
|
||||
readonly property bool hasNotifications: notificationCount > 0
|
||||
|
||||
signal selected()
|
||||
|
||||
Accessible.name: root.displayName
|
||||
Accessible.onPressAction: selected()
|
||||
Keys.onSpacePressed: selected()
|
||||
Keys.onEnterPressed: selected()
|
||||
|
||||
onPressAndHold: createRoomListContextMenu()
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: createRoomListContextMenu()
|
||||
acceptedButtons: Qt.RightButton | Qt.LeftButton
|
||||
onTapped: (eventPoint, button) => {
|
||||
if (button === Qt.RightButton) {
|
||||
root.createRoomListContextMenu();
|
||||
} else {
|
||||
root.selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
|
||||
@@ -249,7 +249,9 @@ QQC2.ScrollView {
|
||||
|
||||
Component {
|
||||
id: directChatDrawerHeader
|
||||
DirectChatDrawerHeader {}
|
||||
DirectChatDrawerHeader {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
onRoomChanged: {
|
||||
|
||||
@@ -39,6 +39,8 @@ Kirigami.Page {
|
||||
sortFilterRoomListModel.filterText = "";
|
||||
}
|
||||
|
||||
Component.onCompleted: Runner.roomListModel = root.roomListModel
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onCurrentRoomChanged() {
|
||||
@@ -84,11 +86,9 @@ Kirigami.Page {
|
||||
goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread));
|
||||
}
|
||||
|
||||
titleDelegate: ExploreComponent {
|
||||
titleDelegate: Loader {
|
||||
Layout.fillWidth: true
|
||||
desiredWidth: root.width - Kirigami.Units.largeSpacing
|
||||
collapsed: root.collapsed
|
||||
connection: root.connection
|
||||
sourceComponent: Kirigami.Settings.isMobile ? userInfo : exploreComponent
|
||||
}
|
||||
|
||||
padding: 0
|
||||
@@ -197,18 +197,22 @@ Kirigami.Page {
|
||||
activeSpaceId: spaceDrawer.selectedSpaceId
|
||||
}
|
||||
|
||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||
section.delegate: root.collapsed ? foldButton : sectionHeader
|
||||
section {
|
||||
property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||
delegate: root.collapsed ? foldButton : sectionHeader
|
||||
}
|
||||
|
||||
Component {
|
||||
id: sectionHeader
|
||||
Kirigami.ListSectionHeader {
|
||||
height: implicitHeight
|
||||
width: listView.width
|
||||
label: roomListModel.categoryName(section)
|
||||
action: Kirigami.Action {
|
||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||
}
|
||||
contentItem.children: QQC2.ToolButton {
|
||||
|
||||
QQC2.ToolButton {
|
||||
icon {
|
||||
name: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
|
||||
width: Kirigami.Units.iconSizes.small
|
||||
@@ -274,7 +278,7 @@ Kirigami.Page {
|
||||
|
||||
visible: categoryVisible || filterText.length > 0 || Config.mergeRoomList
|
||||
|
||||
onClicked: RoomManager.enterRoom(currentRoom)
|
||||
onSelected: RoomManager.enterRoom(currentRoom)
|
||||
|
||||
Keys.onEnterPressed: RoomManager.enterRoom(currentRoom)
|
||||
Keys.onReturnPressed: RoomManager.enterRoom(currentRoom)
|
||||
@@ -284,10 +288,9 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
footer: UserInfo {
|
||||
footer: Loader {
|
||||
width: parent.width
|
||||
visible: !root.collapsed
|
||||
connection: root.connection
|
||||
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -333,6 +336,43 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userInfo
|
||||
UserInfo {
|
||||
visible: !root.collapsed
|
||||
bottomEdge: false
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userInfoDesktop
|
||||
UserInfoDesktop {
|
||||
visible: !root.collapsed
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: exploreComponent
|
||||
ExploreComponent {
|
||||
desiredWidth: root.width - Kirigami.Units.largeSpacing
|
||||
collapsed: root.collapsed
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: exploreComponentMobile
|
||||
ExploreComponentMobile {
|
||||
connection: root.connection
|
||||
|
||||
onTextChanged: (newText) => {
|
||||
sortFilterRoomListModel.filterText = newText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Hold the modifiable currentWidth in a private object so that only internal
|
||||
* members can modify it.
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
@@ -42,18 +45,37 @@ FormCard.FormCardPage {
|
||||
description: i18n("Only invited people can join.")
|
||||
checked: room.joinRule === "invite"
|
||||
enabled: room.canSendState("m.room.join_rules")
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "invite";
|
||||
onCheckedChanged: if (checked && room.joinRule != "invite") {
|
||||
root.room.joinRule = "invite";
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18nc("@option:check", "Space members")
|
||||
description: i18n("Anyone in a space can find and join.") +
|
||||
description: i18n("Anyone in the selected spaces can find and join.") +
|
||||
(!["8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
|
||||
checked: room.joinRule === "restricted"
|
||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version) && false
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "restricted";
|
||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version)
|
||||
onCheckedChanged: if (checked && room.joinRule != "restricted") {
|
||||
selectSpacesDialog.createObject(applicationWindow().overlay).open();
|
||||
}
|
||||
|
||||
contentItem.children: QQC2.Button {
|
||||
visible: root.room.joinRule === "restricted"
|
||||
text: i18n("Select spaces")
|
||||
icon.name: "list-add"
|
||||
|
||||
onClicked: selectSpacesDialog.createObject(applicationWindow().overlay).open();
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectSpacesDialog
|
||||
SelectSpacesDialog {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
@@ -63,8 +85,8 @@ FormCard.FormCardPage {
|
||||
checked: room.joinRule === "knock"
|
||||
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
|
||||
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "knock";
|
||||
onCheckedChanged: if (checked && room.joinRule != "knock") {
|
||||
root.room.joinRule = "knock";
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
@@ -72,8 +94,8 @@ FormCard.FormCardPage {
|
||||
description: i18nc("@option:check", "Anyone can find and join.")
|
||||
checked: room.joinRule === "public"
|
||||
enabled: room.canSendState("m.room.join_rules")
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "public";
|
||||
onCheckedChanged: if (checked && root.room.joinRule != "public") {
|
||||
root.room.joinRule = "public";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
src/qml/Security.qml
Normal file
36
src/qml/Security.qml
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18nc("@title", "Security")
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Keys")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceKey
|
||||
description: i18n("Device key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.encryptionKey
|
||||
description: i18n("Encryption key")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: connection.deviceId
|
||||
description: i18n("Device id")
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/qml/SelectSpacesDialog.qml
Normal file
77
src/qml/SelectSpacesDialog.qml
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The current room this dialog is opened for.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
/**
|
||||
* @brief The current list of space IDs that members of can join this room.
|
||||
*/
|
||||
property list<string> restrictedIds: room.restrictedIds
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title", "Select Spaces")
|
||||
|
||||
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
onAccepted: {
|
||||
let ids = [];
|
||||
for (var i in spaceGroup.buttons) {
|
||||
if (spaceGroup.buttons[i].checked) {
|
||||
ids.push(spaceGroup.buttons[i].modelData.id);
|
||||
}
|
||||
}
|
||||
root.room.setJoinRule("restricted", ids)
|
||||
console.warn(ids)
|
||||
}
|
||||
|
||||
QQC2.ButtonGroup {
|
||||
id: spaceGroup
|
||||
exclusive: false
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: root.room.parentObjects(true)
|
||||
|
||||
delegate: FormCard.FormCheckDelegate {
|
||||
required property var modelData
|
||||
|
||||
text: modelData.displayName
|
||||
description: modelData.canonicalAlias
|
||||
checked: root.restrictedIds.includes(modelData.id)
|
||||
QQC2.ButtonGroup.group: spaceGroup
|
||||
|
||||
leading: Components.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
source: modelData.avatarUrl.toString().length > 0 ? connection.makeMediaUrl(modelData.avatarUrl) : ""
|
||||
name: modelData.displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user