Compare commits

...

6 Commits

Author SHA1 Message Date
Tobias Fella
565fe1975b Remove duplicated corrosion 2025-03-22 17:55:30 +01:00
Tobias Fella
635b77d03e Add licenses 2025-03-22 17:45:08 +01:00
Tobias Fella
4d7b788160 Fix rust flatpak 2025-03-22 17:38:41 +01:00
Tobias Fella
9e24c855b6 Add corrosion 2025-03-22 17:28:34 +01:00
Tobias Fella
18bea02fa0 WIP 2025-03-22 17:17:39 +01:00
Tobias Fella
073e756364 Port to Integral 2025-03-22 17:10:33 +01:00
52 changed files with 8535 additions and 3563 deletions

View File

@@ -22,7 +22,20 @@
"--talk-name=org.freedesktop.secrets", "--talk-name=org.freedesktop.secrets",
"--own-name=org.kde.StatusNotifierItem-2-2" "--own-name=org.kde.StatusNotifierItem-2-2"
], ],
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-stable"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin",
"env": {
"RUST_BACKTRACE": "1",
"CARGO_NET_OFFLINE": "true",
"RUSTFLAGS": "--remap-path-prefix =../"
}
},
"modules": [ "modules": [
"flatpak/corrosion.json",
{ {
"name": "kirigamiaddons", "name": "kirigamiaddons",
"config-opts": [ "config-opts": [
@@ -50,25 +63,6 @@
} }
] ]
}, },
{
"name": "olm",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DOLM_TESTS=OFF"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git",
"tag": "3.2.16",
"x-checker-data": {
"type": "git",
"tag-pattern": "^([\\d.]+)$"
},
"commit": "7e0c8277032e40308987257b711b38af8d77cc69"
}
]
},
{ {
"name": "libsecret", "name": "libsecret",
"buildsystem": "meson", "buildsystem": "meson",
@@ -117,20 +111,15 @@
] ]
}, },
{ {
"name": "libQuotient", "name": "integral",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"sources": [ "sources": [
"flatpak/generated-sources.json",
{ {
"type": "git", "type": "git",
"url": "https://github.com/quotient-im/libQuotient.git", "url": "https://invent.kde.org/tfella/integral.git",
"branch": "dev", "branch": "master"
"disable-submodules": true
} }
],
"config-opts": [
"-DBUILD_WITH_QT6=ON",
"-DQuotient_ENABLE_E2EE=ON",
"-DBUILD_TESTING=OFF"
] ]
}, },
{ {

View File

@@ -8,14 +8,14 @@ include:
- /gitlab-templates/json-validation.yml - /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml - /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml - /gitlab-templates/yaml-lint.yml
- /gitlab-templates/android-qt6.yml # - /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml # - /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml # - /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/windows-qt6.yml # - /gitlab-templates/windows-qt6.yml
# - /gitlab-templates/freebsd-qt6.yml # - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml - /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml # - /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml # - /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml # - /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml # - /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml # - /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -49,8 +49,6 @@ if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake) include(cmake/Flatpak.cmake)
endif() endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_setup_version(${PROJECT_VERSION} ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX NEOCHAT VARIABLE_PREFIX NEOCHAT
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
@@ -107,13 +105,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED) find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif() endif()
find_package(QuotientQt6 0.9) find_package(Integral 0.1 REQUIRED)
set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API"
URL "https://github.com/quotient-im/libQuotient/"
PURPOSE "Talk with matrix server"
)
find_package(cmark) find_package(cmark)
set_package_properties(cmark PROPERTIES set_package_properties(cmark PROPERTIES

30
flatpak/corrosion.json Normal file
View File

@@ -0,0 +1,30 @@
{
"build-options": {
"env": {
"CARGO_HOME": "/run/build/corrosion/cargo"
}
},
"buildsystem": "cmake-ninja",
"cleanup": [
"/app"
],
"config-opts": [
"-DCORROSION_INSTALL_EXECUTABLE=OFF",
"-DCORROSION_BUILD_TESTS=OFF",
"-DCORROSION_DEV_MODE=OFF"
],
"name": "corrosion",
"sources": [
{
"sha256": "843334a9f0f5efbc225dccfa88031fe0f2ec6fd787ca1e7d55ed27b2c25d9c97",
"type": "archive",
"url": "https://github.com/corrosion-rs/corrosion/archive/refs/tags/v0.5.1.tar.gz",
"x-checker-data": {
"type": "anitya",
"project-id": 242799,
"stable-only": true,
"url-template": "https://github.com/corrosion-rs/corrosion/archive/refs/tags/v0.5.1.tar.gz"
}
}
]
}

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: CC0-1.0

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: None
SPDX-License-Identifier: CC0-1.0

16
flatpak/regenerate-sources.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Jonah Brüchert <jbb@kaidan.im>
# SPDX-License-Identifier: CC0-1.0
set -e
export GIT_CLONE_ARGS="--depth 1 --single-branch"
export FLATPAK_DIR="$(readlink -f $(dirname $0))"
cd ${FLATPAK_DIR}
if [ ! -d flatpak-builder-tools ]; then
git clone ${GIT_CLONE_ARGS} https://github.com/flatpak/flatpak-builder-tools
else
git -C flatpak-builder-tools pull
fi
./flatpak-builder-tools/cargo/flatpak-cargo-generator.py -o generated-sources.json ../../integral/src/sdk/Cargo.lock

View File

@@ -10,192 +10,192 @@ endif()
add_library(neochat STATIC add_library(neochat STATIC
controller.cpp controller.cpp
controller.h controller.h
models/emojimodel.cpp # models/emojimodel.cpp
models/emojimodel.h # models/emojimodel.h
emojitones.cpp # emojitones.cpp
emojitones.h # emojitones.h
models/customemojimodel.cpp # models/customemojimodel.cpp
models/customemojimodel.h # models/customemojimodel.h
clipboard.cpp # clipboard.cpp
clipboard.h # clipboard.h
models/timelinemessagemodel.cpp # models/timelinemessagemodel.cpp
models/timelinemessagemodel.h # models/timelinemessagemodel.h
models/messagefiltermodel.cpp # models/messagefiltermodel.cpp
models/messagefiltermodel.h # models/messagefiltermodel.h
models/roomlistmodel.cpp # models/roomlistmodel.cpp
models/roomlistmodel.h # models/roomlistmodel.h
models/sortfilterspacelistmodel.cpp # models/sortfilterspacelistmodel.cpp
models/sortfilterspacelistmodel.h # models/sortfilterspacelistmodel.h
models/accountemoticonmodel.cpp # models/accountemoticonmodel.cpp
models/accountemoticonmodel.h # models/accountemoticonmodel.h
spacehierarchycache.cpp # spacehierarchycache.cpp
spacehierarchycache.h # spacehierarchycache.h
roommanager.cpp # roommanager.cpp
roommanager.h # roommanager.h
neochatroom.cpp neochatroom.cpp
neochatroom.h neochatroom.h
models/userlistmodel.cpp models/userlistmodel.cpp
models/userlistmodel.h models/userlistmodel.h
models/userfiltermodel.cpp models/userfiltermodel.cpp
models/userfiltermodel.h models/userfiltermodel.h
models/publicroomlistmodel.cpp # models/publicroomlistmodel.cpp
models/publicroomlistmodel.h # models/publicroomlistmodel.h
models/spacechildrenmodel.cpp # models/spacechildrenmodel.cpp
models/spacechildrenmodel.h # models/spacechildrenmodel.h
models/spacechildsortfiltermodel.cpp # models/spacechildsortfiltermodel.cpp
models/spacechildsortfiltermodel.h # models/spacechildsortfiltermodel.h
models/spacetreeitem.cpp # models/spacetreeitem.cpp
models/spacetreeitem.h # models/spacetreeitem.h
models/userdirectorylistmodel.cpp # models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h # models/userdirectorylistmodel.h
models/pushrulemodel.cpp models/pushrulemodel.cpp
models/pushrulemodel.h models/pushrulemodel.h
models/emoticonfiltermodel.cpp # models/emoticonfiltermodel.cpp
models/emoticonfiltermodel.h # models/emoticonfiltermodel.h
notificationsmanager.cpp # notificationsmanager.cpp
notificationsmanager.h # notificationsmanager.h
models/sortfilterroomlistmodel.cpp # models/sortfilterroomlistmodel.cpp
models/sortfilterroomlistmodel.h # models/sortfilterroomlistmodel.h
models/roomtreemodel.cpp models/roomtreemodel.cpp
models/roomtreemodel.h models/roomtreemodel.h
chatdocumenthandler.cpp chatdocumenthandler.cpp
chatdocumenthandler.h chatdocumenthandler.h
models/devicesmodel.cpp # models/devicesmodel.cpp
models/devicesmodel.h # models/devicesmodel.h
models/devicesproxymodel.cpp # models/devicesproxymodel.cpp
filetype.cpp # filetype.cpp
filetype.h # filetype.h
login.cpp # login.cpp
login.h # login.h
models/webshortcutmodel.cpp # models/webshortcutmodel.cpp
models/webshortcutmodel.h # models/webshortcutmodel.h
blurhash.cpp blurhash.cpp
blurhash.h blurhash.h
blurhashimageprovider.cpp blurhashimageprovider.cpp
blurhashimageprovider.h blurhashimageprovider.h
models/mediamessagefiltermodel.cpp # models/mediamessagefiltermodel.cpp
models/mediamessagefiltermodel.h # models/mediamessagefiltermodel.h
urlhelper.cpp # urlhelper.cpp
urlhelper.h # urlhelper.h
windowcontroller.cpp windowcontroller.cpp
windowcontroller.h windowcontroller.h
linkpreviewer.cpp # linkpreviewer.cpp
linkpreviewer.h # linkpreviewer.h
models/completionmodel.cpp models/completionmodel.cpp
models/completionmodel.h models/completionmodel.h
models/completionproxymodel.cpp # models/completionproxymodel.cpp
models/completionproxymodel.h # models/completionproxymodel.h
models/actionsmodel.cpp # models/actionsmodel.cpp
models/actionsmodel.h # models/actionsmodel.h
models/serverlistmodel.cpp # models/serverlistmodel.cpp
models/serverlistmodel.h # models/serverlistmodel.h
models/statemodel.cpp # models/statemodel.cpp
models/statemodel.h # models/statemodel.h
models/statefiltermodel.cpp # models/statefiltermodel.cpp
models/statefiltermodel.h # models/statefiltermodel.h
filetransferpseudojob.cpp # filetransferpseudojob.cpp
filetransferpseudojob.h # filetransferpseudojob.h
models/searchmodel.cpp # models/searchmodel.cpp
models/searchmodel.h # models/searchmodel.h
texthandler.cpp # texthandler.cpp
texthandler.h # texthandler.h
logger.cpp logger.cpp
logger.h logger.h
models/stickermodel.cpp # models/stickermodel.cpp
models/stickermodel.h # models/stickermodel.h
models/imagepacksmodel.cpp # models/imagepacksmodel.cpp
models/imagepacksmodel.h # models/imagepacksmodel.h
events/imagepackevent.cpp # events/imagepackevent.cpp
events/imagepackevent.h # events/imagepackevent.h
models/reactionmodel.cpp # models/reactionmodel.cpp
models/reactionmodel.h # models/reactionmodel.h
delegatesizehelper.cpp delegatesizehelper.cpp
delegatesizehelper.h delegatesizehelper.h
models/livelocationsmodel.cpp # models/livelocationsmodel.cpp
models/livelocationsmodel.h # models/livelocationsmodel.h
models/locationsmodel.cpp # models/locationsmodel.cpp
models/locationsmodel.h # models/locationsmodel.h
locationhelper.cpp # locationhelper.cpp
locationhelper.h # locationhelper.h
events/pollevent.cpp # events/pollevent.cpp
pollhandler.cpp # pollhandler.cpp
utils.h utils.h
utils.cpp utils.cpp
registration.cpp # registration.cpp
neochatconnection.cpp neochatconnection.cpp
neochatconnection.h neochatconnection.h
jobs/neochatgetcommonroomsjob.cpp # jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h # jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp mediasizehelper.cpp
mediasizehelper.h mediasizehelper.h
eventhandler.cpp # eventhandler.cpp
enums/delegatetype.h # enums/delegatetype.h
roomlastmessageprovider.cpp # roomlastmessageprovider.cpp
roomlastmessageprovider.h # roomlastmessageprovider.h
chatbarcache.cpp chatbarcache.cpp
chatbarcache.h chatbarcache.h
colorschemer.cpp colorschemer.cpp
colorschemer.h colorschemer.h
models/notificationsmodel.cpp # models/notificationsmodel.cpp
models/notificationsmodel.h # models/notificationsmodel.h
models/timelinemodel.cpp # models/timelinemodel.cpp
models/timelinemodel.h # models/timelinemodel.h
enums/pushrule.h enums/pushrule.h
models/itinerarymodel.cpp # models/itinerarymodel.cpp
models/itinerarymodel.h # models/itinerarymodel.h
proxycontroller.cpp proxycontroller.cpp
proxycontroller.h proxycontroller.h
models/linemodel.cpp models/linemodel.cpp
models/linemodel.h models/linemodel.h
events/locationbeaconevent.h # events/locationbeaconevent.h
events/widgetevent.h # events/widgetevent.h
enums/messagecomponenttype.h # enums/messagecomponenttype.h
models/messagecontentmodel.cpp # models/messagecontentmodel.cpp
models/messagecontentmodel.h # models/messagecontentmodel.h
enums/neochatroomtype.h enums/neochatroomtype.h
models/sortfilterroomtreemodel.cpp models/sortfilterroomtreemodel.cpp
models/sortfilterroomtreemodel.h models/sortfilterroomtreemodel.h
mediamanager.cpp mediamanager.cpp
mediamanager.h mediamanager.h
models/statekeysmodel.cpp # models/statekeysmodel.cpp
models/statekeysmodel.h # models/statekeysmodel.h
sharehandler.cpp # sharehandler.cpp
sharehandler.h # sharehandler.h
models/roomtreeitem.cpp models/roomtreeitem.cpp
models/roomtreeitem.h models/roomtreeitem.h
foreigntypes.h foreigntypes.h
models/threepidmodel.cpp # models/threepidmodel.cpp
models/threepidmodel.h # models/threepidmodel.h
threepidaddhelper.cpp # threepidaddhelper.cpp
threepidaddhelper.h # threepidaddhelper.h
identityserverhelper.cpp # identityserverhelper.cpp
identityserverhelper.h # identityserverhelper.h
enums/powerlevel.cpp # enums/powerlevel.cpp
enums/powerlevel.h # enums/powerlevel.h
models/permissionsmodel.cpp # models/permissionsmodel.cpp
models/permissionsmodel.h # models/permissionsmodel.h
threepidbindhelper.cpp # threepidbindhelper.cpp
threepidbindhelper.h # threepidbindhelper.h
models/readmarkermodel.cpp # models/readmarkermodel.cpp
models/readmarkermodel.h # models/readmarkermodel.h
neochatroommember.cpp # neochatroommember.cpp
neochatroommember.h # neochatroommember.h
models/threadmodel.cpp # models/threadmodel.cpp
models/threadmodel.h # models/threadmodel.h
enums/messagetype.h # enums/messagetype.h
messagecomponent.h # messagecomponent.h
enums/roomsortparameter.cpp enums/roomsortparameter.cpp
enums/roomsortparameter.h enums/roomsortparameter.h
models/roomsortparametermodel.cpp # models/roomsortparametermodel.cpp
models/roomsortparametermodel.h # models/roomsortparametermodel.h
models/messagemodel.cpp # models/messagemodel.cpp
models/messagemodel.h # models/messagemodel.h
models/messagecontentfiltermodel.cpp # models/messagecontentfiltermodel.cpp
models/messagecontentfiltermodel.h # models/messagecontentfiltermodel.h
models/pinnedmessagemodel.cpp # models/pinnedmessagemodel.cpp
models/pinnedmessagemodel.h # models/pinnedmessagemodel.h
models/commonroomsmodel.cpp # models/commonroomsmodel.cpp
models/commonroomsmodel.h # models/commonroomsmodel.h
) )
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -297,8 +297,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AvatarNotification.qml qml/AvatarNotification.qml
qml/ReasonDialog.qml qml/ReasonDialog.qml
SOURCES SOURCES
messageattached.cpp # messageattached.cpp
messageattached.h # messageattached.h
DEPENDENCIES DEPENDENCIES
QtCore QtCore
QtQuick QtQuick
@@ -398,10 +398,10 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER) target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1) target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp) # target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush) if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp) # target_sources(neochat PRIVATE fakerunner.cpp)
endif() endif()
else() else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0) target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
@@ -427,7 +427,7 @@ target_link_libraries(neochat PUBLIC
KF6::IconThemes KF6::IconThemes
KF6::ColorScheme KF6::ColorScheme
KF6::ItemModels KF6::ItemModels
QuotientQt6 Integral
cmark::cmark cmark::cmark
QCoro::Core QCoro::Core
QCoro::Network QCoro::Network

View File

@@ -344,13 +344,13 @@ QQC2.Control {
Item { Item {
implicitWidth: replyComponent.implicitWidth implicitWidth: replyComponent.implicitWidth
implicitHeight: replyComponent.implicitHeight implicitHeight: replyComponent.implicitHeight
ReplyComponent { // ReplyComponent {
id: replyComponent // id: replyComponent
replyEventId: _private.chatBarCache.replyId // replyEventId: _private.chatBarCache.replyId
replyAuthor: _private.chatBarCache.relationAuthor // replyAuthor: _private.chatBarCache.relationAuthor
replyContentModel: _private.chatBarCache.relationEventContentModel // replyContentModel: _private.chatBarCache.relationEventContentModel
Message.maxContentWidth: paneLoader.item.width // Message.maxContentWidth: paneLoader.item.width
} // }
QQC2.Button { QQC2.Button {
id: cancelButton id: cancelButton
@@ -498,23 +498,23 @@ QQC2.Control {
} }
} }
EmojiDialog { // EmojiDialog {
id: emojiDialog // id: emojiDialog
//
x: root.width - width // x: root.width - width
y: -implicitHeight // y: -implicitHeight
//
modal: false // modal: false
includeCustom: true // includeCustom: true
closeOnChosen: false // closeOnChosen: false
//
currentRoom: root.currentRoom // currentRoom: root.currentRoom
//
onChosen: emoji => insertText(emoji) // onChosen: emoji => insertText(emoji)
onClosed: if (emojiAction.checked) { // onClosed: if (emojiAction.checked) {
emojiAction.checked = false; // emojiAction.checked = false;
} // }
} // }
function insertText(text) { function insertText(text) {
let initialCursorPosition = textField.cursorPosition; let initialCursorPosition = textField.cursorPosition;

View File

@@ -2,14 +2,15 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "chatbarcache.h" #include "chatbarcache.h"
#include "chatdocumenthandler.h"
#include <Quotient/roommember.h> #include <Quotient/roommember.h>
#include "chatdocumenthandler.h" // #include "chatdocumenthandler.h"
#include "eventhandler.h" // #include "eventhandler.h"
#include "models/actionsmodel.h" // #include "models/actionsmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "texthandler.h" // #include "texthandler.h"
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@@ -88,7 +89,7 @@ void ChatBarCache::setReplyId(const QString &replyId)
m_relationType = Reply; m_relationType = Reply;
} }
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel; // delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
} }
@@ -118,27 +119,27 @@ void ChatBarCache::setEditId(const QString &editId)
m_relationType = Edit; m_relationType = Edit;
} }
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel; // delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
} }
Quotient::RoomMember ChatBarCache::relationAuthor() const // Quotient::RoomMember ChatBarCache::relationAuthor() const
{ // {
if (parent() == nullptr) { // if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; // qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {}; // return {};
} // }
auto room = dynamic_cast<NeoChatRoom *>(parent()); // auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) { // if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."; // qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {}; // return {};
} // }
if (m_relationId.isEmpty()) { // if (m_relationId.isEmpty()) {
return room->member(QString()); // return room->member(QString());
} // }
return room->member((*room->findInTimeline(m_relationId))->senderId()); // return room->member((*room->findInTimeline(m_relationId))->senderId());
} // }
QString ChatBarCache::relationMessage() const QString ChatBarCache::relationMessage() const
{ {
@@ -155,33 +156,33 @@ QString ChatBarCache::relationMessage() const
return {}; return {};
} }
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) { // if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
return EventHandler::markdownBody(&**event); // return EventHandler::markdownBody(&**event);
} // }
return {}; return {};
} }
MessageContentModel *ChatBarCache::relationEventContentModel() // MessageContentModel *ChatBarCache::relationEventContentModel()
{ // {
if (parent() == nullptr) { // if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; // qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return nullptr; // return nullptr;
} // }
if (m_relationId.isEmpty()) { // if (m_relationId.isEmpty()) {
return nullptr; // return nullptr;
} // }
if (m_relationContentModel != nullptr) { // if (m_relationContentModel != nullptr) {
return m_relationContentModel; // return m_relationContentModel;
} // }
//
auto room = dynamic_cast<NeoChatRoom *>(parent()); // auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) { // if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."; // qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return nullptr; // return nullptr;
} // }
m_relationContentModel = new MessageContentModel(room, m_relationId, true); // m_relationContentModel = new MessageContentModel(room, m_relationId, true);
return m_relationContentModel; // return m_relationContentModel;
} // }
bool ChatBarCache::isThreaded() const bool ChatBarCache::isThreaded() const
{ {
@@ -215,7 +216,7 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
m_attachmentPath = attachmentPath; m_attachmentPath = attachmentPath;
m_relationType = None; m_relationType = None;
const auto oldEventId = std::exchange(m_relationId, QString()); const auto oldEventId = std::exchange(m_relationId, QString());
delete m_relationContentModel; // delete m_relationContentModel;
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
} }
@@ -225,7 +226,7 @@ void ChatBarCache::clearRelations()
const auto oldEventId = std::exchange(m_relationId, QString()); const auto oldEventId = std::exchange(m_relationId, QString());
const auto oldThreadId = std::exchange(m_threadId, QString()); const auto oldThreadId = std::exchange(m_threadId, QString());
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel; // delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT threadIdChanged(oldThreadId, m_threadId); Q_EMIT threadIdChanged(oldThreadId, m_threadId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
@@ -238,7 +239,7 @@ QList<Mention> *ChatBarCache::mentions()
void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler) void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler)
{ {
documentHandler->setDocument(document); // documentHandler->setDocument(document);
if (parent() == nullptr) { if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
@@ -253,35 +254,35 @@ void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHand
return; return;
} }
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) { // if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) { // if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown // // Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(uR"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"_s); // const QRegularExpression re(uR"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"_s);
//
m_mentions.clear(); // m_mentions.clear();
//
int linkSize = 0; // int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent)); // auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
while (matches.hasNext()) { // while (matches.hasNext()) {
const QRegularExpressionMatch match = matches.next(); // const QRegularExpressionMatch match = matches.next();
if (match.hasMatch()) { // if (match.hasMatch()) {
const QString id = match.captured(1); // const QString id = match.captured(1);
const QString name = match.captured(2); // const QString name = match.captured(2);
//
const int position = match.capturedStart(0) - linkSize; // const int position = match.capturedStart(0) - linkSize;
const int end = position + name.length(); // const int end = position + name.length();
linkSize += match.capturedLength(0) - name.length(); // linkSize += match.capturedLength(0) - name.length();
//
QTextCursor cursor(documentHandler->document()->textDocument()); // QTextCursor cursor(documentHandler->document()->textDocument());
cursor.setPosition(position); // cursor.setPosition(position);
cursor.setPosition(end, QTextCursor::KeepAnchor); // cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true); // cursor.setKeepPositionOnInsert(true);
//
m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id}); // m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
} // }
} // }
} // }
} // }
} }
QString ChatBarCache::savedText() const QString ChatBarCache::savedText() const
@@ -308,37 +309,37 @@ void ChatBarCache::postMessage()
return; return;
} }
const auto result = ActionsModel::handleAction(room, this); // const auto result = ActionsModel::handleAction(room, this);
if (!result.second.has_value()) { // if (!result.second.has_value()) {
return; // return;
} // }
TextHandler textHandler; // TextHandler textHandler;
textHandler.setData(*std::get<std::optional<QString>>(result)); // textHandler.setData(*std::get<std::optional<QString>>(result));
const auto sendText = textHandler.handleSendText(); // const auto sendText = textHandler.handleSendText();
//
if (sendText.length() == 0) { // if (sendText.length() == 0) {
return; // return;
} // }
bool isReply = !replyId().isEmpty(); bool isReply = !replyId().isEmpty();
const auto replyIt = room->findInTimeline(replyId()); // const auto replyIt = room->findInTimeline(replyId());
if (replyIt == room->historyEdge()) { // if (replyIt == room->historyEdge()) {
isReply = false; // isReply = false;
} // }
auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s); // auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s);
std::optional<Quotient::EventRelation> relatesTo = std::nullopt; // std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
//
// if (!threadId().isEmpty()) {
// relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
// } else if (!editId().isEmpty()) {
// relatesTo = Quotient::EventRelation::replace(editId());
// } else if (isReply) {
// relatesTo = Quotient::EventRelation::replyTo(replyId());
// }
if (!threadId().isEmpty()) { // room->post<Quotient::RoomMessageEvent>(text(), *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), std::move(content), relatesTo);
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
} else if (!editId().isEmpty()) {
relatesTo = Quotient::EventRelation::replace(editId());
} else if (isReply) {
relatesTo = Quotient::EventRelation::replyTo(replyId());
}
room->post<Quotient::RoomMessageEvent>(text(), *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), std::move(content), relatesTo);
clearCache(); clearCache();
} }

