Compare commits

..

1 Commits

Author SHA1 Message Date
Joshua Goins
a94579e0cf Pin kirigami-addons in the Flatpak build to 1.7.0 2025-02-03 15:48:28 -05:00
172 changed files with 14662 additions and 22884 deletions

View File

@@ -22,39 +22,16 @@
"--talk-name=org.freedesktop.secrets",
"--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": [
"flatpak/corrosion.json",
{
"name": "kirigamiaddons",
"config-opts": [
"-DBUILD_TESTING=OFF"
],
"config-opts": [ "-DBUILD_TESTING=OFF" ],
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
}
]
"sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "tag": "v1.7.0" } ]
},
{
"name": "kquickimageeditor",
"config-opts": [
"-DBUILD_WITH_QT6=ON",
"-DBUILD_TESTING=OFF"
],
"config-opts": [ "-DBUILD_WITH_QT6=ON" ],
"buildsystem": "cmake-ninja",
"sources": [
{
@@ -63,6 +40,23 @@
}
]
},
{
"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.10",
"x-checker-data": {
"type": "git",
"tag-pattern": "^([\\d.]+)$"
},
"commit": "9908862979147a71dc6abaecd521be526ae77be1"
}
]
},
{
"name": "libsecret",
"buildsystem": "meson",
@@ -71,13 +65,13 @@
"-Dvapi=false",
"-Dgtk_doc=false",
"-Dintrospection=false",
"-Dcrypto=disabled"
"-Dgcrypt=false"
],
"sources": [
{
"type": "archive",
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
"url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
"sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
"x-checker-data": {
"type": "gnome",
"name": "libsecret",
@@ -92,13 +86,13 @@
"sources": [
{
"type": "archive",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.15.0.tar.gz",
"sha256": "f4254dc8f0933b06d90672d683eab08ef770acd8336e44dfa030ce041dc2ca22",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
"sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
"x-checker-data": {
"type": "anitya",
"project-id": 4138,
"stable-only": true,
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/$version.tar.gz"
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
}
}
],
@@ -106,50 +100,52 @@
"-DBUILD_WITH_QT6=ON",
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO",
"-DBUILD_TESTING=OFF"
"-DBUILD_TRANSLATIONS=NO"
]
},
{
"name": "integral",
"name": "libQuotient",
"buildsystem": "cmake-ninja",
"sources": [
"flatpak/generated-sources.json",
{
"type": "git",
"url": "https://invent.kde.org/tfella/integral.git",
"branch": "master"
"url": "https://github.com/quotient-im/libQuotient.git",
"branch": "dev",
"disable-submodules": true
}
],
"config-opts": [
"-DBUILD_WITH_QT6=ON",
"-DQuotient_ENABLE_E2EE=ON",
"-DBUILD_TESTING=OFF"
]
},
{
"name": "cmark",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"config-opts": [ "-DCMARK_TESTS=OFF" ],
"sources": [
{
"type": "git",
"url": "https://github.com/commonmark/cmark.git"
}
],
"config-opts": [
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"builddir": true
},
{
"name": "qcoro",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
],
"config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
"sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
"x-checker-data": {
"type": "anitya",
"project-id": 236236,
@@ -162,15 +158,14 @@
{
"name": "neochat",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DBUILD_TESTING=OFF",
"-DNEOCHAT_FLATPAK=ON"
],
"sources": [
{
"type": "dir",
"path": "."
}
],
"config-opts": [
"-DNEOCHAT_FLATPAK=ON"
]
}
]

View File

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

View File

@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
@@ -49,6 +49,8 @@ if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX NEOCHAT
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
@@ -105,7 +107,13 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
find_package(Integral 0.1 REQUIRED)
find_package(QuotientQt6 0.9)
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)
set_package_properties(cmark PROPERTIES

View File

@@ -12,7 +12,6 @@
#include "models/messagecontentmodel.h"
#include "neochatconnection.h"
#include "testutils.h"
using namespace Quotient;
@@ -33,7 +32,7 @@ private Q_SLOTS:
void MessageContentModelTest::initTestCase()
{
connection = new NeoChatConnection;
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
}
void MessageContentModelTest::missingEvent()

View File

@@ -20,11 +20,11 @@ class ReactionModelTest : public QObject
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
MessageContentModel *parentModel;
private Q_SLOTS:
void initTestCase();
void nullModel();
void basicReaction();
void newReaction();
};
@@ -33,13 +33,20 @@ void ReactionModelTest::initTestCase()
{
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
parentModel = new MessageContentModel(room, "123456"_L1);
}
void ReactionModelTest::nullModel()
{
auto model = ReactionModel(nullptr, nullptr);
QCOMPARE(model.rowCount(), 0);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
}
void ReactionModelTest::basicReaction()
{
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = ReactionModel(parentModel, event->id(), room);
auto model = ReactionModel(event, room);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), u"<span style=\"font-family: 'emoji';\">👍</span>"_s);
@@ -51,7 +58,7 @@ void ReactionModelTest::basicReaction()
void ReactionModelTest::newReaction()
{
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = new ReactionModel(parentModel, event->id(), room);
auto model = new ReactionModel(event, room);
QCOMPARE(model->rowCount(), 1);
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s);

View File

