Compare commits

...

95 Commits

Author SHA1 Message Date
Carl Schwan
7309cf7249 Add dbus addons on Windows
BUG: 474678
2023-10-05 09:13:34 +00:00
Carl Schwan
2eb26ffbb3 Fix typo 2023-10-05 11:13:09 +02:00
Carl Schwan
87ef55215f Allow reporting others
Instead of only being able to report yourself

BUG: 475227
2023-10-05 11:02:39 +02:00
Carl Schwan
f6186aad2e Fix right clicking on chat list delegate
BUG: 475226
2023-10-05 10:59:37 +02:00
Carl Schwan
2251edbf86 Fix invalid attempt to destroy() an indestructible object 2023-10-05 10:58:00 +02:00
l10n daemon script
1c55649740 GIT_SILENT Sync po/docbooks with svn 2023-10-05 02:11:56 +00:00
l10n daemon script
aa0b6613de GIT_SILENT Sync po/docbooks with svn 2023-10-04 02:11:42 +00:00
Yuri Chornoivan
f948e813b6 Fix minor typo 2023-10-03 08:36:04 +03:00
l10n daemon script
b5c6411aad GIT_SILENT Sync po/docbooks with svn 2023-10-03 02:13:22 +00:00
James Graham
b1daa76d9f Fix image reply sizing
Use height rather than implicitHeight for the loader so that replies with images always size properly
2023-10-02 18:55:54 +00:00
James Graham
7180fa022b Room Settings Parents
Add the ability to manage parent rooms from a child, this includes:
- viewing parents
- adding a new parent
- removing an existing one

Follows the rules from the matrix spec https://spec.matrix.org/v1.7/client-server-api/#mspaceparent-relationships
2023-10-02 18:41:17 +00:00
Ingo Klöcker
17bc08270d Tag Windows screenshots with new environment attribute
appstream now officially supports an environment attribute for screenshots.
2023-10-02 19:43:56 +02:00
Tobias Fella
d4cb27eca4 Make singletons owned by the C++ side 2023-10-02 16:29:04 +00:00
Fushan Wen
541350e678 appiumtests: port away from deprecated desired_capabilities
AppiumOptions replaces it
2023-10-02 15:48:58 +00:00
l10n daemon script
843deefaf8 GIT_SILENT Sync po/docbooks with svn 2023-10-02 02:16:12 +00:00
James Graham
070d579bc2 Restore the show author functionality to bubble 2023-10-01 14:05:42 +00:00
l10n daemon script
add283c9fb GIT_SILENT Sync po/docbooks with svn 2023-10-01 02:30:40 +00:00
James Graham
fe4230b5fd Use variable placeholder instead of string concatenation 2023-09-30 10:54:07 +01:00
l10n daemon script
e8f40d98de GIT_SILENT Sync po/docbooks with svn 2023-09-30 02:13:47 +00:00
James Graham
eba62103a4 Remove Space Child
Add button to remove a child in a space if the user has the correct power levels
2023-09-29 20:15:17 +00:00
Tobias Fella
925393deab Add type registration for KeyVerificationSession 2023-09-29 19:26:41 +00:00
Laurent Montel
abe881caf7 Add missing include moc 2023-09-29 13:45:46 +02:00
Yuri Chornoivan
237a3c9dfb Fix minor typo 2023-09-29 09:08:37 +03:00
l10n daemon script
9715440854 GIT_SILENT Sync po/docbooks with svn 2023-09-29 02:10:20 +00:00
James Graham
ecdad9f965 Space Home Page
Add a space homepage with the ability to both create new room and add existing rooms to the space. This uses a tree model for the space hierarchy and will go to any number of levels. The user should only see the add options if they have appropriate permissions.

This MR also combines the create space and room pages and adds a lot of optional functionality for managing space children.

![image](/uploads/1764b0319241ff870dc39b18b39f5d51/image.png)
2023-09-28 17:36:23 +00:00
Carl Schwan
08711fc927 Fix missing renaming in roomlastmessageprovider 2023-09-28 10:38:31 +02:00
Carl Schwan
e44cd405b7 Fix import name 2023-09-28 10:31:14 +02:00
Carl Schwan
8945e004e2 Optimize room config 2023-09-28 07:37:22 +00:00
Janet Blackquill
c04d8d6f59 Redraw tray icon
It seems at some point in time the 16x16 tray icon got lost/hastily upscaled to a 22x22 tray icon,
which resulted in proportions as well as icon guidelines being slightly off.

This replaces the tray icon with a new one redrawn to adhere to icon guidelines and proportions closer
to the colour icon.
2023-09-27 23:43:55 -04:00
l10n daemon script
58a73c0208 GIT_SILENT Sync po/docbooks with svn 2023-09-28 02:11:52 +00:00
Joshua Goins
852110debd Make it clear that the session is broken when the keys are lost
If you use your private keys (like when deleting the quotient database)
your session is broken as you have differing keys on the server. While
it is possible to work your way out of it, it's better to warn users to
bite the bullet and log in again.
2023-09-27 15:13:06 -04:00
Joshua Goins
6b71d3c78d Make the key verification message horizontally centered 2023-09-27 15:13:00 -04:00
Christophe Marin
f3a0adee39 Fix manpage installation 2023-09-27 16:23:29 +02:00
l10n daemon script
6e7b6c9ce0 GIT_SILENT Sync po/docbooks with svn 2023-09-27 02:12:21 +00:00
James Graham
f67cd7deb5 Remove the now unused author ID role from MessageEventModel
Remove the now unused author ID role from `MessageEventModel`. This can be obtained from the author roles object.
2023-09-26 20:21:08 +00:00
l10n daemon script
931b4b1f9a GIT_SILENT Sync po/docbooks with svn 2023-09-26 02:22:24 +00:00
l10n daemon script
167ed4eca3 GIT_SILENT made messages (after extraction) 2023-09-26 01:46:11 +00:00
l10n daemon script
7d5b2c1b6a GIT_SILENT Sync po/docbooks with svn 2023-09-25 02:15:50 +00:00
l10n daemon script
be7b1e49b4 GIT_SILENT Sync po/docbooks with svn 2023-09-24 02:09:06 +00:00
Tobias Fella
957419070a Remove unused includes 2023-09-23 22:43:48 +02:00
Carl Schwan
f22107c8ab Colorful emoji in reaction
Use ICU to determine if the string contains only emojis
2023-09-23 22:16:11 +02:00
Tobias Fella
3a4f71de7f Port to declarative type registration 2023-09-23 14:05:50 +00:00
Tobias Fella
4ed4f3f628 Silence invalid user id warnings 2023-09-23 11:42:21 +02:00
Tobias Fella
ba24f1272f Fix crash in completion 2023-09-23 09:23:25 +00:00
Carl Schwan
443661d113 Port away from BasicListItem 2023-09-23 09:07:05 +00:00
Tobias Fella
091c8806db Fix opening account editor 2023-09-23 10:19:44 +02:00
l10n daemon script
041c719a2e GIT_SILENT Sync po/docbooks with svn 2023-09-23 02:10:02 +00:00
James Graham
83a9bfa974 Remove timeline container as it has been replaced. 2023-09-22 23:50:29 +00:00
James Graham
e35a6f7257 Remove the import version from Bubble.qml, TimelineDelegate.qml and TextDelegate.qml 2023-09-22 18:46:17 +01:00
James Graham
6d56251f6f Fix the timeline Part 2: Bubble Rework
This reworks the bubble as a separate component and makes some fixes to prevent the console being spammed with polish loop warnings.
2023-09-22 17:12:56 +00:00
l10n daemon script
486fae9c10 GIT_SILENT Sync po/docbooks with svn 2023-09-22 02:10:59 +00:00
Tobias Fella
1c26d9b811 Remove QML import versions 2023-09-21 19:38:10 +02:00
Tobias Fella
6d7ae99c94 Don't crash when editing pending message 2023-09-21 16:48:09 +00:00
Carl Schwan
442a343097 Rework context menu RoomList
- Finish port to qt6 and replace icon by icon.name
- Use RoundedItemDelegate
2023-09-21 16:43:27 +00:00
Tobias Fella
f0a7216b4b Don't crash when trying to leave empty room
BUG: 474490
2023-09-21 16:38:49 +00:00
James Graham
e926b22524 Fix the Timeline Part 1
This introduces a new base delegate that handles sizing the content of delegate in the timeline, i.e. it handles all the size helper stuff. This is then used for all the other main delegates:
- messages
- state
- read marker

This means they now all have identical base code to do the sizing (read marker still had legacy code).

Because the new base delegate is called `TimelineDelegate` both `TimelineContainer` and `MessageDelegate` have been renamed:
- MessageDelegate -> TextDelegate - this never made sense before images, videos, etc are all technically messages in Matrix parlance
- TimelineContainer -> MessageDelegate - this has always really been the base for messages

Note - this is mostly groundwork for dealing with the layout polish loop spam which will hopefully be fixed in part 2 with a bubble rework.
2023-09-21 16:26:34 +00:00
Carl Schwan
69087c2117 Add top spacing for Room ListView 2023-09-21 15:07:55 +00:00
Carl Schwan
4d2104b54b Decrease top margin in SpaceDrawer
Make it the same as the left margin of the delegate for an increased
consistency.
2023-09-21 15:07:55 +00:00
l10n daemon script
3f85a359e1 GIT_SILENT Sync po/docbooks with svn 2023-09-21 02:18:35 +00:00
ivan tkachenko
3084913940 Opt-out of Kirigami.SpellCheck when a custom QTextDocument handler is used
Uses Kirigami.SpellCheck's new shorter name.

