Compare commits

..

90 Commits

Author SHA1 Message Date
l10n daemon script
bc0d1aff35 GIT_SILENT Sync po/docbooks with svn 2026-01-31 03:19:28 +00:00
l10n daemon script
bc594ec8a2 GIT_SILENT Sync po/docbooks with svn 2026-01-30 03:48:53 +00:00
l10n daemon script
2a7f0b406c GIT_SILENT Sync po/docbooks with svn 2026-01-29 03:51:50 +00:00
l10n daemon script
c9f685a733 GIT_SILENT Sync po/docbooks with svn 2026-01-28 03:25:54 +00:00
Joshua Goins
5be43575fc Remove single tap to maximize code component
This is just more ergonomic (in my opinion) as you usually want to
select some text from a code block, instead of maximizing it. There's
already an easy-to-access button for maximizing if you want to.

BUG: 499048
FIXED-IN: 25.12.2
(cherry picked from commit 332a822996)
2026-01-27 18:08:09 +01:00
Joshua Goins
558519f355 Fix transparency blur not applying to the timeline anymore
We still had the default opaque background for RoomPage. I added a
comment too so it isn't removed accidentally in the future.

BUG: 513363
FIXED-IN: 25.12.2
(cherry picked from commit f145bbe8db)
2026-01-27 18:08:09 +01:00
l10n daemon script
f83e2e2677 GIT_SILENT Sync po/docbooks with svn 2026-01-27 03:20:43 +00:00
Albert Astals Cid
4c4f406c41 GIT_SILENT Update Appstream for new release 2026-01-27 02:13:34 +01:00
Albert Astals Cid
2f8a873202 GIT_SILENT Upgrade release service version to 25.12.2. 2026-01-27 01:14:18 +01:00
l10n daemon script
78b3cfe916 GIT_SILENT made messages (after extraction) 2026-01-25 02:36:47 +00:00
l10n daemon script
0bc529da2d GIT_SILENT Sync po/docbooks with svn 2026-01-24 03:21:02 +00:00
l10n daemon script
6ad6121dfa GIT_SILENT Sync po/docbooks with svn 2026-01-23 03:24:22 +00:00
l10n daemon script
7b5b7a67ae GIT_SILENT Sync po/docbooks with svn 2026-01-20 03:47:09 +00:00
l10n daemon script
59c8b82bc2 GIT_SILENT Sync po/docbooks with svn 2026-01-19 03:23:08 +00:00
l10n daemon script
c6aec89b61 GIT_SILENT Sync po/docbooks with svn 2026-01-18 03:41:28 +00:00
Joshua Goins
2ef3fd9d6c Add hack to fix crash when sending long text reactions
This is some bug in Flow (that is really hard to debug, I can't get it
to exit at all) but we can work around it for a minor visual impact. It
seems to me allow the reaction list to become slightly larger, but
that's about it.

BUG: 504344
FIXED-IN: 25.12.2
(cherry picked from commit 93e932c09c)
2026-01-17 15:08:11 -05:00
Joshua Goins
e64b6033f3 Cleanup few remaining atYEnd usages in TimelineView
These were either mistakes or rebase errors, but we should be using
closeToYEnd here.

(cherry picked from commit 3b8930c2bc)
2026-01-17 15:08:11 -05:00
Joshua Goins
82989e7ef2 Add hack to fix room sidebar not sticking to the top
This is similar to the TimelineView hacks, but this time its the header
item that's changing height as our room topics and such wrap.

(cherry picked from commit 6eb2b2e739)
2026-01-17 15:08:11 -05:00
Joshua Goins
ddf272ab2b Fix left padding for "Rooms" label in the room list
This now emulates the default Kirigami heading behavior now, with the
correct amount of padding.

(cherry picked from commit be89362fdd)
2026-01-17 15:08:11 -05:00
Joshua Goins
4d4b44e011 Fix Quick Switcher not being activatable by Enter/Return key
(cherry picked from commit e53c84d30c)
2026-01-17 15:08:11 -05:00
Joshua Goins
a17d7e18fd Don't show the Share action for non-file messages
This only shares files, if you try it on anything else it crashes
NeoChat.

(cherry picked from commit a90c26f566)
2026-01-17 15:08:10 -05:00
Joshua Goins
1e78f119f1 Add hack around the timeline never settling just right
This is due to some kind of bug in ListView that never resettles
properly for bottom-to-top views. This can arise when the pinned message
is loaded (because that squishes the view) or the window is resized
(because that also resizes the view.)

We can work around it by assuming the following:
1. The RoomPage knows the window is resizing because it gets its height
changed before TimelineView.
2. The first height change can be a marker to position the view at the
beginning.

This fixes the issue for me, I did the following in order to test this:
* Switch between many rooms, especially ones with a pinned message. Now
all of them start at the bottom as they should.
* Resize the window, ensure that if you scrolled it stays around that
position - otherwise it sticks at the bottom.

(cherry picked from commit 39de4d10e4)
2026-01-17 10:22:10 -05:00
Joshua Goins
b6a4b67c22 Improve reliability of restoring the last space and room (again)
I found that 50% of the time, NeoChat won't restore the last space but
instead get stuck at Home. Even worse, it will overwrite Home's last
opened room with the one from the space - resulting in really buggy
behavior.

The reason why this happens is partly due to the space hierarchy cache
(I think) but that's not the real problem in my opinion. During
setCurrentSpace, we needlessly update the last space & room config
despite us being the ones already reading it.

In addition to that I also refactored this code a bit to be more
consolidated and readable.

(cherry picked from commit 4c37dcf518)
2026-01-17 10:22:10 -05:00
Joshua Goins
e965e1680f Add hack around atYEnd
This fixes the annoying "I just scrolled down to the bottom, how come
NeoChat doesn't think I did?"

From what I can tell this is also ListView bug (or something caused by
our style/Kirigami) that creates cases like contentY being -643.2 (for a
ListView of height 643) thus that's not "at Y's end". For our case
though, we don't care and can safely round it.

(cherry picked from commit 3c77711417)
2026-01-17 10:22:10 -05:00
Nate Graham
7d4cc7a5cf Tell room page header message to fill the width
Otherwise it scrunches up as small as possible and breaks the layout.

(cherry picked from commit c50380b448)
2026-01-17 10:21:47 -05:00
l10n daemon script
112b4b54e5 GIT_SILENT Sync po/docbooks with svn 2026-01-17 03:20:03 +00:00
l10n daemon script
782f096f21 GIT_SILENT Sync po/docbooks with svn 2026-01-16 03:46:52 +00:00
l10n daemon script
57e0f04086 GIT_SILENT Sync po/docbooks with svn 2026-01-15 03:45:05 +00:00
l10n daemon script
a639011db6 GIT_SILENT Sync po/docbooks with svn 2026-01-14 03:19:41 +00:00
Joshua Goins
26e7f3780c Small notification improvements
Changed a check to use isDirectChat (which is a clearer indication of
what we want.) I also made sure not to show the account name if you only
have one, since that's just useless noise.

(cherry picked from commit fc6f345036)
2026-01-13 17:55:41 -05:00
Lorenz Wildberg
8c96d05799 fix bug: room settings don't open
(cherry picked from commit 2ec1fa92fa)
2026-01-13 17:55:04 -05:00
Veres Károly
bc6e22bc6d Null check decoded messages in loadPinnedMessage
`decryptMessage` returns null if it fails to decode the passed message. This value
then got fed into `EventHandler::richBody` which logged a warning and cleared `m_pinnedMessage`.
If we instead retain the value as an `EncryptedEvent`, the UI will pin the encrypted event
placeholder instead of hiding the existence of pins.

(cherry picked from commit 71c84be4b4)
2026-01-13 17:55:04 -05:00
Veres Károly
4371c3f7e5 Null check pinned messages after decryption when filling PinnedMessageModel.
`room()->decryptMessage()` returns null if the message fails to decode. Since elements of `m_pinnedEvents`
get directly dereferenced in getEventForIndex, storing null values leads to a segfault.
In this case we should retain the EncryptedEvent to let the UI report the error.

(cherry picked from commit b684fb451d)
2026-01-13 17:55:04 -05:00
Kai Uwe Broulik
b88ee65a4c RoomPage: Fix selected text and hovered link in context menu
They were not forwarded to the menu.
Also, "isThread" argument is no longer there in the signal.

(cherry picked from commit 7a2211f8e0)
2026-01-13 17:54:55 -05:00
Azhar Momin
033e865a27 Fix some runtime qml warnings
(cherry picked from commit 4155e9116a)
2026-01-13 17:54:33 -05:00
Azhar Momin
75ba46e292 Fix pushDialogLayer failing in DelegateContextMenu
(cherry picked from commit a989ef42b2)
2026-01-13 17:54:33 -05:00
Joshua Goins
d492ed038a Decrypt when downloading single events from the server
This fixes issues like not being able to view pinned messages in
encrypted rooms.