@@ -1,30 +0,0 @@
{
"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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,16 +0,0 @@
#!/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

@@ -473,7 +473,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/>

View File

@@ -112,7 +112,6 @@ Comment[ka]=ჩატი Matrix-ზე
Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie
Comment[pt_BR]=Bate papo na Matrix
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
Comment[sl]=Klepet na Matrixu
Comment[sv]=Chatta på Matrix

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
# SPDX-License-Identifier: CC0-1.0
---
name: neochat
base: core24
base: core22
adopt-info: neochat
grade: stable
confinement: strict
@@ -106,6 +106,8 @@ parts:
build-snaps:
- cmake
build-packages:
- gcc-13
- g++-13
- libssl-dev
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
@@ -113,6 +115,11 @@ parts:
- -DBUILD_TESTING=OFF
- -DQuotient_ENABLE_E2EE=ON
- -DBUILD_WITH_QT6=ON
override-build: |
"update-alternatives --install /usr/bin/gcc gcc\
/usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ \
/usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13"
craftctl default
prime:
- -usr/include
- -usr/lib/*/pkgconfig
@@ -125,8 +132,6 @@ parts:
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
@@ -148,8 +153,6 @@ parts:
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
build-packages:
- cmark
- libcmark-dev
@@ -172,13 +175,3 @@ parts:
- libcmark0.30.2
prime:
- usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
#include <QQuickTextDocument>
#include <QTextCursor>
// #include "models/messagecontentmodel.h"
#include "models/messagecontentmodel.h"
class ChatDocumentHandler;
@@ -102,7 +102,7 @@ class ChatBarCache : public QObject
*
* @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.
@@ -116,7 +116,7 @@ class ChatBarCache : public QObject
*
* 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.
@@ -164,10 +164,10 @@ public:
QString editId() const;
void setEditId(const QString &editId);
// Quotient::RoomMember relationAuthor() const;
Quotient::RoomMember relationAuthor() const;
QString relationMessage() const;
// MessageContentModel *relationEventContentModel();
MessageContentModel *relationEventContentModel();
bool isThreaded() const;
QString threadId() const;
@@ -225,7 +225,7 @@ private:
QList<Mention> m_mentions;
QString m_savedText;
// QPointer<MessageContentModel> m_relationContentModel;
QPointer<MessageContentModel> m_relationContentModel;
void clearCache();
};

View File

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

View File

@@ -8,16 +8,19 @@
#include <KLocalizedString>
#include <QFile>
#include <QGuiApplication>
#include <QTimer>
#include <signal.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
// #include "neochatroom.h"
// #include "notificationsmanager.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
@@ -40,13 +43,12 @@
bool testMode = false;
using namespace Integral;
using namespace Qt::Literals::StringLiterals;
using namespace Quotient;
Controller::Controller(QObject *parent)
: QObject(parent)
{
// Connection::setRoomType<NeoChatRoom>();
Connection::setRoomType<NeoChatRoom>();
ProxyController::instance().setApplicationProxy();
@@ -55,18 +57,18 @@ Controller::Controller(QObject *parent)
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
// if (!testMode) {
// QTimer::singleShot(0, this, [this] {
// invokeLogin();
// });
// } else {
// auto c = new NeoChatConnection(this);
// c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
// connect(c, &Connection::connected, this, [c, this]() {
// m_accountRegistry.add(c);
// c->syncLoop();
// });
// }
if (!testMode) {
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
} else {
auto c = new NeoChatConnection(this);
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
connect(c, &Connection::connected, this, [c, this]() {
m_accountRegistry.add(c);
c->syncLoop();
});
}
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
@@ -97,31 +99,31 @@ Controller::Controller(QObject *parent)
#endif
static int oldAccountCount = 0;
// connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
// if (m_accountRegistry.size() > oldAccountCount) {
// auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
// connect(
// connection,
// &NeoChatConnection::syncDone,
// this,
// [this, connection] {
// if (!m_endpoint.isEmpty()) {
// connection->setupPushNotifications(m_endpoint);
// }
// },
// Qt::SingleShotConnection);
// }
// oldAccountCount = m_accountRegistry.size();
// });
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(
connection,
&NeoChatConnection::syncDone,
this,
[this, connection] {
if (!m_endpoint.isEmpty()) {
connection->setupPushNotifications(m_endpoint);
}
},
Qt::SingleShotConnection);
}
oldAccountCount = m_accountRegistry.size();
});
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
m_endpoint = endpoint;
// for (auto &quotientConnection : m_accountRegistry) {
// auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
// connection->setupPushNotifications(endpoint);
// }
for (auto &quotientConnection : m_accountRegistry) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
});
connector->registerClient(
@@ -138,6 +140,147 @@ Controller &Controller::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
{
#ifdef Q_OS_ANDROID
@@ -151,7 +294,7 @@ bool Controller::supportSystemTray() const
void Controller::setQuitOnLastWindowClosed()
{
#ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
if (NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
} else {
@@ -179,7 +322,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
if (m_connection != nullptr) {
m_connection->disconnect(this);
// m_connection->disconnect(&m_notificationsManager);
m_connection->disconnect(&m_notificationsManager);
}
m_connection = connection;
@@ -188,7 +331,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
m_connection->refreshBadgeNotificationCount();
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);
@@ -203,7 +346,7 @@ void Controller::listenForNotifications()
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
// instance().m_notificationsManager.postPushNotification(data);
instance().m_notificationsManager.postPushNotification(data);
timer->stop();
});
@@ -217,7 +360,7 @@ void Controller::listenForNotifications()
void Controller::clearInvitationNotification(const QString &roomId)
{
// m_notificationsManager.clearInvitationNotification(roomId);
m_notificationsManager.clearInvitationNotification(roomId);
}
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
@@ -259,9 +402,9 @@ bool Controller::isFlatpak() const
#endif
}
Accounts &Controller::accounts()
AccountRegistry &Controller::accounts()
{
return m_accounts;
return m_accountRegistry;
}
QString Controller::loadFileContent(const QString &path) const
@@ -277,6 +420,20 @@ void Controller::setTestMode(bool 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
{
return true;

View File

@@ -7,8 +7,8 @@
#include <QQmlEngine>
#include "neochatconnection.h"
// #include "notificationsmanager.h"
#include <Integral/Accounts>
#include "notificationsmanager.h"
#include <Quotient/accountregistry.h>
class TrayIcon;
@@ -63,6 +63,21 @@ public:
void setActiveConnection(NeoChatConnection *connection);
[[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;
bool isFlatpak() const;
@@ -82,10 +97,12 @@ public:
Q_INVOKABLE QString loadFileContent(const QString &path) const;
Integral::Accounts &accounts();
Quotient::AccountRegistry &accounts();
static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId);
bool csSupported() const;
/**
@@ -105,15 +122,18 @@ private:
QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr;
Integral::Accounts m_accounts;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
QString m_endpoint;
QStringList m_shownImages;
// NotificationsManager m_notificationsManager;
NotificationsManager m_notificationsManager;
private Q_SLOTS:
void invokeLogin();
void setQuitOnLastWindowClosed();
void updateBadgeNotificationCount(NeoChatConnection *connection, int count);

View File

@@ -3,7 +3,6 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
@@ -38,7 +37,7 @@ FormCard.FormCardPage {
}
function openEventSource(stateKey: string): void {
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel,
allowEdit: true,
room: root.room,

View File

@@ -50,7 +50,6 @@ public:
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Encrypted, /**< An encrypted message that cannot be decrypted. */
Reply, /**< A component to show a replied-to message. */
Reaction, /**< A component to show the reactions to this message. */
LinkPreview, /**< A preview of a URL in the message. */
LinkPreviewLoad, /**< A loading dialog for a link preview. */
ChatBar, /**< A text edit for editing a message. */

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "joinrulesevent.h"
using namespace Quotient;
QString JoinRulesEvent::joinRule() const
{
return fromJson<QString>(contentJson()["join_rule"_L1]);
}
QJsonArray JoinRulesEvent::allow() const
{
return contentJson()["allow"_L1].toArray();
}

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <Quotient/events/stateevent.h>
namespace Quotient
{
/**
* @class JoinRulesEvent
*
* Class to define a join rule state event.
*
* @sa Quotient::StateEvent
*/
class JoinRulesEvent : public StateEvent
{
public:
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
explicit JoinRulesEvent(const QJsonObject &obj)
: StateEvent(obj)
{
}
/**
* @brief The join rule for the room.
*
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
* the available join rules for a room.
*/
QString joinRule() const;
/**
* @brief The allow rule for restricted rooms.
*
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
* full details on allow rules.
*/
QJsonArray allow() const;
};
}

View File

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

View File

@@ -9,6 +9,7 @@
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "utils.h"
using namespace Quotient;
@@ -21,6 +22,7 @@ LinkPreviewer::LinkPreviewer(const QUrl &url, QObject *parent)
Q_ASSERT(dynamic_cast<Connection *>(this->parent()));
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
loadUrlPreview();
}