See https://invent.kde.org/frameworks/kirigami/-/merge_requests/1261
2023-09-21 02:53:32 +03:00
Joshua Goins
e2670cd6ba Use Qt::ArrowType in FormCard.FormArrow usage on AccountsPage 2023-09-20 14:59:11 -04:00
l10n daemon script
1b6fc3dde5 GIT_SILENT Sync po/docbooks with svn 2023-09-20 02:15:06 +00:00
l10n daemon script
69a19effa2 GIT_SILENT Sync po/docbooks with svn 2023-09-19 02:17:37 +00:00
l10n daemon script
4abdf1f920 GIT_SILENT Sync po/docbooks with svn 2023-09-18 02:21:13 +00:00
l10n daemon script
0b1a6a3f6b GIT_SILENT made messages (after extraction) 2023-09-18 01:59:17 +00:00
l10n daemon script
45544c79bb GIT_SILENT Sync po/docbooks with svn 2023-09-17 02:54:29 +00:00
l10n daemon script
c4dddf6e02 GIT_SILENT Sync po/docbooks with svn 2023-09-16 02:18:05 +00:00
Tobias Fella
7f3f628b7d Fix missing connection 2023-09-15 17:41:08 +00:00
James Graham
6bf552398e Fix getting connection in message edit component 2023-09-15 18:35:23 +01:00
James Graham
78f676d71a Fix the room list menu
As title
2023-09-15 16:22:59 +00:00
James Graham
33c0cae64c Message menu rework
Rework the file menu so that it no longer relies on having a reference to the media delegate to manage a download for either opening externally or copying to clipboard. This allows the menus to be moved out of the delegates and maximize components and have them accessed through RoomManager. This reduces duplication and reduces the number of components in an already heavy delegate.
2023-09-15 13:57:40 +00:00
James Graham
14cdd096cf Fix Delegate Menu in NeochatMaximizeComponent
Make sure NeochatMaximizeComponent provides all the required properties to the delegate menu
2023-09-15 11:00:40 +00:00
James Graham
c04ddfde26 EventSource Refactor
Move showing th event source to a call to RoomManager. This means the SourceRole is no longer required in the message and search models
2023-09-15 10:54:04 +00:00
l10n daemon script
ec4c156a8c GIT_SILENT Sync po/docbooks with svn 2023-09-15 02:14:09 +00:00
Tobias Fella
17ff5b4c56 Add button to reject invitation and ignore user
BUG: 474274
2023-09-14 15:34:19 +00:00
Tobias Fella
0e2275e415 Always require passing tests 2023-09-14 10:23:52 +00:00
l10n daemon script
12fd1875b5 GIT_SILENT Sync po/docbooks with svn 2023-09-14 02:13:41 +00:00
l10n daemon script
10e50804c7 GIT_SILENT made messages (after extraction) 2023-09-14 01:54:51 +00:00
James Graham
c01c638a49 Fix file menu
Pass file menu eventSource rather than source and htmlText
2023-09-13 18:46:41 +00:00
Tobias Fella
399151eb1d Fix opening context menus 2023-09-13 15:48:39 +02:00
Tobias Fella
5e80715898 Cleanup connection handling in QML
Consistently pass connection objects to files that need them instead of randomly using Controller.activeConnection in some of them
2023-09-13 13:12:01 +00:00
Tobias Fella
6439fa48f9 Adapt to QML api changes 2023-09-13 12:57:27 +00:00
Tobias Fella
823f3cdd4e Remove QML Qt5/Qt6 compatibility hacks 2023-09-13 12:57:27 +00:00
Tobias Fella
f542d0b9fd Change rowStride in runner to qsizetype 2023-09-13 14:53:07 +02:00
Tobias Fella
a43990559b Rename source property of MessageDelegateContextMenu
Starting with Qt6.6, this collides with a FINAL property of Loader and thus doesn't run
2023-09-13 14:16:48 +02:00
Tobias Fella
feb2dbc9fb Don't try finding KStatusNotifierItem on android 2023-09-13 12:05:40 +00:00
Tobias Fella
f299d5a245 Fix KF6 crash in emoji completion 2023-09-13 13:41:08 +02:00
Tobias Fella
d69b8fbf8c Fix compilation on FreeBSD 2023-09-13 13:27:33 +02:00
Tobias Fella
234e5c49c4 Re-enable FreeBSD CI
Was accidentally disabled during a merge
2023-09-13 13:23:45 +02:00
Tobias Fella
cee72b6d48 Remove Qt5 compatibility ifdefs 2023-09-13 11:16:02 +00:00
Tobias Fella
539fdcaf2e Remove compatibility with Qt5 in CMake 2023-09-13 11:09:41 +00:00
Joshua Goins
32b3861c3e Only show the "View Source" message action when dev tools are enabled
This is a very developer-oriented action, and other uses of this dialog
(such as interacting with room state events under a room debug page) is
also locked behind this option.
2023-09-13 07:12:18 +00:00
l10n daemon script
f1076a5ced GIT_SILENT Sync po/docbooks with svn 2023-09-13 02:24:16 +00:00
Joshua Goins
bf8f5705d0 Fix message event source dialog not showing any data 2023-09-12 17:10:23 -04:00
Tobias Fella
2656a93ee7 Disable Qt5 CI 2023-09-12 15:39:25 +00:00
282 changed files with 108535 additions and 96282 deletions

View File

@@ -3,12 +3,8 @@
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

View File

@@ -2,35 +2,7 @@
# SPDX-License-Identifier: BSD-2-Clause
Dependencies:
- 'on': ['Linux/Qt5', 'Android/Qt5', 'FreeBSD/Qt5', 'Windows/Qt5']
'require':
'frameworks/extra-cmake-modules': '@stable'
'frameworks/kcoreaddons': '@stable'
'frameworks/kirigami': '@stable'
'frameworks/ki18n': '@stable'
'frameworks/kconfig': '@stable'
'frameworks/syntax-highlighting': '@stable'
'frameworks/kitemmodels': '@stable'
'frameworks/kquickcharts': '@stable'
'frameworks/knotifications': '@stable'
'libraries/kquickimageeditor': '@stable'
'frameworks/sonnet': '@stable'
'libraries/kirigami-addons': '@latest'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows/Qt5', 'Linux/Qt5', 'FreeBSD/Qt5']
'require':
'frameworks/qqc2-desktop-style': '@stable'
'frameworks/kio': '@stable'
'frameworks/kwindowsystem': '@stable'
'frameworks/kconfigwidgets': '@stable'
- 'on': ['Linux/Qt5', 'FreeBSD/Qt5']
'require':
'frameworks/kdbusaddons': '@stable'
- 'on': ['Linux/Qt6', 'Android/Qt6', 'FreeBSD/Qt6', 'Windows/Qt6']
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
'require':
'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6'
@@ -48,21 +20,23 @@ Dependencies:
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows/Qt6', 'Linux/Qt6', 'FreeBSD/Qt6']
- 'on': ['Windows', 'Linux', 'FreeBSD']
'require':
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kconfigwidgets': '@latest-kf6'
- 'on': ['Linux/Qt6', 'FreeBSD/Qt6']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux/Qt6', 'Linux/Qt5']
- 'on': ['Linux']
'require':
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
Options:
per-test-timeout: 90
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]
require-passing-tests-on: [ '@all' ]

View File

@@ -34,7 +34,7 @@ Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/qml/Component/confetti.png src/qml/Component/glowdot.png
Files: src/qml/confetti.png src/qml/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0

View File