(cherry picked from commit 89e42dbc53)
2026-01-13 17:54:33 -05:00
Joshua Goins
b29108a2f7 Fix reply colors being broken if you're faster than the server
This is that bug that causes reply colors to be white, and this error to
print in the log:

qrc:/qt/qml/org/kde/neochat/messagecontent/ReplyComponent.qml:41: TypeError: Cannot read property 'color' of null

The reason why this happens is inside of EventMessageContentModel, it
needs to be able to find the relevant event in the room to fetch the
room member (and then their color.) Dependent on many variables to
align, this can happen easily if you are faster than your server giving
you said events.

But this is an easy fix, we obviously get the event afterwards and just
need to re-evaluate the the author property. I also made sure it falls
back to some color instead of white, which will also quiet the error.

(cherry picked from commit b45967508c)
2026-01-13 17:54:33 -05:00
Joshua Goins
2790d430ae Make clicking room list section headers more reliable
ListSectionHeader itself is an ItemDelegate, which eats up input events.
We can work around this by also listening to onClicked there too.

(cherry picked from commit 3a416990ca)
2026-01-13 17:54:33 -05:00
l10n daemon script
23f61fff36 GIT_SILENT Sync po/docbooks with svn 2026-01-13 11:40:36 +00:00
l10n daemon script
254d105e35 GIT_SILENT Sync po/docbooks with svn 2026-01-12 03:47:38 +00:00
l10n daemon script
6dde57a786 GIT_SILENT Sync po/docbooks with svn 2026-01-10 03:43:02 +00:00
l10n daemon script
d803fcb874 GIT_SILENT Sync po/docbooks with svn 2026-01-09 03:19:23 +00:00
l10n daemon script
90e70a9295 GIT_SILENT Sync po/docbooks with svn 2026-01-08 03:21:39 +00:00
l10n daemon script
2090e4dc0e GIT_SILENT Sync po/docbooks with svn 2026-01-06 03:41:42 +00:00
l10n daemon script
5ced491d54 GIT_SILENT Sync po/docbooks with svn 2026-01-05 03:42:28 +00:00
Azhar Momin
e156d4da90 Fix notification count refresh for low-priority and mentions-only rooms
(cherry picked from commit 5f7967363f)
2026-01-04 17:13:27 -05:00
Joshua Goins
b3aa2abd89 Fix icons on Windows
KirigamiApp currently calls KIconTheme::initTheme too late for Windows,
as a workaround we can go back to calling it ourselves.

(cherry picked from commit a02a04d966)
2026-01-04 17:13:27 -05:00
Veres Károly
7627d6d0e2 Extract the space selection logic from setCurrentRoom and use it for setting lastRoomConfig too.
If a setCurrentRoom call changed the active space at the end of its execution, the new room's ID ended up still being written to the old space's lastRoomConfig.

By extracting this space selection logic into a helper function, we can now calculate this value earlier and use it as the space id when writing lastRoomConfig.

(cherry picked from commit 68b00b9fc5)
2026-01-04 17:13:27 -05:00
Nate Graham
77da7e6c7d Improve hamburger menu button
- Open the menu right beneath the button
- Use pressed state for the button while the menu is open
- Close the menu when clicking the button again
- Hide the tooltip while the menu is open

(cherry picked from commit bd0588ca99)
2026-01-04 17:13:27 -05:00
l10n daemon script
1e3ce9d6cd GIT_SILENT Sync po/docbooks with svn 2026-01-04 03:38:17 +00:00
Heiko Becker
321561fd89 GIT_SILENT Update Appstream for new release 2026-01-03 12:14:39 +01:00
Heiko Becker
856bc7b713 GIT_SILENT Upgrade release service version to 25.12.1. 2026-01-03 11:15:22 +01:00
l10n daemon script
10e9b8d8f8 GIT_SILENT Sync po/docbooks with svn 2026-01-02 03:39:44 +00:00
l10n daemon script
9f1803c551 GIT_SILENT Sync po/docbooks with svn 2025-12-31 03:38:13 +00:00
l10n daemon script
9260c92026 GIT_SILENT Sync po/docbooks with svn 2025-12-30 03:42:39 +00:00
l10n daemon script
9c7030a5db GIT_SILENT Sync po/docbooks with svn 2025-12-29 03:13:12 +00:00
l10n daemon script
909e20889e GIT_SILENT Sync po/docbooks with svn 2025-12-27 03:11:47 +00:00
l10n daemon script
74f4c291a0 GIT_SILENT Sync po/docbooks with svn 2025-12-24 03:36:13 +00:00
Carl Schwan
242a248bf3 Send beautiful red ❤️'s when quick reacting
I wondered for a while (and could tell) when people were using NeoChat
because they would react with cold, monochrome hearts. Let's add more
color to our world!


(cherry picked from commit 531df7a3b2)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2025-12-21 19:59:56 +01:00
Carl Schwan
dfb0bb75f4 Fix missing escape sequence in /shrug command
Before the fix, the upper arm _ characters in the command's output would be parsed as Markdown italic formatting around the (ツ).


(cherry picked from commit 706f1f7836)

Co-authored-by: Veres Károly <mail+kde@karcsesz.hu>
2025-12-21 18:07:46 +01:00
l10n daemon script
3cefd4b1ef GIT_SILENT Sync po/docbooks with svn 2025-12-14 03:13:17 +00:00
l10n daemon script
3f3ce6b421 GIT_SILENT Sync po/docbooks with svn 2025-12-13 03:17:45 +00:00
l10n daemon script
73d910421a GIT_SILENT Sync po/docbooks with svn 2025-12-10 03:32:09 +00:00
l10n daemon script
1da44f3ae3 GIT_SILENT Sync po/docbooks with svn 2025-12-09 03:15:27 +00:00
l10n daemon script
08836010c6 GIT_SILENT Sync po/docbooks with svn 2025-12-07 03:22:57 +00:00
l10n daemon script
13042d9ba6 GIT_SILENT Sync po/docbooks with svn 2025-12-04 03:20:39 +00:00
Heiko Becker
0e4b52ee62 GIT_SILENT Update Appstream for new release 2025-12-04 00:19:55 +01:00
Heiko Becker
4c32280343 GIT_SILENT Upgrade release service version to 25.12.0. 2025-12-03 23:04:35 +01:00
l10n daemon script
a2e540d6ef GIT_SILENT Sync po/docbooks with svn 2025-12-03 03:26:04 +00:00
Carl Schwan
573c8925d2 Fix krunner integration with Flatpak
(cherry picked from commit 1da24191f0)

Co-authored-by: renner 03 <renner0@posteo.de>
2025-12-02 23:00:25 +01:00
l10n daemon script
6c0bd850b0 GIT_SILENT Sync po/docbooks with svn 2025-12-01 03:28:35 +00:00
l10n daemon script
1da9719314 GIT_SILENT Sync po/docbooks with svn 2025-11-25 03:15:21 +00:00
Heiko Becker
3cd5f3a1c6 Drop unused dependencies
Both KF6Crash and KF6IconThemes aren't used anymore after porting to
KirigamiApp in eab45e761a.

(cherry picked from commit 0cb3fd32f4)
2025-11-24 21:56:55 +01:00
Albert Astals Cid
b4108f2eef GIT_SILENT Upgrade release service version to 25.11.90. 2025-11-24 00:57:39 +01:00
l10n daemon script
bb7de18341 GIT_SILENT Sync po/docbooks with svn 2025-11-23 03:19:33 +00:00
l10n daemon script
7b8328fce6 GIT_SILENT Sync po/docbooks with svn 2025-11-21 03:20:12 +00:00
l10n daemon script
aeee6570c0 GIT_SILENT Sync po/docbooks with svn 2025-11-20 03:14:24 +00:00
l10n daemon script
780b9a6f9b GIT_SILENT Sync po/docbooks with svn 2025-11-17 15:49:57 +00:00
l10n daemon script
0ba06882d1 GIT_SILENT Sync po/docbooks with svn 2025-11-17 03:16:16 +00:00
l10n daemon script
ce131a53e5 GIT_SILENT Sync po/docbooks with svn 2025-11-16 03:16:40 +00:00
l10n daemon script
e9421e28dd SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-11-16 03:13:00 +00:00
l10n daemon script
1c64a6b5f0 GIT_SILENT Sync po/docbooks with svn 2025-11-15 03:19:51 +00:00
l10n daemon script
9b5200c344 GIT_SILENT Sync po/docbooks with svn 2025-11-14 03:21:54 +00:00
l10n daemon script
5baf4ab823 GIT_SILENT Sync po/docbooks with svn 2025-11-13 03:20:11 +00:00
l10n daemon script
58a72a08f2 GIT_SILENT Sync po/docbooks with svn 2025-11-11 03:37:16 +00:00
l10n daemon script
c9b97d4d0d GIT_SILENT Sync po/docbooks with svn 2025-11-10 03:26:49 +00:00
Carl Schwan
2a7d61c73b Improve invited room counting
I didn't realize when redoing the tooltip for DMs that directChatInvites
was a boolean, not an integer type. Now it's changed to an integer type,
which fixes the DM invite count.