View File

@@ -8,7 +8,7 @@
#include <QQuickTextDocument> #include <QQuickTextDocument>
#include <QTextCursor> #include <QTextCursor>
#include "models/messagecontentmodel.h" // #include "models/messagecontentmodel.h"
class ChatDocumentHandler; class ChatDocumentHandler;
@@ -102,7 +102,7 @@ class ChatBarCache : public QObject
* *
* @sa Quotient::RoomMember * @sa Quotient::RoomMember
*/ */
Q_PROPERTY(Quotient::RoomMember relationAuthor READ relationAuthor NOTIFY relationIdChanged) // Q_PROPERTY(Quotient::RoomMember relationAuthor READ relationAuthor NOTIFY relationIdChanged)
/** /**
* @brief The content of the related message. * @brief The content of the related message.
@@ -116,7 +116,7 @@ class ChatBarCache : public QObject
* *
* Will be nullptr if no related message. * Will be nullptr if no related message.
*/ */
Q_PROPERTY(MessageContentModel *relationEventContentModel READ relationEventContentModel NOTIFY relationIdChanged) // Q_PROPERTY(MessageContentModel *relationEventContentModel READ relationEventContentModel NOTIFY relationIdChanged)
/** /**
* @brief Whether the chat bar is replying in a thread. * @brief Whether the chat bar is replying in a thread.
@@ -164,10 +164,10 @@ public:
QString editId() const; QString editId() const;
void setEditId(const QString &editId); void setEditId(const QString &editId);
Quotient::RoomMember relationAuthor() const; // Quotient::RoomMember relationAuthor() const;
QString relationMessage() const; QString relationMessage() const;
MessageContentModel *relationEventContentModel(); // MessageContentModel *relationEventContentModel();
bool isThreaded() const; bool isThreaded() const;
QString threadId() const; QString threadId() const;
@@ -225,7 +225,7 @@ private:
QList<Mention> m_mentions; QList<Mention> m_mentions;
QString m_savedText; QString m_savedText;
QPointer<MessageContentModel> m_relationContentModel; // QPointer<MessageContentModel> m_relationContentModel;
void clearCache(); void clearCache();
}; };

View File

@@ -105,7 +105,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
, m_completionModel(new CompletionModel(this)) , m_completionModel(new CompletionModel(this))
{ {
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() { connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
m_completionModel->setRoom(m_room); // m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr; static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) { if (previousRoom) {
disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr); disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr);
@@ -113,7 +113,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
previousRoom = m_room; previousRoom = m_room;
connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() { connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() {
int start = completionStartIndex(); int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start)); // m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
}); });
}); });
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() { connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
@@ -124,7 +124,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
return; return;
} }
int start = completionStartIndex(); int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start)); // m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
}); });
} }
@@ -217,58 +217,58 @@ void ChatDocumentHandler::complete(int index)
qCWarning(ChatDocumentHandling) << "complete called with m_document set to nullptr."; qCWarning(ChatDocumentHandling) << "complete called with m_document set to nullptr.";
return; return;
} }
if (m_completionModel->autoCompletionType() == CompletionModel::None) { // if (m_completionModel->autoCompletionType() == CompletionModel::None) {
qCWarning(ChatDocumentHandling) << "complete called with m_completionModel->autoCompletionType() == CompletionModel::None."; // qCWarning(ChatDocumentHandling) << "complete called with m_completionModel->autoCompletionType() == CompletionModel::None.";
return; // return;
} // }
// Ensure we only search for the beginning of the current completion identifier // Ensure we only search for the beginning of the current completion identifier
const auto fromIndex = qMax(completionStartIndex(), 0); const auto fromIndex = qMax(completionStartIndex(), 0);
if (m_completionModel->autoCompletionType() == CompletionModel::User) { // if (m_completionModel->autoCompletionType() == CompletionModel::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString(); // auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); // auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
auto text = getText(); // auto text = getText();
auto at = text.indexOf(QLatin1Char('@'), fromIndex); // auto at = text.indexOf(QLatin1Char('@'), fromIndex);
QTextCursor cursor(document()->textDocument()); // QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); // cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
cursor.insertText(name + u" "_s); // cursor.insertText(name + u" "_s);
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor); // cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true); // cursor.setKeepPositionOnInsert(true);
pushMention({cursor, name, 0, 0, id}); // pushMention({cursor, name, 0, 0, id});
m_highlighter->rehighlight(); // m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) { // } else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); // auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
auto text = getText(); // auto text = getText();
auto at = text.indexOf(QLatin1Char('/'), fromIndex); // auto at = text.indexOf(QLatin1Char('/'), fromIndex);
QTextCursor cursor(document()->textDocument()); // QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); // cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
cursor.insertText(u"/%1 "_s.arg(command)); // cursor.insertText(u"/%1 "_s.arg(command));
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) { // } else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); // auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
auto text = getText(); // auto text = getText();
auto at = text.indexOf(QLatin1Char('#'), fromIndex); // auto at = text.indexOf(QLatin1Char('#'), fromIndex);
QTextCursor cursor(document()->textDocument()); // QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); // cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
cursor.insertText(alias + u" "_s); // cursor.insertText(alias + u" "_s);
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor); // cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true); // cursor.setKeepPositionOnInsert(true);
pushMention({cursor, alias, 0, 0, alias}); // pushMention({cursor, alias, 0, 0, alias});
m_highlighter->rehighlight(); // m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) { // } else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); // auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
auto text = getText(); // auto text = getText();
auto at = text.indexOf(QLatin1Char(':'), fromIndex); // auto at = text.indexOf(QLatin1Char(':'), fromIndex);
QTextCursor cursor(document()->textDocument()); // QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); // cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); // cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
cursor.insertText(shortcode); // cursor.insertText(shortcode);
} // }
} }
CompletionModel *ChatDocumentHandler::completionModel() const CompletionModel *ChatDocumentHandler::completionModel() const

View File

@@ -8,19 +8,16 @@
#include <KLocalizedString> #include <KLocalizedString>
#include <QFile>
#include <QGuiApplication> #include <QGuiApplication>
#include <QTimer> #include <QTimer>
#include <signal.h> #include <signal.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h" // #include "neochatroom.h"
#include "notificationsmanager.h" // #include "notificationsmanager.h"
#include "proxycontroller.h" #include "proxycontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
@@ -43,12 +40,13 @@
bool testMode = false; bool testMode = false;
using namespace Quotient; using namespace Integral;
using namespace Qt::Literals::StringLiterals;
Controller::Controller(QObject *parent) Controller::Controller(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
Connection::setRoomType<NeoChatRoom>(); // Connection::setRoomType<NeoChatRoom>();
ProxyController::instance().setApplicationProxy(); ProxyController::instance().setApplicationProxy();
@@ -57,18 +55,18 @@ Controller::Controller(QObject *parent)
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed); connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif #endif
if (!testMode) { // if (!testMode) {
QTimer::singleShot(0, this, [this] { // QTimer::singleShot(0, this, [this] {
invokeLogin(); // invokeLogin();
}); // });
} else { // } else {
auto c = new NeoChatConnection(this); // auto c = new NeoChatConnection(this);
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s); // c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
connect(c, &Connection::connected, this, [c, this]() { // connect(c, &Connection::connected, this, [c, this]() {
m_accountRegistry.add(c); // m_accountRegistry.add(c);
c->syncLoop(); // c->syncLoop();
}); // });
} // }
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] { QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon; delete m_trayIcon;
@@ -99,31 +97,31 @@ Controller::Controller(QObject *parent)
#endif #endif
static int oldAccountCount = 0; static int oldAccountCount = 0;
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() { // connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) { // if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]); // auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect( // connect(
connection, // connection,
&NeoChatConnection::syncDone, // &NeoChatConnection::syncDone,
this, // this,
[this, connection] { // [this, connection] {
if (!m_endpoint.isEmpty()) { // if (!m_endpoint.isEmpty()) {
connection->setupPushNotifications(m_endpoint); // connection->setupPushNotifications(m_endpoint);
} // }
}, // },
Qt::SingleShotConnection); // Qt::SingleShotConnection);
} // }
oldAccountCount = m_accountRegistry.size(); // oldAccountCount = m_accountRegistry.size();
}); // });
#ifdef HAVE_KUNIFIEDPUSH #ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s); auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) { connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
m_endpoint = endpoint; m_endpoint = endpoint;
for (auto &quotientConnection : m_accountRegistry) { // for (auto &quotientConnection : m_accountRegistry) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection); // auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint); // connection->setupPushNotifications(endpoint);
} // }
}); });
connector->registerClient( connector->registerClient(
@@ -140,147 +138,6 @@ Controller &Controller::instance()
return _instance; return _instance;
} }
void Controller::addConnection(NeoChatConnection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
m_accountRegistry.add(c);
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [c] {
c->sync(30000);
c->saveState();
});
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
if (accounts().count() > 1) {
// Only set the connection if the account being logged out is currently active
if (c == activeConnection()) {
setActiveConnection(dynamic_cast<NeoChatConnection *>(accounts().accounts()[0]));
}
} else {
setActiveConnection(nullptr);
}
dropConnection(c);
});
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
m_notificationsManager.handleNotifications(c);
});
c->sync();
Q_EMIT connectionAdded(c);
}
void Controller::dropConnection(NeoChatConnection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
c->disconnect(this);
c->disconnect(&m_notificationsManager);
m_accountRegistry.drop(c);
Q_EMIT connectionDropped(c);
}
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_L1).childGroups();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
accessToken = QString::fromLatin1(accessTokenLoadingJob->binaryData());
} else {
return;
}
auto connection = new NeoChatConnection(account.homeserver());
m_connectionsLoading[accountId] = connection;
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
connection->loadState();
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<RoomCreateEvent>()) {
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
m_connectionsLoading.remove(accountId);
Q_EMIT accountsLoadingChanged();
} else {
connect(
connection->allRooms()[0],
&Room::baseStateLoaded,
this,
[this, connection, accountId]() {
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
m_connectionsLoading.remove(accountId);
Q_EMIT accountsLoadingChanged();
},
Qt::SingleShotConnection);
}
});
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
});
}
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId)
{
qDebug() << "Reading access token from the keychain for" << userId;
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(userId);
// Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, job]() {
if (job->error() == QKeychain::Error::NoError) {
return;
}
switch (job->error()) {
case QKeychain::EntryNotFound:
Q_EMIT errorOccured(i18n("Access token wasn't found: Maybe it was deleted?"));
break;
case QKeychain::AccessDeniedByUser:
case QKeychain::AccessDenied:
Q_EMIT errorOccured(i18n("Access to keychain was denied: Please allow NeoChat to read the access token"));
break;
case QKeychain::NoBackendAvailable:
Q_EMIT errorOccured(i18n("No keychain available: Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
break;
case QKeychain::OtherError:
Q_EMIT errorOccured(i18n("Unable to read access token: %1", job->errorString()));
break;
default:
break;
}
});
job->start();
return job;
}
void Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
{
qDebug() << "Save the access token to the keychain for " << userId;
auto job = new QKeychain::WritePasswordJob(qAppName());
job->setAutoDelete(true);
job->setKey(userId);
job->setBinaryData(accessToken);
connect(job, &QKeychain::WritePasswordJob::finished, this, [job]() {
if (job->error()) {
qWarning() << "Could not save access token to the keychain: " << qPrintable(job->errorString());
}
});
job->start();
}
bool Controller::supportSystemTray() const bool Controller::supportSystemTray() const
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
@@ -322,7 +179,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
if (m_connection != nullptr) { if (m_connection != nullptr) {
m_connection->disconnect(this); m_connection->disconnect(this);
m_connection->disconnect(&m_notificationsManager); // m_connection->disconnect(&m_notificationsManager);
} }
m_connection = connection; m_connection = connection;
@@ -331,7 +188,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
m_connection->refreshBadgeNotificationCount(); m_connection->refreshBadgeNotificationCount();
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount()); updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
connect(m_connection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured); // connect(m_connection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
} }
Q_EMIT activeConnectionChanged(m_connection); Q_EMIT activeConnectionChanged(m_connection);
@@ -346,7 +203,7 @@ void Controller::listenForNotifications()
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit); connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) { connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
instance().m_notificationsManager.postPushNotification(data); // instance().m_notificationsManager.postPushNotification(data);
timer->stop(); timer->stop();
}); });
@@ -360,7 +217,7 @@ void Controller::listenForNotifications()
void Controller::clearInvitationNotification(const QString &roomId) void Controller::clearInvitationNotification(const QString &roomId)
{ {
m_notificationsManager.clearInvitationNotification(roomId); // m_notificationsManager.clearInvitationNotification(roomId);
} }
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count) void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
@@ -402,9 +259,9 @@ bool Controller::isFlatpak() const
#endif #endif
} }
AccountRegistry &Controller::accounts() Accounts &Controller::accounts()
{ {
return m_accountRegistry; return m_accounts;
} }
QString Controller::loadFileContent(const QString &path) const QString Controller::loadFileContent(const QString &path) const
@@ -420,20 +277,6 @@ void Controller::setTestMode(bool test)
testMode = test; testMode = test;
} }
void Controller::removeConnection(const QString &userId)
{
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
// entry for it so we need to check both separately.
if (m_accountsLoading.contains(userId)) {
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
}
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
SettingsGroup("Accounts"_L1).remove(userId);
}
}
bool Controller::csSupported() const bool Controller::csSupported() const
{ {
return true; return true;

View File

@@ -7,8 +7,8 @@
#include <QQmlEngine> #include <QQmlEngine>
#include "neochatconnection.h" #include "neochatconnection.h"
#include "notificationsmanager.h" // #include "notificationsmanager.h"
#include <Quotient/accountregistry.h> #include <Integral/Accounts>
class TrayIcon; class TrayIcon;
@@ -63,21 +63,6 @@ public:
void setActiveConnection(NeoChatConnection *connection); void setActiveConnection(NeoChatConnection *connection);
[[nodiscard]] NeoChatConnection *activeConnection() const; [[nodiscard]] NeoChatConnection *activeConnection() const;
/**
* @brief Add a new connection to the account registry.
*/
void addConnection(NeoChatConnection *c);
/**
* @brief Drop a connection from the account registry.
*/
void dropConnection(NeoChatConnection *c);
/**
* @brief Save an access token to the keychain for the given account.
*/
void saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
[[nodiscard]] bool supportSystemTray() const; [[nodiscard]] bool supportSystemTray() const;
bool isFlatpak() const; bool isFlatpak() const;
@@ -97,12 +82,10 @@ public:
Q_INVOKABLE QString loadFileContent(const QString &path) const; Q_INVOKABLE QString loadFileContent(const QString &path) const;
Quotient::AccountRegistry &accounts(); Integral::Accounts &accounts();
static void setTestMode(bool testMode); static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId);
bool csSupported() const; bool csSupported() const;
/** /**
@@ -122,18 +105,15 @@ private:
QPointer<NeoChatConnection> m_connection; QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr; TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account); Integral::Accounts m_accounts;
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading; QStringList m_accountsLoading;
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading; QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
QString m_endpoint; QString m_endpoint;
QStringList m_shownImages; QStringList m_shownImages;
NotificationsManager m_notificationsManager; // NotificationsManager m_notificationsManager;
private Q_SLOTS: private Q_SLOTS:
void invokeLogin();
void setQuitOnLastWindowClosed(); void setQuitOnLastWindowClosed();
void updateBadgeNotificationCount(NeoChatConnection *connection, int count); void updateBadgeNotificationCount(NeoChatConnection *connection, int count);

View File

@@ -4,12 +4,12 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QQmlEngine>
#include "neochatroom.h"
#include <Quotient/quotient_common.h>
#include <KLocalizedString> #include <KLocalizedString>
#include <Integral/lib.rs.h>
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
class NeoChatRoomType : public QObject class NeoChatRoomType : public QObject
@@ -22,7 +22,7 @@ public:
/** /**
* @brief Defines the room list categories a room can be assigned. * @brief Defines the room list categories a room can be assigned.
*/ */
enum Types { enum Type {
Invited, /**< The user has been invited to the room. */ Invited, /**< The user has been invited to the room. */
Favorite, /**< The room is set as a favourite. */ Favorite, /**< The room is set as a favourite. */
Direct, /**< The room is a direct chat. */ Direct, /**< The room is a direct chat. */
@@ -32,23 +32,23 @@ public:
AddDirect, /**< So we can show the add friend delegate. */ AddDirect, /**< So we can show the add friend delegate. */
TypesCount, /**< Number of different types (this should always be last). */ TypesCount, /**< Number of different types (this should always be last). */
}; };
Q_ENUM(Types); Q_ENUM(Type);
static NeoChatRoomType::Types typeForRoom(const NeoChatRoom *room) static NeoChatRoomType::Type typeForRoom(rust::Box<sdk::RoomListRoom> room)
{ {
if (room->isSpace()) { if (room->is_space()) {
return NeoChatRoomType::Space; return NeoChatRoomType::Space;
} }
if (room->joinState() == Quotient::JoinState::Invite) { if (room->state() == 2) {
return NeoChatRoomType::Invited; return NeoChatRoomType::Invited;
} }
if (room->isFavourite()) { if (room->is_favourite()) {
return NeoChatRoomType::Favorite; return NeoChatRoomType::Favorite;
} }
if (room->isLowPriority()) { if (room->is_low_priority()) {
return NeoChatRoomType::Deprioritized; return NeoChatRoomType::Deprioritized;
} }
if (room->isDirectChat()) { if (false /*room->isDirectChat()*/) {
return NeoChatRoomType::Direct; return NeoChatRoomType::Direct;
} }
return NeoChatRoomType::Normal; return NeoChatRoomType::Normal;

View File

@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com> // SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
@@ -5,8 +6,8 @@
#include <algorithm> #include <algorithm>
#include "neochatconfig.h" // #include "neochatconfig.h"
#include "neochatroom.h" #include <Integral/Utils>
namespace namespace
{ {
@@ -56,27 +57,27 @@ QList<RoomSortParameter::Parameter> RoomSortParameter::allParameterList()
QList<RoomSortParameter::Parameter> RoomSortParameter::currentParameterList() QList<RoomSortParameter::Parameter> RoomSortParameter::currentParameterList()
{ {
QList<RoomSortParameter::Parameter> configParamList; QList<RoomSortParameter::Parameter> configParamList = activitySortPriorities;
switch (static_cast<NeoChatConfig::EnumSortOrder::type>(NeoChatConfig::sortOrder())) { // switch (static_cast<NeoChatConfig::EnumSortOrder::type>(NeoChatConfig::sortOrder())) {
case NeoChatConfig::EnumSortOrder::Activity: // case NeoChatConfig::EnumSortOrder::Activity:
configParamList = activitySortPriorities; // configParamList = activitySortPriorities;
break; // break;
case NeoChatConfig::EnumSortOrder::Alphabetical: // case NeoChatConfig::EnumSortOrder::Alphabetical:
configParamList = alphabeticalSortPriorities; // configParamList = alphabeticalSortPriorities;
break; // break;
case NeoChatConfig::EnumSortOrder::LastMessage: // case NeoChatConfig::EnumSortOrder::LastMessage:
configParamList = lastMessageSortPriorities; // configParamList = lastMessageSortPriorities;
break; // break;
case NeoChatConfig::EnumSortOrder::Custom: { // case NeoChatConfig::EnumSortOrder::Custom: {
const auto intList = NeoChatConfig::customSortOrder(); // const auto intList = NeoChatConfig::customSortOrder();
std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) { // std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) {
return static_cast<Parameter>(param); // return static_cast<Parameter>(param);
}); // });
break; // break;
} // }
default: // default:
break; // break;
} // }
if (configParamList.isEmpty()) { if (configParamList.isEmpty()) {
return activitySortPriorities; return activitySortPriorities;
@@ -90,73 +91,74 @@ void RoomSortParameter::saveNewParameterList(const QList<Parameter> &newList)
std::transform(newList.constBegin(), newList.constEnd(), std::back_inserter(intList), [](Parameter param) { std::transform(newList.constBegin(), newList.constEnd(), std::back_inserter(intList), [](Parameter param) {
return static_cast<int>(param); return static_cast<int>(param);
}); });
NeoChatConfig::setCustomSortOrder(intList); // NeoChatConfig::setCustomSortOrder(intList);
NeoChatConfig::setSortOrder(NeoChatConfig::EnumSortOrder::Custom); // NeoChatConfig::setSortOrder(NeoChatConfig::EnumSortOrder::Custom);
NeoChatConfig::self()->save(); // NeoChatConfig::self()->save();
} }
int RoomSortParameter::compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter(Parameter parameter, rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
switch (parameter) { switch (parameter) {
case AlphabeticalAscending: case AlphabeticalAscending:
return compareParameter<AlphabeticalAscending>(leftRoom, rightRoom); return compareParameter<AlphabeticalAscending>(leftRoom->box_me(), rightRoom->box_me());
case AlphabeticalDescending: case AlphabeticalDescending:
return compareParameter<AlphabeticalDescending>(leftRoom, rightRoom); return compareParameter<AlphabeticalDescending>(leftRoom->box_me(), rightRoom->box_me());
case HasUnread: case HasUnread:
return compareParameter<HasUnread>(leftRoom, rightRoom); return compareParameter<HasUnread>(leftRoom->box_me(), rightRoom->box_me());
case MostUnread: case MostUnread:
return compareParameter<MostUnread>(leftRoom, rightRoom); return compareParameter<MostUnread>(leftRoom->box_me(), rightRoom->box_me());
case HasHighlight: case HasHighlight:
return compareParameter<HasHighlight>(leftRoom, rightRoom); return compareParameter<HasHighlight>(leftRoom->box_me(), rightRoom->box_me());
case MostHighlights: case MostHighlights:
return compareParameter<MostHighlights>(leftRoom, rightRoom); return compareParameter<MostHighlights>(leftRoom->box_me(), rightRoom->box_me());
case LastActive: case LastActive:
return compareParameter<LastActive>(leftRoom, rightRoom); return compareParameter<LastActive>(leftRoom->box_me(), rightRoom->box_me());
default: default:
return 0; return 0;
} }
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return -typeCompare(leftRoom->displayName(), rightRoom->displayName()); return -typeCompare(stringFromRust(leftRoom->display_name()), stringFromRust(rightRoom->display_name()));
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(rust::Box<sdk::RoomListRoom> leftRoom,
rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return typeCompare(leftRoom->displayName(), rightRoom->displayName()); return typeCompare(stringFromRust(leftRoom->display_name()), stringFromRust(rightRoom->display_name()));
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return typeCompare(leftRoom->contextAwareNotificationCount() > 0, rightRoom->contextAwareNotificationCount() > 0); return typeCompare(leftRoom->num_unread_messages() > 0, rightRoom->num_unread_messages() > 0);
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return typeCompare(leftRoom->contextAwareNotificationCount(), rightRoom->contextAwareNotificationCount()); return typeCompare(leftRoom->num_unread_messages(), rightRoom->num_unread_messages());
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
const auto leftHighlight = leftRoom->highlightCount() > 0 && leftRoom->contextAwareNotificationCount() > 0; const auto leftHighlight = leftRoom->num_unread_mentions() > 0 && leftRoom->num_unread_messages() > 0;
const auto rightHighlight = rightRoom->highlightCount() > 0 && rightRoom->contextAwareNotificationCount() > 0; const auto rightHighlight = rightRoom->num_unread_mentions() > 0 && rightRoom->num_unread_messages() > 0;
return typeCompare(leftHighlight, rightHighlight); return typeCompare(leftHighlight, rightHighlight);
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return typeCompare(int(leftRoom->highlightCount()), int(rightRoom->highlightCount())); return typeCompare(int(leftRoom->num_unread_mentions()), int(rightRoom->num_unread_mentions()));
} }
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom) int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom)
{ {
return typeCompare(leftRoom->lastActiveTime(), rightRoom->lastActiveTime()); return 1;
} }

View File

@@ -8,7 +8,9 @@
#include <KLocalizedString> #include <KLocalizedString>
class NeoChatRoom; #include <Integral/lib.rs.h>
class Room;
/** /**
* @class RoomSortParameter * @class RoomSortParameter
@@ -116,27 +118,29 @@ public:
* *
* @sa Parameter * @sa Parameter
*/ */
static int compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); static int compareParameter(Parameter parameter, rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);
private: private:
template<Parameter parameter> template<Parameter parameter>
static int compareParameter(NeoChatRoom *, NeoChatRoom *) static int compareParameter(rust::Box<sdk::RoomListRoom>, rust::Box<sdk::RoomListRoom>)
{ {
return false; return false;
} }
}; };
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(rust::Box<sdk::RoomListRoom> leftRoom,
rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(rust::Box<sdk::RoomListRoom> leftRoom,
rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);
template<> template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom); int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(rust::Box<sdk::RoomListRoom> leftRoom, rust::Box<sdk::RoomListRoom> rightRoom);

View File