@@ -14,11 +14,8 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "5.105.0")
set(QT_MIN_VERSION "5.15.2")
if (ANDROID)
set(QT_MIN_VERSION "5.15.10")
endif()
set(KF_MIN_VERSION "5.240.0")
set(QT_MIN_VERSION "6.5")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -48,28 +45,6 @@ if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
if(QT_MAJOR_VERSION STREQUAL "6")
set(BASICLISTITEM_BOLD "font.bold")
set(OVERLAYSHEET_OPEN "onOpened")
set(QTQUICK_MODULE_QML_VERSION "")
set(QTLOCATION_MODULE_QML_VERSION "")
set(QTMULTIMEDIA_MODULE_QML_VERSION "")
set(QTMULTIMEDIA_AUDIO "MediaPlayer")
# in Audio qt6 we don't have it but we disable it in qt5 => it seems ok
set(QTMULTIMEDIA_AUDIO_AUTOLOAD "")
# In Video qml qt6 we don't have it.
set(QTMULTIMEDIA_VIDEO_FLUSHMODE "")
else()
set(BASICLISTITEM_BOLD "bold")
set(OVERLAYSHEET_OPEN "onSheetOpenChanged")
set(QTQUICK_MODULE_QML_VERSION "2.15")
set(QTLOCATION_MODULE_QML_VERSION "5.15")
set(QTMULTIMEDIA_MODULE_QML_VERSION "5.15")
set(QTMULTIMEDIA_AUDIO "Audio")
set(QTMULTIMEDIA_AUDIO_AUTOLOAD "autoLoad: false")
set(QTMULTIMEDIA_VIDEO_FLUSHMODE "flushMode: VideoOutput.FirstFrame")
endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_setup_version(${PROJECT_VERSION}
@@ -77,25 +52,21 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
set_package_properties(KF${QT_MAJOR_VERSION} PROPERTIES
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
if(QT_MAJOR_VERSION STREQUAL "6")
find_package(KF6StatusNotifierItem ${KF_MIN_VERSION} REQUIRED)
endif()
set_package_properties(KF6Kirigami2 PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
if(ANDROID)
find_package(OpenSSL)
@@ -104,24 +75,26 @@ if(ANDROID)
PURPOSE "Encrypted communications"
)
else()
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF${QT_MAJOR_VERSION}QQC2DesktopStyle PROPERTIES
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem StatusNotifierItem)
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
find_package(ICU 61.0 COMPONENTS uc)
set_package_properties(ICU PROPERTIES
TYPE REQUIRED
PURPOSE "Unicode library"
)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
if (NOT ANDROID AND NOT APPLE)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
if(QT_MAJOR_VERSION STREQUAL "6" AND NOT ANDROID AND NOT WIN32)
set(QUOTIENT_SUFFIX "Qt6")
endif()
find_package(Quotient${QUOTIENT_SUFFIX} 0.7)
set_package_properties(Quotient${QUOTIENT_SUFFIX} PROPERTIES
find_package(QuotientQt6 0.7)
set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API"
URL "https://github.com/quotient-im/libQuotient/"
@@ -154,12 +127,12 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments"
)
find_package(QCoro${QT_MAJOR_VERSION} 0.4 COMPONENTS Core REQUIRED)
find_package(QCoro6 0.4 COMPONENTS Core REQUIRED)
qcoro_enable_coroutines()
find_package(KF${QT_MAJOR_VERSION}DocTools ${KF_MIN_VERSION})
set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
find_package(KF6DocTools ${KF_MIN_VERSION})
set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
"Tools to generate documentation"
TYPE OPTIONAL
)
@@ -181,12 +154,12 @@ add_definitions(-DQT_NO_FOREACH)
add_subdirectory(src)
if (BUILD_TESTING)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests)
add_subdirectory(appiumtests)
endif()
if(KF${QT_MAJOR_VERSION}DocTools_FOUND)
if(KF6DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()

View File

@@ -4,25 +4,26 @@
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import unittest
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
import os
import time
import subprocess
import sys
import unittest
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class LoginTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(self):
desired_caps = {}
desired_caps["app"] = "neochat --ignore-ssl-errors"
desired_caps["timeouts"] = {'implicit': 10000}
self.driver = webdriver.Remote(
command_executor='http://127.0.0.1:4723',
desired_capabilities=desired_caps)
self.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
def setUpClass(cls):
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
def setUp(self):
pass
@@ -45,5 +46,6 @@ class LoginTest(unittest.TestCase):
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
if __name__ == '__main__':
unittest.main()

View File

@@ -2,4 +2,4 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR})

View File

@@ -53,13 +53,16 @@
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
<summary xml:lang="pl">Rozmawiaj ze swoimi znajomymi w Matriksie</summary>
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
@@ -280,7 +283,7 @@ to provide a convergent experience across multiple platforms.</p>
<screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
</screenshot>
<screenshot x-kde-os="windows">
<screenshot environment="windows" x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
<caption>Main view with room list, chat, and room information</caption>
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
@@ -292,6 +295,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
@@ -306,7 +310,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
</screenshot>
<screenshot x-kde-os="windows">
<screenshot environment="windows" x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
<caption>Login screen</caption>
<caption xml:lang="ar">شاشة الدخول</caption>
@@ -318,6 +322,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
<caption xml:lang="fr">Écran de connexion</caption>
<caption xml:lang="gl">Pantalla de identificación.</caption>
<caption xml:lang="ia">Schermo de accesso</caption>
<caption xml:lang="it">Schermata di accesso</caption>
<caption xml:lang="ka">შესვლის ეკრანი</caption>
<caption xml:lang="ko">로그인 화면</caption>

View File

@@ -1 +1,8 @@
<svg width="22" height="22" fill="none" version="1.1" id="svg13" xmlns="http://www.w3.org/2000/svg"><style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M2 4h18v11H6.681L3 18.067V15H2zm1 10h1v1.933L6.319 14H19V5H3z" id="path3"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect5" d="M4 7h9v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect7" d="M4 9h7v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect9" d="M4 11h5v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="m16 15.293-1.147-1.146-.707.707 2.853 2.853V14.5h-1z" id="path11"/></svg>
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style>
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M3 3H19V14H8.68787L4 18.1019V14H3V3ZM4 13H5V15.8981L8.31213 13H18V4H4V13Z" fill="currentColor"/>
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M17 15.2929L14.8536 13.1465L14.1465 13.8536L18 17.7071V13.5H17V15.2929Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 6H15V7H5V6Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 8H13V9H5V8Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 10H11V11H5V10Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 928 B

After

Width:  |  Height:  |  Size: 752 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -97,7 +97,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
>Vegeu també</title>
<simplelist>
<member
>Una llista de les preguntes més freqüents quan a Matrix <ulink url="https://matrix.org/faq/"
>Una llista de les preguntes més freqüents quant a Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member

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

4359
po/eo/neochat.po Normal file

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,15 +3,6 @@
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
configure_file(qml/Page/RoomList/RoomDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Page/RoomList/RoomDelegate.qml)
configure_file(qml/Component/QuickSwitcher.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/QuickSwitcher.qml)
configure_file(qml/Dialog/PowerLevelDialog.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Dialog/PowerLevelDialog.qml)
configure_file(qml/Component/Timeline/AudioDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/AudioDelegate.qml)
configure_file(qml/Component/Timeline/VideoDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/VideoDelegate.qml)
configure_file(qml/Component/Timeline/OsmLocationPlugin.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/OsmLocationPlugin.qml)
configure_file(res.qrc ${CMAKE_CURRENT_SOURCE_DIR}/res.generated.qrc)
add_library(neochat STATIC
controller.cpp
controller.h
@@ -49,6 +40,12 @@ add_library(neochat STATIC
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/pushrulemodel.cpp
@@ -64,8 +61,8 @@ add_library(neochat STATIC
models/devicesmodel.cpp
models/devicesmodel.h
models/devicesproxymodel.cpp
filetypesingleton.cpp
filetypesingleton.h
filetype.cpp
filetype.h
login.cpp
login.h
models/webshortcutmodel.cpp
@@ -136,6 +133,157 @@ add_library(neochat STATIC
mediasizehelper.h
eventhandler.cpp
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
QML_FILES
qml/main.qml
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ContextMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml
qml/RoomListPage.qml
qml/SpaceListContextMenu.qml
qml/UserInfo.qml
qml/LoadingPage.qml
qml/RoomPage.qml
qml/RoomWindow.qml
qml/JoinRoomPage.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/StartChatPage.qml
qml/ImageEditorPage.qml
qml/WelcomePage.qml
qml/General.qml
qml/Security.qml
qml/PushNotification.qml
qml/Categories.qml
qml/Permissions.qml
qml/NeochatMaximizeComponent.qml
qml/FancyEffectsContainer.qml
qml/TypingPane.qml
qml/ShimmerGradient.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBox.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
qml/CompletionMenu.qml
qml/PieProgressBar.qml
qml/QuickFormatBar.qml
qml/RoomData.qml
qml/ServerData.qml
qml/EmojiPicker.qml
qml/TimelineDelegate.qml
qml/ReplyComponent.qml
qml/StateDelegate.qml
qml/RichLabel.qml
qml/MessageDelegate.qml
qml/Bubble.qml
qml/SectionDelegate.qml
qml/VideoDelegate.qml
qml/ReactionDelegate.qml
qml/LinkPreviewDelegate.qml
qml/AudioDelegate.qml
qml/FileDelegate.qml
qml/ImageDelegate.qml
qml/EncryptedDelegate.qml
qml/EventDelegate.qml
qml/TextDelegate.qml
qml/ReadMarkerDelegate.qml
qml/PollDelegate.qml
qml/MimeComponent.qml
qml/StateComponent.qml
qml/MessageEditComponent.qml
qml/AvatarFlow.qml
qml/LoginStep.qml
qml/Login.qml
qml/Homeserver.qml
qml/Username.qml
qml/RegisterPassword.qml
qml/Captcha.qml
qml/Terms.qml
qml/Email.qml
qml/Password.qml
qml/LoginRegister.qml
qml/Loading.qml
qml/LoginMethod.qml
qml/Sso.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
qml/PowerLevelDialog.qml
qml/Message.qml
qml/EmojiItem.qml
qml/EmojiRow.qml
qml/EmojiSas.qml
qml/ConfirmDeactivateAccountDialog.qml
qml/VerificationCanceled.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
qml/MessageDelegateContextMenu.qml
qml/FileDelegateContextMenu.qml
qml/MessageSourceSheet.qml
qml/ReportSheet.qml
qml/SettingsPage.qml
qml/ThemeRadioButton.qml
qml/ColorScheme.qml
qml/GeneralSettingsPage.qml
qml/EmoticonsPage.qml
qml/EmoticonEditorPage.qml
qml/EmoticonFormCard.qml
qml/GlobalNotificationsPage.qml
qml/NotificationRuleItem.qml
qml/AppearanceSettingsPage.qml
qml/AccountsPage.qml
qml/AccountEditorPage.qml
qml/DevicesPage.qml
qml/DeviceDelegate.qml
qml/DevicesCard.qml
qml/About.qml
qml/AboutKDE.qml
qml/SonnetConfigPage.qml
qml/NetworkProxyPage.qml
qml/DevtoolsPage.qml
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/SearchPage.qml
qml/LocationDelegate.qml
qml/LocationChooser.qml
qml/TimelineView.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/SpaceDrawer.qml
qml/OsmLocationPlugin.qml
qml/LiveLocationDelegate.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/ShareAction.qml
qml/SpaceHomePage.qml
qml/SpaceHierarchyDelegate.qml
qml/RemoveChildDialog.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
ecm_qt_declare_logging_category(neochat
@@ -156,7 +304,6 @@ ecm_qt_declare_logging_category(neochat
add_executable(neochat-app
main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/res.generated.qrc
)
if(TARGET Qt::WebView)
@@ -178,28 +325,24 @@ if(NOT ANDROID)
target_sources(neochat PRIVATE colorschemer.cpp colorschemer.h)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
if(QT_MAJOR_VERSION STREQUAL "6")
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
endif()
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF${QT_MAJOR_VERSION}::ConfigWidgets KF${QT_MAJOR_VERSION}::WindowSystem)
target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem ICU::uc)
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_sources(neochat-app PRIVATE res_desktop.qrc)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
else()
target_sources(neochat-app PRIVATE res_android.qrc)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark QCoro::Core)
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF6::I18n KF6::Kirigami2 KF6::Notifications KF6::ConfigCore KF6::ConfigGui KF6::CoreAddons KF6::SonnetCore KF6::ItemModels QuotientQt6 cmark::cmark QCoro::Core)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
@@ -287,7 +430,7 @@ if(ANDROID)
"gps"
)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
@@ -295,12 +438,12 @@ if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF${QT_MAJOR_VERSION}::DBusAddons)
target_link_libraries(neochat PUBLIC KF${QT_MAJOR_VERSION}::DBusAddons)
if(TARGET KF6::DBusAddons)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF${QT_MAJOR_VERSION}::KIOWidgets)
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/events/roommessageevent.h>
@@ -32,6 +33,7 @@ class NeoChatRoom;
class ActionsHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The room that messages will be sent to.