View File

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

View File

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

View File

@@ -17,7 +17,6 @@ Kirigami.Page {
property bool showExisting: false
property bool _showExisting: showExisting && root.currentStepString === root.initialStep
property bool showSettings: true
property alias currentStep: module.item
property string currentStepString: initialStep
property string initialStep: "LoginRegister"
@@ -27,10 +26,6 @@ Kirigami.Page {
title: i18n("Welcome")
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
Homeserver {
id: homeserver
}
header: QQC2.Control {
topPadding: 0
bottomPadding: 0
@@ -85,7 +80,7 @@ Kirigami.Page {
FormCard.FormHeader {
id: existingAccountsHeader
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
}
@@ -94,21 +89,15 @@ Kirigami.Page {
maximumWidth: Kirigami.Units.gridUnit * 20
Repeater {
id: loadedAccounts
model: Accounts
model: AccountRegistry
delegate: FormCard.FormButtonDelegate {
id: delegate
required property string matrixId
required property string displayName
required property string avatarUrl
required property int index
required property bool ready
required property Connection connection
required property string userId
required property NeoChatConnection connection
enabled: ready
text: QmlUtils.escapeString(delegate.displayName)
description: delegate.matrixId
text: QmlUtils.escapeString(connection.localUser.displayName)
description: connection.localUser.id
leadingPadding: Kirigami.Units.largeSpacing
onClicked: {
@@ -119,12 +108,62 @@ Kirigami.Page {
id: avatar
name: delegate.text
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: delegate.avatarUrl
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : ""
implicitWidth: 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 {
@@ -148,7 +187,6 @@ Kirigami.Page {
root.currentStepString = nextStep;
headerMessage.text = "";
headerMessage.visible = false;
module.item.homeserver = homeserver
if (!module.item.noControls) {
module.item.forceActiveFocus();
} else {
@@ -172,33 +210,33 @@ Kirigami.Page {
}
}
// Connections {
// target: Registration
//
// function onNextStepChanged() {
// if (Registration.nextStep === "m.login.recaptcha") {
// stepConnections.onProcessed("Captcha");
// }
// if (Registration.nextStep === "m.login.terms") {
// stepConnections.onProcessed("Terms");
// }
// if (Registration.nextStep === "m.login.email.identity") {
// stepConnections.onProcessed("Email");
// }
// if (Registration.nextStep === "loading") {
// stepConnections.onProcessed("Loading");
// }
// }
// }
// Connections {
// target: LoginHelper
//
// function onLoginErrorOccured(message) {
// headerMessage.text = message;
// headerMessage.visible = message.length > 0;
// headerMessage.type = Kirigami.MessageType.Error;
// }
// }
Connections {
target: Registration
function onNextStepChanged() {
if (Registration.nextStep === "m.login.recaptcha") {
stepConnections.onProcessed("Captcha");
}
if (Registration.nextStep === "m.login.terms") {
stepConnections.onProcessed("Terms");
}
if (Registration.nextStep === "m.login.email.identity") {
stepConnections.onProcessed("Email");
}
if (Registration.nextStep === "loading") {
stepConnections.onProcessed("Loading");
}
}
}
Connections {
target: LoginHelper
function onLoginErrorOccured(message) {
headerMessage.text = message;
headerMessage.visible = message.length > 0;
headerMessage.type = Kirigami.MessageType.Error;
}
}
}
FormCard.FormDelegateSeparator {
@@ -227,7 +265,6 @@ Kirigami.Page {
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing * 2
maximumWidth: Kirigami.Units.gridUnit * 20
visible: root.showSettings
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Settings")
icon.name: "settings-configure"
@@ -237,11 +274,11 @@ Kirigami.Page {
}
}
// Component.onCompleted: {
// LoginHelper.init();
// module.item.forceActiveFocus();
// Registration.username = "";
// Registration.password = "";
// Registration.email = "";
// }
Component.onCompleted: {
LoginHelper.init();
module.item.forceActiveFocus();
Registration.username = "";
Registration.password = "";
Registration.email = "";
}
}

View File

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

View File

@@ -1,126 +0,0 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "messageattached.h"
MessageAttached::MessageAttached(QObject *parent)
: QQuickAttachedPropertyPropagator(parent)
{
if (parent == nullptr) {
qWarning() << "Message must be attached to an Item" << parent;
return;
}
initialize();
}
MessageAttached *MessageAttached::qmlAttachedProperties(QObject *object)
{
return new MessageAttached(object);
}
NeoChatRoom *MessageAttached::room() const
{
return m_room;
}
void MessageAttached::setRoom(NeoChatRoom *room)
{
m_explicitRoom = true;
if (m_room == room) {
return;
}
m_room = room;
propagateMessage(this);
Q_EMIT roomChanged();
}
QQuickItem *MessageAttached::timeline() const
{
return m_timeline;
}
void MessageAttached::setTimeline(QQuickItem *timeline)
{
m_explicitTimeline = true;
if (m_timeline == timeline) {
return;
}
m_timeline = timeline;
propagateMessage(this);
Q_EMIT timelineChanged();
}
int MessageAttached::index() const
{
return m_index;
}
void MessageAttached::setIndex(int index)
{
m_explicitIndex = true;
if (m_index == index) {
return;
}
m_index = index;
propagateMessage(this);
Q_EMIT indexChanged();
}
qreal MessageAttached::maxContentWidth() const
{
return m_maxContentWidth;
}
void MessageAttached::setMaxContentWidth(qreal maxContentWidth)
{
m_explicitMaxContentWidth = true;
if (m_maxContentWidth == maxContentWidth) {
return;
}
m_maxContentWidth = maxContentWidth;
propagateMessage(this);
Q_EMIT maxContentWidthChanged();
}
void MessageAttached::propagateMessage(MessageAttached *message)
{
if (m_explicitRoom || m_room != message->room()) {
m_room = message->room();
Q_EMIT roomChanged();
}
if (m_explicitTimeline || m_timeline != message->timeline()) {
m_timeline = message->timeline();
Q_EMIT timelineChanged();
}
if (m_explicitIndex || m_index != message->index()) {
m_index = message->index();
Q_EMIT indexChanged();
}
if (m_explicitMaxContentWidth || m_maxContentWidth != message->maxContentWidth()) {
m_maxContentWidth = message->maxContentWidth();
Q_EMIT maxContentWidthChanged();
}
const auto styles = attachedChildren();
for (auto *child : attachedChildren()) {
MessageAttached *message = qobject_cast<MessageAttached *>(child);
if (message != nullptr) {
message->propagateMessage(this);
}
}
}
void MessageAttached::attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent)
{
Q_UNUSED(oldParent);
MessageAttached *attachedParent = qobject_cast<MessageAttached *>(newParent);
if (attachedParent) {
propagateMessage(attachedParent);
}
}
#include "moc_messageattached.cpp"

View File

@@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QQmlEngine>
#include <QQuickAttachedPropertyPropagator>
#include <QQuickItem>
#include "neochatroom.h"
class MessageAttached : public QQuickAttachedPropertyPropagator
{
Q_OBJECT
QML_NAMED_ELEMENT(Message)
QML_ATTACHED(MessageAttached)
QML_UNCREATABLE("")
/**
* @brief The room that the message comes from.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged FINAL)
/**
* @brief The timeline for the current message.
*/
Q_PROPERTY(QQuickItem *timeline READ timeline WRITE setTimeline NOTIFY timelineChanged FINAL)
/**
* @brief The index of the message in the timeline
*/
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged FINAL)
/**
* @brief The width available to the message content.
*/
Q_PROPERTY(qreal maxContentWidth READ maxContentWidth WRITE setMaxContentWidth NOTIFY maxContentWidthChanged FINAL)
public:
explicit MessageAttached(QObject *parent = nullptr);
static MessageAttached *qmlAttachedProperties(QObject *object);
NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
QQuickItem *timeline() const;
void setTimeline(QQuickItem *timeline);
int index() const;
void setIndex(int index);
qreal maxContentWidth() const;
void setMaxContentWidth(qreal maxContentWidth);
Q_SIGNALS:
void roomChanged();
void timelineChanged();
void indexChanged();
void maxContentWidthChanged();
protected:
void propagateMessage(MessageAttached *message);
void attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent) override;
private:
QPointer<NeoChatRoom> m_room;
bool m_explicitRoom = false;
QPointer<QQuickItem> m_timeline;
bool m_explicitTimeline = false;
int m_index;
bool m_explicitIndex = false;
qreal m_maxContentWidth = -1;
bool m_explicitMaxContentWidth = false;
};

View File

@@ -60,11 +60,6 @@ void CommonRoomsModel::reload()
return;
}
// Checking if you have mutual rooms with yourself doesn't make sense and servers reject it too
if (m_connection->userId() == m_userId) {
return;
}
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
const auto &replyData = job->jsonData();
beginResetModel();

View File

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

View File

@@ -7,7 +7,7 @@
#include <QQmlEngine>
#include <QSortFilterProxyModel>
// #include "roomlistmodel.h"
#include "roomlistmodel.h"
class CompletionProxyModel;
class UserListModel;
@@ -47,7 +47,7 @@ class CompletionModel : public QAbstractListModel
/**
* @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:
/**

View File

@@ -3,7 +3,6 @@
#include "messagecontentmodel.h"
#include "eventhandler.h"
#include "messagecomponenttype.h"
#include "neochatconfig.h"
#include <QImageReader>
@@ -28,7 +27,6 @@
#include "chatbarcache.h"
#include "filetype.h"
#include "linkpreviewer.h"
#include "models/reactionmodel.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "texthandler.h"
@@ -132,6 +130,9 @@ void MessageContentModel::initializeModel()
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
resetContent();
});
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
resetContent();
});
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
if (m_room != nullptr) {
if (senderId().isEmpty() || senderId() == member.id()) {
@@ -151,18 +152,12 @@ void MessageContentModel::initializeModel()
updateReplyModel();
resetModel();
});
connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId == m_eventId) {
updateReactionModel();
}
});
initializeEvent();
if (m_currentState == Available || m_currentState == Pending) {
updateReplyModel();
}
resetModel();
updateReactionModel();
}
void MessageContentModel::initializeEvent()
@@ -345,10 +340,6 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
if (role == ReplyContentModelRole) {
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
}
if (role == ReactionModelRole) {
return QVariant::fromValue<ReactionModel *>(m_reactionModel);
;
}
if (role == ThreadRootRole) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first);
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
@@ -409,7 +400,6 @@ QHash<int, QByteArray> MessageContentModel::roleNamesStatic()
roles[MessageContentModel::ReplyEventIdRole] = "replyEventId";
roles[MessageContentModel::ReplyAuthorRole] = "replyAuthor";
roles[MessageContentModel::ReplyContentModelRole] = "replyContentModel";
roles[MessageContentModel::ReactionModelRole] = "reactionModel";
roles[MessageContentModel::ThreadRootRole] = "threadRoot";
roles[MessageContentModel::LinkPreviewerRole] = "linkPreviewer";
roles[MessageContentModel::ChatBarCacheRole] = "chatBarCache";
@@ -490,16 +480,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
newComponents = addLinkPreviews(newComponents);
}
if ((m_reactionModel && m_reactionModel->rowCount() > 0)) {
newComponents += MessageComponent{MessageComponentType::Reaction, QString(), {}};
}
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
if (NeoChatConfig::self()->threads() && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
#else
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
#endif
newComponents += MessageComponent{MessageComponentType::Separator, {}, {}};
newComponents += MessageComponent{MessageComponentType::ThreadBody, u"Thread Body"_s, {}};
@@ -528,7 +513,7 @@ void MessageContentModel::updateReplyModel()
if (roomMessageEvent == nullptr) {
return;
}
if (!roomMessageEvent->isReply(!NeoChatConfig::self()->threads()) || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
if (m_replyModel) {
delete m_replyModel;
}
@@ -539,7 +524,7 @@ void MessageContentModel::updateReplyModel()
return;
}
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(!NeoChatConfig::self()->threads()), true, false, this);
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -746,25 +731,4 @@ void MessageContentModel::updateItineraryModel()
}
}
void MessageContentModel::updateReactionModel()
{
if (m_reactionModel != nullptr && m_reactionModel->rowCount() > 0) {
return;
}
if (m_reactionModel == nullptr) {
m_reactionModel = new ReactionModel(this, m_eventId, m_room);
connect(m_reactionModel, &ReactionModel::reactionsUpdated, this, &MessageContentModel::updateReactionModel);
}
if (m_reactionModel->rowCount() <= 0) {
m_reactionModel->disconnect(this);
delete m_reactionModel;
m_reactionModel = nullptr;
return;
}
resetContent();
}
#include "moc_messagecontentmodel.cpp"

View File

@@ -7,11 +7,11 @@
#include <QQmlEngine>
#include <Quotient/events/roomevent.h>
#include <Quotient/room.h>
#include "enums/messagecomponenttype.h"
#include "itinerarymodel.h"
#include "messagecomponent.h"
#include "models/reactionmodel.h"
#include "neochatroommember.h"
/**
@@ -57,8 +57,6 @@ public:
ReplyAuthorRole, /**< The author of the event that was replied to. */
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
ReactionModelRole, /**< Reaction model for this event. */
ThreadRootRole, /**< The thread root event ID for the event. */
LinkPreviewerRole, /**< The link preview details. */
@@ -127,7 +125,6 @@ private:
QPointer<MessageContentModel> m_replyModel;
void updateReplyModel();
ReactionModel *m_reactionModel = nullptr;
ItineraryModel *m_itineraryModel = nullptr;
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
@@ -138,6 +135,4 @@ private:
void updateItineraryModel();
bool m_emptyItinerary = false;
void updateReactionModel();
};

View File

@@ -5,7 +5,6 @@
#include "neochatconfig.h"
#include <Quotient/events/encryptedevent.h>
#include <Quotient/events/roommessageevent.h>
#include <Quotient/events/stickerevent.h>
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
@@ -121,12 +120,8 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
}
if (role == ContentModelRole) {
if (event->get().is<EncryptedEvent>()) {
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(event->get().id()));
}
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get());
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()) {
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(roomMessageEvent->threadRootEventId()));
}
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(&event->get()));
@@ -221,9 +216,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
}
if (role == IsThreadedRole) {
if (!NeoChatConfig::self()->threads()) {
return false;
}
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get())) {
return roomMessageEvent->isThreaded();
}
@@ -266,6 +258,18 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
return m_readMarkerModels.contains(event.value().get().id());
}
if (role == ReactionRole) {
if (m_reactionModels.contains(event.value().get().id())) {
return QVariant::fromValue<ReactionModel *>(m_reactionModels[event.value().get().id()].data());
} else {
return QVariantList();
}
}
if (role == ShowReactionsRole) {
return m_reactionModels.contains(event.value().get().id());
}
if (role == VerifiedRole) {
if (event.value().get().originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(event.value().get().originalEvent());
@@ -319,6 +323,8 @@ QHash<int, QByteArray> MessageModel::roleNames() const
roles[ShowSectionRole] = "showSection";
roles[ReadMarkersRole] = "readMarkers";
roles[ShowReadMarkersRole] = "showReadMarkers";
roles[ReactionRole] = "reaction";
roles[ShowReactionsRole] = "showReactions";
roles[VerifiedRole] = "verified";
roles[AuthorDisplayNameRole] = "authorDisplayName";
roles[IsRedactedRole] = "isRedacted";
@@ -448,6 +454,31 @@ void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isP
}
}
}
if (const auto roomEvent = eventCast<const RoomMessageEvent>(event)) {
// ReactionModel handles updates to add and remove reactions, we only need to
// handle adding and removing whole models here.
if (m_reactionModels.contains(eventId)) {
// If a model already exists but now has no reactions remove it
if (m_reactionModels[eventId]->rowCount() <= 0) {
m_reactionModels.remove(eventId);
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
} else {
if (m_room->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
// If a model doesn't exist and there are reactions add it.
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(roomEvent, m_room));
if (reactionModel->rowCount() > 0) {
m_reactionModels[eventId] = reactionModel;
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
}
}
}
}
void MessageModel::clearModel()
@@ -473,6 +504,7 @@ void MessageModel::clearModel()
void MessageModel::clearEventObjects()
{
m_reactionModels.clear();
m_readMarkerModels.clear();
}