@@ -5,44 +5,36 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <Quotient/accountregistry.h> #include <Integral/Accounts>
#include <Quotient/e2ee/sssshandler.h> #include <Integral/Homeserver>
#include <Quotient/keyimport.h>
#include <Quotient/keyverificationsession.h>
#include <Quotient/roommember.h>
#include "controller.h" #include "controller.h"
#include "neochatconfig.h" #include "neochatconfig.h"
struct ForeignAccountRegistry { struct ForeignAccounts {
Q_GADGET Q_GADGET
QML_FOREIGN(Quotient::AccountRegistry) QML_ELEMENT
QML_NAMED_ELEMENT(AccountRegistry)
QML_SINGLETON QML_SINGLETON
public: QML_FOREIGN(Integral::Accounts)
static Quotient::AccountRegistry *create(QQmlEngine *, QJSEngine *) QML_NAMED_ELEMENT(Accounts)
static Integral::Accounts *create(QQmlEngine *, QJSEngine *)
{ {
QQmlEngine::setObjectOwnership(&Controller::instance().accounts(), QQmlEngine::CppOwnership); auto &accounts = Controller::instance().accounts();
return &Controller::instance().accounts(); QQmlEngine::setObjectOwnership(&accounts, QQmlEngine::CppOwnership);
return &accounts;
} }
}; };
struct ForeignKeyVerificationSession { struct ForeignHomeserver {
Q_GADGET Q_GADGET
QML_FOREIGN(Quotient::KeyVerificationSession) QML_ELEMENT
QML_NAMED_ELEMENT(KeyVerificationSession) QML_FOREIGN(Integral::Homeserver)
QML_UNCREATABLE("") QML_NAMED_ELEMENT(Homeserver)
}; };
struct ForeignSSSSHandler { struct ForeignConnection {
Q_GADGET Q_GADGET
QML_FOREIGN(Quotient::SSSSHandler) QML_ELEMENT
QML_NAMED_ELEMENT(SSSSHandler) QML_FOREIGN(Integral::Connection)
}; QML_NAMED_ELEMENT(Connection)
struct ForeignKeyImport {
Q_GADGET
QML_SINGLETON
QML_FOREIGN(Quotient::KeyImport)
QML_NAMED_ELEMENT(KeyImport)
}; };

View File

@@ -16,8 +16,15 @@ LoginStep {
onActiveFocusChanged: if (activeFocus) onActiveFocusChanged: if (activeFocus)
matrixIdField.forceActiveFocus() matrixIdField.forceActiveFocus()
Component.onCompleted: { property Homeserver homeserver
LoginHelper.matrixId = "";
Timer {
id: timer
interval: 500
repeat: false
onTriggered: if (matrixIdField.text.length > 0) {
root.homeserver.resolveFromMatrixId(matrixIdField.text)
}
} }
FormCard.FormTextFieldDelegate { FormCard.FormTextFieldDelegate {
@@ -26,7 +33,7 @@ LoginStep {
placeholderText: "@user:example.org" placeholderText: "@user:example.org"
Accessible.name: i18n("Matrix ID") Accessible.name: i18n("Matrix ID")
onTextChanged: { onTextChanged: {
LoginHelper.matrixId = text; timer.restart()
} }
Keys.onReturnPressed: { Keys.onReturnPressed: {
@@ -35,17 +42,17 @@ LoginStep {
} }
nextAction: Kirigami.Action { nextAction: Kirigami.Action {
text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue") // text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue")
onTriggered: { onTriggered: {
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) { if (root.homeserver.ssoLoginSupported && root.homeserver.passwordLoginSupported) {
processed("LoginMethod"); processed("LoginMethod");
} else if (LoginHelper.supportsSso) { } else if (root.homeserver.ssoLoginSupported) {
processed("Sso"); processed("Sso");
} else { } else {
processed("Password"); processed("Password");
} }
} }
enabled: LoginHelper.homeserverReachable enabled: root.homeserver.loginFlowsLoaded
} }
previousAction: Kirigami.Action { previousAction: Kirigami.Action {
onTriggered: { onTriggered: {

View File

@@ -12,6 +12,8 @@ import org.kde.neochat
LoginStep { LoginStep {
id: root id: root
property Homeserver homeserver
Connections { Connections {
target: LoginHelper target: LoginHelper
function onConnected() { function onConnected() {
@@ -26,7 +28,6 @@ LoginStep {
id: passwordField id: passwordField
label: i18n("Password:") label: i18n("Password:")
onTextChanged: LoginHelper.password = text
enabled: !LoginHelper.isLoggingIn enabled: !LoginHelper.isLoggingIn
echoMode: TextInput.Password echoMode: TextInput.Password
Accessible.name: i18n("Password") Accessible.name: i18n("Password")
@@ -39,10 +40,10 @@ LoginStep {
nextAction: Kirigami.Action { nextAction: Kirigami.Action {
text: i18nc("@action:button", "Login") text: i18nc("@action:button", "Login")
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn // enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
onTriggered: { onTriggered: {
root.clearError(); root.clearError();
LoginHelper.login(); let pending = Accounts.loginWithPassword(root.homeserver.matrixId, passwordField.text)
} }
} }
previousAction: Kirigami.Action { previousAction: Kirigami.Action {

View File

@@ -27,6 +27,10 @@ Kirigami.Page {
title: i18n("Welcome") title: i18n("Welcome")
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
Homeserver {
id: homeserver
}
header: QQC2.Control { header: QQC2.Control {
topPadding: 0 topPadding: 0
bottomPadding: 0 bottomPadding: 0
@@ -81,7 +85,7 @@ Kirigami.Page {
FormCard.FormHeader { FormCard.FormHeader {
id: existingAccountsHeader id: existingAccountsHeader
title: i18nc("@title", "Continue with an existing account") title: i18nc("@title", "Continue with an existing account")
visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting // visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting
maximumWidth: Kirigami.Units.gridUnit * 20 maximumWidth: Kirigami.Units.gridUnit * 20
} }
@@ -90,15 +94,21 @@ Kirigami.Page {
maximumWidth: Kirigami.Units.gridUnit * 20 maximumWidth: Kirigami.Units.gridUnit * 20
Repeater { Repeater {
id: loadedAccounts id: loadedAccounts
model: AccountRegistry model: Accounts
delegate: FormCard.FormButtonDelegate { delegate: FormCard.FormButtonDelegate {
id: delegate id: delegate
required property string userId required property string matrixId
required property NeoChatConnection connection required property string displayName
required property string avatarUrl
required property int index
required property bool ready
required property Connection connection
text: QmlUtils.escapeString(connection.localUser.displayName) enabled: ready
description: connection.localUser.id
text: QmlUtils.escapeString(delegate.displayName)
description: delegate.matrixId
leadingPadding: Kirigami.Units.largeSpacing leadingPadding: Kirigami.Units.largeSpacing
onClicked: { onClicked: {
@@ -109,62 +119,12 @@ Kirigami.Page {
id: avatar id: avatar
name: delegate.text name: delegate.text
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl. // Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : "" source: delegate.avatarUrl
implicitWidth: Kirigami.Units.iconSizes.medium implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium implicitHeight: Kirigami.Units.iconSizes.medium
} }
} }
} }
Repeater {
id: loadingAccounts
model: Controller.accountsLoading
delegate: FormCard.AbstractFormDelegate {
id: loadingDelegate
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
background: null
contentItem: RowLayout {
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: i18nc("As in 'this account is still loading'", "%1 (loading)", modelData)
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
color: Kirigami.Theme.disabledTextColor
Accessible.ignored: true // base class sets this text on root already
}
QQC2.ToolButton {
text: i18nc("@action:button", "Log out of this account")
icon.name: "im-kick-user"
onClicked: Controller.removeConnection(modelData)
display: QQC2.Button.IconOnly
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
enabled: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
}
FormCard.FormArrow {
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
direction: Qt.RightArrow
visible: root.background.visible
}
}
}
onCountChanged: {
if (loadingAccounts.count === 0 && loadedAccounts.count === 1 && showExisting) {
Controller.activeConnection = AccountRegistry.data(AccountRegistry.index(0, 0), 257);
root.connectionChosen();
}
}
}
} }
FormCard.FormHeader { FormCard.FormHeader {
@@ -188,6 +148,7 @@ Kirigami.Page {
root.currentStepString = nextStep; root.currentStepString = nextStep;
headerMessage.text = ""; headerMessage.text = "";
headerMessage.visible = false; headerMessage.visible = false;
module.item.homeserver = homeserver
if (!module.item.noControls) { if (!module.item.noControls) {
module.item.forceActiveFocus(); module.item.forceActiveFocus();
} else { } else {
@@ -211,33 +172,33 @@ Kirigami.Page {
} }
} }
Connections { // Connections {
target: Registration // target: Registration
//
function onNextStepChanged() { // function onNextStepChanged() {
if (Registration.nextStep === "m.login.recaptcha") { // if (Registration.nextStep === "m.login.recaptcha") {
stepConnections.onProcessed("Captcha"); // stepConnections.onProcessed("Captcha");
} // }
if (Registration.nextStep === "m.login.terms") { // if (Registration.nextStep === "m.login.terms") {
stepConnections.onProcessed("Terms"); // stepConnections.onProcessed("Terms");
} // }
if (Registration.nextStep === "m.login.email.identity") { // if (Registration.nextStep === "m.login.email.identity") {
stepConnections.onProcessed("Email"); // stepConnections.onProcessed("Email");
} // }
if (Registration.nextStep === "loading") { // if (Registration.nextStep === "loading") {
stepConnections.onProcessed("Loading"); // stepConnections.onProcessed("Loading");
} // }
} // }
} // }
Connections { // Connections {
target: LoginHelper // target: LoginHelper
//
function onLoginErrorOccured(message) { // function onLoginErrorOccured(message) {
headerMessage.text = message; // headerMessage.text = message;
headerMessage.visible = message.length > 0; // headerMessage.visible = message.length > 0;
headerMessage.type = Kirigami.MessageType.Error; // headerMessage.type = Kirigami.MessageType.Error;
} // }
} // }
} }
FormCard.FormDelegateSeparator { FormCard.FormDelegateSeparator {
@@ -276,11 +237,11 @@ Kirigami.Page {
} }
} }
Component.onCompleted: { // Component.onCompleted: {
LoginHelper.init(); // LoginHelper.init();
module.item.forceActiveFocus(); // module.item.forceActiveFocus();
Registration.username = ""; // Registration.username = "";
Registration.password = ""; // Registration.password = "";
Registration.email = ""; // Registration.email = "";
} // }
} }

View File

@@ -13,7 +13,12 @@
#include <QQuickStyle> #include <QQuickStyle>
#include <QQuickWindow> #include <QQuickWindow>
#include <QtQml/QQmlExtensionPlugin> #include <QtQml/QQmlExtensionPlugin>
#include <Quotient/connection.h>
#include <Integral/Connection_p>
#include <Integral/NetworkAccessManager>
#include <Integral/PendingConnection>
// #include <Quotient/connection.h>
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include <QGuiApplication> #include <QGuiApplication>
@@ -43,31 +48,35 @@
#include "neochat-version.h" #include "neochat-version.h"
#include <Quotient/networkaccessmanager.h> // #include <Quotient/networkaccessmanager.h>
#include "blurhashimageprovider.h" #include "blurhashimageprovider.h"
#include "colorschemer.h" #include "colorschemer.h"
#include "controller.h" // #include "controller.h"
#include "logger.h" #include "logger.h"
#include "roommanager.h" // #include "roommanager.h"
#include "sharehandler.h" // #include "sharehandler.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "windowcontroller.h" #include "windowcontroller.h"
#ifdef HAVE_RUNNER #ifdef HAVE_RUNNER
#include "runner.h" // #include "runner.h"
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusMetaType> #include <QDBusMetaType>
#endif #endif
#if defined(HAVE_RUNNER) && defined(HAVE_KUNIFIEDPUSH) #if defined(HAVE_RUNNER) && defined(HAVE_KUNIFIEDPUSH)
#include "fakerunner.h" // #include "fakerunner.h"
#endif #endif
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
#include <Windows.h> #include <Windows.h>
#endif #endif
using namespace Quotient; using namespace Qt::Literals::StringLiterals;
using namespace Integral;
void qml_register_types_org_kde_neochat(); void qml_register_types_org_kde_neochat();
@@ -158,14 +167,14 @@ int main(int argc, char *argv[])
about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails"));
about.setOrganizationDomain("kde.org"); about.setOrganizationDomain("kde.org");
about.addComponent(u"libQuotient"_s, // about.addComponent(u"libQuotient"_s,
i18n("A Qt 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>)", // i18nc("<version number> (built against <possibly different version number>)",
"%1 (built against %2)", // "%1 (built against %2)",
Quotient::versionString(), // Quotient::versionString(),
QStringLiteral(Quotient_VERSION_STRING)), // QStringLiteral(Quotient_VERSION_STRING)),
u"https://github.com/quotient-im/libquotient"_s, // u"https://github.com/quotient-im/libquotient"_s,
KAboutLicense::LGPL_V2_1); // KAboutLicense::LGPL_V2_1);
KAboutData::setApplicationData(about); KAboutData::setApplicationData(about);
QGuiApplication::setWindowIcon(QIcon::fromTheme(u"org.kde.neochat"_s)); QGuiApplication::setWindowIcon(QIcon::fromTheme(u"org.kde.neochat"_s));
@@ -174,10 +183,13 @@ int main(int argc, char *argv[])
KCrash::initialize(); KCrash::initialize();
#endif #endif
PendingConnection::setConnectionType<NeoChatConnection>();
Connection::setRoomType<NeoChatRoom>();
initLogging(); initLogging();
Connection::setEncryptionDefault(true); // Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true); // Connection::setDirectChatEncryptionDefault(true);
#ifdef NEOCHAT_FLATPAK #ifdef NEOCHAT_FLATPAK
// Copy over the included FontConfig configuration to the // Copy over the included FontConfig configuration to the
@@ -185,7 +197,7 @@ int main(int argc, char *argv[])
QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s); QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s);
#endif #endif
ColorSchemer colorScheme; // ColorSchemer colorScheme;
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(i18n("Client for the matrix communication protocol")); parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
@@ -208,7 +220,7 @@ int main(int argc, char *argv[])
about.setupCommandLine(&parser); about.setupCommandLine(&parser);
parser.process(app); parser.process(app);
about.processCommandLine(&parser); about.processCommandLine(&parser);
Controller::setTestMode(parser.isSet("test"_L1)); // Controller::setTestMode(parser.isSet("test"_L1));
#ifdef HAVE_KUNIFIEDPUSH #ifdef HAVE_KUNIFIEDPUSH
if (parser.isSet(dbusActivatedOption)) { if (parser.isSet(dbusActivatedOption)) {
@@ -220,10 +232,10 @@ int main(int argc, char *argv[])
// Because KRunner may call us on the D-Bus (under the same service name org.kde.neochat) then it may // Because KRunner may call us on the D-Bus (under the same service name org.kde.neochat) then it may
// accidentally activate us for push notifications instead. If this happens, then immediately quit if the fake // accidentally activate us for push notifications instead. If this happens, then immediately quit if the fake
// runner is called. // runner is called.
QDBusConnection::sessionBus().registerObject("/RoomRunner"_L1, new FakeRunner(), QDBusConnection::ExportScriptableContents); // QDBusConnection::sessionBus().registerObject("/RoomRunner"_L1, new FakeRunner(), QDBusConnection::ExportScriptableContents);
#endif #endif
Controller::listenForNotifications(); // Controller::listenForNotifications();
return QCoreApplication::exec(); return QCoreApplication::exec();
} }
#endif #endif
@@ -239,51 +251,51 @@ int main(int argc, char *argv[])
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin) Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat(); qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s); // qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
#ifdef HAVE_KDBUSADDONS // #ifdef HAVE_KDBUSADDONS
service.connect(&service, // service.connect(&service,
&KDBusService::activateRequested, // &KDBusService::activateRequested,
&RoomManager::instance(), // &RoomManager::instance(),
[&engine](const QStringList &arguments, const QString &workingDirectory) { // [&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory); // Q_UNUSED(workingDirectory);
//
QWindow *window = windowFromEngine(&engine); // QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window); // KWindowSystem::updateStartupId(window);
//
WindowController::instance().showAndRaiseWindow(QString()); // // WindowController::instance().showAndRaiseWindow(QString());
//
// Open matrix uri // // Open matrix uri
if (arguments.isEmpty()) { // if (arguments.isEmpty()) {
return; // return;
} // }
//
auto args = arguments; // auto args = arguments;
args.removeFirst(); // args.removeFirst();
if (args.length() == 2 && args[0] == "--share"_L1) { // if (args.length() == 2 && args[0] == "--share"_L1) {
ShareHandler::instance().setText(args[1]); // // ShareHandler::instance().setText(args[1]);
return; // return;
} // }
//
for (const auto &arg : args) { // for (const auto &arg : args) {
RoomManager::instance().resolveResource(arg); // // RoomManager::instance().resolveResource(arg);
} // }
}); // });
#endif // #endif
engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory()); engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
if (parser.isSet("ignore-ssl-errors"_L1)) { if (parser.isSet("ignore-ssl-errors"_L1)) {
QObject::connect(NetworkAccessManager::instance(), &QNetworkAccessManager::sslErrors, NetworkAccessManager::instance(), [](QNetworkReply *reply) { // QObject::connect(NetworkAccessManager::instance(), &QNetworkAccessManager::sslErrors, NetworkAccessManager::instance(), [](QNetworkReply *reply) {
reply->ignoreSslErrors(); // reply->ignoreSslErrors();
}); // });
} }
if (parser.isSet("share"_L1)) { if (parser.isSet("share"_L1)) {
ShareHandler::instance().setText(parser.value(shareOption)); // ShareHandler::instance().setText(parser.value(shareOption));
} }
engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider); engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider);
@@ -294,12 +306,12 @@ int main(int argc, char *argv[])
} }
if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_L1)) { if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_L1)) {
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]); // RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);
} }
#ifdef HAVE_RUNNER #ifdef HAVE_RUNNER
auto runner = Runner::create(&engine, &engine); // auto runner = Runner::create(&engine, &engine);
QDBusConnection::sessionBus().registerObject("/RoomRunner"_L1, runner, QDBusConnection::ExportScriptableContents); // QDBusConnection::sessionBus().registerObject("/RoomRunner"_L1, runner, QDBusConnection::ExportScriptableContents);
#endif #endif
QWindow *window = windowFromEngine(&engine); QWindow *window = windowFromEngine(&engine);

View File

@@ -4,23 +4,23 @@
#include "completionmodel.h" #include "completionmodel.h"
#include <QDebug> #include <QDebug>
#include "actionsmodel.h" // #include "actionsmodel.h"
#include "completionproxymodel.h" // #include "completionproxymodel.h"
#include "customemojimodel.h" // #include "customemojimodel.h"
#include "emojimodel.h" // #include "emojimodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "roommanager.h" // #include "roommanager.h"
#include "userlistmodel.h" // #include "userlistmodel.h"
CompletionModel::CompletionModel(QObject *parent) CompletionModel::CompletionModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_filterModel(new CompletionProxyModel()) // , m_filterModel(new CompletionProxyModel())
, m_userListModel(RoomManager::instance().userListModel()) // , m_userListModel(RoomManager::instance().userListModel())
, m_emojiModel(new QConcatenateTablesProxyModel(this)) , m_emojiModel(new QConcatenateTablesProxyModel(this))
{ {
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion); connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
m_emojiModel->addSourceModel(&CustomEmojiModel::instance()); // m_emojiModel->addSourceModel(&CustomEmojiModel::instance());
m_emojiModel->addSourceModel(&EmojiModel::instance()); // m_emojiModel->addSourceModel(&EmojiModel::instance());
} }
QString CompletionModel::text() const QString CompletionModel::text() const
@@ -41,67 +41,68 @@ int CompletionModel::rowCount(const QModelIndex &parent) const
if (m_autoCompletionType == None) { if (m_autoCompletionType == None) {
return 0; return 0;
} }
return m_filterModel->rowCount(); // return m_filterModel->rowCount();
return {};
} }
QVariant CompletionModel::data(const QModelIndex &index, int role) const QVariant CompletionModel::data(const QModelIndex &index, int role) const
{ {
if (index.row() < 0 || index.row() >= m_filterModel->rowCount()) { // if (index.row() < 0 || index.row() >= m_filterModel->rowCount()) {
return {}; // return {};
} // }
auto filterIndex = m_filterModel->index(index.row(), 0); // auto filterIndex = m_filterModel->index(index.row(), 0);
if (m_autoCompletionType == User) { // if (m_autoCompletionType == User) {
if (role == DisplayNameRole) { // if (role == DisplayNameRole) {
return m_filterModel->data(filterIndex, UserListModel::DisplayNameRole); // return m_filterModel->data(filterIndex, UserListModel::DisplayNameRole);
} // }
if (role == SubtitleRole) { // if (role == SubtitleRole) {
return m_filterModel->data(filterIndex, UserListModel::UserIdRole); // return m_filterModel->data(filterIndex, UserListModel::UserIdRole);
} // }
if (role == IconNameRole) { // if (role == IconNameRole) {
return m_filterModel->data(filterIndex, UserListModel::AvatarRole); // return m_filterModel->data(filterIndex, UserListModel::AvatarRole);
} // }
} // }
//
if (m_autoCompletionType == Command) { // if (m_autoCompletionType == Command) {
if (role == DisplayNameRole) { // if (role == DisplayNameRole) {
return u"%1 %2"_s.arg(m_filterModel->data(filterIndex, ActionsModel::Prefix).toString(), // return u"%1 %2"_s.arg(m_filterModel->data(filterIndex, ActionsModel::Prefix).toString(),
m_filterModel->data(filterIndex, ActionsModel::Parameters).toString()); // m_filterModel->data(filterIndex, ActionsModel::Parameters).toString());
} // }
if (role == SubtitleRole) { // if (role == SubtitleRole) {
return m_filterModel->data(filterIndex, ActionsModel::Description); // return m_filterModel->data(filterIndex, ActionsModel::Description);
} // }
if (role == IconNameRole) { // if (role == IconNameRole) {
return u"invalid"_s; // return u"invalid"_s;
} // }
if (role == ReplacedTextRole) { // if (role == ReplacedTextRole) {
return m_filterModel->data(filterIndex, ActionsModel::Prefix); // return m_filterModel->data(filterIndex, ActionsModel::Prefix);
} // }
} // }
if (m_autoCompletionType == Room) { // if (m_autoCompletionType == Room) {
if (role == DisplayNameRole) { // if (role == DisplayNameRole) {
return m_filterModel->data(filterIndex, RoomListModel::DisplayNameRole); // return m_filterModel->data(filterIndex, RoomListModel::DisplayNameRole);
} // }
if (role == SubtitleRole) { // if (role == SubtitleRole) {
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole); // return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
} // }
if (role == IconNameRole) { // if (role == IconNameRole) {
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString(); // return m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
} // }
} // }
if (m_autoCompletionType == Emoji) { // if (m_autoCompletionType == Emoji) {
if (role == DisplayNameRole) { // if (role == DisplayNameRole) {
return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole); // return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole);
} // }
if (role == IconNameRole) { // if (role == IconNameRole) {
return m_filterModel->data(filterIndex, CustomEmojiModel::MxcUrl); // return m_filterModel->data(filterIndex, CustomEmojiModel::MxcUrl);
} // }
if (role == ReplacedTextRole) { // if (role == ReplacedTextRole) {
return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole); // return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole);
} // }
if (role == SubtitleRole) { // if (role == SubtitleRole) {
return m_filterModel->data(filterIndex, EmojiModel::DescriptionRole); // return m_filterModel->data(filterIndex, EmojiModel::DescriptionRole);
} // }
} // }
return {}; return {};
} }
@@ -118,50 +119,50 @@ QHash<int, QByteArray> CompletionModel::roleNames() const
void CompletionModel::updateCompletion() void CompletionModel::updateCompletion()
{ {
if (text().startsWith(QLatin1Char('@'))) { // if (text().startsWith(QLatin1Char('@'))) {
m_filterModel->setSourceModel(m_userListModel); // m_filterModel->setSourceModel(m_userListModel);
m_filterModel->setFilterRole(UserListModel::UserIdRole); // m_filterModel->setFilterRole(UserListModel::UserIdRole);
m_filterModel->setSecondaryFilterRole(UserListModel::DisplayNameRole); // m_filterModel->setSecondaryFilterRole(UserListModel::DisplayNameRole);
m_filterModel->setFullText(m_fullText); // m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text); // m_filterModel->setFilterText(m_text);
m_autoCompletionType = User; // m_autoCompletionType = User;
m_filterModel->invalidate(); // m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char('/'))) { // } else if (text().startsWith(QLatin1Char('/'))) {
m_filterModel->setSourceModel(&ActionsModel::instance()); // m_filterModel->setSourceModel(&ActionsModel::instance());
m_filterModel->setFilterRole(ActionsModel::Prefix); // m_filterModel->setFilterRole(ActionsModel::Prefix);
m_filterModel->setSecondaryFilterRole(-1); // m_filterModel->setSecondaryFilterRole(-1);
m_filterModel->setFullText(m_fullText); // m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text.mid(1)); // m_filterModel->setFilterText(m_text.mid(1));
m_autoCompletionType = Command; // m_autoCompletionType = Command;
m_filterModel->invalidate(); // m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char('#'))) { // } else if (text().startsWith(QLatin1Char('#'))) {
m_autoCompletionType = Room; // m_autoCompletionType = Room;
m_filterModel->setSourceModel(m_roomListModel); // m_filterModel->setSourceModel(m_roomListModel);
m_filterModel->setFilterRole(RoomListModel::CanonicalAliasRole); // m_filterModel->setFilterRole(RoomListModel::CanonicalAliasRole);
m_filterModel->setSecondaryFilterRole(RoomListModel::DisplayNameRole); // m_filterModel->setSecondaryFilterRole(RoomListModel::DisplayNameRole);
m_filterModel->setFullText(m_fullText); // m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text); // m_filterModel->setFilterText(m_text);
m_filterModel->invalidate(); // m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char(':')) && text().size() > 1 && !text()[1].isUpper() // } else if (text().startsWith(QLatin1Char(':')) && text().size() > 1 && !text()[1].isUpper()
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1 // && (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) { // || (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
m_filterModel->setSourceModel(m_emojiModel); // m_filterModel->setSourceModel(m_emojiModel);
m_autoCompletionType = Emoji; // m_autoCompletionType = Emoji;
m_filterModel->setFilterRole(CustomEmojiModel::Name); // m_filterModel->setFilterRole(CustomEmojiModel::Name);
m_filterModel->setSecondaryFilterRole(EmojiModel::DescriptionRole); // m_filterModel->setSecondaryFilterRole(EmojiModel::DescriptionRole);
m_filterModel->setFullText(m_fullText); // m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text); // m_filterModel->setFilterText(m_text);
m_filterModel->invalidate(); // m_filterModel->invalidate();
} else { // } else {
m_autoCompletionType = None; // m_autoCompletionType = None;
} // }
beginResetModel(); beginResetModel();
endResetModel(); endResetModel();
} }
NeoChatRoom *CompletionModel::room() const NeoChatRoom *CompletionModel::room() const
{ {
return m_room; return m_room.get();
} }
void CompletionModel::setRoom(NeoChatRoom *room) void CompletionModel::setRoom(NeoChatRoom *room)