View File

@@ -134,7 +134,7 @@ int ChatDocumentHandler::completionStartIndex() const
return 0;
}
#if !defined(Q_OS_ANDROID) && QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
#if !defined(Q_OS_ANDROID)
const long long cursor = cursorPosition();
#else
const auto cursor = cursorPosition();

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QQuickTextDocument>
#include <QTextCursor>
@@ -58,6 +59,7 @@ class SyntaxHighlighter;
class ChatDocumentHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief Is the instance being used to handle an edit message.

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
class QClipboard;
class QImage;
@@ -18,6 +19,8 @@ class QImage;
class Clipboard : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief Whether the current clipboard content is an image.

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
class QAbstractItemModel;
class KColorSchemeManager;
@@ -19,6 +20,8 @@ class KColorSchemeManager;
class ColorSchemer : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief A QAbstractItemModel of all available color schemes.

View File

@@ -4,11 +4,7 @@
#include "controller.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <qt5keychain/keychain.h>
#else
#include <qt6keychain/keychain.h>
#endif
#include <KConfig>
#include <KConfigGroup>

View File

@@ -5,6 +5,7 @@
#include "models/pushrulemodel.h"
#include <QObject>
#include <QQmlEngine>
#include <QQuickItem>
#include <KFormat>
@@ -40,6 +41,8 @@ class ReadPasswordJob;
class Controller : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief The current connection for the rest of NeoChat to use.
@@ -90,6 +93,11 @@ public:
Q_ENUM(PasswordStatus)
static Controller &instance();
static Controller *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
void setActiveConnection(NeoChatConnection *connection);
[[nodiscard]] NeoChatConnection *activeConnection() const;

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
/**
* @class DelegateSizeHelper
@@ -23,6 +24,7 @@
class DelegateSizeHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The width of the component's parent.

View File

@@ -4,15 +4,18 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
/**
* @class DelegateType
*
* This class is designed to define the DelegateType enumeration.
*/
class DelegateType
class DelegateType : public QObject
{
Q_GADGET
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**

View File

@@ -986,3 +986,5 @@ QString EventHandler::getReadMarkersString() const
readMarkersString.chop(2);
return readMarkersString;
}
#include "moc_eventhandler.cpp"

116
src/filetype.cpp Normal file
View File

@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
#include "filetype.h"
#include <QImageReader>
#include <QMovie>
static QStringList byteArrayListToStringList(const QByteArrayList &byteArrayList)
{
QStringList stringList;
for (const QByteArray &byteArray : byteArrayList) {
stringList.append(QString::fromLocal8Bit(byteArray));
}
return stringList;
}
class FileTypePrivate
{
Q_DECLARE_PUBLIC(FileType)
Q_DISABLE_COPY(FileTypePrivate)
public:
FileTypePrivate(FileType *qq);
FileType *const q_ptr;
QMimeDatabase mimetypeDatabase;
QStringList supportedImageFormats = byteArrayListToStringList(QImageReader::supportedImageFormats());
QStringList supportedAnimatedImageFormats = byteArrayListToStringList(QMovie::supportedFormats());
};
FileTypePrivate::FileTypePrivate(FileType *qq)
: q_ptr(qq)
{
}
FileType::FileType(QObject *parent)
: QObject(parent)
, d_ptr(new FileTypePrivate(this))
{
}
FileType::~FileType() noexcept
{
}
QMimeType FileType::mimeTypeForName(const QString &nameOrAlias) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForName(nameOrAlias);
}
QMimeType FileType::mimeTypeForFile(const QString &fileName, MatchMode mode) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForFile(fileName, static_cast<QMimeDatabase::MatchMode>(mode));
}
QMimeType FileType::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForFile(fileInfo, static_cast<QMimeDatabase::MatchMode>(mode));
}
QList<QMimeType> FileType::mimeTypesForFileName(const QString &fileName) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypesForFileName(fileName);
}
QMimeType FileType::mimeTypeForData(const QByteArray &data) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForData(data);
}
QMimeType FileType::mimeTypeForData(QIODevice *device) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForData(device);
}
QMimeType FileType::mimeTypeForUrl(const QUrl &url) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForUrl(url);
}
QMimeType FileType::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, device);
}
QMimeType FileType::mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const
{
Q_D(const FileType);
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, data);
}
QString FileType::suffixForFileName(const QString &fileName) const
{
Q_D(const FileType);
return d->mimetypeDatabase.suffixForFileName(fileName);
}
QStringList FileType::supportedImageFormats() const
{
Q_D(const FileType);
return d->supportedImageFormats;
}
QStringList FileType::supportedAnimatedImageFormats() const
{
Q_D(const FileType);
return d->supportedAnimatedImageFormats;
}
#include "moc_filetype.cpp"

View File

@@ -8,9 +8,10 @@
#include <QFileInfo>
#include <QMimeDatabase>
#include <QObject>
#include <QQmlEngine>
#include <qqml.h>
class FileTypeSingletonPrivate;
class FileTypePrivate;
/**
* @class FileTypeSingleton
@@ -19,9 +20,11 @@ class FileTypeSingletonPrivate;
*
* @sa QMimeDatabase
*/
class FileTypeSingleton : public QObject
class FileType : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief List of supported image formats.
@@ -37,12 +40,9 @@ class FileTypeSingleton : public QObject
*/
Q_PROPERTY(QStringList supportedAnimatedImageFormats READ supportedAnimatedImageFormats CONSTANT FINAL)
QML_NAMED_ELEMENT(FileType)
QML_SINGLETON
public:
explicit FileTypeSingleton(QObject *parent = nullptr);
~FileTypeSingleton();
explicit FileType(QObject *parent = nullptr);
~FileType();
/**
* @brief Returns a MIME type for nameOrAlias or an invalid one if none found.
@@ -59,14 +59,14 @@ public:
*
* @sa QMimeDatabase
*/
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, FileTypeSingleton::MatchMode mode = MatchDefault) const;
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, FileType::MatchMode mode = MatchDefault) const;
/**
* @brief Returns a MIME type for fileInfo.
*
* @sa QMimeDatabase
*/
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, FileTypeSingleton::MatchMode mode = MatchDefault) const;
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, FileType::MatchMode mode = MatchDefault) const;
/**
* @brief Returns the MIME types for the file name fileName.
@@ -121,9 +121,7 @@ public:
QStringList supportedAnimatedImageFormats() const;
private:
const QScopedPointer<FileTypeSingletonPrivate> d_ptr;
Q_DECLARE_PRIVATE(FileTypeSingleton)
Q_DISABLE_COPY(FileTypeSingleton)
const QScopedPointer<FileTypePrivate> d_ptr;
Q_DECLARE_PRIVATE(FileType)
Q_DISABLE_COPY(FileType)
};
QML_DECLARE_TYPE(FileTypeSingleton)

View File

@@ -1,116 +0,0 @@
// SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
// SPDX-License-Identifier: LicenseRef-KDE-Accepted-LGPL
#include "filetypesingleton.h"
#include <QImageReader>
#include <QMovie>
static QStringList byteArrayListToStringList(const QByteArrayList &byteArrayList)
{
QStringList stringList;
for (const QByteArray &byteArray : byteArrayList) {
stringList.append(QString::fromLocal8Bit(byteArray));
}
return stringList;
}
class FileTypeSingletonPrivate
{
Q_DECLARE_PUBLIC(FileTypeSingleton)
Q_DISABLE_COPY(FileTypeSingletonPrivate)
public:
FileTypeSingletonPrivate(FileTypeSingleton *qq);
FileTypeSingleton *const q_ptr;
QMimeDatabase mimetypeDatabase;
QStringList supportedImageFormats = byteArrayListToStringList(QImageReader::supportedImageFormats());
QStringList supportedAnimatedImageFormats = byteArrayListToStringList(QMovie::supportedFormats());
};
FileTypeSingletonPrivate::FileTypeSingletonPrivate(FileTypeSingleton *qq)
: q_ptr(qq)
{
}
FileTypeSingleton::FileTypeSingleton(QObject *parent)
: QObject(parent)
, d_ptr(new FileTypeSingletonPrivate(this))
{
}
FileTypeSingleton::~FileTypeSingleton() noexcept
{
}
QMimeType FileTypeSingleton::mimeTypeForName(const QString &nameOrAlias) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForName(nameOrAlias);
}
QMimeType FileTypeSingleton::mimeTypeForFile(const QString &fileName, MatchMode mode) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForFile(fileName, static_cast<QMimeDatabase::MatchMode>(mode));
}
QMimeType FileTypeSingleton::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForFile(fileInfo, static_cast<QMimeDatabase::MatchMode>(mode));
}
QList<QMimeType> FileTypeSingleton::mimeTypesForFileName(const QString &fileName) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypesForFileName(fileName);
}
QMimeType FileTypeSingleton::mimeTypeForData(const QByteArray &data) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForData(data);
}
QMimeType FileTypeSingleton::mimeTypeForData(QIODevice *device) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForData(device);
}
QMimeType FileTypeSingleton::mimeTypeForUrl(const QUrl &url) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForUrl(url);
}
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, device);
}
QMimeType FileTypeSingleton::mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.mimeTypeForFileNameAndData(fileName, data);
}
QString FileTypeSingleton::suffixForFileName(const QString &fileName) const
{
Q_D(const FileTypeSingleton);
return d->mimetypeDatabase.suffixForFileName(fileName);
}
QStringList FileTypeSingleton::supportedImageFormats() const
{
Q_D(const FileTypeSingleton);
return d->supportedImageFormats;
}
QStringList FileTypeSingleton::supportedAnimatedImageFormats() const
{
Q_D(const FileTypeSingleton);
return d->supportedAnimatedImageFormats;
}
#include "moc_filetypesingleton.cpp"

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QUrl>
class NeoChatRoom;
@@ -19,6 +20,8 @@ class NeoChatRoom;
class LinkPreviewer : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The URL to get the preview for.
*/