NeoChat apparently didn't count normal room invites until now either, so
now Home is highlighted in that case. Now it should be harder to miss
these kinds of invites.


(cherry picked from commit 887865c0aa)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2025-11-09 22:23:09 +01:00
l10n daemon script
f55bd28e10 GIT_SILENT Sync po/docbooks with svn 2025-11-09 03:45:49 +00:00
Albert Astals Cid
2a67861099 GIT_SILENT Upgrade release service version to 25.11.80. 2025-11-06 17:40:37 +01:00
236 changed files with 40289 additions and 63583 deletions

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.10",
"runtime-version": "6.9",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -31,6 +31,19 @@
"/share/ndk-modules"
],
"modules": [
{
"name": "kirigamiaddons",
"config-opts": [
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
}
]
},
{
"name": "opencv",
"config-opts": [
@@ -65,7 +78,6 @@
"name": "olm",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"-DOLM_TESTS=OFF"
],
"sources": [
@@ -172,8 +184,8 @@
"sources": [
{
"type": "archive",
"url": "https://download.kde.org/stable/release-service/25.08.3/src/kunifiedpush-25.08.3.tar.xz",
"sha256": "e8c924438d5359f0fa0930ab35111012076e3a0ff4e959d6929595571383320a",
"url": "https://download.kde.org/stable/release-service/25.08.0/src/kunifiedpush-25.08.0.tar.xz",
"sha256": "846db6ffc7d93f6afea7ce0d5a9f10b52792157ceb593856542279f4197f3518",
"x-checker-data": {
"type": "anitya",
"project-id": 8763,

View File

@@ -43,4 +43,3 @@ Options:
per-test-timeout: 90
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
run-qmllint: True
enable-lsan: True

View File

@@ -7,9 +7,9 @@
cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "26")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "12")
set(RELEASE_SERVICE_VERSION_MICRO "2")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
@@ -46,6 +46,10 @@ if (NOT ANDROID)
include(KDEClangFormat)
endif()
if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)

View File

@@ -25,10 +25,15 @@ Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
## Features
NeoChat aims to be a fully featured application for the Matrix specification. As such, most parts of the current specification are supported, with the notable exceptions
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the Matrix spec constantly
NeoChat aims to be a fully featured application for the Matrix specification. As such most parts of the current specification are supported, with the notable exceptions
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the fact that the Matrix spec is constantly
evolving, but the aim remains to provide eventual support for the entire spec.
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
- Polls - MSC3381
- Sticker Packs - MSC2545
- Location Events - MSC3488
## Get it
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
@@ -43,12 +48,12 @@ The best way to build KDE apps during development is to use `kdesrc-build`. The
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
is primarily aimed at Linux development.
For Windows and Android, [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
## Running
Start the executable in your preferred way either from the build directory or from the installed location.
Just start the executable in your preferred way - either from the build directory or from the installed location.
## Tests
@@ -61,12 +66,12 @@ be complete.
![coverage](https://invent.kde.org/network/neochat/badges/master/pipeline.svg)
Currently, the number of tests is limited but growing. If anyone wants to help improve this, those
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
contributions would be especially welcome.
## Contributing
As is the case throughout the KDE ecosystem, contributions are welcome from all. The code base is managed in the
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
- [Code of Conduct](https://kde.org/code-of-conduct)
@@ -81,7 +86,7 @@ The best place to reach the maintainers is on the KDE Matrix instance in the Neo
## Acknowledgement
NeoChat uses [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
NeoChat utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
NeoChat is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).

View File

@@ -11,7 +11,7 @@ add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test(
neochatroomtest.cpp
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
LINK_LIBRARIES neochat Qt::Test
TEST_NAME neochatroomtest
)
@@ -41,7 +41,7 @@ ecm_add_test(
ecm_add_test(
chatbarcachetest.cpp
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatbarcachetest
)
@@ -104,9 +104,3 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME roommanagertest
)
ecm_add_test(
modeltest.cpp
LINK_LIBRARIES neochat Qt::Test neochat_server Devtools
TEST_NAME modeltest
)

View File

@@ -88,7 +88,7 @@ void ActionsTest::testActions()
QFETCH(std::optional<QString>, resultText);
QFETCH(std::optional<Quotient::RoomMessageEvent::MsgType>, type);
auto cache = new ChatBarCache(this);
auto cache = new ChatBarCache();
cache->setText(command);
auto result = ActionsModel::handleAction(room, cache);
QCOMPARE(resultText, std::get<std::optional<QString>>(result));

View File

@@ -11,13 +11,9 @@
#include <Quotient/syncdata.h>
#include <qtestcase.h>
#include <KLocalizedString>
#include "accountmanager.h"
#include "chatbarcache.h"
#include "neochatroom.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
@@ -28,9 +24,7 @@ class ChatBarCacheTest : public QObject
private:
Connection *connection = nullptr;
NeoChatRoom *room = nullptr;
Server server;
QString eventId;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
@@ -46,31 +40,8 @@ private Q_SLOTS:
void ChatBarCacheTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true, this);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
eventId = server.sendEvent(roomId,
u"m.room.message"_s,
QJsonObject{
{u"body"_s, u"foo"_s},
{u"msgtype"_s, u"m.text"_s},
});
QSignalSpy syncSpy(connection, &Connection::syncDone);
// We need to wait for two syncs, as the next one won't have the changes yet
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
server.joinUser(room->id(), u"@foo:server.com"_s);
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, "test-min-sync.json"_L1);
}
void ChatBarCacheTest::empty()
@@ -89,9 +60,8 @@ void ChatBarCacheTest::empty()
void ChatBarCacheTest::noRoom()
{
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
chatBarCache->setReplyId(eventId);
chatBarCache->setReplyId(u"$153456789:example.org"_s);
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
@@ -105,10 +75,9 @@ void ChatBarCacheTest::noRoom()
void ChatBarCacheTest::badParent()
{
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QScopedPointer<QObject> badParent(new QObject());
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
chatBarCache->setReplyId(eventId);
chatBarCache->setReplyId(u"$153456789:example.org"_s);
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
@@ -125,15 +94,15 @@ void ChatBarCacheTest::reply()
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s);
chatBarCache->setAttachmentPath(u"some/path"_s);
chatBarCache->setReplyId(eventId);
chatBarCache->setReplyId(u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), eventId);
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
QCOMPARE(chatBarCache->attachmentPath(), QString());
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
}
@@ -143,26 +112,22 @@ void ChatBarCacheTest::replyMissingUser()
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s);
chatBarCache->setAttachmentPath(u"some/path"_s);
chatBarCache->setReplyId(eventId);
chatBarCache->setReplyId(u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), eventId);
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
QCOMPARE(chatBarCache->attachmentPath(), QString());
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
QSignalSpy relationAuthorIsPresentSpy(chatBarCache.get(), &ChatBarCache::relationAuthorIsPresentChanged);
// sync again, which will simulate the reply user leaving the room
QSignalSpy syncSpy(connection, &Connection::syncDone);
server.sendStateEvent(room->id(), u"m.room.member"_s, u"@foo:server.com"_s, {{u"membership"_s, u"leave"_s}});
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room->syncNewEvents(u"test-min-sync-extra-sync.json"_s);
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
@@ -174,19 +139,19 @@ void ChatBarCacheTest::edit()
chatBarCache->setText(u"some text"_s);
chatBarCache->setAttachmentPath(u"some/path"_s);
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
QCOMPARE(oldEventId, QString());
QCOMPARE(newEventId, eventId);
QCOMPARE(newEventId, QString(u"$153456789:example.org"_s));
});
chatBarCache->setEditId(eventId);
chatBarCache->setEditId(u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), eventId);
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
QCOMPARE(chatBarCache->editId(), u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
@@ -194,7 +159,7 @@ void ChatBarCacheTest::attachment()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s);
chatBarCache->setEditId(eventId);
chatBarCache->setEditId(u"$153456789:example.org"_s);
chatBarCache->setAttachmentPath(u"some/path"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);

View File

@@ -0,0 +1,20 @@
{
"state": {
"events": [
{
"content": {
"membership": "leave"
},
"event_id": "$1432735824666PhrSA:example.org",
"origin_server_ts": 1432735824666,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$143273582443PhrSn:example.org"
}
}
]
}
}

View File