View File

@@ -7,7 +7,7 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include "roomlistmodel.h" // #include "roomlistmodel.h"
class CompletionProxyModel; class CompletionProxyModel;
class UserListModel; class UserListModel;
@@ -47,7 +47,7 @@ class CompletionModel : public QAbstractListModel
/** /**
* @brief The RoomListModel to be used for room completions. * @brief The RoomListModel to be used for room completions.
*/ */
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged) // Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged)
public: public:
/** /**

View File

@@ -5,10 +5,10 @@
#include <QDebug> #include <QDebug>
#include <Quotient/converters.h> // #include <Quotient/converters.h>
#include <Quotient/csapi/definitions/push_ruleset.h> // #include <Quotient/csapi/definitions/push_ruleset.h>
#include <Quotient/csapi/pushrules.h> // #include <Quotient/csapi/pushrules.h>
#include <Quotient/jobs/basejob.h> // #include <Quotient/jobs/basejob.h>
#include "neochatconfig.h" #include "neochatconfig.h"
@@ -74,18 +74,18 @@ void PushRuleModel::updateNotificationRules(const QString &type)
return; return;
} }
const QJsonObject ruleDataJson = m_connection->accountDataJson(u"m.push_rules"_s); // const QJsonObject ruleDataJson = m_connection->accountDataJson(u"m.push_rules"_s);
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"_L1].toObject()); // const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"_L1].toObject());
beginResetModel(); beginResetModel();
m_rules.clear(); m_rules.clear();
// Doing this 5 times because PushRuleset is a struct. // Doing this 5 times because PushRuleset is a struct.
setRules(ruleData.override, PushRuleKind::Override); // setRules(ruleData.override, PushRuleKind::Override);
setRules(ruleData.content, PushRuleKind::Content); // setRules(ruleData.content, PushRuleKind::Content);
setRules(ruleData.room, PushRuleKind::Room); // setRules(ruleData.room, PushRuleKind::Room);
setRules(ruleData.sender, PushRuleKind::Sender); // setRules(ruleData.sender, PushRuleKind::Sender);
setRules(ruleData.underride, PushRuleKind::Underride); // setRules(ruleData.underride, PushRuleKind::Underride);
Q_EMIT globalNotificationsEnabledChanged(); Q_EMIT globalNotificationsEnabledChanged();
Q_EMIT globalNotificationsSetChanged(); Q_EMIT globalNotificationsSetChanged();
@@ -93,28 +93,28 @@ void PushRuleModel::updateNotificationRules(const QString &type)
endResetModel(); endResetModel();
} }
void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind) // void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind)
{ // {
for (const auto &rule : rules) { // for (const auto &rule : rules) {
QString roomId; // QString roomId;
if (rule.conditions.size() > 0) { // if (rule.conditions.size() > 0) {
for (const auto &condition : std::as_const(rule.conditions)) { // for (const auto &condition : std::as_const(rule.conditions)) {
if (condition.key == u"room_id"_s) { // if (condition.key == u"room_id"_s) {
roomId = condition.pattern; // roomId = condition.pattern;
} // }
} // }
} // }
//
m_rules.append(Rule{ // m_rules.append(Rule{
rule.ruleId, // rule.ruleId,
kind, // kind,
variantToAction(rule.actions, rule.enabled), // variantToAction(rule.actions, rule.enabled),
getSection(rule), // getSection(rule),
rule.enabled, // rule.enabled,
roomId, // roomId,
}); // });
} // }
} // }
int PushRuleModel::getRuleIndex(const QString &ruleId) const int PushRuleModel::getRuleIndex(const QString &ruleId) const
{ {
@@ -126,51 +126,51 @@ int PushRuleModel::getRuleIndex(const QString &ruleId) const
return -1; return -1;
} }
PushRuleSection::Section PushRuleModel::getSection(Quotient::PushRule rule) // PushRuleSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
{ // {
auto ruleId = rule.ruleId; // auto ruleId = rule.ruleId;
//
if (defaultSections.contains(ruleId)) { // if (defaultSections.contains(ruleId)) {
return defaultSections.value(ruleId); // return defaultSections.value(ruleId);
} else { // } else {
if (rule.ruleId.startsWith(u'.')) { // if (rule.ruleId.startsWith(u'.')) {
return PushRuleSection::Unknown; // return PushRuleSection::Unknown;
} // }
/** // /**
* If the rule name resolves to a matrix id for a room that the user is part // * If the rule name resolves to a matrix id for a room that the user is part
* of it shouldn't appear in the global list as it's overriding the global // * of it shouldn't appear in the global list as it's overriding the global
* state for that room. // * state for that room.
* // *
* Rooms that the user hasn't joined shouldn't have a rule. // * Rooms that the user hasn't joined shouldn't have a rule.
*/ // */
if (m_connection->room(ruleId) != nullptr) { // if (m_connection->room(ruleId) != nullptr) {
return PushRuleSection::Undefined; // return PushRuleSection::Undefined;
} // }
/** // /**
* If the rule name resolves to a matrix id for a user it shouldn't appear // * If the rule name resolves to a matrix id for a user it shouldn't appear
* in the global list as it's a rule to block notifications from a user and // * in the global list as it's a rule to block notifications from a user and
* is handled elsewhere. // * is handled elsewhere.
*/ // */
auto testUserId = ruleId; // auto testUserId = ruleId;
// Rules for user matrix IDs often don't have the @ on the beginning so add // // Rules for user matrix IDs often don't have the @ on the beginning so add
// if not there to avoid malformed ID. // // if not there to avoid malformed ID.
if (!testUserId.startsWith(u'@')) { // if (!testUserId.startsWith(u'@')) {
testUserId.prepend(u'@'); // testUserId.prepend(u'@');
} // }
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && m_connection->user(testUserId) != nullptr) { // if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && m_connection->user(testUserId) != nullptr) {
return PushRuleSection::Undefined; // return PushRuleSection::Undefined;
} // }
// If the rule has push conditions and one is a room ID it is a room only keyword. // // If the rule has push conditions and one is a room ID it is a room only keyword.
if (!rule.conditions.isEmpty()) { // if (!rule.conditions.isEmpty()) {
for (const auto &condition : std::as_const(rule.conditions)) { // for (const auto &condition : std::as_const(rule.conditions)) {
if (condition.key == u"room_id"_s) { // if (condition.key == u"room_id"_s) {
return PushRuleSection::RoomKeywords; // return PushRuleSection::RoomKeywords;
} // }
} // }
} // }
return PushRuleSection::Keywords; // return PushRuleSection::Keywords;
} // }
} // }
PushRuleAction::Action PushRuleModel::defaultState() const PushRuleAction::Action PushRuleModel::defaultState() const
{ {
@@ -294,33 +294,33 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
{ {
PushRuleKind::Kind kind = PushRuleKind::Content; PushRuleKind::Kind kind = PushRuleKind::Content;
const QList<QVariant> actions = actionToVariant(m_defaultKeywordAction); const QList<QVariant> actions = actionToVariant(m_defaultKeywordAction);
QList<Quotient::PushCondition> pushConditions; // QList<Quotient::PushCondition> pushConditions;
if (!roomId.isEmpty()) { // if (!roomId.isEmpty()) {
kind = PushRuleKind::Override; // kind = PushRuleKind::Override;
//
Quotient::PushCondition roomCondition; // Quotient::PushCondition roomCondition;
roomCondition.kind = u"event_match"_s; // roomCondition.kind = u"event_match"_s;
roomCondition.key = u"room_id"_s; // roomCondition.key = u"room_id"_s;
roomCondition.pattern = roomId; // roomCondition.pattern = roomId;
pushConditions.append(roomCondition); // pushConditions.append(roomCondition);
//
Quotient::PushCondition keywordCondition; // Quotient::PushCondition keywordCondition;
keywordCondition.kind = u"event_match"_s; // keywordCondition.kind = u"event_match"_s;
keywordCondition.key = u"content.body"_s; // keywordCondition.key = u"content.body"_s;
keywordCondition.pattern = keyword; // keywordCondition.pattern = keyword;
pushConditions.append(keywordCondition); // pushConditions.append(keywordCondition);
} // }
//
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(PushRuleKind::kindString(kind), // auto job = m_connection->callApi<Quotient::SetPushRuleJob>(PushRuleKind::kindString(kind),
keyword, // keyword,
actions, // actions,
QString(), // QString(),
QString(), // QString(),
pushConditions, // pushConditions,
roomId.isEmpty() ? keyword : QString()); // roomId.isEmpty() ? keyword : QString());
connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() { // connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() {
qWarning() << "Unable to set push rule for keyword %1: "_L1.arg(keyword) << job->errorString(); // qWarning() << "Unable to set push rule for keyword %1: "_L1.arg(keyword) << job->errorString();
}); // });
} }
/** /**
@@ -336,20 +336,20 @@ void PushRuleModel::removeKeyword(const QString &keyword)
} }
auto kind = PushRuleKind::kindString(m_rules[index].kind); auto kind = PushRuleKind::kindString(m_rules[index].kind);
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(kind, m_rules[index].id); // auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(kind, m_rules[index].id);
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() { // connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
qWarning() << "Unable to remove push rule for keyword %1: "_L1.arg(m_rules[index].id) << job->errorString(); // qWarning() << "Unable to remove push rule for keyword %1: "_L1.arg(m_rules[index].id) << job->errorString();
}); // });
} }
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled) void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
{ {
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(kind, ruleId); // auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(kind, ruleId);
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled, this]() { // connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled, this]() {
if (job->enabled() != enabled) { // if (job->enabled() != enabled) {
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(kind, ruleId, enabled); // m_connection->callApi<Quotient::SetPushRuleEnabledJob>(kind, ruleId, enabled);
} // }
}); // });
} }
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action) void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action)
@@ -361,7 +361,7 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
actions = actionToVariant(action); actions = actionToVariant(action);
} }
m_connection->callApi<Quotient::SetPushRuleActionsJob>(kind, ruleId, actions); // m_connection->callApi<Quotient::SetPushRuleActionsJob>(kind, ruleId, actions);
} }
PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled) PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
@@ -378,14 +378,14 @@ PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &act
continue; continue;
} }
QJsonObject action = i.toJsonObject(); // QJsonObject action = i.toJsonObject();
if (action["set_tweak"_L1].toString() == u"sound"_s) { // if (action["set_tweak"_L1].toString() == u"sound"_s) {
isNoisy = true; // isNoisy = true;
} else if (action["set_tweak"_L1].toString() == u"highlight"_s) { // } else if (action["set_tweak"_L1].toString() == u"highlight"_s) {
if (action["value"_L1].toString() != u"false"_s) { // if (action["value"_L1].toString() != u"false"_s) {
highlightEnabled = true; // highlightEnabled = true;
} // }
} // }
} }
if (!enabled) { if (!enabled) {
@@ -424,15 +424,15 @@ QList<QVariant> PushRuleModel::actionToVariant(PushRuleAction::Action action, co
actions.append(u"dont_notify"_s); actions.append(u"dont_notify"_s);
} }
if (action == PushRuleAction::Noisy || action == PushRuleAction::NoisyHighlight) { if (action == PushRuleAction::Noisy || action == PushRuleAction::NoisyHighlight) {
QJsonObject soundTweak; // QJsonObject soundTweak;
soundTweak.insert("set_tweak"_L1, u"sound"_s); // soundTweak.insert("set_tweak"_L1, u"sound"_s);
soundTweak.insert("value"_L1, sound); // soundTweak.insert("value"_L1, sound);
actions.append(soundTweak); // actions.append(soundTweak);
} }
if (action == PushRuleAction::Highlight || action == PushRuleAction::NoisyHighlight) { if (action == PushRuleAction::Highlight || action == PushRuleAction::NoisyHighlight) {
QJsonObject highlightTweak; // QJsonObject highlightTweak;
highlightTweak.insert("set_tweak"_L1, u"highlight"_s); // highlightTweak.insert("set_tweak"_L1, u"highlight"_s);
actions.append(highlightTweak); // actions.append(highlightTweak);
} }
return actions; return actions;
@@ -452,7 +452,7 @@ void PushRuleModel::setConnection(NeoChatConnection *connection)
Q_EMIT connectionChanged(); Q_EMIT connectionChanged();
if (m_connection) { if (m_connection) {
connect(m_connection, &NeoChatConnection::accountDataChanged, this, &PushRuleModel::updateNotificationRules); // connect(m_connection, &NeoChatConnection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
updateNotificationRules(u"m.push_rules"_s); updateNotificationRules(u"m.push_rules"_s);
} }
} }

View File

@@ -6,7 +6,7 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QQmlEngine> #include <QQmlEngine>
#include <Quotient/csapi/definitions/push_rule.h> // #include <Quotient/csapi/definitions/push_rule.h>
#include "enums/pushrule.h" #include "enums/pushrule.h"
#include "neochatconnection.h" #include "neochatconnection.h"
@@ -130,10 +130,10 @@ private:
QList<Rule> m_rules; QList<Rule> m_rules;
QPointer<NeoChatConnection> m_connection; QPointer<NeoChatConnection> m_connection;
void setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind); // void setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind);
int getRuleIndex(const QString &ruleId) const; int getRuleIndex(const QString &ruleId) const;
PushRuleSection::Section getSection(Quotient::PushRule rule); // PushRuleSection::Section getSection(Quotient::PushRule rule);
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled); void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action); void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action);

View File

@@ -12,11 +12,11 @@ RoomTreeItem::RoomTreeItem(TreeData data, RoomTreeItem *parent)
bool RoomTreeItem::operator==(const RoomTreeItem &other) const bool RoomTreeItem::operator==(const RoomTreeItem &other) const
{ {
if (std::holds_alternative<NeoChatRoomType::Types>(m_data) && std::holds_alternative<NeoChatRoomType::Types>(other.data())) { if (std::holds_alternative<NeoChatRoomType::Type>(m_data) && std::holds_alternative<NeoChatRoomType::Type>(other.data())) {
return std::get<NeoChatRoomType::Types>(m_data) == std::get<NeoChatRoomType::Types>(m_data); return std::get<NeoChatRoomType::Type>(m_data) == std::get<NeoChatRoomType::Type>(m_data);
} }
if (std::holds_alternative<NeoChatRoom *>(m_data) && std::holds_alternative<NeoChatRoom *>(other.data())) { if (std::holds_alternative<RoomWrapper *>(m_data) && std::holds_alternative<RoomWrapper *>(other.data())) {
return std::get<NeoChatRoom *>(m_data)->id() == std::get<NeoChatRoom *>(m_data)->id(); return (*std::get<RoomWrapper *>(m_data)->item)->id() == (*std::get<RoomWrapper *>(other.data())->item)->id();
} }
return false; return false;
} }
@@ -84,13 +84,13 @@ RoomTreeItem::TreeData RoomTreeItem::data() const
return m_data; return m_data;
} }
std::optional<int> RoomTreeItem::rowForRoom(Quotient::Room *room) const std::optional<int> RoomTreeItem::rowForRoom(rust::Box<sdk::RoomListRoom> room) const
{ {
Q_ASSERT_X(std::holds_alternative<NeoChatRoomType::Types>(m_data), __FUNCTION__, "rowForRoom only works items for rooms not categories"); Q_ASSERT_X(std::holds_alternative<NeoChatRoomType::Type>(m_data), __FUNCTION__, "rowForRoom only works items for rooms not categories");
int i = 0; int i = 0;
for (const auto &child : m_children) { for (const auto &child : m_children) {
if (std::get<NeoChatRoom *>(child->data()) == room) { if ((*std::get<RoomWrapper *>(child->data())->item)->id() == room->id()) {
return i; return i;
} }
i++; i++;

View File

@@ -1,27 +1,28 @@
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu> // SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com> // SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "enums/neochatroomtype.h" #include "neochatroomtype.h"
class NeoChatRoom; namespace sdk
{
struct RoomListRoom;
}
struct RoomWrapper {
std::optional<rust::Box<sdk::RoomListRoom>> item;
};
/** /**
* @class RoomTreeItem * @class RoomTreeItem
* *
* This class defines an item in the space tree hierarchy model. * This class defines an item in a room tree.
*
* @note This is separate from Quotient::Room and NeoChatRoom because we don't have
* full room information for any room/space the user hasn't joined and we
* don't want to create one for ever possible child in a space as that would
* be expensive.
*
* @sa Quotient::Room, NeoChatRoom
*/ */
class RoomTreeItem class RoomTreeItem
{ {
public: public:
using TreeData = std::variant<NeoChatRoom *, NeoChatRoomType::Types>; using TreeData = std::variant<RoomWrapper *, NeoChatRoomType::Type>;
explicit RoomTreeItem(TreeData data, RoomTreeItem *parent = nullptr); explicit RoomTreeItem(TreeData data, RoomTreeItem *parent = nullptr);
@@ -68,7 +69,7 @@ public:
*/ */
TreeData data() const; TreeData data() const;
std::optional<int> rowForRoom(Quotient::Room *room) const; std::optional<int> rowForRoom(rust::Box<sdk::RoomListRoom> room) const;
private: private:
std::vector<std::unique_ptr<RoomTreeItem>> m_children; std::vector<std::unique_ptr<RoomTreeItem>> m_children;

View File

@@ -1,23 +1,45 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: LGPL-2.0-or-later
#include "roomtreemodel.h" #include "roomtreemodel.h"
#include <Quotient/room.h>
#include "eventhandler.h"
#include "neochatconnection.h" #include "neochatconnection.h"
// #include "eventhandler.h"
#include "neochatroomtype.h" #include "neochatroomtype.h"
#include "spacehierarchycache.h" #include "rust/cxx.h"
#include <Integral/lib.rs.h>
// #include "spacehierarchycache.h"
#include <Integral/RoomStream>
#include <Integral/Utils>
using namespace Quotient; using namespace Integral;
class RoomTreeModel::Private
{
public:
QPointer<Integral::Connection> connection;
std::unique_ptr<RoomStream> roomStream = nullptr;
std::unique_ptr<RoomTreeItem> rootItem;
// Since the rooms are streamed as vector diffs we need to keep track of them
// for things like the index value of insert to make sense.
QList<QPersistentModelIndex> roomIndexes;
void roomsUpdate();
void resetTree();
RoomTreeModel *q = nullptr;
};
RoomTreeModel::RoomTreeModel(QObject *parent) RoomTreeModel::RoomTreeModel(QObject *parent)
: QAbstractItemModel(parent) : QAbstractItemModel(parent)
, m_rootItem(new RoomTreeItem(nullptr)) , d(std::make_unique<Private>())
{ {
d->q = this;
} }
RoomTreeModel::~RoomTreeModel() = default;
RoomTreeItem *RoomTreeModel::getItem(const QModelIndex &index) const RoomTreeItem *RoomTreeModel::getItem(const QModelIndex &index) const
{ {
if (index.isValid()) { if (index.isValid()) {
@@ -26,179 +48,226 @@ RoomTreeItem *RoomTreeModel::getItem(const QModelIndex &index) const
return item; return item;
} }
} }
return m_rootItem.get(); return d->rootItem.get();
} }
void RoomTreeModel::resetModel() void RoomTreeModel::resetModel()
{ {
if (m_connection == nullptr) { if (d->connection == nullptr) {
beginResetModel(); beginResetModel();
m_rootItem.reset(); d->rootItem.reset();
d->roomStream.reset();
endResetModel(); endResetModel();
return; return;
} }
beginResetModel(); beginResetModel();
m_rootItem.reset(new RoomTreeItem(nullptr)); d->resetTree();
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) { d->roomStream = d->connection->roomStream();
m_rootItem->insertChild(std::make_unique<RoomTreeItem>(NeoChatRoomType::Types(i), m_rootItem.get())); connect(d->roomStream.get(), &RoomStream::roomsUpdate, this, [this]() {
} d->roomsUpdate();
});
for (const auto &r : m_connection->allRooms()) { d->roomStream->startStream();
const auto room = dynamic_cast<NeoChatRoom *>(r);
const auto type = NeoChatRoomType::typeForRoom(room);
const auto categoryItem = m_rootItem->child(type);
if (categoryItem->insertChild(std::make_unique<RoomTreeItem>(room, categoryItem))) {
connectRoomSignals(room);
}
}
endResetModel(); endResetModel();
} }
void RoomTreeModel::setConnection(NeoChatConnection *connection) void RoomTreeModel::Private::resetTree()
{ {
if (m_connection == connection) { rootItem.reset(new RoomTreeItem(nullptr));
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
rootItem->insertChild(std::make_unique<RoomTreeItem>(NeoChatRoomType::Type(i), rootItem.get()));
}
}
void RoomTreeModel::setConnection(Connection *connection)
{
if (d->connection == connection) {
return; return;
} }
if (m_connection) { if (d->connection) {
disconnect(m_connection.get(), nullptr, this, nullptr); d->connection->disconnect(this);
} }
m_connection = connection; d->connection = connection;
resetModel(); resetModel();
connect(connection, &Connection::newRoom, this, &RoomTreeModel::newRoom);
connect(connection, &Connection::leftRoom, this, &RoomTreeModel::leftRoom);
connect(connection, &Connection::aboutToDeleteRoom, this, &RoomTreeModel::leftRoom);
Q_EMIT connectionChanged(); Q_EMIT connectionChanged();
} }
void RoomTreeModel::newRoom(Room *r) void RoomTreeModel::Private::roomsUpdate()
{ {
const auto room = dynamic_cast<NeoChatRoom *>(r); const auto diff = roomStream->next();
const auto type = NeoChatRoomType::typeForRoom(room);
// Check if the room is already in the model. switch (diff->op()) {
const auto checkRoomIndex = indexForRoom(room); case 0: { // Append
if (checkRoomIndex.isValid()) { for (const auto &it : diff->items_vec()) {
// If the room is in the wrong type category for whatever reason, move it. const auto type = NeoChatRoomType::typeForRoom(it.box_me());
if (checkRoomIndex.parent().row() != type) { const auto parentItem = rootItem->child(type);
moveRoom(room); q->beginInsertRows(q->index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{it.box_me()}, parentItem))) {
// connectRoomSignals(room);
}
q->endInsertRows();
roomIndexes.append(q->indexForRoom(it.box_me()));
} }
return; break;
} }
case 1: { // Clear
const auto parentItem = m_rootItem->child(type); q->beginResetModel();
beginInsertRows(index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount()); resetTree();
parentItem->insertChild(std::make_unique<RoomTreeItem>(room, parentItem)); roomIndexes.clear();
connectRoomSignals(room); q->endResetModel();
endInsertRows(); break;
}
void RoomTreeModel::leftRoom(Room *r)
{
const auto room = dynamic_cast<NeoChatRoom *>(r);
auto index = indexForRoom(room);
if (!index.isValid()) {
return;
} }
case 2: { // Push Front
const auto parentItem = getItem(index.parent()); const auto type = NeoChatRoomType::typeForRoom(diff->item());
Q_ASSERT(parentItem); const auto parentItem = rootItem->child(type);
q->beginInsertRows(q->index(parentItem->row(), 0), 0, 0);
beginRemoveRows(index.parent(), index.row(), index.row()); if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{diff->item()}, parentItem))) {
parentItem->removeChild(index.row()); // connectRoomSignals(room);
room->disconnect(this);
endRemoveRows();
}
void RoomTreeModel::moveRoom(Quotient::Room *room)
{
// We can't assume the type as it has changed so currently the return of
// NeoChatRoomType::typeForRoom doesn't match it's current location. So find the room.
NeoChatRoomType::Types oldType;
int oldRow = -1;
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
const auto categoryItem = m_rootItem->child(i);
const auto row = categoryItem->rowForRoom(room);
if (row) {
oldType = static_cast<NeoChatRoomType::Types>(i);
oldRow = *row;
} }
q->endInsertRows();
roomIndexes.prepend(q->indexForRoom(diff->item()));
break;
} }
case 3: { // Push Back
if (oldRow == -1) { const auto type = NeoChatRoomType::typeForRoom(diff->item());
return; const auto parentItem = rootItem->child(type);
q->beginInsertRows(q->index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{diff->item()}, parentItem))) {
// connectRoomSignals(room);
}
q->endInsertRows();
roomIndexes.append(q->indexForRoom(diff->item()));
break;
} }
auto neochatRoom = dynamic_cast<NeoChatRoom *>(room); case 4: { // Pop Front
const auto newType = NeoChatRoomType::typeForRoom(neochatRoom); const auto index = roomIndexes.front();
if (newType == oldType) { q->beginRemoveRows(index.parent(), index.row(), index.row());
return; const auto parentItem = q->getItem(index.parent());
parentItem->removeChild(index.row());
roomIndexes.removeFirst();
q->endRemoveRows();
break;
} }
case 5: { // Pop Back
const auto index = roomIndexes.back();
q->beginRemoveRows(index.parent(), index.row(), index.row());
const auto parentItem = q->getItem(index.parent());
parentItem->removeChild(index.row());
roomIndexes.removeLast();
q->endRemoveRows();
break;
}
case 6: { // Insert
const auto type = NeoChatRoomType::typeForRoom(diff->item());
const auto parentItem = rootItem->child(type);
q->beginInsertRows(q->index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{diff->item()}, parentItem))) {
// connectRoomSignals(room);
}
q->endInsertRows();
roomIndexes.insert(diff->index(), q->indexForRoom(diff->item()));
break;
}
case 7: { // Set
const auto index = roomIndexes.at(diff->index());
q->beginRemoveRows(index.parent(), index.row(), index.row());
q->getItem(index.parent())->removeChild(index.row());
q->endRemoveRows();
const auto oldParent = index(oldType, 0, {}); const auto type = NeoChatRoomType::typeForRoom(diff->item());
auto oldParentItem = getItem(oldParent); const auto parentItem = rootItem->child(type);
Q_ASSERT(oldParentItem); q->beginInsertRows(q->index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{diff->item()}, parentItem))) {
// connectRoomSignals(room);
}
q->endInsertRows();
roomIndexes[diff->index()] = q->indexForRoom(diff->item());
break;
}
case 8: { // Remove
const auto index = roomIndexes.at(diff->index());
q->beginRemoveRows(index.parent(), index.row(), index.row());
q->getItem(index.parent())->removeChild(index.row());
q->endRemoveRows();
roomIndexes.removeAt(diff->index());
break;
}
case 9: { // Truncate
for (int i = q->rowCount({}) - 1; i >= int(diff->index()); i--) {
const auto index = roomIndexes.at(i);
q->beginRemoveRows(index.parent(), index.row(), index.row());
q->getItem(index.parent())->removeChild(index.row());
q->endRemoveRows();
roomIndexes.removeAt(i);
}
break;
}
case 10: { // Reset
q->beginResetModel();
resetTree();
roomIndexes.clear();
q->endResetModel();
const auto newParent = index(newType, 0, {}); for (const auto &it : diff->items_vec()) {
auto newParentItem = getItem(newParent); const auto type = NeoChatRoomType::typeForRoom(it.box_me());
Q_ASSERT(newParentItem); const auto parentItem = rootItem->child(type);
q->beginInsertRows(q->index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
// HACK: We're doing this as a remove then insert because moving doesn't work if (parentItem->insertChild(std::make_unique<RoomTreeItem>(new RoomWrapper{it.box_me()}, parentItem))) {
// properly with DelegateChooser for whatever reason. // connectRoomSignals(room);
Q_ASSERT(checkIndex(index(oldRow, 0, oldParent), QAbstractItemModel::CheckIndexOption::IndexIsValid)); }
beginRemoveRows(oldParent, oldRow, oldRow); q->endInsertRows();
const bool success = oldParentItem->removeChild(oldRow); roomIndexes.append(q->indexForRoom(it.box_me()));
Q_ASSERT(success); }
endRemoveRows(); break;
beginInsertRows(newParent, newParentItem->childCount(), newParentItem->childCount()); }
newParentItem->insertChild(std::make_unique<RoomTreeItem>(neochatRoom, newParentItem)); }
endInsertRows();
} }
void RoomTreeModel::connectRoomSignals(NeoChatRoom *room) // void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
{ // {
connect(room, &Room::displaynameChanged, this, [this, room] { // connect(room, &Room::displaynameChanged, this, [this, room] {
refreshRoomRoles(room, {DisplayNameRole}); // refreshRoomRoles(room, {DisplayNameRole});
}); // });
connect(room, &Room::unreadStatsChanged, this, [this, room] { // connect(room, &Room::unreadStatsChanged, this, [this, room] {
refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole}); // refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole});
}); // });
connect(room, &Room::avatarChanged, this, [this, room] { // connect(room, &Room::avatarChanged, this, [this, room] {
refreshRoomRoles(room, {AvatarRole}); // refreshRoomRoles(room, {AvatarRole});
}); // });
connect(room, &Room::tagsChanged, this, [this, room] { // connect(room, &Room::tagsChanged, this, [this, room] {
moveRoom(room); // moveRoom(room);
}); // });
connect(room, &Room::joinStateChanged, this, [this, room] { // connect(room, &Room::joinStateChanged, this, [this, room] {
refreshRoomRoles(room); // refreshRoomRoles(room);
}); // });
connect(room, &Room::addedMessages, this, [this, room] { // connect(room, &Room::addedMessages, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole}); // refreshRoomRoles(room, {SubtitleTextRole});
}); // });
connect(room, &Room::pendingEventMerged, this, [this, room] { // connect(room, &Room::pendingEventMerged, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole}); // refreshRoomRoles(room, {SubtitleTextRole});
}); // });
connect(room, &NeoChatRoom::pushNotificationStateChanged, this, [this, room] { // connect(room, &NeoChatRoom::pushNotificationStateChanged, this, [this, room] {
refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole}); // refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole});
}); // });
} // }
void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles) // void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles)
{ // {
const auto index = indexForRoom(room); // const auto index = indexForRoom(room);
if (!index.isValid()) { // if (!index.isValid()) {
qCritical() << "Room" << room->id() << "not found in the room list"; // qCritical() << "Room" << room->id() << "not found in the room list";
return; // return;
} // }
Q_EMIT dataChanged(index, index, roles); // Q_EMIT dataChanged(index, index, roles);
} // }
NeoChatConnection *RoomTreeModel::connection() const Connection *RoomTreeModel::connection() const
{ {
return m_connection; return d->connection;
} }
int RoomTreeModel::columnCount(const QModelIndex &parent) const int RoomTreeModel::columnCount(const QModelIndex &parent) const
@@ -215,7 +284,7 @@ int RoomTreeModel::rowCount(const QModelIndex &parent) const
} }
if (!parent.isValid()) { if (!parent.isValid()) {
parentItem = m_rootItem.get(); parentItem = d->rootItem.get();
} else { } else {
parentItem = static_cast<RoomTreeItem *>(parent.internalPointer()); parentItem = static_cast<RoomTreeItem *>(parent.internalPointer());
} }
@@ -239,7 +308,7 @@ QModelIndex RoomTreeModel::parent(const QModelIndex &index) const
} }
RoomTreeItem *parentItem = childItem->parentItem(); RoomTreeItem *parentItem = childItem->parentItem();
if (parentItem == m_rootItem.get()) { if (parentItem == d->rootItem.get()) {
return QModelIndex(); return QModelIndex();
} }
@@ -295,7 +364,7 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
} }
RoomTreeItem *child = getItem(index); RoomTreeItem *child = getItem(index);
if (std::holds_alternative<NeoChatRoomType::Types>(child->data())) { if (std::holds_alternative<NeoChatRoomType::Type>(child->data())) {
if (role == DisplayNameRole) { if (role == DisplayNameRole) {
return NeoChatRoomType::typeName(index.row()); return NeoChatRoomType::typeName(index.row());
} }
@@ -314,98 +383,91 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return {}; return {};
} }
const auto room = std::get<NeoChatRoom *>(child->data()); const auto room = std::get<RoomWrapper *>(child->data());
Q_ASSERT(room); Q_ASSERT(room);
if (role == DisplayNameRole) { if (role == DisplayNameRole) {
return room->displayName(); return stringFromRust((*room->item)->display_name()).toHtmlEscaped();
} }
if (role == AvatarRole) { if (role == AvatarRole) {
return room->avatarMediaUrl(); return u"%1?user_id=%2"_s.arg(stringFromRust((*room->item)->avatar_url()), d->connection->matrixId());
} }
if (role == CanonicalAliasRole) { if (role == CanonicalAliasRole) {
return room->canonicalAlias(); return stringFromRust((*room->item)->canonical_alias()).toHtmlEscaped();
} }
if (role == TopicRole) { if (role == TopicRole) {
return room->topic(); return stringFromRust((*room->item)->topic()).toHtmlEscaped();
} }
if (role == CategoryRole) { if (role == CategoryRole) {
return NeoChatRoomType::typeForRoom(room); return NeoChatRoomType::typeForRoom((*room->item)->box_me());
} }
if (role == ContextNotificationCountRole) { if (role == ContextNotificationCountRole) {
return int(room->contextAwareNotificationCount()); return int((*room->item)->num_unread_messages());
} }
if (role == HasHighlightNotificationsRole) { if (role == HasHighlightNotificationsRole) {
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0; return (*room->item)->num_unread_mentions() > 0 && (*room->item)->num_unread_messages() > 0;
} }
if (role == JoinStateRole) { if (role == JoinStateRole) {
if (!room->successorId().isEmpty()) { if (!(*room->item)->tombstone()->replacement_room().empty()) {
return u"upgraded"_s; return u"upgraded"_s;
} }
return QVariant::fromValue(room->joinState()); return QVariant::fromValue((*room->item)->state());
} }
if (role == CurrentRoomRole) { if (role == CurrentRoomRole) {
return QVariant::fromValue(room); return {};
// return QVariant::fromValue(room);
} }
if (role == SubtitleTextRole) { if (role == SubtitleTextRole) {
if (room->isInvite()) { return {};
if (room->isDirectChat()) { // if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return i18nc("@info:label", "Invited you to chat"); // return QString();
} // }
return i18nc("@info:label", "%1 invited you", room->member(room->invitingUserId()).displayName()); // return EventHandler::subtitleText(room, room->lastEvent());
}
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return QString();
}
return EventHandler::subtitleText(room, room->lastEvent());
} }
if (role == AvatarImageRole) { if (role == AvatarImageRole) {
return room->avatar(128); return {};
// return room->avatar(128);
} }
if (role == RoomIdRole) { if (role == RoomIdRole) {
return room->id(); return stringFromRust((*room->item)->id()).toHtmlEscaped();
} }
if (role == IsSpaceRole) { if (role == IsSpaceRole) {
return room->isSpace(); return (*room->item)->is_space();
} }
if (role == IsChildSpaceRole) { if (role == IsChildSpaceRole) {
return SpaceHierarchyCache::instance().isChild(room->id()); return false;
// return SpaceHierarchyCache::instance().isChild(room->id());
} }
if (role == ReplacementIdRole) { if (role == ReplacementIdRole) {
return room->successorId(); return stringFromRust((*room->item)->tombstone()->replacement_room()).toHtmlEscaped();
} }
if (role == IsDirectChat) { if (role == IsDirectChat) {
return room->isDirectChat(); return false;
// return room->isDirectChat();
} }
if (role == DelegateTypeRole) { if (role == DelegateTypeRole) {
return u"normal"_s; return u"normal"_s;
} }
if (role == RoomTypeRole) { if (role == RoomTypeRole) {
if (room->creation()) { return stringFromRust((*room->item)->room_type()).toHtmlEscaped();
return room->creation()->contentPart<QString>("type"_L1);
}
} }
return {}; return {};
} }
QModelIndex RoomTreeModel::indexForRoom(NeoChatRoom *room) const QModelIndex RoomTreeModel::indexForRoom(rust::Box<sdk::RoomListRoom> room) const
{ {
if (room == nullptr) {
return {};
}
// Try and find by checking type. // Try and find by checking type.
const auto type = NeoChatRoomType::typeForRoom(room); const auto type = NeoChatRoomType::typeForRoom(room->box_me());
const auto parentItem = m_rootItem->child(type); const auto parentItem = d->rootItem->child(type);
const auto row = parentItem->rowForRoom(room); const auto row = parentItem->rowForRoom(room->box_me());
if (row) { if (row) {
return index(*row, 0, index(type, 0)); return index(*row, 0, index(type, 0));
} }
// Double check that the room isn't in the wrong category. // Double check that the room isn't in the wrong category.
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) { for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
const auto parentItem = m_rootItem->child(i); const auto parentItem = d->rootItem->child(i);
const auto row = parentItem->rowForRoom(room); const auto row = parentItem->rowForRoom(room->box_me());
if (row) { if (row) {
return index(*row, 0, index(i, 0)); return index(*row, 0, index(i, 0));
} }
@@ -414,4 +476,13 @@ QModelIndex RoomTreeModel::indexForRoom(NeoChatRoom *room) const
return {}; return {};
} }
std::optional<rust::Box<sdk::RoomListRoom>> RoomTreeModel::roomForIndex(QModelIndex index) const
{
RoomTreeItem *child = getItem(index);
if (std::holds_alternative<NeoChatRoomType::Type>(child->data())) {
return std::nullopt;
}
return (*std::get<RoomWrapper *>(child->data())->item)->box_me();
}
#include "moc_roomtreemodel.cpp" #include "moc_roomtreemodel.cpp"