View File

@@ -2,14 +2,17 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "linkpreviewer.h"
#include <QMetaType>
#include <QObject>
#include <QQmlEngine>
#include <QRectF>
/** Location related helper functions for QML. */
class LocationHelper
class LocationHelper : public QObject
{
Q_GADGET
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/** Unite two rectanlges. */
Q_INVOKABLE static QRectF unite(const QRectF &r1, const QRectF &r2);

View File

@@ -13,13 +13,13 @@
using namespace Quotient;
Login::Login(QObject *parent)
LoginHelper::LoginHelper(QObject *parent)
: QObject(parent)
{
init();
}
void Login::init()
void LoginHelper::init()
{
m_homeserverReachable = false;
m_connection = new NeoChatConnection();
@@ -31,7 +31,7 @@ void Login::init()
m_supportsPassword = false;
m_ssoUrl = QUrl();
connect(this, &Login::matrixIdChanged, this, [this]() {
connect(this, &LoginHelper::matrixIdChanged, this, [this]() {
setHomeserverReachable(false);
QRegularExpression validator(QStringLiteral("^\\@?[a-zA-Z0-9\\._=\\-/]+\\:[a-zA-Z0-9\\-]+(\\.[a-zA-Z0-9\\-]+)*(\\:[0-9]+)?$"));
if (!validator.match(m_matrixId).hasMatch()) {
@@ -105,23 +105,23 @@ void Login::init()
});
}
void Login::setHomeserverReachable(bool reachable)
void LoginHelper::setHomeserverReachable(bool reachable)
{
m_homeserverReachable = reachable;
Q_EMIT homeserverReachableChanged();
}
bool Login::homeserverReachable() const
bool LoginHelper::homeserverReachable() const
{
return m_homeserverReachable;
}
QString Login::matrixId() const
QString LoginHelper::matrixId() const
{
return m_matrixId;
}
void Login::setMatrixId(const QString &matrixId)
void LoginHelper::setMatrixId(const QString &matrixId)
{
m_matrixId = matrixId;
if (!m_matrixId.startsWith(QLatin1Char('@'))) {
@@ -130,30 +130,30 @@ void Login::setMatrixId(const QString &matrixId)
Q_EMIT matrixIdChanged();
}
QString Login::password() const
QString LoginHelper::password() const
{
return m_password;
}
void Login::setPassword(const QString &password)
void LoginHelper::setPassword(const QString &password)
{
setInvalidPassword(false);
m_password = password;
Q_EMIT passwordChanged();
}
QString Login::deviceName() const
QString LoginHelper::deviceName() const
{
return m_deviceName;
}
void Login::setDeviceName(const QString &deviceName)
void LoginHelper::setDeviceName(const QString &deviceName)
{
m_deviceName = deviceName;
Q_EMIT deviceNameChanged();
}
void Login::login()
void LoginHelper::login()
{
m_isLoggingIn = true;
Q_EMIT isLoggingInChanged();
@@ -164,22 +164,22 @@ void Login::login()
m_connection->loginWithPassword(username, m_password, m_deviceName, QString());
}
bool Login::supportsPassword() const
bool LoginHelper::supportsPassword() const
{
return m_supportsPassword;
}
bool Login::supportsSso() const
bool LoginHelper::supportsSso() const
{
return m_supportsSso;
}
QUrl Login::ssoUrl() const
QUrl LoginHelper::ssoUrl() const
{
return m_ssoUrl;
}
void Login::loginWithSso()
void LoginHelper::loginWithSso()
{
m_connection->resolveServer(m_matrixId);
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
@@ -189,28 +189,28 @@ void Login::loginWithSso()
});
}
bool Login::testing() const
bool LoginHelper::testing() const
{
return m_testing;
}
bool Login::isLoggingIn() const
bool LoginHelper::isLoggingIn() const
{
return m_isLoggingIn;
}
bool Login::isLoggedIn() const
bool LoginHelper::isLoggedIn() const
{
return m_isLoggedIn;
}
void Login::setInvalidPassword(bool invalid)
void LoginHelper::setInvalidPassword(bool invalid)
{
m_invalidPassword = invalid;
Q_EMIT isInvalidPasswordChanged();
}
bool Login::isInvalidPassword() const
bool LoginHelper::isInvalidPassword() const
{
return m_invalidPassword;
}

View File

@@ -4,18 +4,21 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QUrl>
class NeoChatConnection;
/**
* @class Login
* @class LoginHelper
*
* A helper class for logging into a Matrix account.
*/
class Login : public QObject
class LoginHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief Whether the home server for the account is reachable.
@@ -76,7 +79,7 @@ class Login : public QObject
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
public:
explicit Login(QObject *parent = nullptr);
explicit LoginHelper(QObject *parent = nullptr);
Q_INVOKABLE void init();

View File

@@ -34,74 +34,25 @@
#include "neochat-version.h"
#include <Quotient/accountregistry.h>
#include <Quotient/keyverificationsession.h>
#include <Quotient/networkaccessmanager.h>
#include <Quotient/room.h>
#include <Quotient/user.h>
#include <Quotient/util.h>
#include "actionshandler.h"
#include "blurhashimageprovider.h"
#include "chatdocumenthandler.h"
#include "clipboard.h"
#include "controller.h"
#include "delegatesizehelper.h"
#include "enums/delegatetype.h"
#include "filetypesingleton.h"
#include "linkpreviewer.h"
#include "locationhelper.h"
#include "logger.h"
#include "login.h"
#include "matriximageprovider.h"
#include "mediasizehelper.h"
#include "models/accountemoticonmodel.h"
#include "models/customemojimodel.h"
#include "models/devicesmodel.h"
#include "models/devicesproxymodel.h"
#include "models/emojimodel.h"
#include "models/emoticonfiltermodel.h"
#include "models/imagepacksmodel.h"
#include "models/livelocationsmodel.h"
#include "models/locationsmodel.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/pushrulemodel.h"
#include "models/reactionmodel.h"
#include "models/roomlistmodel.h"
#include "models/searchmodel.h"
#include "models/serverlistmodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/statefiltermodel.h"
#include "models/stickermodel.h"
#include "models/userdirectorylistmodel.h"
#include "models/userfiltermodel.h"
#include "models/userlistmodel.h"
#include "models/webshortcutmodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "pollhandler.h"
#include "roommanager.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include "windowcontroller.h"
#ifdef HAVE_COLORSCHEME
#include "colorschemer.h"
#endif
#include "models/completionmodel.h"
#include "models/statemodel.h"
#ifdef HAVE_RUNNER
#include "runner.h"
#include <QDBusConnection>
#endif
#include "registration.h"
#ifdef Q_OS_WINDOWS
#include <Windows.h>
@@ -109,6 +60,8 @@
using namespace Quotient;
void qml_register_types_org_kde_neochat();
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
{
QNetworkAccessManager *create(QObject *) override
@@ -140,19 +93,13 @@ Q_DECL_EXPORT
#endif
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QNetworkProxyFactory::setUseSystemConfiguration(true);
#ifdef HAVE_WEBVIEW
QtWebView::initialize();
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
#endif
#endif
#ifdef Q_OS_ANDROID
QGuiApplication app(argc, argv);
@@ -221,99 +168,18 @@ int main(int argc, char *argv[])
QStringLiteral("/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"));
#endif
Clipboard clipboard;
auto config = NeoChatConfig::self();
FileTypeSingleton fileTypeSingleton;
Login *login = new Login();
UrlHelper urlHelper;
#ifdef HAVE_COLORSCHEME
ColorSchemer colorScheme;
qmlRegisterSingletonInstance<ColorSchemer>("org.kde.neochat", 1, 0, "ColorSchemer", &colorScheme);
if (!config->colorScheme().isEmpty()) {
colorScheme.apply(config->colorScheme());
if (!NeoChatConfig::self()->colorScheme().isEmpty()) {
colorScheme.apply(NeoChatConfig::self()->colorScheme());
}
#endif
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "NotificationsManager", &NotificationsManager::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "RoomManager", &RoomManager::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", &EmojiModel::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Controller::instance().accounts());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Registration", &Registration::instance());
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
qmlRegisterType<ReactionModel>("org.kde.neochat", 1, 0, "ReactionModel");
qmlRegisterType<MediaMessageFilterModel>("org.kde.neochat", 1, 0, "MediaMessageFilterModel");
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel");
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
qmlRegisterType<DevicesProxyModel>("org.kde.neochat", 1, 0, "DevicesProxyModel");
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
qmlRegisterType<StateFilterModel>("org.kde.neochat", 1, 0, "StateFilterModel");
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
qmlRegisterType<LiveLocationsModel>("org.kde.neochat", 1, 0, "LiveLocationsModel");
qmlRegisterType<LocationsModel>("org.kde.neochat", 1, 0, "LocationsModel");
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
qmlRegisterType<PushRuleModel>("org.kde.neochat", 1, 0, "PushRuleModel");
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel");
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper");
qmlRegisterType<MediaSizeHelper>("org.kde.neochat", 1, 0, "MediaSizeHelper");
qmlRegisterUncreatableType<DelegateType>("org.kde.neochat", 1, 0, "DelegateType", "ENUM"_ls);
qmlRegisterUncreatableType<PushNotificationKind>("org.kde.neochat", 1, 0, "PushNotificationKind", "ENUM"_ls);
qmlRegisterUncreatableType<PushNotificationSection>("org.kde.neochat", 1, 0, "PushNotificationSection", "ENUM"_ls);
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM"_ls);
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM"_ls);
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM"_ls);
qmlRegisterUncreatableType<User>("org.kde.neochat", 1, 0, "User", {});
qmlRegisterUncreatableType<NeoChatRoom>("org.kde.neochat", 1, 0, "NeoChatRoom", {});
qmlRegisterUncreatableType<NeoChatConnection>("org.kde.neochat", 1, 0, "NeoChatConnection", {});
qml_register_types_org_kde_neochat();
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
qRegisterMetaType<User *>("User*");
qRegisterMetaType<User *>("const User*");
qRegisterMetaType<User *>("const Quotient::User*");
qRegisterMetaType<Room *>("Room*");
qRegisterMetaType<MessageEventType>("MessageEventType");
qRegisterMetaType<NeoChatRoom *>("NeoChatRoom*");
qRegisterMetaType<User *>("User*");
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
qRegisterMetaType<QMimeType>("QMimeType");
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
return engine->toScriptValue(KAboutData::applicationData());
});
qmlRegisterSingletonType(QUrl("qrc:/OsmLocationPlugin.qml"_ls), "org.kde.neochat", 1, 0, "OsmLocationPlugin");
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "LocationHelper", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
return engine->toScriptValue(LocationHelper());
});
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qRegisterMetaTypeStreamOperators<Emoji>();
#endif
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
QQmlApplicationEngine engine;
@@ -364,7 +230,7 @@ int main(int argc, char *argv[])
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.load(QUrl(QStringLiteral("qrc:/org/kde/neochat/qml/main.qml")));
if (engine.rootObjects().isEmpty()) {
return -1;
}

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QSize>
/**
@@ -28,6 +29,7 @@
class MediaSizeHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The maximum width (in px) the media can be.

View File

@@ -9,6 +9,7 @@
#include <QCoroTask>
#include <QObject>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
#include <Quotient/connection.h>
@@ -23,6 +24,8 @@
class AccountEmoticonModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to get emoticons from.
*/