@@ -40,6 +40,7 @@ private Q_SLOTS:
void nullSingleLineDisplayName();
void time();
void nullTime();
void timeString();
void highlighted();
void nullHighlighted();
void hidden();
@@ -99,12 +100,12 @@ void EventHandlerTest::time()
{
const auto event = room->messageEvents().at(0).get();
QCOMPARE(EventHandler::dateTime(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
QCOMPARE(EventHandler::time(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);
const auto pendingIt = room->findPendingEvent(txID);
QCOMPARE(EventHandler::dateTime(room, pendingIt->event(), true), pendingIt->lastUpdated());
QCOMPARE(EventHandler::time(room, pendingIt->event(), true), pendingIt->lastUpdated());
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
@@ -113,10 +114,40 @@ void EventHandlerTest::time()
void EventHandlerTest::nullTime()
{
QTest::ignoreMessage(QtWarningMsg, "time called with room set to nullptr.");
QCOMPARE(EventHandler::dateTime(nullptr, nullptr), QDateTime());
QCOMPARE(EventHandler::time(nullptr, nullptr), QDateTime());
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
QCOMPARE(EventHandler::dateTime(room, nullptr), QDateTime());
QCOMPARE(EventHandler::time(room, nullptr), QDateTime());
}
void EventHandlerTest::timeString()
{
const auto event = room->messageEvents().at(0).get();
KFormat format;
QCOMPARE(EventHandler::timeString(room, event, false),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);
const auto pendingIt = room->findPendingEvent(txID);
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::ShortFormat, true),
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::ShortFormat, true),
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::LongFormat, true),
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::LongFormat, true),
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::LongFormat));
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
}
void EventHandlerTest::highlighted()

View File

@@ -19,7 +19,13 @@ class LinkPreviewerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
@@ -30,6 +36,12 @@ private Q_SLOTS:
void linkPreviewsReject();
};
void LinkPreviewerTest::initTestCase()
{
connection = Connection::makeMockConnection(u"@bob:example.org"_s);
room = new TestUtils::TestRoom(connection, u"!test:example.org"_s);
}
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("inputString");

View File