View File

@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: LGPL-2.0-or-later
@@ -6,23 +7,20 @@
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QPointer> #include <QPointer>
#include "enums/neochatroomtype.h"
#include "roomtreeitem.h" #include "roomtreeitem.h"
namespace Quotient namespace Integral
{ {
class Connection;
class Room; class Room;
} }
class NeoChatConnection;
class NeoChatRoom;
class RoomTreeModel : public QAbstractItemModel class RoomTreeModel : public QAbstractItemModel
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged) Q_PROPERTY(Integral::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public: public:
/** /**
@@ -51,9 +49,10 @@ public:
}; };
Q_ENUM(EventRoles) Q_ENUM(EventRoles)
explicit RoomTreeModel(QObject *parent = nullptr); explicit RoomTreeModel(QObject *parent = nullptr);
~RoomTreeModel();
void setConnection(NeoChatConnection *connection); void setConnection(Integral::Connection *connection);
NeoChatConnection *connection() const; Integral::Connection *connection() const;
/** /**
* @brief Get the given role value at the given index. * @brief Get the given role value at the given index.
@@ -75,23 +74,21 @@ public:
int columnCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE QModelIndex indexForRoom(NeoChatRoom *room) const; QModelIndex indexForRoom(rust::Box<sdk::RoomListRoom> room) const;
std::optional<rust::Box<sdk::RoomListRoom>> roomForIndex(QModelIndex index) const;
Q_SIGNALS: Q_SIGNALS:
void connectionChanged(); void connectionChanged();
private: private:
QPointer<NeoChatConnection> m_connection; class Private;
std::unique_ptr<RoomTreeItem> m_rootItem; std::unique_ptr<Private> d;
RoomTreeItem *getItem(const QModelIndex &index) const; RoomTreeItem *getItem(const QModelIndex &index) const;
void resetModel(); void resetModel();
void connectRoomSignals(NeoChatRoom *room);
void newRoom(Quotient::Room *room); // void connectRoomSignals(NeoChatRoom *room);
void leftRoom(Quotient::Room *room);
void moveRoom(Quotient::Room *room);
void refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles = {}); // void refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles = {});
}; };

View File

@@ -4,26 +4,26 @@
#include "sortfilterroomtreemodel.h" #include "sortfilterroomtreemodel.h"
#include "enums/roomsortparameter.h" #include "roomsortparameter.h"
#include "neochatconfig.h" // #include "neochatconfig.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h"
#include "neochatroomtype.h" #include "neochatroomtype.h"
#include "roommanager.h" #include <Integral/Room>
// #include "roommanager.h"
#include "roomtreemodel.h" #include "roomtreemodel.h"
#include "spacehierarchycache.h" // #include "spacehierarchycache.h"
SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent) SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
Q_ASSERT(sourceModel); // Q_ASSERT(sourceModel);
setSourceModel(sourceModel); // setSourceModel(sourceModel);
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder())); // setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() { // connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder())); // setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
invalidateFilter(); // invalidateFilter();
}); // });
setRecursiveFilteringEnabled(true); setRecursiveFilteringEnabled(true);
sort(0); sort(0);
@@ -34,13 +34,13 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QOb
connect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter); connect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
}); });
connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter); // connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
connect(NeoChatConfig::self(), &NeoChatConfig::AllRoomsInHomeChanged, this, [this]() { // connect(NeoChatConfig::self(), &NeoChatConfig::AllRoomsInHomeChanged, this, [this]() {
invalidateFilter(); // invalidateFilter();
if (NeoChatConfig::self()->allRoomsInHome()) { // if (NeoChatConfig::self()->allRoomsInHome()) {
RoomManager::instance().resetState(); // RoomManager::instance().resetState();
} // }
}); // });
} }
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder) void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
@@ -78,14 +78,14 @@ bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QMo
return false; return false;
} }
const auto leftRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_left.data(RoomTreeModel::RoomIdRole).toString())); const auto leftRoom = treeModel->roomForIndex(source_left);
const auto rightRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_right.data(RoomTreeModel::RoomIdRole).toString())); const auto rightRoom = treeModel->roomForIndex(source_right);
if (leftRoom == nullptr || rightRoom == nullptr) { if (!leftRoom.has_value() || !rightRoom.has_value()) {
return false; return false;
} }
for (auto sortRole : RoomSortParameter::currentParameterList()) { for (auto sortRole : RoomSortParameter::currentParameterList()) {
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom, rightRoom); auto result = RoomSortParameter::compareParameter(sortRole, leftRoom.value()->box_me(), rightRoom.value()->box_me());
if (result != 0) { if (result != 0) {
return result > 0; return result > 0;
@@ -141,20 +141,22 @@ bool SortFilterRoomTreeModel::filterAcceptsRow(int source_row, const QModelIndex
return false; return false;
} }
static auto config = NeoChatConfig::self(); return acceptRoom;
if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
return acceptRoom;
}
if (m_activeSpaceId.isEmpty()) { // static auto config = NeoChatConfig::self();
if (!SpaceHierarchyCache::instance().isChild(sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString())) { // if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
return acceptRoom; // return acceptRoom;
} // }
return false; //
} else { // if (m_activeSpaceId.isEmpty()) {
const auto &rooms = SpaceHierarchyCache::instance().getRoomListForSpace(m_activeSpaceId, false); // if (!SpaceHierarchyCache::instance().isChild(sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString())) {
return std::find(rooms.begin(), rooms.end(), sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString()) != rooms.end() && acceptRoom; // return acceptRoom;
} // }
// return false;
// } else {
// const auto &rooms = SpaceHierarchyCache::instance().getRoomListForSpace(m_activeSpaceId, false);
// return std::find(rooms.begin(), rooms.end(), sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString()) != rooms.end() && acceptRoom;
// }
} }
QString SortFilterRoomTreeModel::activeSpaceId() const QString SortFilterRoomTreeModel::activeSpaceId() const
@@ -192,7 +194,7 @@ QModelIndex SortFilterRoomTreeModel::currentRoomIndex() const
return {}; return {};
} }
return mapFromSource(roomModel->indexForRoom(RoomManager::instance().currentRoom())); return {}; // mapFromSource(roomModel->indexForRoom(RoomManager::instance().currentRoom()));
} }
#include "moc_sortfilterroomtreemodel.cpp" #include "moc_sortfilterroomtreemodel.cpp"

View File

@@ -32,7 +32,7 @@ class SortFilterRoomTreeModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("") // QML_UNCREATABLE("")
/** /**
* @brief The text to use to filter room names. * @brief The text to use to filter room names.
@@ -64,7 +64,7 @@ public:
}; };
Q_ENUM(Mode) Q_ENUM(Mode)
explicit SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent = nullptr); explicit SortFilterRoomTreeModel(QObject *parent = nullptr);
void setRoomSortOrder(RoomSortOrder sortOrder); void setRoomSortOrder(RoomSortOrder sortOrder);

View File

@@ -5,8 +5,8 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <Quotient/avatar.h> // #include <Quotient/avatar.h>
#include <Quotient/events/roompowerlevelsevent.h> // #include <Quotient/events/roompowerlevelsevent.h>
#include "enums/powerlevel.h" #include "enums/powerlevel.h"
#include "neochatroom.h" #include "neochatroom.h"
@@ -30,7 +30,7 @@ void UserListModel::setRoom(NeoChatRoom *room)
// last room's objects before the room is actually changed // last room's objects before the room is actually changed
beginResetModel(); beginResetModel();
m_currentRoom->disconnect(this); m_currentRoom->disconnect(this);
m_currentRoom->connection()->disconnect(this); // m_currentRoom->connection()->disconnect(this);
m_currentRoom = nullptr; m_currentRoom = nullptr;
m_members.clear(); m_members.clear();
endResetModel(); endResetModel();
@@ -39,21 +39,21 @@ void UserListModel::setRoom(NeoChatRoom *room)
m_currentRoom = room; m_currentRoom = room;
if (m_currentRoom) { if (m_currentRoom) {
connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined); // connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined);
connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft); // connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft);
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) { // connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
refreshMember(member, {DisplayNameRole}); // refreshMember(member, {DisplayNameRole});
}); // });
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) { // connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
refreshMember(member, {AvatarRole}); // refreshMember(member, {AvatarRole});
}); // });
connect(m_currentRoom, &Room::memberListChanged, this, [this]() { // connect(m_currentRoom, &Room::memberListChanged, this, [this]() {
// this is slow // // this is slow
UserListModel::refreshAllMembers(); // UserListModel::refreshAllMembers();
}); // });
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() { // connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr); // setRoom(nullptr);
}); // });
} }
m_active = false; m_active = false;
@@ -80,40 +80,40 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
return {}; return {};
} }
auto memberId = m_members.at(index.row()); auto memberId = m_members.at(index.row());
if (role == DisplayNameRole) { // if (role == DisplayNameRole) {
return m_currentRoom->member(memberId).disambiguatedName(); // return m_currentRoom->member(memberId).disambiguatedName();
} // }
if (role == UserIdRole) { // if (role == UserIdRole) {
return memberId; // return memberId;
} // }
if (role == AvatarRole) { // if (role == AvatarRole) {
return m_currentRoom->member(memberId).avatarUrl(); // return m_currentRoom->member(memberId).avatarUrl();
} // }
if (role == ObjectRole) { // if (role == ObjectRole) {
return QVariant::fromValue(memberId); // return QVariant::fromValue(memberId);
} // }
if (role == PowerLevelRole) { // if (role == PowerLevelRole) {
auto plEvent = m_currentRoom->currentState().get<RoomPowerLevelsEvent>(); // auto plEvent = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) { // if (!plEvent) {
return 0; // return 0;
} // }
return plEvent->powerLevelForUser(memberId); // return plEvent->powerLevelForUser(memberId);
} // }
if (role == PowerLevelStringRole) { // if (role == PowerLevelStringRole) {
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>(); // auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
// User might not in the room yet, in this case pl can be nullptr. // // User might not in the room yet, in this case pl can be nullptr.
// e.g. When invited but user not accepted or denied the invitation. // // e.g. When invited but user not accepted or denied the invitation.
if (!pl) { // if (!pl) {
return u"Not Available"_s; // return u"Not Available"_s;
} // }
//
auto userPl = pl->powerLevelForUser(memberId); // auto userPl = pl->powerLevelForUser(memberId);
//
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.", // return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
"%1 (%2)", // "%1 (%2)",
PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)), // PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)),
userPl); // userPl);
} // }
return {}; return {};
} }
@@ -134,65 +134,65 @@ bool UserListModel::event(QEvent *event)
return QObject::event(event); return QObject::event(event);
} }
void UserListModel::memberJoined(const Quotient::RoomMember &member) // void UserListModel::memberJoined(const Quotient::RoomMember &member)
{ // {
auto pos = findUserPos(member); // auto pos = findUserPos(member);
beginInsertRows(QModelIndex(), pos, pos); // beginInsertRows(QModelIndex(), pos, pos);
m_members.insert(pos, member.id()); // m_members.insert(pos, member.id());
endInsertRows(); // endInsertRows();
} // }
//
void UserListModel::memberLeft(const Quotient::RoomMember &member) // void UserListModel::memberLeft(const Quotient::RoomMember &member)
{ // {
auto pos = findUserPos(member); // auto pos = findUserPos(member);
if (pos != m_members.size()) { // if (pos != m_members.size()) {
beginRemoveRows(QModelIndex(), pos, pos); // beginRemoveRows(QModelIndex(), pos, pos);
m_members.removeAt(pos); // m_members.removeAt(pos);
endRemoveRows(); // endRemoveRows();
} else { // } else {
qWarning() << "Trying to remove a room member not in the user list"; // qWarning() << "Trying to remove a room member not in the user list";
} // }
} // }
//
void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles) // void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles)
{ // {
auto pos = findUserPos(member); // auto pos = findUserPos(member);
if (pos != m_members.size()) { // if (pos != m_members.size()) {
// The update will have changed the state event so we need to insert the updated member object. // // The update will have changed the state event so we need to insert the updated member object.
m_members.insert(pos, member.id()); // m_members.insert(pos, member.id());
Q_EMIT dataChanged(index(pos), index(pos), roles); // Q_EMIT dataChanged(index(pos), index(pos), roles);
} else { // } else {
qWarning() << "Trying to access a room member not in the user list"; // qWarning() << "Trying to access a room member not in the user list";
} // }
} // }
void UserListModel::refreshAllMembers() void UserListModel::refreshAllMembers()
{ {
beginResetModel(); beginResetModel();
if (m_currentRoom != nullptr) { if (m_currentRoom != nullptr) {
m_members = m_currentRoom->joinedMemberIds(); // m_members = m_currentRoom->joinedMemberIds();
MemberSorter sorter; // MemberSorter sorter;
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) { // std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
const auto leftPl = m_currentRoom->memberEffectivePowerLevel(left); // const auto leftPl = m_currentRoom->memberEffectivePowerLevel(left);
const auto rightPl = m_currentRoom->memberEffectivePowerLevel(right); // const auto rightPl = m_currentRoom->memberEffectivePowerLevel(right);
if (leftPl > rightPl) { // if (leftPl > rightPl) {
return true; // return true;
} else if (rightPl > leftPl) { // } else if (rightPl > leftPl) {
return false; // return false;
} // }
//
return sorter(m_currentRoom->member(left), m_currentRoom->member(right)); // return sorter(m_currentRoom->member(left), m_currentRoom->member(right));
}); // });
} }
endResetModel(); endResetModel();
Q_EMIT usersRefreshed(); Q_EMIT usersRefreshed();
} }
int UserListModel::findUserPos(const RoomMember &member) const // int UserListModel::findUserPos(const RoomMember &member) const
{ // {
return findUserPos(member.id()); // return findUserPos(member.id());
} // }
int UserListModel::findUserPos(const QString &userId) const int UserListModel::findUserPos(const QString &userId) const
{ {

View File

@@ -3,7 +3,7 @@
#pragma once #pragma once
#include <Quotient/room.h> #include <Integral/Room>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QObject> #include <QObject>
@@ -87,9 +87,9 @@ protected:
bool event(QEvent *event) override; bool event(QEvent *event) override;
private Q_SLOTS: private Q_SLOTS:
void memberJoined(const Quotient::RoomMember &member); // void memberJoined(const Quotient::RoomMember &member);
void memberLeft(const Quotient::RoomMember &member); // void memberLeft(const Quotient::RoomMember &member);
void refreshMember(const Quotient::RoomMember &member, const QList<int> &roles = {}); // void refreshMember(const Quotient::RoomMember &member, const QList<int> &roles = {});
void refreshAllMembers(); void refreshAllMembers();
private: private:
@@ -98,6 +98,6 @@ private:
bool m_active = false; bool m_active = false;
int findUserPos(const Quotient::RoomMember &member) const; // int findUserPos(const Quotient::RoomMember &member) const;
[[nodiscard]] int findUserPos(const QString &username) const; [[nodiscard]] int findUserPos(const QString &username) const;
}; };

View File

@@ -6,147 +6,142 @@
#include <QImageReader> #include <QImageReader>
#include <QJsonDocument> #include <QJsonDocument>
#include "neochatconfig.h" // #include "neochatconfig.h"
#include "neochatroom.h" // #include "neochatroom.h"
#include "spacehierarchycache.h" // #include "spacehierarchycache.h"
#include <Quotient/jobs/basejob.h> // #include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h> // #include <Quotient/quotient_common.h>
#include <qt6keychain/keychain.h> #include <qt6keychain/keychain.h>
#include <KLocalizedString> #include <KLocalizedString>
#include <Quotient/csapi/content-repo.h> // #include <Quotient/csapi/content-repo.h>
#include <Quotient/csapi/profile.h> // #include <Quotient/csapi/profile.h>
#include <Quotient/csapi/registration.h> // #include <Quotient/csapi/registration.h>
#include <Quotient/csapi/versions.h> // #include <Quotient/csapi/versions.h>
#include <Quotient/jobs/downloadfilejob.h> // #include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h> // #include <Quotient/qt_connection_util.h>
#include <Quotient/room.h> // #include <Quotient/room.h>
#include <Quotient/settings.h> // #include <Quotient/settings.h>
#include <Quotient/user.h> // #include <Quotient/user.h>
#ifdef HAVE_KUNIFIEDPUSH // #ifdef HAVE_KUNIFIEDPUSH
#include <QCoroNetwork> // #include <QCoroNetwork>
#include <Quotient/csapi/pusher.h> // #include <Quotient/csapi/pusher.h>
#include <Quotient/networkaccessmanager.h> // #include <Quotient/networkaccessmanager.h>
#endif // #endif
using namespace Quotient; #include <Integral/Connection_p>
using namespace Integral;
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
NeoChatConnection::NeoChatConnection(QObject *parent) NeoChatConnection::NeoChatConnection(std::unique_ptr<Connection::Private> d)
: Connection(parent) : Connection(std::move(d))
{ {
m_linkPreviewers.setMaxCost(20); // m_linkPreviewers.setMaxCost(20);
connectSignals();
}
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
: Connection(server, parent)
{
m_linkPreviewers.setMaxCost(20);
connectSignals(); connectSignals();
} }
void NeoChatConnection::connectSignals() void NeoChatConnection::connectSignals()
{ {
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) { // connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == u"org.kde.neochat.account_label"_s) { // if (type == u"org.kde.neochat.account_label"_s) {
Q_EMIT labelChanged(); // Q_EMIT labelChanged();
} // }
if (type == u"m.identity_server"_s) { // if (type == u"m.identity_server"_s) {
Q_EMIT identityServerChanged(); // Q_EMIT identityServerChanged();
} // }
}); // });
connect(this, &NeoChatConnection::syncDone, this, [this] { // connect(this, &NeoChatConnection::syncDone, this, [this] {
setIsOnline(true); // setIsOnline(true);
}); // });
connect(this, &NeoChatConnection::networkError, this, [this]() { // connect(this, &NeoChatConnection::networkError, this, [this]() {
setIsOnline(false); // setIsOnline(false);
}); // });
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) { // connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) { // if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl()); // Q_EMIT userConsentRequired(job->errorUrl());
} // }
}); // });
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) { // connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_L1].toString() == "M_TOO_LARGE"_L1) { // if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_L1].toString() == "M_TOO_LARGE"_L1) {
Q_EMIT showMessage(MessageType::Warning, i18n("File too large to download.<br />Contact your matrix server administrator for support.")); // Q_EMIT showMessage(MessageType::Warning, i18n("File too large to download.<br />Contact your matrix server administrator for support."));
} // }
}); // });
connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) { // connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) {
Q_EMIT directChatInvitesChanged(); // Q_EMIT directChatInvitesChanged();
for (const auto &chatId : additions) { // for (const auto &chatId : additions) {
if (const auto chat = room(chatId)) { // if (const auto chat = room(chatId)) {
connect(chat, &Room::unreadStatsChanged, this, [this]() { // connect(chat, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount(); // refreshBadgeNotificationCount();
Q_EMIT directChatNotificationsChanged(); // Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged(); // Q_EMIT directChatsHaveHighlightNotificationsChanged();
}); // });
} // }
} // }
for (const auto &chatId : removals) { // for (const auto &chatId : removals) {
if (const auto chat = room(chatId)) { // if (const auto chat = room(chatId)) {
disconnect(chat, &Room::unreadStatsChanged, this, nullptr); // disconnect(chat, &Room::unreadStatsChanged, this, nullptr);
} // }
} // }
}); // });
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) { // connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
if (room->isDirectChat()) { // if (room->isDirectChat()) {
connect(room, &Room::unreadStatsChanged, this, [this]() { // connect(room, &Room::unreadStatsChanged, this, [this]() {
Q_EMIT directChatNotificationsChanged(); // Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged(); // Q_EMIT directChatsHaveHighlightNotificationsChanged();
}); // });
} // }
connect(room, &Room::unreadStatsChanged, this, [this]() { // connect(room, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount(); // refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged(); // Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged(); // Q_EMIT homeHaveHighlightNotificationsChanged();
}); // });
}); // });
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) { // connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
Q_UNUSED(room) // Q_UNUSED(room)
if (prev && prev->isDirectChat()) { // if (prev && prev->isDirectChat()) {
Q_EMIT directChatInvitesChanged(); // Q_EMIT directChatInvitesChanged();
Q_EMIT directChatNotificationsChanged(); // Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged(); // Q_EMIT directChatsHaveHighlightNotificationsChanged();
} // }
refreshBadgeNotificationCount(); // refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged(); // Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged(); // Q_EMIT homeHaveHighlightNotificationsChanged();
}); // });
//
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() { // connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
refreshBadgeNotificationCount(); // refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged(); // Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged(); // Q_EMIT homeHaveHighlightNotificationsChanged();
}); // });
//
// Fetch unstable features // // Fetch unstable features
// TODO: Expose unstableFeatures() in libQuotient // // TODO: Expose unstableFeatures() in libQuotient
connect( // connect(
this, // this,
&Connection::connected, // &Connection::connected,
this, // this,
[this] { // [this] {
auto job = callApi<GetVersionsJob>(BackgroundRequest); // auto job = callApi<GetVersionsJob>(BackgroundRequest);
connect(job, &GetVersionsJob::success, this, [this, job] { // connect(job, &GetVersionsJob::success, this, [this, job] {
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1); // m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
Q_EMIT canCheckMutualRoomsChanged(); // Q_EMIT canCheckMutualRoomsChanged();
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1); // m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
Q_EMIT canEraseDataChanged(); // Q_EMIT canEraseDataChanged();
}); // });
}, // },
Qt::SingleShotConnection); // Qt::SingleShotConnection);
setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption()); // setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] { // connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] {
setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption()); // setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
}); // });
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview()); // setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() { // connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview()); // setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
}); // });
} }
int NeoChatConnection::badgeNotificationCount() const int NeoChatConnection::badgeNotificationCount() const
@@ -157,11 +152,11 @@ int NeoChatConnection::badgeNotificationCount() const
void NeoChatConnection::refreshBadgeNotificationCount() void NeoChatConnection::refreshBadgeNotificationCount()
{ {
int count = 0; int count = 0;
for (const auto &r : allRooms()) { // for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) { // if (const auto room = static_cast<NeoChatRoom *>(r)) {
count += room->contextAwareNotificationCount(); // count += room->contextAwareNotificationCount();
} // }
} // }
if (count != m_badgeNotificationCount) { if (count != m_badgeNotificationCount) {
m_badgeNotificationCount = count; m_badgeNotificationCount = count;
@@ -182,56 +177,58 @@ void NeoChatConnection::setGlobalUrlPreviewEnabled(bool newState)
m_globalUrlPreviewEnabled = newState; m_globalUrlPreviewEnabled = newState;
if (!m_globalUrlPreviewEnabled) { if (!m_globalUrlPreviewEnabled) {
m_linkPreviewers.clear(); // m_linkPreviewers.clear();
} }
NeoChatConfig::setShowLinkPreview(m_globalUrlPreviewEnabled); // NeoChatConfig::setShowLinkPreview(m_globalUrlPreviewEnabled);
Q_EMIT globalUrlPreviewEnabledChanged(); Q_EMIT globalUrlPreviewEnabledChanged();
} }
void NeoChatConnection::logout(bool serverSideLogout) void NeoChatConnection::logout(bool serverSideLogout)
{ {
SettingsGroup(u"Accounts"_s).remove(userId()); // SettingsGroup(u"Accounts"_s).remove(userId());
//
QKeychain::DeletePasswordJob job(qAppName()); // QKeychain::DeletePasswordJob job(qAppName());
job.setAutoDelete(true); // job.setAutoDelete(true);
job.setKey(userId()); // job.setKey(userId());
QEventLoop loop; // QEventLoop loop;
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); // QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start(); // job.start();
loop.exec(); // loop.exec();
//
if (!serverSideLogout) { // if (!serverSideLogout) {
return; // return;
} // }
Connection::logout(); // Connection::logout();
} }
bool NeoChatConnection::setAvatar(const QUrl &avatarSource) bool NeoChatConnection::setAvatar(const QUrl &avatarSource)
{ {
QString decoded = avatarSource.path(); // QString decoded = avatarSource.path();
if (decoded.isEmpty()) { // if (decoded.isEmpty()) {
callApi<SetAvatarUrlJob>(user()->id(), avatarSource); // callApi<SetAvatarUrlJob>(user()->id(), avatarSource);
return true; // return true;
} // }
if (QImageReader(decoded).read().isNull()) { // if (QImageReader(decoded).read().isNull()) {
return false; // return false;
} else { // } else {
return user()->setAvatar(decoded); // return user()->setAvatar(decoded);
} // }
return {};
} }
QVariantList NeoChatConnection::getSupportedRoomVersions() const QVariantList NeoChatConnection::getSupportedRoomVersions() const
{ {
const auto &roomVersions = availableRoomVersions(); // const auto &roomVersions = availableRoomVersions();
QVariantList supportedRoomVersions; // QVariantList supportedRoomVersions;
for (const auto &v : roomVersions) { // for (const auto &v : roomVersions) {
QVariantMap roomVersionMap; // QVariantMap roomVersionMap;
roomVersionMap.insert("id"_L1, v.id); // roomVersionMap.insert("id"_L1, v.id);
roomVersionMap.insert("status"_L1, v.status); // roomVersionMap.insert("status"_L1, v.status);
roomVersionMap.insert("isStable"_L1, v.isStable()); // roomVersionMap.insert("isStable"_L1, v.isStable());
supportedRoomVersions.append(roomVersionMap); // supportedRoomVersions.append(roomVersionMap);
} // }
return supportedRoomVersions; // return supportedRoomVersions;
return {};
} }
bool NeoChatConnection::canCheckMutualRooms() const bool NeoChatConnection::canCheckMutualRooms() const
@@ -241,85 +238,86 @@ bool NeoChatConnection::canCheckMutualRooms() const
void NeoChatConnection::changePassword(const QString &currentPassword, const QString &newPassword) void NeoChatConnection::changePassword(const QString &currentPassword, const QString &newPassword)
{ {
auto job = callApi<ChangePasswordJob>(newPassword, false); // auto job = callApi<ChangePasswordJob>(newPassword, false);
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] { // connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] {
if (job->error() == 103) { // if (job->error() == 103) {
QJsonObject replyData = job->jsonData(); // QJsonObject replyData = job->jsonData();
AuthenticationData authData; // AuthenticationData authData;
authData.session = replyData["session"_L1].toString(); // authData.session = replyData["session"_L1].toString();
authData.type = "m.login.password"_L1; // authData.type = "m.login.password"_L1;
authData.authInfo["password"_L1] = currentPassword; // authData.authInfo["password"_L1] = currentPassword;
authData.authInfo["user"_L1] = user()->id(); // authData.authInfo["user"_L1] = user()->id();
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}}; // authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData); // auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData);
connect(innerJob, &BaseJob::success, this, [this]() { // connect(innerJob, &BaseJob::success, this, [this]() {
Q_EMIT passwordStatus(PasswordStatus::Success); // Q_EMIT passwordStatus(PasswordStatus::Success);
}); // });
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() { // connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
Q_EMIT passwordStatus(innerJob->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other); // Q_EMIT passwordStatus(innerJob->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other);
}); // });
} // }
}); // });
} }
void NeoChatConnection::setLabel(const QString &label) void NeoChatConnection::setLabel(const QString &label)
{ {
QJsonObject json{ // QJsonObject json{
{"account_label"_L1, label}, // {"account_label"_L1, label},
}; // };
setAccountData("org.kde.neochat.account_label"_L1, json); // setAccountData("org.kde.neochat.account_label"_L1, json);
Q_EMIT labelChanged(); // Q_EMIT labelChanged();
} }
QString NeoChatConnection::label() const QString NeoChatConnection::label() const
{ {
return accountDataJson("org.kde.neochat.account_label"_L1)["account_label"_L1].toString(); // return accountDataJson("org.kde.neochat.account_label"_L1)["account_label"_L1].toString();
return {};
} }
void NeoChatConnection::deactivateAccount(const QString &password, const bool erase) void NeoChatConnection::deactivateAccount(const QString &password, const bool erase)
{ {
auto job = callApi<DeactivateAccountJob>(); // auto job = callApi<DeactivateAccountJob>();
connect(job, &BaseJob::result, this, [this, job, password, erase] { // connect(job, &BaseJob::result, this, [this, job, password, erase] {
if (job->error() == 103) { // if (job->error() == 103) {
QJsonObject replyData = job->jsonData(); // QJsonObject replyData = job->jsonData();
AuthenticationData authData; // AuthenticationData authData;
authData.session = replyData["session"_L1].toString(); // authData.session = replyData["session"_L1].toString();
authData.authInfo["password"_L1] = password; // authData.authInfo["password"_L1] = password;
authData.type = "m.login.password"_L1; // authData.type = "m.login.password"_L1;
authData.authInfo["user"_L1] = user()->id(); // authData.authInfo["user"_L1] = user()->id();
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}}; // QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
authData.authInfo["identifier"_L1] = identifier; // authData.authInfo["identifier"_L1] = identifier;
auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase); // auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase);
connect(innerJob, &BaseJob::success, this, [this]() { // connect(innerJob, &BaseJob::success, this, [this]() {
logout(false); // logout(false);
}); // });
} // }
}); // });
} }
bool NeoChatConnection::hasIdentityServer() const bool NeoChatConnection::hasIdentityServer() const
{ {
if (!hasAccountData(u"m.identity_server"_s)) { // if (!hasAccountData(u"m.identity_server"_s)) {
return false; // return false;
} // }
const auto url = accountData(u"m.identity_server"_s)->contentPart<QUrl>("base_url"_L1); // const auto url = accountData(u"m.identity_server"_s)->contentPart<QUrl>("base_url"_L1);
if (!url.isEmpty()) { // if (!url.isEmpty()) {
return true; // return true;
} // }
return false; return false;
} }
QUrl NeoChatConnection::identityServer() const QUrl NeoChatConnection::identityServer() const
{ {
if (!hasAccountData(u"m.identity_server"_s)) { // if (!hasAccountData(u"m.identity_server"_s)) {
return {}; // return {};
} // }
const auto url = accountData(u"m.identity_server"_s)->contentPart<QUrl>("base_url"_L1); // const auto url = accountData(u"m.identity_server"_s)->contentPart<QUrl>("base_url"_L1);
if (!url.isEmpty()) { // if (!url.isEmpty()) {
return url; // return url;
} // }
return {}; return {};
} }
@@ -334,92 +332,93 @@ QString NeoChatConnection::identityServerUIString() const
void NeoChatConnection::createRoom(const QString &name, const QString &topic, const QString &parent, bool setChildParent) void NeoChatConnection::createRoom(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
{ {
QList<CreateRoomJob::StateEvent> initialStateEvents; // QList<CreateRoomJob::StateEvent> initialStateEvents;
if (!parent.isEmpty()) { // if (!parent.isEmpty()) {
initialStateEvents.append(CreateRoomJob::StateEvent{ // initialStateEvents.append(CreateRoomJob::StateEvent{
"m.space.parent"_L1, // "m.space.parent"_L1,
QJsonObject{ // QJsonObject{
{"canonical"_L1, true}, // {"canonical"_L1, true},
{"via"_L1, QJsonArray{domain()}}, // {"via"_L1, QJsonArray{domain()}},
}, // },
parent, // parent,
}); // });
} // }
//
const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents); // const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents);
if (!parent.isEmpty()) { // if (!parent.isEmpty()) {
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() { // connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
if (setChildParent) { // if (setChildParent) {
if (auto parentRoom = room(parent)) { // if (auto parentRoom = room(parent)) {
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}}); // parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
} // }
} // }
}); // });
} // }
connect(job, &CreateRoomJob::failure, this, [this, job] { // connect(job, &CreateRoomJob::failure, this, [this, job] {
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString())); // Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
}); // });
} }
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent) void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
{ {
QList<CreateRoomJob::StateEvent> initialStateEvents; // QList<CreateRoomJob::StateEvent> initialStateEvents;
if (!parent.isEmpty()) { // if (!parent.isEmpty()) {
initialStateEvents.append(CreateRoomJob::StateEvent{ // initialStateEvents.append(CreateRoomJob::StateEvent{
"m.space.parent"_L1, // "m.space.parent"_L1,
QJsonObject{ // QJsonObject{
{"canonical"_L1, true}, // {"canonical"_L1, true},
{"via"_L1, QJsonArray{domain()}}, // {"via"_L1, QJsonArray{domain()}},
}, // },
parent, // parent,
}); // });
} // }
//
const auto job = // const auto job =
Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1, "m.space"_L1}}); // Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1,
if (!parent.isEmpty()) { // "m.space"_L1}});
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() { // if (!parent.isEmpty()) {
if (setChildParent) { // connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
if (auto parentRoom = room(parent)) { // if (setChildParent) {
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}}); // if (auto parentRoom = room(parent)) {
} // parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
} // }
}); // }
} // });
connect(job, &CreateRoomJob::failure, this, [this, job] { // }
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString())); // connect(job, &CreateRoomJob::failure, this, [this, job] {
}); // Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
// });
} }
bool NeoChatConnection::directChatExists(Quotient::User *user) // bool NeoChatConnection::directChatExists(Quotient::User *user)
{ // {
return directChats().contains(user); // return directChats().contains(user);
} // }
qsizetype NeoChatConnection::directChatNotifications() const qsizetype NeoChatConnection::directChatNotifications() const
{ {
qsizetype notifications = 0; qsizetype notifications = 0;
QStringList added; // The same ID can be in the list multiple times. QStringList added; // The same ID can be in the list multiple times.
for (const auto &chatId : directChats()) { // for (const auto &chatId : directChats()) {
if (!added.contains(chatId)) { // if (!added.contains(chatId)) {
if (const auto chat = room(chatId)) { // if (const auto chat = room(chatId)) {
notifications += dynamic_cast<NeoChatRoom *>(chat)->contextAwareNotificationCount(); // notifications += dynamic_cast<NeoChatRoom *>(chat)->contextAwareNotificationCount();
added += chatId; // added += chatId;
} // }
} // }
} // }
return notifications; return notifications;
} }
bool NeoChatConnection::directChatsHaveHighlightNotifications() const bool NeoChatConnection::directChatsHaveHighlightNotifications() const
{ {
for (const auto &childId : directChats()) { // for (const auto &childId : directChats()) {
if (const auto child = static_cast<NeoChatRoom *>(room(childId))) { // if (const auto child = static_cast<NeoChatRoom *>(room(childId))) {
if (child->highlightCount() > 0) { // if (child->highlightCount() > 0) {
return true; // return true;
} // }
} // }
} // }
return false; return false;
} }
@@ -427,78 +426,78 @@ qsizetype NeoChatConnection::homeNotifications() const
{ {
qsizetype notifications = 0; qsizetype notifications = 0;
QStringList added; QStringList added;
const auto &spaceHierarchyCache = SpaceHierarchyCache::instance(); // const auto &spaceHierarchyCache = SpaceHierarchyCache::instance();
for (const auto &r : allRooms()) { // for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) { // if (const auto room = static_cast<NeoChatRoom *>(r)) {
if (!added.contains(room->id()) && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id())) { // if (!added.contains(room->id()) && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id())) {
notifications += dynamic_cast<NeoChatRoom *>(room)->contextAwareNotificationCount(); // notifications += dynamic_cast<NeoChatRoom *>(room)->contextAwareNotificationCount();
added += room->id(); // added += room->id();
} // }
} // }
} // }
return notifications; return notifications;
} }
bool NeoChatConnection::homeHaveHighlightNotifications() const bool NeoChatConnection::homeHaveHighlightNotifications() const
{ {
const auto &spaceHierarchyCache = SpaceHierarchyCache::instance(); // const auto &spaceHierarchyCache = SpaceHierarchyCache::instance();
for (const auto &r : allRooms()) { // for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) { // if (const auto room = static_cast<NeoChatRoom *>(r)) {
if (!room->isDirectChat() && !spaceHierarchyCache.isChild(room->id()) && room->highlightCount() > 0) { // if (!room->isDirectChat() && !spaceHierarchyCache.isChild(room->id()) && room->highlightCount() > 0) {
return true; // return true;
} // }
} // }
} // }
return false; return false;
} }
bool NeoChatConnection::directChatInvites() const bool NeoChatConnection::directChatInvites() const
{ {
auto inviteRooms = rooms(JoinState::Invite); // auto inviteRooms = rooms(JoinState::Invite);
for (const auto inviteRoom : inviteRooms) { // for (const auto inviteRoom : inviteRooms) {
if (inviteRoom->isDirectChat()) { // if (inviteRoom->isDirectChat()) {
return true; // return true;
} // }
} // }
return false; return false;
} }
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint) // QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
{ // {
#ifdef HAVE_KUNIFIEDPUSH // #ifdef HAVE_KUNIFIEDPUSH
QUrl gatewayEndpoint(endpoint); // QUrl gatewayEndpoint(endpoint);
gatewayEndpoint.setPath(u"/_matrix/push/v1/notify"_s); // gatewayEndpoint.setPath(u"/_matrix/push/v1/notify"_s);
//
QNetworkRequest checkGateway(gatewayEndpoint); // QNetworkRequest checkGateway(gatewayEndpoint);
auto reply = co_await NetworkAccessManager::instance()->get(checkGateway); // auto reply = co_await NetworkAccessManager::instance()->get(checkGateway);
//
// We want to check if this UnifiedPush server has a Matrix gateway // // We want to check if this UnifiedPush server has a Matrix gateway
// This is because Matrix does not natively support UnifiedPush // // This is because Matrix does not natively support UnifiedPush
const auto &replyJson = QJsonDocument::fromJson(reply->readAll()).object(); // const auto &replyJson = QJsonDocument::fromJson(reply->readAll()).object();
//
if (replyJson["unifiedpush"_L1]["gateway"_L1].toString() == u"matrix"_s) { // if (replyJson["unifiedpush"_L1]["gateway"_L1].toString() == u"matrix"_s) {
callApi<PostPusherJob>(endpoint, // callApi<PostPusherJob>(endpoint,
u"http"_s, // u"http"_s,
u"org.kde.neochat"_s, // u"org.kde.neochat"_s,
u"NeoChat"_s, // u"NeoChat"_s,
deviceId(), // deviceId(),
QString(), // profileTag is intentionally left empty for now, it's optional // QString(), // profileTag is intentionally left empty for now, it's optional
u"en-US"_s, // u"en-US"_s,
PostPusherJob::PusherData{QUrl::fromUserInput(gatewayEndpoint.toString()), u" "_s}, // PostPusherJob::PusherData{QUrl::fromUserInput(gatewayEndpoint.toString()), u" "_s},
false); // false);
//
qInfo() << "Registered for push notifications"; // qInfo() << "Registered for push notifications";
m_pushNotificationsEnabled = true; // m_pushNotificationsEnabled = true;
} else { // } else {
qWarning() << "There's no gateway, not setting up push notifications."; // qWarning() << "There's no gateway, not setting up push notifications.";
m_pushNotificationsEnabled = false; // m_pushNotificationsEnabled = false;
} // }
Q_EMIT enablePushNotificationsChanged(); // Q_EMIT enablePushNotificationsChanged();
#else // #else
Q_UNUSED(endpoint) // Q_UNUSED(endpoint)
co_return; // co_return;
#endif // #endif
} // }
bool NeoChatConnection::isOnline() const bool NeoChatConnection::isOnline() const
{ {
@@ -516,39 +515,39 @@ void NeoChatConnection::setIsOnline(bool isOnline)
QString NeoChatConnection::accountDataJsonString(const QString &type) const QString NeoChatConnection::accountDataJsonString(const QString &type) const
{ {
return QString::fromUtf8(QJsonDocument(accountDataJson(type)).toJson()); return {}; // QString::fromUtf8(QJsonDocument(accountDataJson(type)).toJson());
} }
LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link) // LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
{ // {
if (!m_globalUrlPreviewEnabled) { // if (!m_globalUrlPreviewEnabled) {
return nullptr; // return nullptr;
} // }
//
// auto previewer = m_linkPreviewers.object(link);
// if (previewer != nullptr) {
// return previewer;
// }
//
// previewer = new LinkPreviewer(link, this);
// m_linkPreviewers.insert(link, previewer);
// return previewer;
// }
auto previewer = m_linkPreviewers.object(link); // KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphrase, const QString &path)
if (previewer != nullptr) { // {
return previewer; // KeyImport keyImport;
} // auto result = keyImport.exportKeys(passphrase, this);
// if (!result.has_value()) {
previewer = new LinkPreviewer(link, this); // return result.error();
m_linkPreviewers.insert(link, previewer); // }
return previewer; // QUrl url(path);
} // QFile file(url.toLocalFile());
// file.open(QFile::WriteOnly);
KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphrase, const QString &path) // file.write(result.value());
{ // file.close();
KeyImport keyImport; // return KeyImport::Success;
auto result = keyImport.exportKeys(passphrase, this); // }
if (!result.has_value()) {
return result.error();
}
QUrl url(path);
QFile file(url.toLocalFile());
file.open(QFile::WriteOnly);
file.write(result.value());
file.close();
return KeyImport::Success;
}
bool NeoChatConnection::canEraseData() const bool NeoChatConnection::canEraseData() const
{ {

View File

@@ -7,15 +7,16 @@
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
#include <Integral/Connection>
#include <QCoroTask> #include <QCoroTask>
#include <Quotient/connection.h>
#include <Quotient/keyimport.h> // #include <Quotient/keyimport.h>
#include "enums/messagetype.h" // #include "enums/messagetype.h"
#include "linkpreviewer.h" // #include "linkpreviewer.h"
// #include "models/threepidmodel.h"
class NeoChatConnection : public Quotient::Connection class NeoChatConnection : public Integral::Connection
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
@@ -110,8 +111,7 @@ public:
}; };
Q_ENUM(PasswordStatus) Q_ENUM(PasswordStatus)
NeoChatConnection(QObject *parent = nullptr); NeoChatConnection(std::unique_ptr<Integral::Connection::Private> d);
NeoChatConnection(const QUrl &server, QObject *parent = nullptr);
Q_INVOKABLE void logout(bool serverSideLogout); Q_INVOKABLE void logout(bool serverSideLogout);
Q_INVOKABLE QVariantList getSupportedRoomVersions() const; Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
@@ -162,14 +162,14 @@ public:
/** /**
* @brief Whether a direct chat with the user exists. * @brief Whether a direct chat with the user exists.
*/ */
Q_INVOKABLE bool directChatExists(Quotient::User *user); // Q_INVOKABLE bool directChatExists(Quotient::User *user);
/** /**
* @brief Get the account data with \param type as a formatted JSON string. * @brief Get the account data with \param type as a formatted JSON string.
*/ */
Q_INVOKABLE QString accountDataJsonString(const QString &type) const; Q_INVOKABLE QString accountDataJsonString(const QString &type) const;
Q_INVOKABLE Quotient::KeyImport::Error exportMegolmSessions(const QString &passphrase, const QString &path); // Q_INVOKABLE Quotient::KeyImport::Error exportMegolmSessions(const QString &passphrase, const QString &path);
qsizetype directChatNotifications() const; qsizetype directChatNotifications() const;
bool directChatsHaveHighlightNotifications() const; bool directChatsHaveHighlightNotifications() const;
@@ -186,14 +186,14 @@ public:
// note: this is intentionally a copied QString because // note: this is intentionally a copied QString because
// the reference could be destroyed before the task is finished // the reference could be destroyed before the task is finished
QCoro::Task<void> setupPushNotifications(QString endpoint); // QCoro::Task<void> setupPushNotifications(QString endpoint);
bool pushNotificationsAvailable() const; bool pushNotificationsAvailable() const;
bool enablePushNotifications() const; bool enablePushNotifications() const;
bool isOnline() const; bool isOnline() const;
LinkPreviewer *previewerForLink(const QUrl &link); // LinkPreviewer *previewerForLink(const QUrl &link);
Q_SIGNALS: Q_SIGNALS:
void globalUrlPreviewEnabledChanged(); void globalUrlPreviewEnabledChanged();
@@ -215,7 +215,7 @@ Q_SIGNALS:
/** /**
* @brief Request a message be shown to the user of the given type. * @brief Request a message be shown to the user of the given type.
*/ */
void showMessage(MessageType::Type messageType, const QString &message); // void showMessage(MessageType::Type messageType, const QString &message);
/** /**
* @brief Request a error message be shown to the user. * @brief Request a error message be shown to the user.
@@ -231,7 +231,7 @@ private:
int m_badgeNotificationCount = 0; int m_badgeNotificationCount = 0;
bool m_globalUrlPreviewEnabled = true; bool m_globalUrlPreviewEnabled = true;
QCache<QUrl, LinkPreviewer> m_linkPreviewers; // QCache<QUrl, LinkPreviewer> m_linkPreviewers;
bool m_canCheckMutualRooms = false; bool m_canCheckMutualRooms = false;
bool m_canEraseData = false; bool m_canEraseData = false;

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,9 @@
#pragma once #pragma once
#include <Quotient/events/roomevent.h> // #include <Quotient/events/roomevent.h>
#include <Quotient/room.h> // #include <Quotient/room.h>
#include <Integral/Room>
#include <QCache> #include <QCache>
#include <QObject> #include <QObject>
@@ -13,12 +14,12 @@
#include <QCoroTask> #include <QCoroTask>
#include <Quotient/roommember.h> #include <Quotient/roommember.h>
#include "enums/messagetype.h"
#include "enums/pushrule.h" #include "enums/pushrule.h"
#include "models/messagecontentmodel.h" // #include "enums/messagetype.h"
#include "models/threadmodel.h" // #include "models/messagecontentmodel.h"
#include "neochatroommember.h" // #include "models/threadmodel.h"
#include "pollhandler.h" // #include "neochatroommember.h"
// #include "pollhandler.h"
namespace Quotient namespace Quotient
{ {
@@ -39,7 +40,7 @@ class ChatBarCache;
* *
* @sa Quotient::Room * @sa Quotient::Room
*/ */
class NeoChatRoom : public Quotient::Room class NeoChatRoom : public Integral::Room
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
@@ -79,7 +80,7 @@ class NeoChatRoom : public Quotient::Room
/** /**
* @brief Get a RoomMember object for the other person in a direct chat. * @brief Get a RoomMember object for the other person in a direct chat.
*/ */
Q_PROPERTY(NeochatRoomMember *directChatRemoteMember READ directChatRemoteMember CONSTANT) // Q_PROPERTY(NeochatRoomMember *directChatRemoteMember READ directChatRemoteMember CONSTANT)
/** /**
* @brief The Matrix IDs of this room's parents. * @brief The Matrix IDs of this room's parents.
@@ -183,20 +184,20 @@ class NeoChatRoom : public Quotient::Room
/** /**
* @brief The cache for the main chat bar in the room. * @brief The cache for the main chat bar in the room.
*/ */
Q_PROPERTY(ChatBarCache *mainCache READ mainCache CONSTANT) // Q_PROPERTY(ChatBarCache *mainCache READ mainCache CONSTANT)
/** /**
* @brief The cache for the edit chat bar in the room. * @brief The cache for the edit chat bar in the room.
*/ */
Q_PROPERTY(ChatBarCache *editCache READ editCache CONSTANT) // Q_PROPERTY(ChatBarCache *editCache READ editCache CONSTANT)
/** /**
* @brief The cache for the thread chat bar in the room. * @brief The cache for the thread chat bar in the room.
*/ */
Q_PROPERTY(ChatBarCache *threadCache READ threadCache CONSTANT) // Q_PROPERTY(ChatBarCache *threadCache READ threadCache CONSTANT)
public: public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {}); explicit NeoChatRoom(std::unique_ptr<Private> d, QObject *parent = nullptr);
bool visible() const; bool visible() const;
void setVisible(bool visible); void setVisible(bool visible);
@@ -212,7 +213,7 @@ public:
* @warning This function can return an empty pointer if the room does not have * @warning This function can return an empty pointer if the room does not have
* any RoomMessageEvents loaded. * any RoomMessageEvents loaded.
*/ */
[[nodiscard]] const Quotient::RoomEvent *lastEvent() const; // [[nodiscard]] const Quotient::RoomEvent *lastEvent() const;
/** /**
* @brief Convenient way to check if the last event looks like it has spoilers. * @brief Convenient way to check if the last event looks like it has spoilers.
@@ -259,7 +260,7 @@ public:
* An event is highlighted if it contains the local user's id but was not sent by the * An event is highlighted if it contains the local user's id but was not sent by the
* local user. * local user.
*/ */
bool isEventHighlighted(const Quotient::RoomEvent *e) const; // bool isEventHighlighted(const Quotient::RoomEvent *e) const;
/** /**
* @brief Convenience function to find out if the room contains the given user. * @brief Convenience function to find out if the room contains the given user.
@@ -312,7 +313,7 @@ public:
[[nodiscard]] QUrl avatarMediaUrl() const; [[nodiscard]] QUrl avatarMediaUrl() const;
NeochatRoomMember *directChatRemoteMember(); // NeochatRoomMember *directChatRemoteMember();
/** /**
* @brief Whether this room has one or more parent spaces set. * @brief Whether this room has one or more parent spaces set.
@@ -501,14 +502,14 @@ public:
* *
* @sa PollHandler * @sa PollHandler
*/ */
PollHandler *poll(const QString &eventId) const; // PollHandler *poll(const QString &eventId) const;
/** /**
* @brief Create a PollHandler object for the given event. * @brief Create a PollHandler object for the given event.
* *
* @sa PollHandler * @sa PollHandler
*/ */
void createPollHandler(const Quotient::PollStartEvent *event); // void createPollHandler(const Quotient::PollStartEvent *event);
/** /**
* @brief Get the full Json data for a given room account data event. * @brief Get the full Json data for a given room account data event.
@@ -533,12 +534,12 @@ public:
* *
* The result will be nullptr if not found so needs to be managed. * The result will be nullptr if not found so needs to be managed.
*/ */
std::pair<const Quotient::RoomEvent *, bool> getEvent(const QString &eventId) const; // std::pair<const Quotient::RoomEvent *, bool> getEvent(const QString &eventId) const;
/** /**
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply. * @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.
*/ */
const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const; // const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const;
/** /**
* If we're invited to this room, the user that invited us. Undefined in other cases. * If we're invited to this room, the user that invited us. Undefined in other cases.
@@ -551,7 +552,7 @@ public:
* If we downloaded the file previously, return a struct with Completed status * If we downloaded the file previously, return a struct with Completed status
* and the local file path stored in KSharedCOnfig * and the local file path stored in KSharedCOnfig
*/ */
Quotient::FileTransferInfo cachedFileTransferInfo(const Quotient::RoomEvent *event) const; // Quotient::FileTransferInfo cachedFileTransferInfo(const Quotient::RoomEvent *event) const;
/** /**
* @brief Return a NeochatRoomMember object for the given user ID. * @brief Return a NeochatRoomMember object for the given user ID.
@@ -562,7 +563,7 @@ public:
* responsibility of the caller to ensure that they only ask for objects * responsibility of the caller to ensure that they only ask for objects
* for real senders. * for real senders.
*/ */
NeochatRoomMember *qmlSafeMember(const QString &memberId); // NeochatRoomMember *qmlSafeMember(const QString &memberId);
/** /**
* @brief Returns the content model for the given event ID. * @brief Returns the content model for the given event ID.
@@ -576,7 +577,7 @@ public:
* *
* @warning Do NOT use for pending events as this function has no way to differentiate. * @warning Do NOT use for pending events as this function has no way to differentiate.
*/ */
MessageContentModel *contentModelForEvent(const QString &evtOrTxnId); // MessageContentModel *contentModelForEvent(const QString &evtOrTxnId);
/** /**
* @brief Returns the content model for the given event. * @brief Returns the content model for the given event.
@@ -591,7 +592,7 @@ public:
* *
* @note This version must be used for pending events as it can differentiate. * @note This version must be used for pending events as it can differentiate.
*/ */
MessageContentModel *contentModelForEvent(const Quotient::RoomEvent *event); // MessageContentModel *contentModelForEvent(const Quotient::RoomEvent *event);
/** /**
* @brief Returns the thread model for the given thread root event ID. * @brief Returns the thread model for the given thread root event ID.
@@ -599,7 +600,7 @@ public:
* A model is created is one doesn't exist. Will return nullptr if threadRootId * A model is created is one doesn't exist. Will return nullptr if threadRootId
* is empty. * is empty.
*/ */
Q_INVOKABLE ThreadModel *modelForThread(const QString &threadRootId); // Q_INVOKABLE ThreadModel *modelForThread(const QString &threadRootId);
/** /**
* @brief Pin a message in the room. * @brief Pin a message in the room.
@@ -621,7 +622,7 @@ public:
private: private:
bool m_visible = false; bool m_visible = false;
QSet<const Quotient::RoomEvent *> highlights; // QSet<const Quotient::RoomEvent *> highlights;
bool m_hasFileUploading = false; bool m_hasFileUploading = false;
int m_fileUploadingProgress = 0; int m_fileUploadingProgress = 0;
@@ -629,29 +630,29 @@ private:
PushNotificationState::State m_currentPushNotificationState = PushNotificationState::Unknown; PushNotificationState::State m_currentPushNotificationState = PushNotificationState::Unknown;
bool m_pushNotificationStateUpdating = false; bool m_pushNotificationStateUpdating = false;
void checkForHighlights(const Quotient::TimelineItem &ti); // void checkForHighlights(const Quotient::TimelineItem &ti);
void onAddNewTimelineEvents(timeline_iter_t from) override; // void onAddNewTimelineEvents(timeline_iter_t from) override;
void onAddHistoricalTimelineEvents(rev_iter_t from) override; // void onAddHistoricalTimelineEvents(rev_iter_t from) override;
void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override; // void onRedaction(const Quotient::RoomEvent &prevEvent, const Quotient::RoomEvent &after) override;
QCoro::Task<void> doDeleteMessagesByUser(const QString &user, QString reason); // QCoro::Task<void> doDeleteMessagesByUser(const QString &user, QString reason);
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString()); QCoro::Task<void> doUploadFile(QUrl url, QString body = QString());
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent; // std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
ChatBarCache *m_mainCache; ChatBarCache *m_mainCache;
ChatBarCache *m_editCache; ChatBarCache *m_editCache;
ChatBarCache *m_threadCache; ChatBarCache *m_threadCache;
QCache<QString, PollHandler> m_polls; // QCache<QString, PollHandler> m_polls;
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents; // std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
void cleanupExtraEventRange(Quotient::RoomEventsRange events); // void cleanupExtraEventRange(Quotient::RoomEventsRange events);
void cleanupExtraEvent(const QString &eventId); void cleanupExtraEvent(const QString &eventId);
std::unordered_map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects; // std::unordered_map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
std::unordered_map<QString, std::unique_ptr<MessageContentModel>> m_eventContentModels; // std::unordered_map<QString, std::unique_ptr<MessageContentModel>> m_eventContentModels;
std::unordered_map<QString, std::unique_ptr<ThreadModel>> m_threadModels; // std::unordered_map<QString, std::unique_ptr<ThreadModel>> m_threadModels;
private Q_SLOTS: private Q_SLOTS:
void updatePushNotificationState(QString type); void updatePushNotificationState(QString type);
@@ -681,11 +682,12 @@ Q_SIGNALS:
void maxRoomVersionChanged(); void maxRoomVersionChanged();
void extraEventLoaded(const QString &eventId); void extraEventLoaded(const QString &eventId);
void extraEventNotFound(const QString &eventId); void extraEventNotFound(const QString &eventId);
void avatarChanged();
/** /**
* @brief Request a message be shown to the user of the given type. * @brief Request a message be shown to the user of the given type.
*/ */
void showMessage(MessageType::Type messageType, const QString &message); // void showMessage(MessageType::Type messageType, const QString &message);
public Q_SLOTS: public Q_SLOTS:
/** /**

View File

@@ -75,7 +75,7 @@ Kirigami.Dialog {
} }
} }
clip: true clip: true
model: AccountRegistry model: Accounts
keyNavigationEnabled: false keyNavigationEnabled: false
Keys.onDownPressed: { Keys.onDownPressed: {

View File

@@ -23,8 +23,8 @@ Kirigami.ApplicationWindow {
title: { title: {
if (NeoChatConfig.windowTitleFocus) { if (NeoChatConfig.windowTitleFocus) {
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : ""); return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
} else if (RoomManager.currentRoom) { // } else if (RoomManager.currentRoom) {
return RoomManager.currentRoom.displayName; // return RoomManager.currentRoom.displayName;
} else { } else {
return Application.displayName; return Application.displayName;
} }
@@ -49,20 +49,20 @@ Kirigami.ApplicationWindow {
} }
onConnectionChanged: { onConnectionChanged: {
CustomEmojiModel.connection = root.connection; // CustomEmojiModel.connection = root.connection;
SpaceHierarchyCache.connection = root.connection; // SpaceHierarchyCache.connection = root.connection;
NeoChatSettingsView.connection = root.connection; NeoChatSettingsView.connection = root.connection;
if (ShareHandler.text && root.connection) { // if (ShareHandler.text && root.connection) {
root.handleShare(); // root.handleShare();
} // }
} }
Connections { // Connections {
target: LoginHelper // target: LoginHelper
function onLoaded() { // function onLoaded() {
root.load(); // root.load();
} // }
} // }
Connections { Connections {
target: root.quitAction target: root.quitAction
@@ -83,64 +83,64 @@ Kirigami.ApplicationWindow {
configGroupName: "MainWindow" configGroupName: "MainWindow"
} }
QuickSwitcher { // QuickSwitcher {
id: quickSwitcher // id: quickSwitcher
connection: root.connection // connection: root.connection
} // }
Connections {
target: RoomManager
function onCurrentRoomChanged() {
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
connection: root.connection
});
roomPage.backRequested.connect(event => {
RoomManager.clearCurrentRoom();
});
}
}
function onAskJoinRoom(room) {
Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
room: room,
connection: root.connection
}).open();
}
function onShowUserDetail(user, room) {
root.showUserDetail(user, room);
}
function goToEvent(event) {
if (event.length > 0) {
roomItem.goToEvent(event);
}
roomItem.forceActiveFocus();
}
function onAskDirectChatConfirmation(user) {
Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
user: user
}).open();
}
function onExternalUrl(url) {
let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this);
dialog.link = url;
dialog.open();
}
}
function pushReplaceLayer(page, args) {
if (pageStack.layers.depth === 2) {
pageStack.layers.replace(page, args);
} else {
pageStack.layers.push(page, args);
}
}
// Connections {
// target: RoomManager
//
// function onCurrentRoomChanged() {
// if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
// let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
// connection: root.connection
// });
// roomPage.backRequested.connect(event => {
// RoomManager.clearCurrentRoom();
// });
// }
// }
//
// function onAskJoinRoom(room) {
// Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
// room: room,
// connection: root.connection
// }).open();
// }
//
// function onShowUserDetail(user, room) {
// root.showUserDetail(user, room);
// }
//
// function goToEvent(event) {
// if (event.length > 0) {
// roomItem.goToEvent(event);
// }
// roomItem.forceActiveFocus();
// }
//
// function onAskDirectChatConfirmation(user) {
// Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
// user: user
// }).open();
// }
//
// function onExternalUrl(url) {
// let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this);
// dialog.link = url;
// dialog.open();
// }
// }
//
// function pushReplaceLayer(page, args) {
// if (pageStack.layers.depth === 2) {
// pageStack.layers.replace(page, args);
// } else {
// pageStack.layers.push(page, args);
// }
// }
//
function openRoomDrawer() { function openRoomDrawer() {
pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), { pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection connection: root.connection
@@ -183,15 +183,15 @@ Kirigami.ApplicationWindow {
} }
Component.onCompleted: { Component.onCompleted: {
CustomEmojiModel.connection = root.connection; // CustomEmojiModel.connection = root.connection;
SpaceHierarchyCache.connection = root.connection; // SpaceHierarchyCache.connection = root.connection;
RoomSettingsView.window = root; // RoomSettingsView.window = root;
NeoChatSettingsView.window = root; NeoChatSettingsView.window = root;
NeoChatSettingsView.connection = root.connection; NeoChatSettingsView.connection = root.connection;
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout); WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
if (ShareHandler.text && root.connection) { // if (ShareHandler.text && root.connection) {
root.handleShare() // root.handleShare()
} // }
const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray; const hasSystemTray = Controller.supportSystemTray && NeoChatConfig.systemTray;
if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) { if (Kirigami.Settings.isMobile || !(hasSystemTray && NeoChatConfig.minimizeToSystemTrayOnStartup)) {
visible = true; visible = true;
@@ -210,7 +210,7 @@ Kirigami.ApplicationWindow {
// blur effect // blur effect
color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? "transparent" : Kirigami.Theme.backgroundColor
// we need to apply the translucency effect separately on top of the color // // we need to apply the translucency effect separately on top of the color
background: Rectangle { background: Rectangle {
color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - NeoChatConfig.transparency) : "transparent" color: NeoChatConfig.blur && !NeoChatConfig.compactLayout ? Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 1 - NeoChatConfig.transparency) : "transparent"
} }
@@ -220,7 +220,7 @@ Kirigami.ApplicationWindow {
RoomListPage { RoomListPage {
id: roomList id: roomList
onSearch: quickSwitcher.open() // onSearch: quickSwitcher.open()
connection: root.connection connection: root.connection
@@ -255,9 +255,9 @@ Kirigami.ApplicationWindow {
} }
Connections { Connections {
target: AccountRegistry target: Accounts
function onRowsRemoved() { function onRowsRemoved() {
if (AccountRegistry.rowCount() === 0) { if (Accounts.rowCount() === 0) {
pageStack.clear(); pageStack.clear();
pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage')); pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'));
} }
@@ -272,22 +272,22 @@ Kirigami.ApplicationWindow {
} }
} }
Connections { // Connections {
target: root.connection // target: root.connection
//
function onNewKeyVerificationSession(session) { // function onNewKeyVerificationSession(session) {
root.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "KeyVerificationDialog"), { // root.pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "KeyVerificationDialog"), {
session: session // session: session
}, { // }, {
title: i18nc("@title:window", "Session Verification") // title: i18nc("@title:window", "Session Verification")
}); // });
} // }
function onUserConsentRequired(url) { // function onUserConsentRequired(url) {
Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, { // Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, {
url: url // url: url
}).open(); // }).open();
} // }
} // }
HoverLinkIndicator { HoverLinkIndicator {
id: linkIndicator id: linkIndicator
@@ -303,36 +303,36 @@ Kirigami.ApplicationWindow {
} }
} }
Connections { // Connections {
target: ShareHandler // target: ShareHandler
function onTextChanged(): void { // function onTextChanged(): void {
if (root.connection && ShareHandler.text.length > 0) { // if (root.connection && ShareHandler.text.length > 0) {
root.handleShare(); // root.handleShare();
} // }
} // }
} // }
function handleShare(): void { // function handleShare(): void {
const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), { // const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
connection: root.connection // connection: root.connection
}, { // }, {
title: i18nc("@title", "Share"), // title: i18nc("@title", "Share"),
width: Kirigami.Units.gridUnit * 25 // width: Kirigami.Units.gridUnit * 25
}) // })
dialog.chosen.connect(function(targetRoomId) { // dialog.chosen.connect(function(targetRoomId) {
RoomManager.resolveResource(targetRoomId) // RoomManager.resolveResource(targetRoomId)
ShareHandler.room = targetRoomId // ShareHandler.room = targetRoomId
dialog.closeDialog() // dialog.closeDialog()
}) // })
} // }
function showUserDetail(user, room) { // function showUserDetail(user, room) {
const dialog = Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root, { // const dialog = Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root, {
room: room, // room: room,
user: user, // user: user,
connection: root.connection, // connection: root.connection,
}); // });
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again // dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
dialog.open(); // dialog.open();
} // }
function load() { function load() {
pageStack.replace(roomListComponent); pageStack.replace(roomListComponent);

View File

@@ -19,7 +19,7 @@ Delegates.RoundedItemDelegate {
required property int index required property int index
required property int contextNotificationCount required property int contextNotificationCount
required property bool hasHighlightNotifications required property bool hasHighlightNotifications
required property NeoChatRoom currentRoom required property string roomId
required property NeoChatConnection connection required property NeoChatConnection connection
required property url avatar required property url avatar
required property string subtitleText required property string subtitleText
@@ -37,11 +37,13 @@ Delegates.RoundedItemDelegate {
onClicked: { onClicked: {
if (root.openOnClick) { if (root.openOnClick) {
RoomManager.resolveResource(currentRoom.id); root.connection.open(root.roomId)
pageStack.currentIndex = 1; pageStack.currentIndex = 1;
} }
} }
onPressAndHold: createRoomListContextMenu()
Keys.onSpacePressed: clicked() Keys.onSpacePressed: clicked()
Keys.onEnterPressed: clicked() Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked() Keys.onReturnPressed: clicked()
@@ -52,19 +54,14 @@ Delegates.RoundedItemDelegate {
onTapped: (eventPoint, button) => root.createRoomListContextMenu() onTapped: (eventPoint, button) => root.createRoomListContextMenu()
} }
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onLongPressed: root.createRoomListContextMenu()
}
contentItem: RowLayout { contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
AvatarNotification { AvatarNotification {
source: root.avatar source: root.avatar
name: root.displayName name: root.displayName
visible: NeoChatConfig.showAvatarInRoomDrawer // visible: NeoChatConfig.showAvatarInRoomDrawer
implicitHeight: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 // (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2)
implicitWidth: visible ? implicitHeight : 0 implicitWidth: visible ? implicitHeight : 0
notificationCount: root.contextNotificationCount notificationCount: root.contextNotificationCount
@@ -101,7 +98,7 @@ Delegates.RoundedItemDelegate {
elide: Text.ElideRight elide: Text.ElideRight
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont
opacity: root.hasNotifications ? 0.9 : 0.7 opacity: root.hasNotifications ? 0.9 : 0.7
visible: !NeoChatConfig.compactRoomList && text.length > 0 // visible: !NeoChatConfig.compactRoomList && text.length > 0
textFormat: Text.PlainText textFormat: Text.PlainText
Layout.fillWidth: true Layout.fillWidth: true
@@ -147,7 +144,7 @@ Delegates.RoundedItemDelegate {
QQC2.Button { QQC2.Button {
id: configButton id: configButton
visible: root.hovered && !Kirigami.Settings.isMobile && !NeoChatConfig.compactRoomList && !root.collapsed && root.showConfigure visible: root.hovered && !Kirigami.Settings.isMobile /*&& !NeoChatConfig.compactRoomList*/ && !root.collapsed && root.showConfigure
text: i18n("Configure room") text: i18n("Configure room")
display: QQC2.Button.IconOnly display: QQC2.Button.IconOnly