View File

@@ -77,6 +77,8 @@ public:
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
ReactionRole, /**< List model for this event. */
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
@@ -153,6 +155,7 @@ private:
bool movingEvent = false;
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
};

View File

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

View File

@@ -6,7 +6,7 @@
#include <QAbstractListModel>
#include <QQmlEngine>
// #include <Quotient/csapi/definitions/push_rule.h>
#include <Quotient/csapi/definitions/push_rule.h>
#include "enums/pushrule.h"
#include "neochatconnection.h"
@@ -130,10 +130,10 @@ private:
QList<Rule> m_rules;
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;
// PushRuleSection::Section getSection(Quotient::PushRule rule);
PushRuleSection::Section getSection(Quotient::PushRule rule);
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action);

View File

@@ -9,27 +9,22 @@
#include <KLocalizedString>
#include "neochatroom.h"
using namespace Qt::StringLiterals;
ReactionModel::ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room)
: QAbstractListModel(parent)
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
: QAbstractListModel(nullptr)
, m_room(room)
, m_eventId(eventId)
, m_event(event)
{
Q_ASSERT(parent);
Q_ASSERT(parent != nullptr);
Q_ASSERT(!eventId.isEmpty());
Q_ASSERT(room != nullptr);
if (m_event != nullptr && m_room != nullptr) {
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
if (m_event && m_event->id() == eventId) {
updateReactions();
}
});
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
if (m_eventId == eventId) {
updateReactions();
}
});
updateReactions();
updateReactions();
}
}
QVariant ReactionModel::data(const QModelIndex &index, int role) const
@@ -104,16 +99,12 @@ int ReactionModel::rowCount(const QModelIndex &parent) const
void ReactionModel::updateReactions()
{
if (m_room == nullptr) {
return;
}
beginResetModel();
m_reactions.clear();
m_shortcodes.clear();
const auto &annotations = m_room->relatedEvents(m_eventId, Quotient::EventRelation::AnnotationType);
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
if (annotations.isEmpty()) {
endResetModel();
return;

View File

@@ -3,20 +3,11 @@
#pragma once
#include "neochatroom.h"
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/reactionevent.h>
#include <Quotient/roommember.h>
namespace Quotient
{
class RoomMessageEvent;
}
class MessageContentModel;
class NeoChatRoom;
/**
* @class ReactionModel
*
@@ -47,7 +38,7 @@ public:
HasLocalMember, /**< Whether the local member is in the list of authors. */
};
explicit ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room);
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
/**
* @brief Get the given role value at the given index.
@@ -70,15 +61,9 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
/**
* @brief The reactions in the model have been updated.
*/
void reactionsUpdated();
private:
QPointer<NeoChatRoom> m_room;
QString m_eventId;
const Quotient::RoomMessageEvent *m_event;
QList<Reaction> m_reactions;
QMap<QString, QString> m_shortcodes;