View File

@@ -87,8 +87,11 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
}
if (role == IconNameRole) {
return QStringLiteral("mxc://%1")
.arg(m_roomListModel->connection()->makeMediaUrl(m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toUrl()).toString());
auto mediaId = m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
if (mediaId.isEmpty()) {
return QVariant();
}
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
}
}
if (m_autoCompletionType == Emoji) {
@@ -145,7 +148,7 @@ void CompletionModel::updateCompletion()
m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text);
m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char(':')) && !text()[1].isUpper()
} else if (text().startsWith(QLatin1Char(':')) && text().size() > 1 && !text()[1].isUpper()
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
m_filterModel->setSourceModel(m_emojiModel);

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QConcatenateTablesProxyModel>
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include "roomlistmodel.h"
@@ -24,6 +25,7 @@ class RoomListModel;
class CompletionModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current text to search for completions.

View File

@@ -22,11 +22,7 @@ bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
&& sourceModel()
->data(sourceModel()->index(sourceRow, 0), secondaryFilterRole())
.toString()
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
.startsWith(QStringView(m_filterText).sliced(1), Qt::CaseInsensitive));
#else
.startsWith(m_filterText.midRef(1), Qt::CaseInsensitive));
#endif
}
bool CompletionProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QRegularExpression>
#include <memory>
@@ -27,6 +28,8 @@ struct CustomEmoji {
class CustomEmojiModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
/**
@@ -48,6 +51,11 @@ public:
static CustomEmojiModel _instance;
return _instance;
}
static CustomEmojiModel *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
/**
* @brief Get the given role value at the given index.

View File

@@ -5,6 +5,7 @@
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <Quotient/csapi/definitions/client_device.h>
@@ -25,6 +26,7 @@ class Connection;
class DevicesModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current connection that the model is getting its devices from.

View File

@@ -3,11 +3,14 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
class DevicesProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged)
public:

View File

@@ -5,6 +5,7 @@
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine>
#include <QSettings>
struct Emoji {
@@ -59,6 +60,8 @@ Q_DECLARE_METATYPE(Emoji)
class EmojiModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
/**
* @brief Return a list of recently used emojis.
@@ -83,6 +86,11 @@ public:
static EmojiModel _instance;
return _instance;
}
static EmojiModel *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
/**
* @brief Defines the model roles.

View File

@@ -3,6 +3,7 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
@@ -14,6 +15,7 @@
class EmoticonFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief Whether stickers should be shown

View File

@@ -6,6 +6,7 @@
#include "events/imagepackevent.h"
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
class NeoChatRoom;
@@ -21,6 +22,7 @@ class NeoChatRoom;
class ImagePacksModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current room that the model is being used in.

View File

@@ -8,6 +8,7 @@
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <QRectF>
struct LiveLocationData {
@@ -24,6 +25,8 @@ bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs);
class LiveLocationsModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(NeoChatRoom *room MEMBER m_room NOTIFY roomChanged)
/** The event id of the beacon start event, ie. the one all suspequent
* events use to relate to the same beacon.

View File

@@ -5,6 +5,7 @@
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <QRectF>
#include "neochatroom.h"
@@ -15,6 +16,7 @@
class LocationsModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
enum Roles {

View File

@@ -3,8 +3,8 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include <qobjectdefs.h>
#include "models/messagefiltermodel.h"
@@ -20,6 +20,8 @@ class MessageFilterModel;
class MediaMessageFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
public:
enum MediaType {
Image = 0,

View File

@@ -55,8 +55,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[ShowReadMarkersRole] = "showReadMarkers";
roles[ReactionRole] = "reaction";
roles[ShowReactionsRole] = "showReactions";
roles[SourceRole] = "jsonSource";
roles[AuthorIdRole] = "authorId";
roles[VerifiedRole] = "verified";
roles[AuthorDisplayNameRole] = "authorDisplayName";
roles[IsRedactedRole] = "isRedacted";
@@ -467,10 +465,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return eventHandler.getPlainBody();
}
if (role == SourceRole) {
return QJsonDocument(evt.fullJson()).toJson();
}
if (role == DelegateTypeRole) {
return eventHandler.getDelegateType();
}
@@ -664,10 +658,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return m_reactionModels.contains(evt.id());
}
if (role == AuthorIdRole) {
return evt.senderId();
}
if (role == VerifiedRole) {
if (evt.originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());

View File

@@ -5,6 +5,7 @@
#include <KFormat>
#include <QAbstractListModel>
#include <QQmlEngine>
#include "linkpreviewer.h"
#include "neochatroom.h"
@@ -25,6 +26,7 @@ class ReactionModel;
class MessageEventModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current room that the model is getting its messages from.
@@ -70,9 +72,6 @@ public:
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. */
SourceRole, /**< The full message source JSON. */
AuthorIdRole, /**< Matrix ID of the message author. */
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. */

View File

@@ -3,6 +3,7 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include "messageeventmodel.h"
@@ -21,6 +22,8 @@
class MessageFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
public:
/**
* @brief Defines the model roles.

View File

@@ -5,6 +5,7 @@
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine>
#include <Quotient/csapi/list_public_rooms.h>
@@ -27,6 +28,7 @@ class Connection;
class PublicRoomListModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current connection that the model is getting its rooms from.

View File

@@ -171,7 +171,7 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
if (!testUserId.startsWith(u'@')) {
testUserId.prepend(u'@');
}
if (connection->user(testUserId) != nullptr) {
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && connection->user(testUserId) != nullptr) {
return PushNotificationSection::Undefined;
}
// If the rule has push conditions and one is a room ID it is a room only keyword.

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/csapi/definitions/push_rule.h>
@@ -20,6 +21,8 @@
class PushNotificationKind : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
@@ -71,6 +74,8 @@ public:
class PushNotificationSection : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
@@ -130,6 +135,7 @@ public:
class PushRuleModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The default state for any newly created keyword rule.

View File

@@ -4,6 +4,12 @@
#include "reactionmodel.h"
#include <QDebug>
#ifdef HAVE_ICU
#include <QTextBoundaryFinder>
#include <QTextCharFormat>
#include <unicode/uchar.h>
#include <unicode/urename.h>
#endif
#include <KLocalizedString>
@@ -29,11 +35,38 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
const auto &reaction = m_reactions.at(index.row());
if (role == TextRole) {
const auto isEmoji = [](const QString &text) {
#ifdef HAVE_ICU
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
int from = 0;
while (finder.toNextBoundary() != -1) {
auto to = finder.position();
if (text[from].isSpace()) {
from = to;
continue;
}
auto first = text.mid(from, to - from).toUcs4()[0];
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
return false;
}
from = to;
}
return true;
#else
return false;
#endif
};
const auto reactionText = isEmoji(reaction.reaction)
? QStringLiteral("<span style=\"font-family: 'emoji';\">") + reaction.reaction + QStringLiteral("</span>")
: reaction.reaction;
if (role == TextContentRole) {
if (reaction.authors.count() > 1) {
return QStringLiteral("%1 %2").arg(reaction.reaction, QString::number(reaction.authors.count()));
return QStringLiteral("%1 %2").arg(reactionText, QString::number(reaction.authors.count()));
} else {
return reaction.reaction;
return reactionText;
}
}
@@ -64,7 +97,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
"%2 reacted with %3",
reaction.authors.count(),
text,
reaction.reaction);
reactionText);
return text;
}
@@ -101,7 +134,7 @@ void ReactionModel::setReactions(QList<Reaction> reactions)
QHash<int, QByteArray> ReactionModel::roleNames() const
{
return {
{TextRole, "text"},
{TextContentRole, "textContent"},
{ReactionRole, "reaction"},
{ToolTipRole, "toolTip"},
{AuthorsRole, "authors"},

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
namespace Quotient
{
@@ -18,6 +19,7 @@ class User;
class ReactionModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
/**
@@ -32,7 +34,7 @@ public:
* @brief Defines the model roles.
*/
enum Roles {
TextRole = Qt::DisplayRole, /**< The text to show in the reaction. */
TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
ReactionRole, /**< The reaction emoji. */
ToolTipRole, /**< The tool tip to show for the reaction. */
AuthorsRole, /**< The list of authors who sent the given reaction. */

View File

@@ -6,6 +6,7 @@
#include <Quotient/events/roomevent.h>
#include <QAbstractListModel>
#include <QQmlEngine>
class NeoChatRoom;
@@ -18,6 +19,8 @@ class Room;
class NeoChatRoomType : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
@@ -42,6 +45,7 @@ public:
class RoomListModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current connection that the model is getting its rooms from.

View File

@@ -184,7 +184,6 @@ QHash<int, QByteArray> SearchModel::roleNames() const
{MimeTypeRole, "mimeType"},
{ShowLinkPreviewRole, "showLinkPreview"},
{LinkPreviewRole, "linkPreview"},
{SourceRole, "jsonSource"},
};
}