View File

@@ -1,295 +1,117 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org> // SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu> // SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-License-Identifier: GPL-3.0-only
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import QtQml.Models
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.delegates as Delegates import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat import org.kde.neochat
Kirigami.ScrollablePage {
Kirigami.Page {
id: root id: root
/** title: i18nc("@title", "Rooms")
* @brief The current width of the room list.
*
* @note Other objects can access the value but the private function makes sure
* that only the internal members can modify it.
*/
readonly property int currentWidth: _private.currentWidth + spaceDrawer.width + 1
required property NeoChatConnection connection required property NeoChatConnection connection
readonly property bool collapsed: NeoChatConfig.collapsed actions: [
Kirigami.Action {
signal search text: i18nc("@action:button", "Log out")
onTriggered: root.connection.logout()
onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth },
Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth Kirigami.Action {
text: i18nc("@action:button", "Create Room")
onTriggered: root.connection.createRoom("Hello", "World", "")
onCollapsedChanged: {
if (collapsed) {
RoomManager.sortFilterRoomTreeModel.filterText = "";
} }
} ]
function goToNextRoomFiltered(condition) { Connections {
let index = treeView.rowAtIndex(RoomManager.sortFilterRoomTreeModel.currentRoomIndex()); target: root.connection
while (index++ < treeView.rows) { function onOpenRoom(): void {
let item = treeView.itemAtIndex(treeView.index(index, 0)) room => pageStack.push(Qt.createComponent("org.kde.neochat", "RoomPage"), {
if (condition(item)) { room: room,
RoomManager.resolveResource(item.currentRoom.id) connection: connection,
return; });
}
} }
} }
function goToPreviousRoomFiltered(condition) {
let index = treeView.rowAtIndex(RoomManager.sortFilterRoomTreeModel.currentRoomIndex());
while (index-- > 0) {
let item = treeView.itemAtIndex(treeView.index(index, 0))
if (condition(item)) {
RoomManager.resolveResource(item.currentRoom.id)
return;
}
}
}
function goToNextRoom() {
goToNextRoomFiltered(item => (item && item instanceof RoomDelegate));
}
function goToPreviousRoom() {
goToPreviousRoomFiltered(item => (item && item instanceof RoomDelegate));
}
function goToNextUnreadRoom() {
goToNextRoomFiltered(item => (item && item instanceof RoomDelegate && item.hasUnread));
}
function goToPreviousUnreadRoom() {
goToPreviousRoomFiltered(item => (item && item instanceof RoomDelegate && item.hasUnread));
}
titleDelegate: Loader { titleDelegate: Loader {
Layout.fillWidth: true Layout.fillWidth: true
sourceComponent: Kirigami.Settings.isMobile ? userInfo : exploreComponent sourceComponent: Kirigami.Settings.isMobile ? userInfo : exploreComponent
} }
padding: 0
Connections {
target: RoomManager
function onCurrentSpaceChanged() {
treeView.expandRecursively();
}
function onCurrentRoomChanged() {
treeView.positionViewAtIndex(RoomManager.sortFilterRoomTreeModel.currentRoomIndex(), TableView.AlignVCenter)
}
}
RowLayout {
anchors.fill: parent
spacing: 0
SpaceDrawer {
id: spaceDrawer
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.fillHeight: true
connection: root.connection
}
Kirigami.Separator {
Layout.fillHeight: true
Layout.preferredWidth: 1
}
QQC2.ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
}
Keys.onDownPressed: ; // Do not delete 🫠
Keys.onUpPressed: ; // These make sure the scrollview doesn't also scroll while going through the roomlist using the arrow keys
contentItem: TreeView {
id: treeView
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
clip: true
reuseItems: false
model: RoomManager.sortFilterRoomTreeModel
selectionModel: ItemSelectionModel {}
delegate: DelegateChooser {
role: "delegateType"
DelegateChoice {
roleValue: "section"
delegate: RoomTreeSection {
collapsed: root.collapsed
}
}
DelegateChoice {
roleValue: "normal"
delegate: RoomDelegate {
id: roomDelegate
required property int row
required property TreeView treeView
required property bool current
onCurrentChanged: if (current) {
forceActiveFocus(Qt.TabFocusReason);
}
implicitWidth: treeView.width
connection: root.connection
collapsed: root.collapsed
highlighted: RoomManager.currentRoom === currentRoom
}
}
DelegateChoice {
roleValue: "addDirect"
delegate: Delegates.RoundedItemDelegate {
text: i18n("Find your friends")
icon.name: "list-add-user"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
}
}
}
}
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
anchors.horizontalCenterOffset: (spaceDrawer.width + 1) / 2
width: scrollView.width - Kirigami.Units.largeSpacing * 4
visible: treeView.rows == 0
text: if (RoomManager.sortFilterRoomTreeModel.filterText.length > 0) {
return spaceDrawer.showDirectChats ? i18n("No friends found") : i18n("No rooms found");
} else {
return spaceDrawer.showDirectChats ? i18n("You haven't added any of your friends yet, click below to search for them.") : i18n("Join some rooms to get started");
}
helpfulAction: spaceDrawer.showDirectChats ? userSearchAction : exploreRoomAction
Kirigami.Action {
id: exploreRoomAction
icon.name: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
onTriggered: {
let dialog = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection,
keyword: RoomManager.sortFilterRoomTreeModel.filterText
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});
}
}
Kirigami.Action {
id: userSearchAction
icon.name: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "list-add"
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18n("Search in friend directory") : i18n("Find your friends")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
}
footer: Loader { footer: Loader {
width: parent.width width: parent.width
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
} }
MouseArea { TreeView {
anchors.top: parent.top id: treeView
anchors.bottom: parent.bottom topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
parent: applicationWindow().overlay.parent
x: root.currentWidth - width / 2 clip: true
width: Kirigami.Units.smallSpacing * 2 reuseItems: false
z: root.z + 1
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
visible: enabled
cursorShape: Qt.SplitHCursor
property int _lastX model: SortFilterRoomTreeModel {
sourceModel: RoomTreeModel {
onPressed: mouse => { connection: root.connection
_lastX = mouse.x;
}
onPositionChanged: mouse => {
if (_lastX == -1) {
return;
} }
if (mouse.x > _lastX) { }
// we moved to the right
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) { selectionModel: ItemSelectionModel {}
// Here we get back directly to a more wide mode.
_private.currentWidth = _private.defaultWidth; delegate: DelegateChooser {
NeoChatConfig.collapsed = false; role: "delegateType"
} else if (_private.currentWidth >= _private.collapseWidth) {
// Increase page width DelegateChoice {
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX)); roleValue: "section"
delegate: RoomTreeSection {
collapsed: root.collapsed
} }
} else if (mouse.x < _lastX) { }
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
if (tmpWidth < _private.collapseWidth) { DelegateChoice {
_private.currentWidth = Qt.binding(() => _private.collapsedSize); roleValue: "normal"
NeoChatConfig.collapsed = true; delegate: RoomDelegate {
} else { id: roomDelegate
_private.currentWidth = tmpWidth; required property int row
required property TreeView treeView
required property bool current
onCurrentChanged: if (current) {
forceActiveFocus(Qt.TabFocusReason);
}
implicitWidth: treeView.width
connection: root.connection
collapsed: root.collapsed
highlighted: RoomManager.currentRoom === currentRoom
}
}
DelegateChoice {
roleValue: "addDirect"
delegate: Delegates.RoundedItemDelegate {
text: i18n("Find your friends")
icon.name: "list-add-user"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
} }
} }
} }
} }
Component {
id: userInfo
UserInfo {
bottomEdge: false
connection: root.connection
}
}
Component {
id: userInfoDesktop
UserInfoDesktop {
connection: root.connection
collapsed: root.collapsed
}
}
Component { Component {
id: exploreComponent id: exploreComponent
@@ -308,25 +130,10 @@ Kirigami.Page {
} }
Component { Component {
id: exploreComponentMobile id: userInfoDesktop
ExploreComponentMobile { UserInfoDesktop {
connection: root.connection connection: root.connection
collapsed: root.collapsed
onTextChanged: newText => {
RoomManager.sortFilterRoomTreeModel.filterText = newText;
}
} }
} }
/*
* Hold the modifiable currentWidth in a private object so that only internal
* members can modify it.
*/
QtObject {
id: _private
property int currentWidth: NeoChatConfig.collapsed ? collapsedSize : defaultWidth
readonly property int defaultWidth: Kirigami.Units.gridUnit * 15
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
readonly property int collapsedSize: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) + Kirigami.Units.largeSpacing * 2 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
}
} }