View File

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

View File

@@ -1,28 +1,27 @@
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "neochatroomtype.h"
#include "enums/neochatroomtype.h"
namespace sdk
{
struct RoomListRoom;
}
struct RoomWrapper {
std::optional<rust::Box<sdk::RoomListRoom>> item;
};
class NeoChatRoom;
/**
* @class RoomTreeItem
*
* This class defines an item in a room tree.
* This class defines an item in the space tree hierarchy model.
*
* @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
{
public:
using TreeData = std::variant<RoomWrapper *, NeoChatRoomType::Type>;
using TreeData = std::variant<NeoChatRoom *, NeoChatRoomType::Types>;
explicit RoomTreeItem(TreeData data, RoomTreeItem *parent = nullptr);
@@ -69,7 +68,7 @@ public:
*/
TreeData data() const;
std::optional<int> rowForRoom(rust::Box<sdk::RoomListRoom> room) const;
std::optional<int> rowForRoom(Quotient::Room *room) const;
private:
std::vector<std::unique_ptr<RoomTreeItem>> m_children;

View File

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

View File

@@ -1,4 +1,3 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
@@ -7,20 +6,23 @@
#include <QAbstractItemModel>
#include <QPointer>
#include "enums/neochatroomtype.h"
#include "roomtreeitem.h"
namespace Integral
namespace Quotient
{
class Connection;
class Room;
}
class NeoChatConnection;
class NeoChatRoom;
class RoomTreeModel : public QAbstractItemModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(Integral::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
/**
@@ -49,10 +51,9 @@ public:
};
Q_ENUM(EventRoles)
explicit RoomTreeModel(QObject *parent = nullptr);
~RoomTreeModel();
void setConnection(Integral::Connection *connection);
Integral::Connection *connection() const;
void setConnection(NeoChatConnection *connection);
NeoChatConnection *connection() const;
/**
* @brief Get the given role value at the given index.
@@ -74,21 +75,23 @@ public:
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex indexForRoom(rust::Box<sdk::RoomListRoom> room) const;
std::optional<rust::Box<sdk::RoomListRoom>> roomForIndex(QModelIndex index) const;
Q_INVOKABLE QModelIndex indexForRoom(NeoChatRoom *room) const;
Q_SIGNALS:
void connectionChanged();
private:
class Private;
std::unique_ptr<Private> d;
QPointer<NeoChatConnection> m_connection;
std::unique_ptr<RoomTreeItem> m_rootItem;
RoomTreeItem *getItem(const QModelIndex &index) const;
void resetModel();
void connectRoomSignals(NeoChatRoom *room);
// void connectRoomSignals(NeoChatRoom *room);
void newRoom(Quotient::Room *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 "roomsortparameter.h"
// #include "neochatconfig.h"
#include "enums/roomsortparameter.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "neochatroomtype.h"
#include <Integral/Room>
// #include "roommanager.h"
#include "roommanager.h"
#include "roomtreemodel.h"
// #include "spacehierarchycache.h"
#include "spacehierarchycache.h"
SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent)
: QSortFilterProxyModel(parent)
{
// Q_ASSERT(sourceModel);
// setSourceModel(sourceModel);
Q_ASSERT(sourceModel);
setSourceModel(sourceModel);
// setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
// connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
// setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
// invalidateFilter();
// });
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
invalidateFilter();
});
setRecursiveFilteringEnabled(true);
sort(0);
@@ -34,13 +34,13 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
connect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
});
// connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
// connect(NeoChatConfig::self(), &NeoChatConfig::AllRoomsInHomeChanged, this, [this]() {
// invalidateFilter();
// if (NeoChatConfig::self()->allRoomsInHome()) {
// RoomManager::instance().resetState();
// }
// });
connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
connect(NeoChatConfig::self(), &NeoChatConfig::AllRoomsInHomeChanged, this, [this]() {
invalidateFilter();
if (NeoChatConfig::self()->allRoomsInHome()) {
RoomManager::instance().resetState();
}
});
}
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
@@ -78,14 +78,14 @@ bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QMo
return false;
}
const auto leftRoom = treeModel->roomForIndex(source_left);
const auto rightRoom = treeModel->roomForIndex(source_right);
if (!leftRoom.has_value() || !rightRoom.has_value()) {
const auto leftRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_left.data(RoomTreeModel::RoomIdRole).toString()));
const auto rightRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_right.data(RoomTreeModel::RoomIdRole).toString()));
if (leftRoom == nullptr || rightRoom == nullptr) {
return false;
}
for (auto sortRole : RoomSortParameter::currentParameterList()) {
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom.value()->box_me(), rightRoom.value()->box_me());
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom, rightRoom);
if (result != 0) {
return result > 0;
@@ -141,22 +141,20 @@ bool SortFilterRoomTreeModel::filterAcceptsRow(int source_row, const QModelIndex
return false;
}
return acceptRoom;
static auto config = NeoChatConfig::self();
if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
return acceptRoom;
}
// static auto config = NeoChatConfig::self();
// if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
// return acceptRoom;
// }
//
// if (m_activeSpaceId.isEmpty()) {
// if (!SpaceHierarchyCache::instance().isChild(sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString())) {
// 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;
// }
if (m_activeSpaceId.isEmpty()) {
if (!SpaceHierarchyCache::instance().isChild(sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString())) {
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
@@ -194,7 +192,7 @@ QModelIndex SortFilterRoomTreeModel::currentRoomIndex() const
return {};
}
return {}; // mapFromSource(roomModel->indexForRoom(RoomManager::instance().currentRoom()));
return mapFromSource(roomModel->indexForRoom(RoomManager::instance().currentRoom()));
}
#include "moc_sortfilterroomtreemodel.cpp"

View File

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

View File

@@ -19,6 +19,7 @@
#include "messagecontentmodel.h"
class NeoChatRoom;
class ReactionModel;
/**
* @class ThreadFetchModel
@@ -171,6 +172,8 @@ private:
ThreadFetchModel *m_threadFetchModel;
ThreadChatBarModel *m_threadChatBarModel;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr;
std::optional<QString> m_nextBatch = QString();
bool m_addingPending = false;

View File

@@ -13,35 +13,13 @@
using namespace Qt::StringLiterals;
ThreePIdModel::ThreePIdModel(QObject *parent)
: QAbstractListModel(parent)
ThreePIdModel::ThreePIdModel(NeoChatConnection *connection)
: QAbstractListModel(connection)
{
}
NeoChatConnection *ThreePIdModel::connection() const
{
return m_connection;
}
void ThreePIdModel::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
}
m_connection = connection;
if (m_connection) {
connect(m_connection, &NeoChatConnection::stateChanged, this, [this]() {
refreshModel();
});
Q_ASSERT(connection);
connect(connection, &NeoChatConnection::stateChanged, this, [this]() {
refreshModel();
}
Q_EMIT connectionChanged();
});
}
QVariant ThreePIdModel::data(const QModelIndex &index, int role) const
@@ -84,14 +62,12 @@ QHash<int, QByteArray> ThreePIdModel::roleNames() const
void ThreePIdModel::refreshModel()
{
if (m_connection != nullptr && m_connection->isLoggedIn()) {
if (m_job.isRunning()) {
m_job.cancel();
}
m_job = m_connection->callApi<Quotient::GetAccount3PIDsJob>();
connect(m_job, &Quotient::BaseJob::success, this, [this]() {
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
if (connection != nullptr && connection->isLoggedIn()) {
const auto threePIdJob = connection->callApi<Quotient::GetAccount3PIDsJob>();
connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() {
beginResetModel();
m_threePIds = m_job->threepids();
m_threePIds = threePIdJob->threepids();
endResetModel();
refreshBindStatus();
@@ -101,24 +77,25 @@ void ThreePIdModel::refreshModel()
void ThreePIdModel::refreshBindStatus()
{
if (m_connection == nullptr || !m_connection->hasIdentityServer()) {
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
if (connection == nullptr || !connection->hasIdentityServer()) {
return;
}
const auto openIdJob = m_connection->callApi<Quotient::RequestOpenIdTokenJob>(m_connection->userId());
connect(openIdJob, &Quotient::BaseJob::success, this, [this, openIdJob]() {
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/account/register"_s);
const auto openIdJob = connection->callApi<Quotient::RequestOpenIdTokenJob>(connection->userId());
connect(openIdJob, &Quotient::BaseJob::success, this, [this, connection, openIdJob]() {
const auto requestUrl = QUrl(connection->identityServer().toString() + u"/_matrix/identity/v2/account/register"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
QNetworkRequest request(requestUrl);
auto newRequest = Quotient::NetworkAccessManager::instance()->post(request, QJsonDocument(openIdJob->jsonData()).toJson());
connect(newRequest, &QNetworkReply::finished, this, [this, newRequest]() {
connect(newRequest, &QNetworkReply::finished, this, [this, connection, newRequest]() {
QJsonObject replyJson = QJsonDocument::fromJson(newRequest->readAll()).object();
const auto identityServerToken = replyJson["token"_L1].toString();
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/hash_details"_s);
const auto requestUrl = QUrl(connection->identityServer().toString() + u"/_matrix/identity/v2/hash_details"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
@@ -127,11 +104,11 @@ void ThreePIdModel::refreshBindStatus()
hashRequest.setRawHeader("Authorization", "Bearer " + identityServerToken.toLatin1());
auto hashReply = Quotient::NetworkAccessManager::instance()->get(hashRequest);
connect(hashReply, &QNetworkReply::finished, this, [this, identityServerToken, hashReply]() {
connect(hashReply, &QNetworkReply::finished, this, [this, connection, identityServerToken, hashReply]() {
QJsonObject replyJson = QJsonDocument::fromJson(hashReply->readAll()).object();
const auto lookupPepper = replyJson["lookup_pepper"_L1].toString();
const auto requestUrl = QUrl(m_connection->identityServer().toString() + u"/_matrix/identity/v2/lookup"_s);
const auto requestUrl = QUrl(connection->identityServer().toString() + u"/_matrix/identity/v2/lookup"_s);
if (!(requestUrl.scheme() == u"https"_s || requestUrl.scheme() == u"http"_s)) {
return;
}
@@ -150,13 +127,13 @@ void ThreePIdModel::refreshBindStatus()
requestData["addresses"_L1] = idLookups;
auto lookupReply = Quotient::NetworkAccessManager::instance()->post(lookupRequest, QJsonDocument(requestData).toJson(QJsonDocument::Compact));
connect(lookupReply, &QNetworkReply::finished, this, [this, lookupReply]() {
connect(lookupReply, &QNetworkReply::finished, this, [this, connection, lookupReply]() {
beginResetModel();
m_bindings.clear();
QJsonObject mappings = QJsonDocument::fromJson(lookupReply->readAll()).object()["mappings"_L1].toObject();
for (const auto &id : mappings.keys()) {
if (mappings[id] == m_connection->userId()) {
if (mappings[id] == connection->userId()) {
m_bindings += id.section(u' ', 0, 0);
}
}

View File

@@ -7,7 +7,6 @@
#include <QQmlEngine>
#include <Quotient/csapi/administrative_contact.h>
#include <Quotient/jobs/jobhandle.h>
class NeoChatConnection;
@@ -20,27 +19,19 @@ class ThreePIdModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current connection for the model to use.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
QML_UNCREATABLE("")
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
enum EventRoles {
AddressRole = Qt::DisplayRole, /**< The third-party identifier address. */
MediumRole, /**< The medium of the third-party identifier. One of: [email, msisdn]. */
IsBoundRole, /**< Whether the 3PID is bound to the current identity server. */
};
Q_ENUM(Roles)
explicit ThreePIdModel(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
explicit ThreePIdModel(NeoChatConnection *parent);
/**
* @brief Get the given role value at the given index.
@@ -65,15 +56,9 @@ public:
Q_INVOKABLE void refreshModel();
Q_SIGNALS:
void connectionChanged();
private:
QPointer<NeoChatConnection> m_connection;
QVector<Quotient::GetAccount3PIDsJob::ThirdPartyIdentifier> m_threePIds;
Quotient::JobHandle<Quotient::GetAccount3PIDsJob> m_job;
QList<QString> m_bindings;
void refreshBindStatus();

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