View File

@@ -4,6 +4,7 @@
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QString>
#include <Quotient/csapi/search.h>
@@ -23,6 +24,7 @@ class NeoChatRoom;
class SearchModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The text to search messages for.
@@ -83,7 +85,6 @@ public:
MimeTypeRole,
ShowLinkPreviewRole,
LinkPreviewRole,
SourceRole,
};
Q_ENUM(Roles)
explicit SearchModel(QObject *parent = nullptr);

View File

@@ -7,6 +7,7 @@
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <QUrl>
/**
@@ -24,6 +25,7 @@
class ServerListModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
/**

View File

@@ -3,6 +3,7 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
@@ -27,6 +28,7 @@
class SortFilterRoomListModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The order by which the rooms will be sorted.

View File

@@ -3,6 +3,7 @@
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
@@ -16,6 +17,8 @@
class SortFilterSpaceListModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The number of spaces in the model.
*/

View File

@@ -0,0 +1,334 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "spacechildrenmodel.h"
#include <Quotient/connection.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/room.h>
#include "controller.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent)
{
m_rootItem = new SpaceTreeItem();
}
SpaceChildrenModel::~SpaceChildrenModel()
{
delete m_rootItem;
}
NeoChatRoom *SpaceChildrenModel::space() const
{
return m_space;
}
void SpaceChildrenModel::setSpace(NeoChatRoom *space)
{
if (space == m_space) {
return;
}
// disconnect the new room signal from the old connection in case it is different.
if (m_space != nullptr) {
disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr);
}
m_space = space;
Q_EMIT spaceChanged();
for (auto job : m_currentJobs) {
if (job) {
job->abandon();
}
}
m_currentJobs.clear();
auto connection = m_space->connection();
connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
if (m_pendingChildren.contains(room->name())) {
m_pendingChildren.removeAll(room->name());
refreshModel();
}
});
connect(m_space, &Quotient::Room::changed, this, [this]() {
refreshModel();
});
refreshModel();
}
bool SpaceChildrenModel::loading() const
{
return m_loading;
}
void SpaceChildrenModel::refreshModel()
{
beginResetModel();
m_replacedRooms.clear();
delete m_rootItem;
m_loading = true;
Q_EMIT loadingChanged();
m_rootItem = new SpaceTreeItem(nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
endResetModel();
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), Quotient::none, Quotient::none, 1);
m_currentJobs.append(job);
connect(job, &Quotient::BaseJob::success, this, [this, job]() {
insertChildren(job->rooms());
});
}
void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent)
{
SpaceTreeItem *parentItem = getItem(parent);
if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) {
children.erase(children.begin());
}
// If this is the first set of children added to the root item then we need to
// set it so that we are no longer loading.
if (rowCount(QModelIndex()) == 0 && !children.empty()) {
m_loading = false;
Q_EMIT loadingChanged();
}
beginInsertRows(parent, parentItem->childCount(), parentItem->childCount() + children.size() - 1);
for (unsigned long i = 0; i < children.size(); ++i) {
if (children[i].roomId == m_space->id() || children[i].roomId == parentItem->id()) {
continue;
} else {
int insertRow = parentItem->childCount();
if (const auto room = m_space->connection()->room(children[i].roomId)) {
const auto predecessorId = room->predecessorId();
if (!predecessorId.isEmpty()) {
m_replacedRooms += predecessorId;
}
const auto successorId = room->successorId();
if (!successorId.isEmpty()) {
m_replacedRooms += successorId;
}
}
parentItem->insertChild(insertRow,
new SpaceTreeItem(parentItem,
children[i].roomId,
children[i].name,
children[i].canonicalAlias,
children[i].topic,
children[i].numJoinedMembers,
children[i].avatarUrl,
children[i].guestCanJoin,
children[i].worldReadable,
children[i].roomType == QLatin1String("m.space")));
if (children[i].childrenState.size() > 0) {
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);
m_currentJobs.append(job);
connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
insertChildren(job->rooms(), index(insertRow, 0, parent));
});
}
}
}
endInsertRows();
}
SpaceTreeItem *SpaceChildrenModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
SpaceTreeItem *item = static_cast<SpaceTreeItem *>(index.internalPointer());
if (item) {
return item;
}
}
return m_rootItem;
}
QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
SpaceTreeItem *child = getItem(index);
if (role == DisplayNameRole) {
auto displayName = child->name();
if (!displayName.isEmpty()) {
return displayName;
}
displayName = child->canonicalAlias();
if (!displayName.isEmpty()) {
return displayName;
}
return child->id();
}
if (role == AvatarUrlRole) {
return child->avatarUrl();
}
if (role == TopicRole) {
return child->topic();
}
if (role == RoomIDRole) {
return child->id();
}
if (role == AliasRole) {
return child->canonicalAlias();
}
if (role == MemberCountRole) {
return child->memberCount();
}
if (role == AllowGuestsRole) {
return child->allowGuests();
}
if (role == WorldReadableRole) {
return child->worldReadable();
}
if (role == IsJoinedRole) {
return child->isJoined();
}
if (role == IsSpaceRole) {
return child->isSpace();
}
if (role == CanAddChildrenRole) {
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
return room->canSendState(QLatin1String("m.space.child"));
}
return false;
}
if (role == ParentDisplayNameRole) {
const auto parent = child->parentItem();
auto displayName = parent->name();
if (!displayName.isEmpty()) {
return displayName;
}
displayName = parent->canonicalAlias();
if (!displayName.isEmpty()) {
return displayName;
}
return parent->id();
}
if (role == CanSetParentRole) {
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
return room->canSendState(QLatin1String("m.space.parent"));
}
return false;
}
if (role == IsDeclaredParentRole) {
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
return room->currentState().contains(QLatin1String("m.space.parent"), child->parentItem()->id());
}
return false;
}
if (role == CanRemove) {
const auto parent = child->parentItem();
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(parent->id()))) {
return room->canSendState(QLatin1String("m.space.child"));
}
return false;
}
if (role == ParentRoomRole) {
if (const auto parentRoom = static_cast<NeoChatRoom *>(m_space->connection()->room(child->parentItem()->id()))) {
return QVariant::fromValue(parentRoom);
}
return QVariant::fromValue(nullptr);
}
return {};
}
QModelIndex SpaceChildrenModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
SpaceTreeItem *parentItem = getItem(parent);
if (!parentItem) {
return QModelIndex();
}
SpaceTreeItem *childItem = parentItem->child(row);
if (childItem) {
return createIndex(row, column, childItem);
}
return QModelIndex();
}
QModelIndex SpaceChildrenModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) {
return QModelIndex();
}
SpaceTreeItem *childItem = static_cast<SpaceTreeItem *>(index.internalPointer());
SpaceTreeItem *parentItem = childItem->parentItem();
if (parentItem == m_rootItem) {
return QModelIndex();
}
return createIndex(parentItem->row(), 0, parentItem);
}
int SpaceChildrenModel::rowCount(const QModelIndex &parent) const
{
SpaceTreeItem *parentItem;
if (parent.column() > 0) {
return 0;
}
if (!parent.isValid()) {
parentItem = m_rootItem;
} else {
parentItem = static_cast<SpaceTreeItem *>(parent.internalPointer());
}
return parentItem->childCount();
}
int SpaceChildrenModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 1;
}
QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DisplayNameRole] = "displayName";
roles[AvatarUrlRole] = "avatarUrl";
roles[TopicRole] = "topic";
roles[RoomIDRole] = "roomId";
roles[MemberCountRole] = "memberCount";
roles[AllowGuestsRole] = "allowGuests";
roles[WorldReadableRole] = "worldReadable";
roles[IsJoinedRole] = "isJoined";
roles[AliasRole] = "alias";
roles[IsSpaceRole] = "isSpace";
roles[CanAddChildrenRole] = "canAddChildren";
roles[ParentDisplayNameRole] = "parentDisplayName";
roles[CanSetParentRole] = "canSetParent";
roles[IsDeclaredParentRole] = "isDeclaredParent";
roles[CanRemove] = "canRemove";
roles[ParentRoomRole] = "parentRoom";
return roles;
}
bool SpaceChildrenModel::isRoomReplaced(const QString &roomId) const
{
return m_replacedRooms.contains(roomId);
}
void SpaceChildrenModel::addPendingChild(const QString &childName)
{
m_pendingChildren += childName;
}
#include "moc_spacechildrenmodel.cpp"