View File

@@ -38,32 +38,32 @@ QQC2.ScrollView {
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890) // HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView { // ListView {
clip: true // clip: true
verticalLayoutDirection: ListView.BottomToTop // verticalLayoutDirection: ListView.BottomToTop
//
model: RoomManager.mediaMessageFilterModel // model: RoomManager.mediaMessageFilterModel
//
delegate: DelegateChooser { // delegate: DelegateChooser {
role: "type" // role: "type"
//
DelegateChoice { // DelegateChoice {
roleValue: MediaMessageFilterModel.Image // roleValue: MediaMessageFilterModel.Image
delegate: MessageDelegate { // delegate: MessageDelegate {
alwaysFillWidth: true // alwaysFillWidth: true
cardBackground: false // cardBackground: false
room: root.currentRoom // room: root.currentRoom
} // }
} // }
//
DelegateChoice { // DelegateChoice {
roleValue: MediaMessageFilterModel.Video // roleValue: MediaMessageFilterModel.Video
delegate: MessageDelegate { // delegate: MessageDelegate {
alwaysFillWidth: true // alwaysFillWidth: true
cardBackground: false // cardBackground: false
room: root.currentRoom // room: root.currentRoom
} // }
} // }
} // }
} // }
} }

View File

@@ -32,7 +32,7 @@ Kirigami.Page {
* *
* @sa TimelineModel * @sa TimelineModel
*/ */
property TimelineModel timelineModel: RoomManager.timelineModel // property TimelineModel timelineModel: RoomManager.timelineModel
/** /**
* @brief The MessageFilterModel to use. * @brief The MessageFilterModel to use.
@@ -44,7 +44,7 @@ Kirigami.Page {
* *
* @sa TimelineModel, MessageFilterModel * @sa TimelineModel, MessageFilterModel
*/ */
property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel // property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel
/** /**
* @brief The MediaMessageFilterModel to use. * @brief The MediaMessageFilterModel to use.
@@ -57,7 +57,7 @@ Kirigami.Page {
* *
* @sa TimelineModel, MessageFilterModel * @sa TimelineModel, MessageFilterModel
*/ */
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel // property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded) property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
@@ -106,58 +106,58 @@ Kirigami.Page {
position: Kirigami.InlineMessage.Position.Header position: Kirigami.InlineMessage.Position.Header
} }
Loader { // Loader {
id: timelineViewLoader // id: timelineViewLoader
anchors.fill: parent // anchors.fill: parent
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace // active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
sourceComponent: TimelineView { // sourceComponent: TimelineView {
id: timelineView // id: timelineView
currentRoom: root.currentRoom // currentRoom: root.currentRoom
page: root // page: root
timelineModel: root.timelineModel // timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel // messageFilterModel: root.messageFilterModel
onFocusChatBar: { // onFocusChatBar: {
if (chatBarLoader.item) { // if (chatBarLoader.item) {
chatBarLoader.item.forceActiveFocus(); // chatBarLoader.item.forceActiveFocus();
} // }
} // }
} // }
} // }
Loader { // Loader {
id: invitationLoader // id: invitationLoader
active: root.currentRoom && root.currentRoom.isInvite // active: root.currentRoom && root.currentRoom.isInvite
anchors.centerIn: parent // anchors.centerIn: parent
sourceComponent: InvitationView { // sourceComponent: InvitationView {
currentRoom: root.currentRoom // currentRoom: root.currentRoom
anchors.centerIn: parent // anchors.centerIn: parent
} // }
} // }
Loader { // Loader {
id: spaceLoader // id: spaceLoader
active: root.currentRoom && root.currentRoom.isSpace // active: root.currentRoom && root.currentRoom.isSpace
anchors.fill: parent // anchors.fill: parent
sourceComponent: SpaceHomePage {} // sourceComponent: SpaceHomePage {}
} // }
Loader { // Loader {
active: !RoomManager.currentRoom // active: !RoomManager.currentRoom
anchors.centerIn: parent // anchors.centerIn: parent
sourceComponent: Kirigami.PlaceholderMessage { // sourceComponent: Kirigami.PlaceholderMessage {
icon.name: "org.kde.neochat" // icon.name: "org.kde.neochat"
text: i18n("Welcome to NeoChat") // text: i18n("Welcome to NeoChat")
explanation: i18n("Select or join a room to get started") // explanation: i18n("Select or join a room to get started")
} // }
} // }
Loader { // Loader {
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active // active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
anchors.centerIn: parent // anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder { // sourceComponent: Kirigami.LoadingPlaceholder {
anchors.centerIn: parent // anchors.centerIn: parent
} // }
} // }
background: Rectangle { background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
@@ -290,26 +290,26 @@ Kirigami.Page {
} }
} }
Component { // Component {
id: messageDelegateContextMenu // id: messageDelegateContextMenu
MessageDelegateContextMenu { // MessageDelegateContextMenu {
connection: root.connection // connection: root.connection
} // }
} // }
Component { // Component {
id: fileDelegateContextMenu // id: fileDelegateContextMenu
FileDelegateContextMenu { // FileDelegateContextMenu {
connection: root.connection // connection: root.connection
} // }
} // }
Component { // Component {
id: maximizeComponent // id: maximizeComponent
NeochatMaximizeComponent { // NeochatMaximizeComponent {
currentRoom: root.currentRoom // currentRoom: root.currentRoom
model: root.mediaMessageFilterModel // model: root.mediaMessageFilterModel
parent: root.QQC2.Overlay.overlay // parent: root.QQC2.Overlay.overlay
} // }
} // }
} }