@@ -1,620 +0,0 @@
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QAbstractItemModelTester>
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <QVariantList>
#include <Quotient/connection.h>
#include "accountmanager.h"
#include "contentprovider.h"
#include "enums/powerlevel.h"
#include "enums/roomsortparameter.h"
#include "models/accountemoticonmodel.h"
#include "models/actionsmodel.h"
#include "models/commonroomsmodel.h"
#include "models/completionmodel.h"
#include "models/completionproxymodel.h"
#include "models/customemojimodel.h"
#include "models/devicesmodel.h"
#include "models/devicesproxymodel.h"
#include "models/emojimodel.h"
#include "models/emoticonfiltermodel.h"
#include "models/eventmessagecontentmodel.h"
#include "models/imagepacksmodel.h"
#include "models/linemodel.h"
#include "models/livelocationsmodel.h"
#include "models/locationsmodel.h"
#include "models/messagecontentfiltermodel.h"
#include "models/notificationsmodel.h"
#include "models/permissionsmodel.h"
#include "models/pinnedmessagemodel.h"
#include "models/pollanswermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/pushrulemodel.h"
#include "models/readmarkermodel.h"
#include "models/roomsortparametermodel.h"
#include "models/searchmodel.h"
#include "models/serverlistmodel.h"
#include "models/spacechildrenmodel.h"
#include "models/spacechildsortfiltermodel.h"
#include "models/statefiltermodel.h"
#include "models/statekeysmodel.h"
#include "models/statemodel.h"
#include "models/stickermodel.h"
#include "models/threadmodel.h"
#include "models/threepidmodel.h"
#include "models/userdirectorylistmodel.h"
#include "models/userfiltermodel.h"
#include "models/webshortcutmodel.h"
#include "neochatroom.h"
#include "pollhandler.h"
#include "roommanager.h"
#include "server.h"
using namespace Quotient;
// TODO: Add data to all models as relevant.
// Performs basic tests on all models in NeoChat
// When adding a new test, create the model first, then the tester, then initialize the model (e.g., setConnection and setRoom).
// That way, the models are also tested for whether they can handle having no connection etc.
class ModelTest : public QObject
{
Q_OBJECT
private:
NeoChatConnection *connection = nullptr;
NeoChatRoom *room = nullptr;
QString eventId;
Server server;
private Q_SLOTS:
void initTestCase();
void testRoomTreeModel();
void testMessageContentModel();
void testEventMessageContentModel();
void testThreadModel();
void testThreadFetchModel();
void testThreadChatBarModel();
void testReactionModel();
void testPollAnswerModel();
void testLineModel();
void testSpaceChildrenModel();
void testItineraryModel();
void testPublicRoomListModel();
void testMessageFilterModel();
void testThreePIdModel();
void testMediaMessageFilterModel();
void testWebshortcutModel();
void testTimelineMessageModel();
void testReadMarkerModel();
void testSearchModel();
void testStateModel();
void testTimelineModel();
void testStateKeysModel();
void testPinnedMessageModel();
void testUserListModel();
void testStickerModel();
void testPowerLevelModel();
void testImagePacksModel();
void testCompletionModel();
void testRoomListModel();
void testCommonRoomsModel();
void testNotificationsModel();
void testLocationsModel();
void testServerListModel();
void testEmojiModel();
void testCustomEmojiModel();
void testPushRuleModel();
void testActionsModel();
void testDevicesModel();
void testUserDirectoryListModel();
void testAccountEmoticonModel();
void testPermissionsModel();
void testLiveLocationsModel();
void testRoomSortParameterModel();
void testSortFilterRoomTreeModel();
void testSortFilterSpaceListModel();
void testSortFilterRoomListModel();
void testSpaceChildSortFilterModel();
void testStateFilterModel();
void testMessageContentFilterModel();
void testUserFilterModel();
void testEmoticonFilterModel();
void testDevicesProxyModel();
void testCompletionProxyModel();
};
void ModelTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true, this);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
eventId = server.sendEvent(roomId,
u"m.room.message"_s,
QJsonObject{
{u"body"_s, u"foo"_s},
{u"msgtype"_s, u"m.text"_s},
});
server.sendEvent(roomId,
u"m.room.message"_s,
QJsonObject{
{u"body"_s, u"asdf"_s},
{u"m.relates_to"_s,
QJsonObject{
{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s},
{u"is_falling_back"_s, true},
{u"m.in_reply_to"_s, QJsonObject{{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s}}},
{u"rel_type"_s, u"m.thread"_s},
}},
{u"msgtype"_s, u"m.text"_s},
});
QSignalSpy syncSpy(connection, &Connection::syncDone);
// We need to wait for two syncs, as the next one won't have the changes yet
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
}
void ModelTest::testRoomTreeModel()
{
auto roomTreeModel = new RoomTreeModel(this);
auto tester = new QAbstractItemModelTester(roomTreeModel, roomTreeModel);
tester->setUseFetchMore(true);
roomTreeModel->setConnection(connection);
}
void ModelTest::testMessageContentModel()
{
auto contentModel = std::make_unique<MessageContentModel>(room, nullptr, eventId);
auto tester = new QAbstractItemModelTester(contentModel.get(), contentModel.get());
tester->setUseFetchMore(true);
}
void ModelTest::testEventMessageContentModel()
{
auto model = std::make_unique<EventMessageContentModel>(room, eventId);
auto tester = new QAbstractItemModelTester(model.get(), model.get());
tester->setUseFetchMore(true);
}
void ModelTest::testThreadModel()
{
auto model = new ThreadModel(eventId, room);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testThreadFetchModel()
{
auto model = new ThreadFetchModel(new ThreadModel(eventId, room));
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testThreadChatBarModel()
{
auto model = new ThreadChatBarModel(new ThreadModel(eventId, room), room);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testReactionModel()
{
auto messageContentModel = std::make_unique<MessageContentModel>(room);
auto model = new ReactionModel(messageContentModel.get(), eventId, room);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testPollAnswerModel()
{
auto handler = std::make_unique<PollHandler>(room, eventId);
auto model = new PollAnswerModel(handler.get());
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testLineModel()
{
auto model = new LineModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto document = new QTextDocument(this);
model->setDocument(document);
document->setPlainText(u"foo\nbar\n\nbaz"_s);
}
void ModelTest::testSpaceChildrenModel()
{
auto model = new SpaceChildrenModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setSpace(room);
}
void ModelTest::testItineraryModel()
{
auto model = new ItineraryModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testPublicRoomListModel()
{
auto model = new PublicRoomListModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testMessageFilterModel()
{
auto timelineModel = new TimelineModel(this);
auto model = new MessageFilterModel(this, timelineModel);
auto tester = new QAbstractItemModelTester(model, model);
timelineModel->setRoom(room);
tester->setUseFetchMore(true);
}
void ModelTest::testThreePIdModel()
{
auto model = new ThreePIdModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testMediaMessageFilterModel()
{
auto timelineModel = new TimelineModel(this);
auto messageFilterModel = new MessageFilterModel(this, timelineModel);
auto model = new MediaMessageFilterModel(this, messageFilterModel);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
timelineModel->setRoom(room);
}
void ModelTest::testWebshortcutModel()
{
auto model = new WebShortcutModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setSelectedText(u"Foo"_s);
}
void ModelTest::testTimelineMessageModel()
{
auto model = new TimelineMessageModel();
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testReadMarkerModel()
{
auto model = std::make_unique<ReadMarkerModel>(eventId, room);
auto tester = new QAbstractItemModelTester(model.get(), model.get());
tester->setUseFetchMore(true);
}
void ModelTest::testSearchModel()
{
auto model = new SearchModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setSearchText(u"foo"_s);
model->setRoom(room);
}
void ModelTest::testStateModel()
{
auto model = new StateModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testTimelineModel()
{
auto model = new TimelineModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testStateKeysModel()
{
auto model = new StateKeysModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setEventType(u"m.room.member"_s);
model->setRoom(room);
}
void ModelTest::testPinnedMessageModel()
{
auto model = new PinnedMessageModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testUserListModel()
{
auto model = new UserListModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testStickerModel()
{
auto model = new StickerModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setPackIndex(0);
model->setRoom(room);
auto imagePacksModel = new ImagePacksModel(this);
model->setModel(imagePacksModel);
imagePacksModel->setRoom(room);
imagePacksModel->setShowEmoticons(true);
imagePacksModel->setShowStickers(true);
}
void ModelTest::testPowerLevelModel()
{
auto model = new PowerLevelModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testImagePacksModel()
{
auto model = new ImagePacksModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
model->setShowEmoticons(true);
model->setShowStickers(true);
}
void ModelTest::testCompletionModel()
{
auto model = new CompletionModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
model->setAutoCompletionType(CompletionModel::Room);
model->setText(u"foo"_s, u"#foo"_s);
auto roomListModel = new RoomListModel(this);
roomListModel->setConnection(connection);
model->setRoomListModel(roomListModel);
}
void ModelTest::testRoomListModel()
{
auto model = new RoomListModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testCommonRoomsModel()
{
auto model = new CommonRoomsModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
model->setUserId(u"@user:example.com"_s);
}
void ModelTest::testNotificationsModel()
{
auto model = new NotificationsModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testLocationsModel()
{
auto model = new LocationsModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testServerListModel()
{
auto model = new ServerListModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testEmojiModel()
{
auto tester = new QAbstractItemModelTester(&EmojiModel::instance(), &EmojiModel::instance());
tester->setUseFetchMore(true);
}
void ModelTest::testCustomEmojiModel()
{
auto tester = new QAbstractItemModelTester(&CustomEmojiModel::instance(), &CustomEmojiModel::instance());
tester->setUseFetchMore(true);
CustomEmojiModel::instance().setConnection(connection);
}
void ModelTest::testPushRuleModel()
{
auto model = new PushRuleModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testActionsModel()
{
auto tester = new QAbstractItemModelTester(&ActionsModel::instance(), &ActionsModel::instance());
tester->setUseFetchMore(true);
}
void ModelTest::testDevicesModel()
{
auto model = new DevicesModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testUserDirectoryListModel()
{
auto model = new UserDirectoryListModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
model->setSearchText(u"foo"_s);
}
void ModelTest::testAccountEmoticonModel()
{
auto model = new AccountEmoticonModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setConnection(connection);
}
void ModelTest::testPermissionsModel()
{
auto model = new PermissionsModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testLiveLocationsModel()
{
auto model = new LiveLocationsModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setRoom(room);
}
void ModelTest::testRoomSortParameterModel()
{
auto model = new RoomSortParameterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
}
void ModelTest::testSortFilterRoomTreeModel()
{
auto sourceModel = new RoomTreeModel(this);
auto model = new SortFilterRoomTreeModel(sourceModel, sourceModel);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
sourceModel->setConnection(connection);
}
void ModelTest::testSortFilterSpaceListModel()
{
auto sourceModel = new RoomListModel(this);
auto model = new SortFilterSpaceListModel(sourceModel, sourceModel);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
sourceModel->setConnection(connection);
}
void ModelTest::testSortFilterRoomListModel()
{
auto sourceModel = new RoomListModel(this);
auto model = new SortFilterRoomListModel(sourceModel, sourceModel);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
sourceModel->setConnection(connection);
}
void ModelTest::testSpaceChildSortFilterModel()
{
auto model = new SpaceChildSortFilterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto spaceChildrenModel = new SpaceChildrenModel(this);
model->setSourceModel(spaceChildrenModel);
spaceChildrenModel->setSpace(nullptr);
}
void ModelTest::testStateFilterModel()
{
auto model = new StateFilterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto stateModel = new StateModel(this);
model->setSourceModel(stateModel);
stateModel->setRoom(room);
}
void ModelTest::testMessageContentFilterModel()
{
auto model = new MessageContentFilterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setSourceModel(ContentProvider::self().contentModelForEvent(room, eventId));
}
void ModelTest::testUserFilterModel()
{
auto model = new UserFilterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto userListModel = new UserListModel(this);
model->setSourceModel(userListModel);
userListModel->setRoom(room);
}
void ModelTest::testEmoticonFilterModel()
{
auto model = new EmoticonFilterModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto accountEmoticonModel = new AccountEmoticonModel(this);
model->setSourceModel(accountEmoticonModel);
model->setShowEmojis(true);
model->setShowStickers(true);
accountEmoticonModel->setConnection(connection);
}
void ModelTest::testDevicesProxyModel()
{
auto model = new DevicesProxyModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
auto devicesModel = new DevicesModel(this);
model->setSourceModel(devicesModel);
devicesModel->setConnection(dynamic_cast<NeoChatConnection *>(connection));
}
void ModelTest::testCompletionProxyModel()
{
auto model = new CompletionProxyModel(this);
auto tester = new QAbstractItemModelTester(model, model);
tester->setUseFetchMore(true);
model->setSourceModel(&EmojiModel::instance());
}
QTEST_MAIN(ModelTest)
#include "modeltest.moc"

View File

@@ -9,10 +9,6 @@
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include <KLocalizedString>
#include "accountmanager.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
@@ -22,8 +18,7 @@ class NeoChatRoomTest : public QObject {
private:
Connection *connection = nullptr;
NeoChatRoom *room = nullptr;
Server server;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
@@ -32,27 +27,8 @@ private Q_SLOTS:
void NeoChatRoomTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true, this);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
server.sendEvent(roomId,
u"m.room.message"_s,
QJsonObject{
{u"body"_s, u"foo"_s},
{u"msgtype"_s, u"m.text"_s},
});
QSignalSpy syncSpy(connection, &Connection::syncDone);
// We need to wait for two syncs, as the next one won't have the changes yet
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
}
void NeoChatRoomTest::eventTest()

View File

@@ -127,7 +127,7 @@ void Server::start()
qFatal() << "Server failed to listen on a port.";
return;
} else {
qInfo() << "Server listening";
qWarning() << "Server listening";
}
}
@@ -203,25 +203,6 @@ QString Server::sendEvent(const QString &roomId, const QString &eventType, const
return eventId;
}
QString Server::sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content)
{
Changes changes;
const auto eventId = generateEventId();
const auto json = QJsonObject{{u"type"_s, eventType},
{u"content"_s, content},
{u"sender"_s, u"@foo:server.com"_s},
{u"event_id"_s, eventId},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"state_key"_s, stateKey}};
changes.events += Changes::Event{
.fullJson = json,
};
changes.stateEvents += Changes::Event{.fullJson = json};
m_state += changes;
return eventId;
}
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
{
QJsonObject joinRooms;
@@ -353,18 +334,6 @@ void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &respo
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &state : change.stateEvents) {
const auto &roomId = state.fullJson[u"room_id"_s].toString();
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
auto stateEvents = joinRooms[roomId][u"state"_s][u"events"_s].toArray();
stateEvents.append(state.fullJson);
auto room = joinRooms[roomId].toObject();
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
joinRooms[roomId] = room;
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &event : change.events) {
// TODO the room might be in a different join state.
@@ -397,5 +366,6 @@ void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &respo
syncData[u"rooms"_s] = rooms;
}
qWarning() << syncData;
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
}

View File

@@ -35,7 +35,6 @@ struct Changes {
QJsonObject fullJson;
};
QList<Event> events;
QList<Event> stateEvents;
};
struct RoomData {
@@ -68,7 +67,6 @@ public:
*/
QString createServerNoticesRoom(const QString &matrixId);
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
QString sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content);
private:
QHttpServer m_server;

View File

@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
model->setRoom(room);
QCOMPARE(model->indexForEventId(u"$153456789:example.org"_s).row(), 0);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
}
void TimelineMessageModelTest::cleanup()

14
cmake/Flatpak.cmake Normal file
View File

@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
# SPDX-License-Identifier: BSD-2-Clause
include(GNUInstallDirs)
# Include FontConfig config which uses the Emoji One font from the
# KDE Flatpak SDK.
install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
DESTINATION
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
)

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -322,6 +322,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Anonymous donor, Akseli</value>
</custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots>

View File

@@ -108,7 +108,6 @@ Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[ko]=Matrix에서 대화하기
Comment[lt]=Pokalbiai per Matrix
Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
>carl@carlschwan.eu</email
></author>
<date
>20221101</date>
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
><title
>Telif Hakkı</title>
<para
>Telif hakkı &copy; 20202022 Tobias Fella </para>
>Telif hakkı &copy; 2020-2022 Tobias Fella </para>
<para
>Telif hakkı &copy; 20202022 Carl Schwan </para>
>Telif hakkı &copy; 2020-2022 Carl Schwan </para>
<para
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(neochat STATIC
add_library(neochat STATIC
controller.cpp
controller.h
roommanager.cpp
@@ -35,10 +35,6 @@ qt_add_library(neochat STATIC
models/commonroomsmodel.h
texttospeechhelper.h
texttospeechhelper.cpp
models/limitermodel.cpp
models/limitermodel.h
supportcontroller.cpp
supportcontroller.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -108,8 +104,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/NewPollDialog.qml
qml/UserMenu.qml
qml/MeetingDialog.qml
qml/SeenByDialog.qml
qml/SupportDialog.qml
DEPENDENCIES
QtCore
QtQuick
@@ -146,17 +140,10 @@ if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
qt_add_executable(neochat-app
add_executable(neochat-app
main.cpp
)
if(ANDROID)
set_target_properties(neochat-app PROPERTIES
OUTPUT_NAME "neochat-app"
PREFIX "lib"
)
endif()
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
@@ -197,7 +184,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline

View File

@@ -246,10 +246,7 @@ void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatC
if (newConnection) {
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
// Refresh and update manually, in case we init too late for the badge count to actually change.
newConnection->refreshBadgeNotificationCount();
updateBadgeNotificationCount(newConnection->badgeNotificationCount());
}
Q_EMIT activeConnectionChanged(newConnection);
}

View File

@@ -166,6 +166,12 @@ int main(int argc, char *argv[])
Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true);
#ifdef NEOCHAT_FLATPAK
// Copy over the included FontConfig configuration to the
// app's config dir:
QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s);
#endif
ColorSchemer colorScheme;
QCommandLineParser parser;

View File

@@ -5,7 +5,6 @@
#include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication>
#include <Quotient/room.h>
using namespace Quotient;
@@ -40,22 +39,8 @@ void CommonRoomsModel::setUserId(const QString &userId)
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{
auto roomId = m_commonRooms[index.row()];
auto room = connection()->room(roomId);
if (!room) {
return {};
}
switch (roleName) {
case Qt::DisplayRole:
case RoomNameRole:
return room->displayName();
case RoomAvatarRole:
return room->avatarUrl();
case RoomIdRole:
return roomId;
}
Q_UNUSED(index)
Q_UNUSED(roleName)
return {};
}
@@ -65,15 +50,6 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
return m_commonRooms.size();
}
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
{
return {
{RoomIdRole, "roomId"},
{RoomNameRole, "roomName"},
{RoomAvatarRole, "roomAvatar"},
};
}
void CommonRoomsModel::reload()
{
if (!m_connection || m_userId.isEmpty()) {

View File

@@ -24,9 +24,7 @@ class CommonRoomsModel : public QAbstractListModel
public:
enum Roles {
RoomIdRole = Qt::UserRole,
RoomNameRole,
RoomAvatarRole,
RoomIdRole = Qt::DisplayRole,
};
Q_ENUM(Roles)
@@ -41,8 +39,6 @@ public:
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void connectionChanged();
void userIdChanged();

View File

@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "models/limitermodel.h"
LimiterModel::LimiterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
}
int LimiterModel::maximumCount() const
{
return m_maximumCount;
}
void LimiterModel::setMaximumCount(int maximumCount)
{
if (m_maximumCount != maximumCount) {
m_maximumCount = maximumCount;
Q_EMIT maximumCountChanged();
}
}
int LimiterModel::extraCount() const
{
if (sourceModel()) {
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
}
return 0;
}
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
return source_row < maximumCount();
}
#include "moc_limitermodel.cpp"

View File

@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.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 LimiterModel
*
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
*
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
*/
class LimiterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
public:
explicit LimiterModel(QObject *parent = nullptr);
[[nodiscard]] int maximumCount() const;
void setMaximumCount(int maximumCount);
[[nodiscard]] int extraCount() const;
Q_SIGNALS:
void maximumCountChanged();
void extraCountChanged();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
int m_maximumCount = 0;
};

View File

@@ -92,9 +92,7 @@ void NotificationsModel::setConnection(NeoChatConnection *connection)
void NotificationsModel::loadData()
{
if (!m_connection) {
return;
}
Q_ASSERT(m_connection);
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
return;
}

View File

@@ -287,7 +287,6 @@ Name[ia]=Comparti
Name[it]=Condivisione
Name[ka]=გაზიარება
Name[ko]=공유
Name[lt]=Bendrinti
Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
@@ -323,7 +322,6 @@ Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[ko]=콘텐츠 공유 결과
Comment[lt]=Turinio dalies bendrinimo rezultatas
Comment[lv]=Satura kopīgošanas rezultāts
Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald

View File

@@ -211,6 +211,10 @@
<label>Enable threads</label>
<default>false</default>
</entry>
<entry name="SecretBackup" type="bool">
<label>Enable secret backup</label>
<default>false</default>
</entry>
<entry name="Phone3PId" type="bool">
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>

View File

@@ -38,7 +38,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
{
}
void NotificationsManager::handleNotifications(const QPointer<NeoChatConnection> &connection)
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
{
if (KNotificationPermission::checkPermission() == Qt::PermissionStatus::Granted) {
startNotificationJob(connection);
@@ -68,7 +68,7 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
}
}
void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnection> &connection, const GetNotificationsJob *job, const bool initialization)
void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization)
{
if (!job || !connection || !connection->isLoggedIn()) {
return;
@@ -82,7 +82,8 @@ void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnecti
if (!m_initialTimestamp.contains(connectionId)) {
m_initialTimestamp[connectionId] = notification["ts"_L1].toVariant().toLongLong();
} else {
if (const auto timestamp = notification["ts"_L1].toVariant().toLongLong(); timestamp > m_initialTimestamp[connectionId]) {
qint64 timestamp = notification["ts"_L1].toVariant().toLongLong();
if (timestamp > m_initialTimestamp[connectionId]) {
m_initialTimestamp[connectionId] = timestamp;
}
}
@@ -159,29 +160,29 @@ void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnecti
}
}
bool NotificationsManager::shouldPostNotification(const QPointer<NeoChatConnection> &connection, const QJsonValue &notification)
bool NotificationsManager::shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue &notification)
{
if (connection == nullptr || !connection->isLoggedIn()) {
return false;
}
const auto room = connection->room(notification["room_id"_L1].toString());
auto room = connection->room(notification["room_id"_L1].toString());
if (room == nullptr) {
return false;
}
// If the room is the current room and the application is active, the notification
// If the room is the current room and the application is active the notification
// should not be shown.
// This is set up so that if the application is inactive, the notification will
// This is setup so that if the application is inactive the notification will
// always be posted, even if the room is the current room.
if (RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id()
&& QGuiApplication::applicationState() == Qt::ApplicationActive) {
bool isCurrentRoom = RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id();
if (isCurrentRoom && QGuiApplication::applicationState() == Qt::ApplicationActive) {
return false;
}
// If the notification timestamp is earlier than the initial timestamp, assume
// If the notification timestamp is earlier than the initial timestamp assume
// the notification is old and shouldn't be posted.
const auto timestamp = notification["ts"_L1].toDouble();
qint64 timestamp = notification["ts"_L1].toDouble();
if (timestamp < m_initialTimestamp[connection->user()->id()]) {
return false;
}
@@ -198,7 +199,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
const QString &text,
const QImage &icon,
const QString &replyEventId,
const bool canReply,
bool canReply,
qint64 timestamp)
{
const QString roomId = room->id();
@@ -270,8 +271,10 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
if (NeoChatConfig::rejectUnknownInvites()) {
auto job = room->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
connect(job, &BaseJob::result, this, [this, job, room] {
if (QJsonObject replyData = job->jsonData(); replyData.contains(u"joined"_s)) {
if (!replyData["joined"_L1].toArray().isEmpty()) {
QJsonObject replyData = job->jsonData();
if (replyData.contains(u"joined"_s)) {
const bool inAnyOfOurRooms = !replyData["joined"_L1].toArray().isEmpty();
if (inAnyOfOurRooms) {
doPostInviteNotification(room);
} else {
room->forget();
@@ -283,7 +286,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
}
}
void NotificationsManager::doPostInviteNotification(const QPointer<NeoChatRoom> &room)
void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
{
const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
if (roomMemberEvent == nullptr) {
@@ -292,18 +295,18 @@ void NotificationsManager::doPostInviteNotification(const QPointer<NeoChatRoom>
const auto sender = room->member(roomMemberEvent->senderId());
QImage avatar_image;
if (!room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
} else {
qWarning() << "using this room's avatar";
avatar_image = room->avatar(128);
}
const auto notification = new KNotification(u"invite"_s);
KNotification *notification = new KNotification(u"invite"_s);
notification->setText(i18n("%1 invited you to a room", sender.htmlSafeDisplayName()));
notification->setTitle(room->displayName());
notification->setPixmap(createNotificationImage(avatar_image, nullptr));
const auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
if (!room) {
return;
@@ -364,9 +367,11 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
{
const auto json = QJsonDocument::fromJson(message).object();
const auto type = json["notification"_L1]["type"_L1].toString();
// the only two types of push notifications we support right now
if (const auto type = json["notification"_L1]["type"_L1].toString(); type == u"m.room.message"_s || type == u"m.room.encrypted"_s) {
const auto notification = new KNotification("message"_L1);
if (type == u"m.room.message"_s || type == u"m.room.encrypted"_s) {
auto notification = new KNotification("message"_L1);
const auto sender = json["notification"_L1]["sender_display_name"_L1].toString();
const auto roomName = json["notification"_L1]["room_name"_L1].toString();
@@ -386,13 +391,13 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
}
#ifdef HAVE_KIO
const auto openAction = notification->addAction(i18n("Open NeoChat"));
auto openAction = notification->addAction(i18n("Open NeoChat"));
connect(openAction, &KNotificationAction::activated, notification, [=]() {
QString properId = roomId;
properId = properId.replace(u"#"_s, QString());
properId = properId.replace(u"!"_s, QString());
const auto job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(u"org.kde.neochat"_s));
auto *job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(u"org.kde.neochat"_s));
job->setUrls({QUrl::fromUserInput(u"matrix:r/%1"_s.arg(properId))});
job->start();
});
@@ -423,12 +428,13 @@ QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoCha
painter.setBrush(Qt::white);
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
const QBrush brush(icon.scaledToHeight(biggestDimension));
QBrush brush(icon.scaledToHeight(biggestDimension));
painter.setBrush(brush);
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
if (room) {
if (const auto roomAvatar = room->avatar(imageRect.width(), imageRect.height()); !roomAvatar.isNull() && icon != roomAvatar) {
if (room != nullptr) {
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
if (!roomAvatar.isNull() && icon != roomAvatar) {
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
painter.setBrush(Qt::white);

View File

@@ -58,7 +58,7 @@ public:
/**
* @brief Handle the notifications for the given connection.
*/
void handleNotifications(const QPointer<NeoChatConnection> &connection);
void handleNotifications(QPointer<NeoChatConnection> connection);
private:
QHash<QString, qint64> m_initialTimestamp;
@@ -67,8 +67,8 @@ private:
QStringList m_connActiveJob;
void startNotificationJob(QPointer<NeoChatConnection> connection);
static QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
bool shouldPostNotification(const QPointer<NeoChatConnection> &connection, const QJsonValue &notification);
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue &notification);
void postNotification(NeoChatRoom *room,
const QString &sender,
const QString &text,
@@ -77,7 +77,7 @@ private:
bool canReply,
qint64 timestamp);
void doPostInviteNotification(const QPointer<NeoChatRoom> &room);
void doPostInviteNotification(QPointer<NeoChatRoom> room);
QHash<QString, std::pair<qint64, KNotification *>> m_notifications;
QHash<QString, QPointer<KNotification>> m_invitations;
@@ -85,5 +85,5 @@ private:
bool permissionAsked = false;
private Q_SLOTS:
void processNotificationJob(const QPointer<NeoChatConnection> &connection, const Quotient::GetNotificationsJob *job, bool initialization);
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
};

View File

@@ -5,7 +5,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtMultimedia
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
@@ -19,10 +18,6 @@ KirigamiComponents.ConvergentContextMenu {
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow window
data: MediaDevices {
id: devices
}
Kirigami.Action {
text: i18nc("@action:button", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
@@ -38,14 +33,12 @@ KirigamiComponents.ConvergentContextMenu {
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Scan a QR Code")
icon.name: "document-scan-symbolic"
visible: devices.videoInputs.length > 0
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "QrScannerPage"), {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection
}, {
title: i18nc("@title", "Scan a QR Code")
})
}) as Kirigami.Dialog).open();
}
Kirigami.Action {
@@ -62,6 +55,14 @@ KirigamiComponents.ConvergentContextMenu {
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Devices")
icon.name: "computer-symbolic"
onTriggered: {
NeoChatSettingsView.open('devices');
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Open Developer Tools")
icon.name: "tools"
@@ -75,6 +76,15 @@ KirigamiComponents.ConvergentContextMenu {
})
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Open Secret Backup")
icon.name: "unlock"
visible: NeoChatConfig.secretBackup
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
title: i18nc("@title:window", "Open Key Backup")
})
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
@@ -94,25 +104,10 @@ KirigamiComponents.ConvergentContextMenu {
}
Kirigami.Action {
text: i18nc("@action:inmenu Open support dialog", "Support")
icon.name: "help-contents-symbolic"
onTriggered: {
(Qt.createComponent("org.kde.neochat", "SupportDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection,
}) as SupportDialog).open();
}
}
Kirigami.Action {
separator: true
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
text: i18nc("@action:inmenu", "Logout…")
icon.name: "im-kick-user"
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection
}) as Kirigami.Dialog).open();
}) as Kirigami.Dialog).open()
}
}

View File

@@ -61,10 +61,10 @@ Kirigami.Dialog {
}
onClicked: {
root.close();
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
title: i18nc("@title:window", "Login")
});
root.close();
}
Keys.onUpPressed: {
accountView.currentIndex = accountView.count - 1;

View File

@@ -4,6 +4,9 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import org.kde.kirigami as Kirigami
import org.kde.neochat

View File

@@ -20,9 +20,9 @@ Components.AbstractMaximizeComponent {
property NeochatRoomMember author
/**
* @brief The timestamp of the event as a NeoChatDateTime.
* @brief The timestamp of the message.
*/
required property NeoChatDateTime dateTime
property var time
/**
* @brief The code text to show.
@@ -64,7 +64,7 @@ Components.AbstractMaximizeComponent {
}
QQC2.Label {
id: dateTimeLabel
text: root.dateTime.relativeDateTime
text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
color: Kirigami.Theme.disabledTextColor
elide: Text.ElideRight
}
@@ -79,7 +79,7 @@ Components.AbstractMaximizeComponent {
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
QQC2.TextArea {
id: codeTextEdit
id: codeText
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
@@ -100,15 +100,15 @@ Components.AbstractMaximizeComponent {
SyntaxHighlighter {
property string definitionName: Repository.definitionForName(root.language).name
textEdit: definitionName == "None" ? null : codeTextEdit
textEdit: definitionName == "None" ? null : codeText
definition: definitionName
}
ColumnLayout {
id: lineNumberColumn
anchors {
top: codeTextEdit.top
topMargin: codeTextEdit.topPadding + 1
left: codeTextEdit.left
top: codeText.top
topMargin: codeText.topPadding + 1
left: codeText.left
leftMargin: Kirigami.Units.smallSpacing
}
spacing: 0
@@ -116,7 +116,7 @@ Components.AbstractMaximizeComponent {
id: repeater
model: LineModel {
id: lineModel
Component.onCompleted: setDocument(codeTextEdit.textDocument)
document: codeText.textDocument
}
delegate: QQC2.Label {
id: label

View File

@@ -3,6 +3,8 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQml.Models
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents

View File

@@ -13,7 +13,7 @@ Kirigami.PromptDialog {
required property NeoChatRoom room
title: root.room.isSpace ? i18nc("@title:dialog", "Confirm Leaving Space") : i18nc("@title:dialog", "Confirm Leaving Room")
title: i18nc("@title:dialog", "Confirm Leaving Room")
subtitle: root.room ? i18nc("Do you really want to leave <room name>?", "Do you really want to leave %1?", root.room.displayNameForHtml) : ""
dialogType: Kirigami.PromptDialog.Warning

View File

@@ -39,7 +39,7 @@ Kirigami.Page {
icon.name: "document-edit"
onTriggered: {
root.room.setRoomState(root.type, root.stateKey, sourceTextArea.text);
root.Kirigami.PageStack.closeDialog();
root.closeDialog();
}
enabled: QmlUtils.isValidJson(sourceTextArea.text)
}
@@ -85,7 +85,7 @@ Kirigami.Page {
id: repeater
model: LineModel {
id: lineModel
Component.onCompleted: setDocument(sourceTextArea.textDocument)
document: sourceTextArea.textDocument
}
delegate: QQC2.Label {
id: label

View File

@@ -41,11 +41,13 @@ ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They match")
icon.name: "dialog-ok"
onClicked: root.accept()
}
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They don't match")
icon.name: "dialog-cancel"
onClicked: root.reject()

View File

@@ -9,6 +9,8 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
Delegates.RoundedItemDelegate {
id: root

View File

@@ -45,12 +45,14 @@ Labs.MenuBar {
}
Labs.MenuItem {
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
text: i18nc("@action:inmenu", "Explore Rooms")
enabled: root.connection
onTriggered: {
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {});
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -27,17 +27,16 @@ ColumnLayout {
Layout.fillHeight: true
}
KirigamiComponents.AvatarButton {
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
name: root.invitingMember.displayName
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
Loader {
@@ -55,12 +54,6 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
@@ -68,7 +61,12 @@ ColumnLayout {
visible: root.currentRoom && root.currentRoom.canonicalAlias
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
horizontalAlignment: Text.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
}
}
@@ -139,24 +137,8 @@ ColumnLayout {
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: viewProfileDelegate
icon.name: "user-properties-symbolic"
text: i18nc("@action:button View this user's profile", "View %1's Profile", root.invitingMember.displayName)
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
FormCard.FormDelegateSeparator {
above: viewProfileDelegate
below: ignoreUserDelegate
}
FormCard.FormButtonDelegate {
id: ignoreUserDelegate
icon.name: "list-remove-symbolic"
text: i18nc("@action:button Ignore the user", "Ignore %1 and Reject Invite", root.invitingMember.displayName)
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
onClicked: {
root.currentRoom.forget()
@@ -166,7 +148,7 @@ ColumnLayout {
}
RowLayout {
visible: (root.currentRoom.connection as NeoChatConnection).canCheckMutualRooms
visible: root.currentRoom.connection.canCheckMutualRooms
spacing: 0
Layout.topMargin: Kirigami.Units.largeSpacing * 2

View File

@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
@@ -10,6 +8,7 @@ import QtQuick.Window
import QtQml
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
Kirigami.Page {
@@ -24,63 +23,72 @@ Kirigami.Page {
name: "cancelled"
when: root.session.state === KeyVerificationSession.CANCELED
PropertyChanges {
stateLoader.sourceComponent: verificationCanceled
target: stateLoader
sourceComponent: verificationCanceled
}
},
State {
name: "waitingForVerification"
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
PropertyChanges {
stateLoader.sourceComponent: emojiSas
target: stateLoader
sourceComponent: emojiSas
}
},
State {
name: "waitingForReady"
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "incoming"
when: root.session.state === KeyVerificationSession.INCOMING
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForKey"
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForAccept"
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForMac"
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "ready"
when: root.session.state === KeyVerificationSession.READY
PropertyChanges {
stateLoader.sourceComponent: chooseVerificationComponent
target: stateLoader
sourceComponent: chooseVerificationComponent
}
},
State {
name: "done"
when: root.session.state === KeyVerificationSession.DONE
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
}
]

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtLocation
import QtPositioning

View File

@@ -100,7 +100,7 @@ Kirigami.ApplicationWindow {
function onCurrentRoomChanged() {
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
let roomPage = root.pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
roomPage.forceActiveFocus();
roomPage.backRequested.connect(event => {
RoomManager.clearCurrentRoom();
@@ -151,6 +151,8 @@ Kirigami.ApplicationWindow {
}
contextDrawer: RoomDrawer {
id: contextDrawer
// This is a memory for all user initiated actions on the drawer, i.e. clicking the button
// It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width
property bool drawerUserState: NeoChatConfig.autoRoomInfoDrawer
@@ -176,9 +178,9 @@ Kirigami.ApplicationWindow {
// Connect to the onClicked function of the RoomDrawer handle button
Connections {
target: root.contextDrawer.handle.children[0]
target: contextDrawer.handle.children[0]
function onClicked() {
root.contextDrawer.drawerUserState = root.contextDrawer.drawerOpen;
contextDrawer.drawerUserState = contextDrawer.drawerOpen;
}
}

View File

@@ -72,9 +72,9 @@ Kirigami.Dialog {
return null;
}
if (isAlias()) {
return root.connection.roomByAlias(text) as NeoChatRoom;
return root.connection.roomByAlias(text);
} else {
return root.connection.room(text) as NeoChatRoom;
return root.connection.room(text);
}
}

View File

@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-only
pragma ComponentBehavior: Bound
import QtQuick
import org.kde.kirigami as Kirigami
@@ -18,7 +16,7 @@ Kirigami.PromptDialog {
customFooterActions: Kirigami.Action {
icon.name: "camera-video-symbolic"
text: root.hasExistingMeeting ? i18nc("@action:button Join the Jitsi meeting", "Join") : i18nc("@action:button Start a new Jitsi meeting", "Start")
text: hasExistingMeeting ? i18nc("@action:button Join the Jitsi meeting", "Join") : i18nc("@action:button Start a new Jitsi meeting", "Start")
onTriggered: root.accept()
}
}

View File

@@ -103,7 +103,7 @@ Kirigami.Page {
id: repeater
model: LineModel {
id: lineModel
Component.onCompleted: setDocument(sourceTextArea.textDocument)
document: sourceTextArea.textDocument
}
delegate: QQC2.Label {
id: label

View File

@@ -23,45 +23,13 @@ Components.AlbumMaximizeComponent {
*/
required property NeoChatRoom currentRoom
readonly property string currentEventId: {
const idx = (content as ListView).currentIndex;
readonly property string currentEventId: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.EventIdRole)
if (idx === -1) {
return ""
}
readonly property var currentAuthor: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.AuthorRole)
return model.data(model.index(idx, 0), TimelineMessageModel.EventIdRole)
}
readonly property var currentTime: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.TimeRole)
readonly property var currentAuthor: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
return model.data(model.index(idx, 0), TimelineMessageModel.AuthorRole)
}
readonly property var currentTime: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
model.data(model.index(idx, 0), TimelineMessageModel.TimeRole)
}
readonly property var currentProgressInfo: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
model.data(model.index(idx, 0), TimelineMessageModel.ProgressInfoRole)
}
readonly property var currentProgressInfo: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
actions: [
ShareAction {
@@ -154,10 +122,7 @@ Components.AlbumMaximizeComponent {
onOpened: forceActiveFocus()
onItemRightClicked: {
const event = root.currentRoom.findEvent(root.currentEventId);
RoomManager.viewEventMenu(root.QQC2.Overlay.overlay, event, root.currentRoom)
}
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
onSaveItem: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;

View File

@@ -66,7 +66,7 @@ Kirigami.Dialog {
id: optionModel
readonly property bool allValuesSet: {
for (let i = 0; i < optionModel.rowCount(); i++) {
for( var i = 0; i < optionModel.rowCount(); i++ ) {
if (optionModel.get(i).optionText.length <= 0) {
return false;
}
@@ -83,7 +83,7 @@ Kirigami.Dialog {
function values() {
let textValues = []
for(let i = 0; i < optionModel.rowCount(); i++) {
for( var i = 0; i < optionModel.rowCount(); i++ ) {
textValues.push(optionModel.get(i).optionText);
}
return textValues;

View File

@@ -18,7 +18,7 @@ Kirigami.Page {
required property NeoChatConnection connection
padding: 0
Component.onCompleted: session.camera.start()
Component.onCompleted: camera.start()
Connections {
target: root.QQC2.ApplicationWindow.window
@@ -55,10 +55,10 @@ Kirigami.Page {
formats: Prison.Format.QRCode | Prison.Format.Aztec
onResultChanged: {
if (result.text.length > 0 && result.text != scanner.previousText) {
root.Kirigami.PageStack.closeDialog();
RoomManager.resolveResource(result.text, "qr");
scanner.previousText = result.text;
}
root.closeDialog();
}
videoSink: viewFinder.videoSink
}
@@ -66,8 +66,12 @@ Kirigami.Page {
CaptureSession {
id: session
camera: Camera {}
imageCapture: ImageCapture {}
camera: Camera {
id: camera
}
imageCapture: ImageCapture {
id: imageCapture
}
videoOutput: viewFinder
}
}

View File

@@ -30,13 +30,15 @@ Kirigami.SearchDialog {
emptyText: i18nc("Placeholder message", "No room found")
Kirigami.Action {
id: exploreRoomAction
text: i18nc("@action:button Explore public rooms and spaces", "Explore")
text: i18nc("@action:button", "Explore rooms")
icon.name: "compass"
onTriggered: {
root.close()
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {});
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

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