View File

@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractItemModel>
#include <QQmlEngine>
#include <Quotient/csapi/space_hierarchy.h>
#include <qtmetamacros.h>
#include "neochatroom.h"
#include "spacetreeitem.h"
/**
* @class SpaceChildrenModel
*
* Create a model that contains a list of the child rooms for any given space id.
*/
class SpaceChildrenModel : public QAbstractItemModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The current space that the hierarchy is being generated for.
*/
Q_PROPERTY(NeoChatRoom *space READ space WRITE setSpace NOTIFY spaceChanged)
/**
* @brief Whether the model is loading the initial set of children.
*/
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
public:
enum Roles {
DisplayNameRole = Qt::DisplayRole,
AvatarUrlRole,
TopicRole,
RoomIDRole,
AliasRole,
MemberCountRole,
AllowGuestsRole,
WorldReadableRole,
IsJoinedRole,
IsSpaceRole,
CanAddChildrenRole,
ParentDisplayNameRole,
CanSetParentRole,
IsDeclaredParentRole,
CanRemove,
ParentRoomRole,
};
explicit SpaceChildrenModel(QObject *parent = nullptr);
~SpaceChildrenModel();
NeoChatRoom *space() const;
void setSpace(NeoChatRoom *space);
bool loading() const;
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
QVariant data(const QModelIndex &index, int role = DisplayNameRole) const override;
/**
* @brief Returns the index of the item in the model specified by the given row, column and parent index.
*
* @sa QAbstractItemModel::index
*/
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns the parent of the model item with the given index.
*
* If the item has no parent, an invalid QModelIndex is returned.
*
* @sa QAbstractItemModel::parent
*/
QModelIndex parent(const QModelIndex &index) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Number of columns in the model.
*
* @sa QAbstractItemModel::columnCount
*/
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
QHash<int, QByteArray> roleNames() const override;
/**
* @brief Whether the room has been replaced.
*
* @note This information is only available if the local user is either a member
* of the replaced room or is a member of the successor room as currently
* there is no other way to obtain the required information.
*/
bool isRoomReplaced(const QString &roomId) const;
/**
* @brief Add the name of new child room that is expected to be added soon.
*
* A pending child is one where Quotient::Connection::createRoom has been called
* but the room hasn't synced with the server yet. This list is used to check
* whether a new room loading should trigger a refresh of the model, as we only
* want to trigger a refresh if the loading room is part of this space.
*/
Q_INVOKABLE void addPendingChild(const QString &childName);
Q_SIGNALS:
void spaceChanged();
void loadingChanged();
private:
NeoChatRoom *m_space = nullptr;
SpaceTreeItem *m_rootItem;
bool m_loading = false;
QList<QPointer<Quotient::GetSpaceHierarchyJob>> m_currentJobs;
QList<QString> m_pendingChildren;
QList<QString> m_replacedRooms;
SpaceTreeItem *getItem(const QModelIndex &index) const;
void refreshModel();
void insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent = QModelIndex());
};

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "spacechildsortfiltermodel.h"
#include "spacechildrenmodel.h"
SpaceChildSortFilterModel::SpaceChildSortFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
setRecursiveFilteringEnabled(true);
sort(0);
}
void SpaceChildSortFilterModel::setFilterText(const QString &filterText)
{
m_filterText = filterText;
Q_EMIT filterTextChanged();
invalidateFilter();
}
QString SpaceChildSortFilterModel::filterText() const
{
return m_filterText;
}
bool SpaceChildSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
if (!source_left.data(SpaceChildrenModel::IsSpaceRole).toBool() && source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
return false;
}
return true;
}
bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
if (auto sourceModel = static_cast<SpaceChildrenModel *>(this->sourceModel())) {
bool isReplaced = sourceModel->isRoomReplaced(index.data(SpaceChildrenModel::RoomIDRole).toString());
bool acceptRoom = index.data(SpaceChildrenModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive);
return !isReplaced && acceptRoom;
}
return true;
}
#include "moc_spacechildsortfiltermodel.cpp"

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
* @class SpaceChildSortFilterModel
*
* This class creates a custom QSortFilterProxyModel for filtering and sorting spaces
* in a SpaceChildrenModel.
*
* @sa SpaceChildrenModel
*/
class SpaceChildSortFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The text to use to filter room names.
*/
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
public:
SpaceChildSortFilterModel(QObject *parent = nullptr);
void setFilterText(const QString &filterText);
[[nodiscard]] QString filterText() const;
protected:
/**
* @brief Returns true if the value of source_left is less than source_right.
*
* @sa QSortFilterProxyModel::lessThan
*/
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
/**
* @brief Custom filter function checking if an event type has been filtered out.
*
* The filter rejects a row if the room is known been replaced or if a search
* string is set it will only return rooms that match.
*/
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
Q_SIGNALS:
void filterTextChanged();
private:
QString m_filterText;
};

View File

@@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "spacetreeitem.h"
#include "controller.h"
SpaceTreeItem::SpaceTreeItem(SpaceTreeItem *parent,
const QString &id,
const QString &name,
const QString &canonicalAlias,
const QString &topic,
int memberCount,
const QUrl &avatarUrl,
bool allowGuests,
bool worldReadable,
bool isSpace)
: m_parentItem(parent)
, m_id(id)
, m_name(name)
, m_canonicalAlias(canonicalAlias)
, m_topic(topic)
, m_memberCount(memberCount)
, m_avatarUrl(avatarUrl)
, m_allowGuests(allowGuests)
, m_worldReadable(worldReadable)
, m_isSpace(isSpace)
{
}
SpaceTreeItem::~SpaceTreeItem()
{
qDeleteAll(m_children);
}
SpaceTreeItem *SpaceTreeItem::child(int number)
{
if (number < 0 || number >= m_children.size()) {
return nullptr;
}
return m_children[number];
}
int SpaceTreeItem::childCount() const
{
return m_children.count();
}
bool SpaceTreeItem::insertChild(int row, SpaceTreeItem *newChild)
{
if (row < 0 || row > m_children.size()) {
return false;
}
m_children.insert(row, newChild);
return true;
}
bool SpaceTreeItem::removeChild(int row)
{
if (row < 0 || row >= m_children.size()) {
return false;
}
delete m_children.takeAt(row);
return true;
}
int SpaceTreeItem::row() const
{
if (m_parentItem) {
return m_parentItem->m_children.indexOf(const_cast<SpaceTreeItem *>(this));
}
return 0;
}
SpaceTreeItem *SpaceTreeItem::parentItem()
{
return m_parentItem;
}
QString SpaceTreeItem::id() const
{
return m_id;
}
QString SpaceTreeItem::name() const
{
return m_name;
}
QString SpaceTreeItem::canonicalAlias() const
{
return m_canonicalAlias;
}
QString SpaceTreeItem::topic() const
{
return m_topic;
}
int SpaceTreeItem::memberCount() const
{
return m_memberCount;
}
QUrl SpaceTreeItem::avatarUrl() const
{
if (m_avatarUrl.isEmpty() || m_avatarUrl.scheme() != QLatin1String("mxc")) {
return {};
}
auto connection = Controller::instance().activeConnection();
auto url = connection->makeMediaUrl(m_avatarUrl);
if (url.scheme() == QLatin1String("mxc")) {
return url;
}
return {};
}
bool SpaceTreeItem::allowGuests() const
{
return m_allowGuests;
}
bool SpaceTreeItem::worldReadable() const
{
return m_worldReadable;
}
bool SpaceTreeItem::isJoined() const
{
auto connection = Controller::instance().activeConnection();
if (!connection) {
return false;
}
return connection->room(id(), Quotient::JoinState::Join) != nullptr;
}
bool SpaceTreeItem::isSpace() const
{
return m_isSpace;
}

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