View File

@@ -47,7 +47,7 @@ QQC2.ItemDelegate {
opacity: 0.7 opacity: 0.7
level: 5 level: 5
type: Kirigami.Heading.Primary type: Kirigami.Heading.Primary
text: root.collapsed ? "" : model.displayName text: root.collapsed ? "" : root.displayName
elide: Text.ElideRight elide: Text.ElideRight
// we override the Primary type's font weight (DemiBold) for Bold for contrast with small text // we override the Primary type's font weight (DemiBold) for Bold for contrast with small text

View File

@@ -54,7 +54,7 @@ RowLayout {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar { KirigamiComponents.Avatar {
readonly property url avatarUrl: root.connection.localUser.avatarUrl readonly property url avatarUrl: root.connection.avatarUrl
Layout.preferredWidth: Kirigami.Units.iconSizes.medium Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium Layout.preferredHeight: Kirigami.Units.iconSizes.medium
@@ -62,7 +62,7 @@ RowLayout {
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl. // Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(avatarUrl) : "" source: avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(avatarUrl) : ""
name: root.connection.localUser.displayName name: root.connection.displayName
} }
ColumnLayout { ColumnLayout {
@@ -73,14 +73,14 @@ RowLayout {
QQC2.Label { QQC2.Label {
id: displayNameLabel id: displayNameLabel
Layout.fillWidth: true Layout.fillWidth: true
text: root.connection.localUser.displayName text: root.connection.displayName
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
} }
QQC2.Label { QQC2.Label {
id: idLabel id: idLabel
Layout.fillWidth: true Layout.fillWidth: true
text: (root.connection.label.length > 0 ? (root.connection.label + " ") : "") + root.connection.localUser.id text: root.connection.matrixId
font.pointSize: displayNameLabel.font.pointSize * 0.8 font.pointSize: displayNameLabel.font.pointSize * 0.8
opacity: 0.7 opacity: 0.7
textFormat: Text.PlainText textFormat: Text.PlainText

View File

@@ -7,9 +7,9 @@
#include <KSharedConfig> #include <KSharedConfig>
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
#include <Quotient/room.h> // #include <Quotient/room.h>
#include <Quotient/roommember.h> // #include <Quotient/roommember.h>
#include <Quotient/uriresolver.h> // #include <Quotient/uriresolver.h>
#include "chatdocumenthandler.h" #include "chatdocumenthandler.h"
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"

View File

@@ -4,7 +4,7 @@
qt_add_library(settings STATIC) qt_add_library(settings STATIC)
set_source_files_properties( set_source_files_properties(
RoomSettingsView.qml # RoomSettingsView.qml
NeoChatSettingsView.qml NeoChatSettingsView.qml
PROPERTIES PROPERTIES
QT_QML_SINGLETON_TYPE TRUE QT_QML_SINGLETON_TYPE TRUE
@@ -15,33 +15,33 @@ ecm_add_qml_module(settings GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/settings OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/settings
QML_FILES QML_FILES
NeoChatSettingsView.qml NeoChatSettingsView.qml
RoomSettingsView.qml # RoomSettingsView.qml
AccountsPage.qml # AccountsPage.qml
AccountEditorPage.qml # AccountEditorPage.qml
AppearanceSettingsPage.qml AppearanceSettingsPage.qml
DevicesPage.qml # DevicesPage.qml
EmoticonsPage.qml # EmoticonsPage.qml
EmoticonEditorPage.qml # EmoticonEditorPage.qml
GlobalNotificationsPage.qml GlobalNotificationsPage.qml
NeoChatGeneralPage.qml NeoChatGeneralPage.qml
NeoChatSecurityPage.qml NeoChatSecurityPage.qml
NetworkProxyPage.qml NetworkProxyPage.qml
Permissions.qml # Permissions.qml
PushNotification.qml # PushNotification.qml
RoomGeneralPage.qml # RoomGeneralPage.qml
RoomSecurityPage.qml # RoomSecurityPage.qml
ColorScheme.qml # ColorScheme.qml
DevicesCard.qml # DevicesCard.qml
DeviceDelegate.qml # DeviceDelegate.qml
EmoticonFormCard.qml # EmoticonFormCard.qml
IdentityServerDelegate.qml # IdentityServerDelegate.qml
IgnoredUsersDialog.qml # IgnoredUsersDialog.qml
NotificationRuleItem.qml NotificationRuleItem.qml
PasswordSheet.qml # PasswordSheet.qml
ThemeRadioButton.qml ThemeRadioButton.qml
ThreePIdCard.qml # ThreePIdCard.qml
ImportKeysDialog.qml # ImportKeysDialog.qml
ExportKeysDialog.qml # ExportKeysDialog.qml
RoomSortParameterDialog.qml # RoomSortParameterDialog.qml
RoomProfile.qml # RoomProfile.qml
) )

View File

@@ -10,13 +10,9 @@
#include <unicode/urename.h> #include <unicode/urename.h>
#endif #endif
#include <Quotient/connection.h>
#include <QJsonDocument> #include <QJsonDocument>
#include <QQuickWindow> #include <QQuickWindow>
using namespace Quotient;
bool QmlUtils::isEmoji(const QString &text) bool QmlUtils::isEmoji(const QString &text)
{ {
return Utils::isEmoji(text); return Utils::isEmoji(text);
@@ -48,10 +44,10 @@ QQuickItem *QmlUtils::focusedWindowItem()
} }
} }
QString QmlUtils::nameForPowerLevelValue(const int value) // QString QmlUtils::nameForPowerLevelValue(const int value)
{ // {
return PowerLevel::nameForLevel(PowerLevel::levelForValue(value)); // return PowerLevel::nameForLevel(PowerLevel::levelForValue(value));
} // }
bool Utils::isEmoji(const QString &text) bool Utils::isEmoji(const QString &text)
{ {

View File

@@ -10,9 +10,7 @@
#include <QQuickItem> #include <QQuickItem>
#include <QRegularExpression> #include <QRegularExpression>
#include <Quotient/user.h> // #include "enums/powerlevel.h"
#include "enums/powerlevel.h"
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@@ -43,7 +41,7 @@ public:
/** /**
* @brief Invokable version of PowerLevel::nameForLevel which also calls PowerLevel::levelForValue. * @brief Invokable version of PowerLevel::nameForLevel which also calls PowerLevel::levelForValue.
*/ */
Q_INVOKABLE QString nameForPowerLevelValue(int value); // Q_INVOKABLE QString nameForPowerLevelValue(int value);
private: private:
QmlUtils() = default; QmlUtils() = default;