Compare commits

...

373 Commits

Author SHA1 Message Date
Tobias Fella
0566687ba0 Don't store RoomMembers in unique ptrs
Causes crashes due to double-free
2025-09-09 09:34:21 +02:00
l10n daemon script
67d5711cc4 GIT_SILENT Sync po/docbooks with svn 2025-09-09 02:18:13 +00:00
l10n daemon script
9f51b1e7ab 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-09-09 02:04:03 +00:00
l10n daemon script
5ac296ee71 GIT_SILENT made messages (after extraction) 2025-09-09 00:54:31 +00:00
Joshua Goins
1b8adcc91a Don't import Purpose on Android
This prevents the app from launching on Android (and probably Windows
too) because our module declares a dependency on a non-existent Purpose.
2025-09-08 16:10:12 +02:00
Joshua Goins
1e78209cd7 Fix missing appWindow property for GlobalMenuStub
This was one of the issues with the current Android version.
2025-09-08 15:47:04 +02:00
Tobias Fella
0fd924be7b Fix showing permissions settings for v12 rooms 2025-09-08 08:14:17 +00:00
l10n daemon script
d67cb89505 GIT_SILENT Sync po/docbooks with svn 2025-09-08 02:13:16 +00:00
l10n daemon script
0d0f88e251 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-09-08 01:51:40 +00:00
Joshua Goins
4cbd4a034c Remove incorrect context for "Always allow device verification" 2025-09-07 12:57:08 -04:00
Joshua Goins
e7ed07e386 Show the "Verify This Device" warning when turning on reverification
This is the only way to reverify your device again, so it's useful to
show this in that case.
2025-09-07 12:57:08 -04:00
Joshua Goins
390654fa4a Fix deprecation warning about QHoverEvent::pos
We should be using position().
2025-09-07 12:26:27 -04:00
Joshua Goins
e8830b56d8 Add "Done" button when verification session is finished
It isn't immediately obvious when to close the window, other clients
(e.g. Element Web) has a "Got It" button in addition to the regular
dialog close button.
2025-09-07 12:25:16 -04:00
Tobias Fella
bbf2587908 Don't use raw json for getting reply event id 2025-09-07 16:43:35 +02:00
Joshua Goins
409b6da160 Bring up the room confirmation dialog in more situations
This super helpful and nice dialog went mostly unused, especially if you
only joined links to rooms through matrix.to or within NeoChat itself.

The reason why this never showed up for said links is because we were
greedily overwriting the "action". I don't think the code made solid
sense anyway, so I redone it a bit so there's a clearer
"join_confirmed" our UI uses. I added a test case to showcase this
working.
2025-09-07 08:43:51 -04:00
l10n daemon script
9d8c9853ce GIT_SILENT Sync po/docbooks with svn 2025-09-07 01:40:17 +00:00
Joshua Goins
b3781e0db4 Turn NeoChatConnection::isVerifiedSession into a notifiable property
This should allow the devices page to hide the "Verify this Device"
warning, once you actually complete verification. I also made sure to
update the other consumer - the menu item under the user menu.

Additionally, I also fixed the password field not being automatically
focused when trying to remove a device.
2025-09-06 07:51:38 -04:00
l10n daemon script
fbcb2379fb GIT_SILENT Sync po/docbooks with svn 2025-09-06 01:40:56 +00:00
Tobias Fella
e8ad4a5dee Fixes for logging out the active connection
- Don't crash
- Fall back to WelcomePage
2025-09-05 10:11:43 +00:00
l10n daemon script
6c57c41993 GIT_SILENT Sync po/docbooks with svn 2025-09-05 01:41:21 +00:00
Joshua Goins
ce0014f846 Don't accidentally hide relevant error banners
We use error banners for intermittent errors (failing to join a room),
along with more long-standing/important errors (like going offline.) And
due to the nature of how we manage banner visibility - for example, the
online check could accidentally hide joining rooms errors which is
pretty bad.

To fix this, each error banner is assigned an id. All errors can still
overwrite each other as they could before, but they can only hide
banners of their own kind.
2025-09-04 20:48:56 -04:00
l10n daemon script
e5df412258 GIT_SILENT Sync po/docbooks with svn 2025-09-04 01:41:16 +00:00
Heiko Becker
0c482aa463 GIT_SILENT Update Appstream for new release
(cherry picked from commit 681a3c4036)
2025-09-04 01:31:22 +02:00
Joshua Goins
2df3a21fb5 Show the room name when failing to join it
The previous error message generically said you failed to join a room,
but it doesn't say *which* one!
2025-09-03 10:19:26 -04:00
l10n daemon script
9dd438e8a2 GIT_SILENT Sync po/docbooks with svn 2025-09-03 01:45:00 +00:00
Tobias Fella
cea5006e15 Revert "Use libQuotient qml module"
This reverts commit b80a2f94f4.
2025-09-03 00:00:16 +02:00
Tobias Fella
8ff3b31497 Fix some qml warnings in RoomDelegate 2025-09-02 23:00:15 +02:00
Tobias Fella
c7a9f9f07e Fix qml warnings in NotificationsView 2025-09-02 22:57:39 +02:00
Tobias Fella
1de10a4b87 Fix qml warnings in RoomAdvancedPage 2025-09-02 22:51:45 +02:00
Tobias Fella
d7f19efbd6 Fix qml warnings in MimeComponent 2025-09-02 22:49:27 +02:00
Tobias Fella
a3a28b2472 Fix qml warnings in PolLDelegate 2025-09-02 22:48:19 +02:00
Tobias Fella
919fc8b821 Fix qml warnings in DelegateContextMenu 2025-09-02 20:43:46 +00:00
Tobias Fella
b80a2f94f4 Use libQuotient qml module 2025-09-02 22:36:07 +02:00
Tobias Fella
6d18d1a237 Fix qml warnings 2025-09-02 15:36:21 +02:00
Tobias Fella
a2d64d2955 Fix some qml warnings 2025-09-02 15:06:58 +02:00
l10n daemon script
95617e3210 GIT_SILENT Sync po/docbooks with svn 2025-09-02 01:52:19 +00:00
Tobias Fella
1522f7086e Fix various qmllint warnings 2025-09-01 23:50:17 +02:00
Tobias Fella
14167d197f Fix some qml warnings 2025-09-01 23:33:25 +02:00
Tobias Fella
c377cf05b8 Fix qml warnings 2025-09-01 23:27:21 +02:00
Tobias Fella
4a52bc04fb Fix warning 2025-09-01 23:12:31 +02:00
Tobias Fella
53cd230d16 Remove redundant TapHandlers 2025-09-01 23:09:18 +02:00
Tobias Fella
e7b204b9fd Fix various qmllint warnings 2025-09-01 23:06:46 +02:00
Tobias Fella
4d91ae96e3 Only build required parts of opencv 2025-09-01 15:46:08 +02:00
Tobias Fella
b422d641de Add opencv to flatpak
Dependency of KQuickImageEditor
2025-09-01 14:58:27 +02:00
Tobias Fella
adc23e22cd Fix qml warnings in ExploreComponentMobile 2025-09-01 13:39:13 +02:00
Tobias Fella
a673d97144 Fix qml warnings in ExploreComponent 2025-09-01 13:25:36 +02:00
Tobias Fella
5a71dfcd7a Fix qml warnings in SpaceHomePage 2025-09-01 13:22:05 +02:00
Tobias Fella
5f6c248181 Also disable i18n warnings 2025-09-01 13:12:53 +02:00
Tobias Fella
8e04a5ed2f Fix warnings in InvitationView 2025-09-01 13:10:11 +02:00
Tobias Fella
f45582dc03 Fix warnings and add message contexts in ServerComboBox 2025-09-01 13:06:04 +02:00
Tobias Fella
19cfe118ac Fix warnings in SpaceListContextMenu 2025-09-01 13:03:31 +02:00
Tobias Fella
79b8987d85 Fix UserSearchPage warnings 2025-09-01 13:00:57 +02:00
l10n daemon script
1d9708ffa4 GIT_SILENT Sync po/docbooks with svn 2025-09-01 01:41:21 +00:00
Tobias Fella
c55f30be5b Fix some more warnings around emoji components 2025-08-31 23:01:51 +02:00
Tobias Fella
669a00a7e3 Fix most qml warnings in RoomContextMenu 2025-08-31 22:55:58 +02:00
Tobias Fella
51fe090043 Fix warnings in EmojiPicker 2025-08-31 22:51:30 +02:00
Tobias Fella
cf85d0fc0d Fix some qml warnings 2025-08-31 22:38:24 +02:00
Tobias Fella
c257ac504b Fix warning 2025-08-31 22:35:19 +02:00
Joshua Goins
9412f7e189 Fix compact mode not filling all available space
This seems to be an unintentional change in compact mode caused by 054f87cae2,
where an if check for "straight line" or "fill width" mode ended up
being removed. But this was needed for availableWidth() to return the
correct width for compact mode, otherwise it got weirdly centered and
ended up with a limited width.
2025-08-31 10:03:57 -04:00
Joshua Goins
385fafa51c Re-arrange appearance page to group similar settings together
I re-arranged everything now so it makes more semantic sense, e.g. all
the message layout stuff is together in one card. I also made
bubble-only options hide themselves to reduce the amount of clutter
for compact layout users. Some small grammatical things were changed
like "Show Avatar" -> "Show Avatars".
2025-08-31 10:03:46 -04:00
Joshua Goins
c7e409abe9 Improve the event loading indicator somewhat
I added ellipses so it matches the other loading placeholder we have. I
also removed the spacing in it's layout, because there is more than
enough space inside of BusyIndicator itself that it makes the
additional spacing look odd.
2025-08-30 22:01:19 -04:00
Joshua Goins
98816aedd4 Set the correct palette colors for selections in quotes
This bothered me a for a while, but I finally remembered to change it
now.
2025-08-30 22:01:07 -04:00
Joshua Goins
38c66d1b7d Clip commonly seen labels used for display names to prevent Unicode spam
Unfortunately a pretty common spam/annoyance online is putting "spam"
via abusing Unicode symbols - which is used for multilingual support -
in display names. This typically results in an unreadable word jumble in
our UI, which looks bad.

So I decided to put a stop to that by clipping some labels that could be
used for this crap. I didn't cover *everything* yet, but this at least
prevents these idiots from showing up in the completion menu, the room
member list and as authors for messages.
2025-08-30 22:00:55 -04:00
Joshua Goins
0e01a43aab Ensure we update the avatar item when switching to Compact mode
This fixes an always-reproducible bug by switching from Bubbles (with
"move local messages to the right" enabled) to Compact, and your
avatar is missing until you switch rooms or restart NeoChat.

The various flags to inform the rest of the delegate of the avatar were
returning true, but the actual avatar item was never created when the
option was switched on.
2025-08-30 21:52:33 -04:00
l10n daemon script
ddf67aef94 GIT_SILENT Sync po/docbooks with svn 2025-08-31 01:42:36 +00:00
Joshua Goins
fa8294d4b9 Add action to user detail dialog to search for their messages in room
In other messaging applications (e.g. Discord) this is possible through
text modifiers like "from:@user". We don't support that, and I'm not
super keen on implementing yet-another-parsing-thing, so an action in
the user detail dialog should work for now.

Very useful to sift through large rooms but when you only care about a
specific person's messages (maybe your own?)
2025-08-30 13:19:46 -04:00
James Graham
8ad822fd0b Fix the link preview insert lambda to make sure that it doesn't crash
Fix the link preview insert lambda to make sure that it doesn't crash and also make sure that the system doesn't lock up on link preview load
2025-08-30 12:49:11 +01:00
Tobias Fella
69568c628f Also fix live locations 2025-08-30 11:39:20 +00:00
Tobias Fella
62a770b3e2 Fix showing Location events
- Initialize required properties
- Use event's body as display
2025-08-30 11:39:20 +00:00
Tobias Fella
9b65ae1e66 Cleanup LocationsModel
Use libQuotient's event properties instead of accessing data from raw json
2025-08-30 11:30:28 +00:00
Tobias Fella
5d5295d06d Modernize job handling 2025-08-30 10:23:45 +00:00
l10n daemon script
8f7be9993c GIT_SILENT Sync po/docbooks with svn 2025-08-30 01:43:35 +00:00
Joshua Goins
821d7621e3 Change chat bar text field back to plain text
This was changed in 9ed5224470 but this
ended up causing all sorts of strange formatting issues like the quick
format bar not working, and pastes from other applications that kept the
formatting.
2025-08-29 18:18:01 -04:00
Tobias Fella
91b00a34b7 Simplify code 2025-08-29 22:22:44 +02:00
Tobias Fella
19e74b60a9 Reduce nesting 2025-08-29 22:22:44 +02:00
Tobias Fella
5ee5991c39 Use contentPart instead of contentJson 2025-08-29 22:22:44 +02:00
Tobias Fella
abffeec938 Refactor accountmanager 2025-08-29 17:08:15 +02:00
Tobias Fella
77ac811498 Make delegate take maximum width while editing a message 2025-08-29 15:07:20 +00:00
Tobias Fella
1ba6b840db Bump Qt version dependency 2025-08-29 15:06:57 +00:00
Tobias Fella
bfb640487f Fix connection 2025-08-29 14:21:19 +00:00
Joshua Goins
4b3cc750a1 Remove Kirigami.OverlayZStacking from HoverLinkIndicator
I forgot this is for Popups only, let's just go back to the good
old days of hardcoding a Z value.
2025-08-29 10:06:08 -04:00
Joshua Goins
8bef9713fd Hide Purpose's own "Copy to Clipboard" function
This was added recently to Purpose, and it's not too useful for us.
We already have a bunch of better, more integrated ways to copy
events to the clipboard.
2025-08-29 10:02:50 -04:00
Tobias Fella
889bf9cbc6 Simplify event context menu open
There's no reason to pass the author from QML to C++, we can query the author in the backend
2025-08-29 15:17:19 +02:00
l10n daemon script
003ab4f278 GIT_SILENT Sync po/docbooks with svn 2025-08-29 01:41:59 +00:00
Tobias Fella
a6b9759a31 Unify Delegate context menus
The menus have always been split into a menu for file-like content and text-like content

This split makes some things a bit more complicated then necessary.
2025-08-28 22:43:42 +02:00
Tobias Fella
5c5dcd555b Fix compilation warnings about QFile::open 2025-08-28 15:16:08 +02:00
Tobias Fella
8098fce461 Fix qml name of timeline-memtest 2025-08-28 14:32:54 +02:00
l10n daemon script
227ea231d5 GIT_SILENT Sync po/docbooks with svn 2025-08-28 01:46:47 +00:00
Tobias Fella
35ac1b7a55 Tests: Rewrite server sync handling
Implements #696
2025-08-27 22:59:39 +02:00
l10n daemon script
86be1d82bc GIT_SILENT Sync po/docbooks with svn 2025-08-27 01:44:37 +00:00
Tobias Fella
c6dd82d2ff Don't link kirigami in memorytests 2025-08-26 13:40:25 +02:00
l10n daemon script
2624ed1817 GIT_SILENT Sync po/docbooks with svn 2025-08-26 01:42:10 +00:00
Tobias Fella
8706ee950e Minor fix to StateModel 2025-08-25 15:21:51 +02:00
l10n daemon script
bde27dc826 GIT_SILENT Sync po/docbooks with svn 2025-08-25 01:42:35 +00:00
Tobias Fella
25b4ee2efb Ifdef trayicon a bit more for android 2025-08-24 16:25:52 +02:00
Tobias Fella
e7f6adaa01 Refactor Controller 2025-08-24 16:12:52 +02:00
Tobias Fella
bb3e28bc43 Fix runtime qml warning 2025-08-24 11:56:22 +02:00
Tobias Fella
026db80391 Use logging category for some qDebugs 2025-08-24 10:07:59 +02:00
l10n daemon script
2c09a48c8d GIT_SILENT Sync po/docbooks with svn 2025-08-24 01:40:42 +00:00
Joshua Goins
6e0931e31b Improve search experience somewhat
So I noticed that searching for messages feels "buggy". I dove into the
code and gave it a pretty nice refresh, I know you'll like it once you
try it!

Some of the issues I noticed and are now fixed:
* The search doesn't clear immediately when you clear the text box, it's
delayed like everything else. This also has the knock-on effect of
starting random network requests.
* If you now press the search button (either with a mouse, pen, etc. or
by the keyboard) the search delay timer was still running.
* The "nothing is here" placeholder message appears when the search
timer is running, making you think no messages were found.

These changes also help other places where SearchPage is used, not just
message search.
2025-08-23 17:52:11 -04:00
Joshua Goins
1424b8ce42 Add icons to placeholder messages in search pages 2025-08-23 17:52:11 -04:00
Joshua Goins
f5f151681d Fix HAVE_KUNIFIEDPUSH not applying anymore
This compile-time definition was separated from the code during some
refactor.
2025-08-23 17:23:53 -04:00
Tobias Fella
306d75a9b0 Refactor saved room management and fix closing room when switching account
Relevant fixes hidden behind some refactoring:
- set m_lastRoomConfig per-user while setting the connection
- fold m_lastSpaceConfig into m_lastRoomConfig
- set current room to empty explicitely when there is no room
2025-08-23 23:04:39 +02:00
Tobias Fella
ee33a70bb2 Fix opening "Home space" on startup
There was a mismatch here between the value used in the config ("Home") and the value used in the property (empty string)
2025-08-23 23:04:39 +02:00
Tobias Fella
bd80390daa Improve InviteUserPage 2025-08-23 22:53:42 +02:00
Tobias Fella
0fa490f532 Fix crash on shutdown and simplify code 2025-08-23 22:39:25 +02:00
Tobias Fella
1c7cc45d8e Remove unused member 2025-08-23 12:17:49 +02:00
l10n daemon script
f3288c2e34 GIT_SILENT Sync po/docbooks with svn 2025-08-23 01:42:57 +00:00
Volker Krause
7eff1eec56 Only build the KUnifiedPush client inside a Flatpak
We neither need nor want the distributor nor the host configuration UI
here.

While at it, update to 25.08.0.
2025-08-22 17:34:05 +02:00
Tobias Fella
85c7b1a3fc Enable qmllint on CI 2025-08-22 14:15:55 +00:00
Tobias Fella
5c82c07f06 Fix search menu items 2025-08-22 10:49:39 +02:00
l10n daemon script
fcf125f9ca GIT_SILENT Sync po/docbooks with svn 2025-08-22 01:41:37 +00:00
l10n daemon script
8ae13adbcd GIT_SILENT made messages (after extraction) 2025-08-22 00:42:21 +00:00
Tobias Fella
1e1ad389e3 UserSearchPage: Move "Enter User ID" action into placeholder 2025-08-21 17:50:48 -04:00
Joshua Goins
f58212e8de Improve look of the HoverLinkIndicator and other refactorings
It still looks generally the same, except now:
* It doesn't take up the full width of the window (this was a mistake.)
* It now nicely fades in and out.
* It uses Kirigami's built-in OverlayZStacking object instead of
hardcoding a Z of 20.
* A border is now added which helps make it stand out compared to the
chat bar, which it frequently is on top of.
* The seemingly unused accessibility string is removed.
2025-08-21 17:50:33 -04:00
Tobias Fella
8622087e51 Cleanup Permissions page 2025-08-21 23:20:32 +02:00
Tobias Fella
5fc59b0d66 Cleanup RoomSecurityPage 2025-08-21 23:20:32 +02:00
Tobias Fella
5aeefea5c0 Cleanup RoomGeneralPage 2025-08-21 23:20:32 +02:00
Tobias Fella
52e9c9e8e2 Fix room list placeholders 2025-08-21 23:16:28 +02:00
Tobias Fella
ae69401d57 Fix clicking on a reply to get to the replied-to message 2025-08-21 17:27:45 +02:00
Justin Zobel
2339cad49f Uppercase OK 2025-08-21 22:58:00 +09:30
Tobias Fella
8f6683fd1d Fix most qml warnings in RoomListPage 2025-08-21 09:30:17 +02:00
Tobias Fella
74deb684e1 Don't show AddDirect delegate if we don't have any DMs
In this case, it's nicer to fall back to the placeholder, which has the same action
2025-08-21 09:15:36 +02:00
l10n daemon script
54a918b0cf GIT_SILENT Sync po/docbooks with svn 2025-08-21 01:45:15 +00:00
Tobias Fella
55c9b09b24 Add translation context 2025-08-20 20:19:13 +02:00
James Graham
0d63fce59a Apply all the required styling in cpp
Apply all the required styling to show links, table, spoilers, etc in cpp. This also updates the method of revealing spoilers so now you can click to reveal then click again to hide.
2025-08-20 17:10:44 +01:00
Joshua Goins
4498d4457b Set source size in link preview images
This prevents them from looking super jagged when we scale down a
high-res image into a small (usually ~70px in height) space.
2025-08-20 08:58:37 -04:00
Joshua Goins
83415d202a Handle more states in KeyVerificationDialog
We were specifically missing WAITINGFORKEY and WAITINGFORACCEPT, which
does happen and could be delayed - resulting in a blank screen for a few
seconds.

CCBUG: 508483
2025-08-20 08:58:26 -04:00
Tobias Fella
ed65855cdd Add autotest for server notice handling 2025-08-19 23:09:38 +02:00
Tobias Fella
1477159376 Show banner informing the user about server notices 2025-08-19 23:09:38 +02:00
Tobias Fella
53a88708d6 Only show server notices at the top if they have unread messages 2025-08-19 23:09:13 +02:00
Tobias Fella
8eb8803afd Add room category for server notices 2025-08-19 23:09:10 +02:00
l10n daemon script
20e30982cf GIT_SILENT Sync po/docbooks with svn 2025-08-19 02:05:09 +00:00
Tobias Fella
e13b82f66a Revert patch using features not yet available in flatpak 2025-08-18 17:09:50 +00:00
Tobias Fella
8e50388031 Revert KF6 version bump in flatpak 2025-08-18 17:09:50 +00:00
Tobias Fella
fb21686894 Bump KF6 dependency version 2025-08-18 17:09:50 +00:00
l10n daemon script
62faf0f784 GIT_SILENT Sync po/docbooks with svn 2025-08-18 01:41:27 +00:00
Tobias Fella
be377d9ad8 Update uri for libquotient 2025-08-17 22:01:09 +02:00
l10n daemon script
ab8e2f7573 GIT_SILENT Sync po/docbooks with svn 2025-08-17 01:40:12 +00:00
Darshan Phaldesai
7991429ef4 appstream: fix developer id 2025-08-16 12:28:53 +09:30
l10n daemon script
e3b70a14be GIT_SILENT Sync po/docbooks with svn 2025-08-16 01:42:20 +00:00
Tobias Fella
39abf6b5f3 Start adding tests for RoomManager 2025-08-15 08:10:08 +00:00
l10n daemon script
096842bd3a GIT_SILENT Sync po/docbooks with svn 2025-08-15 01:51:42 +00:00
Tobias Fella
c69db9d375 Improve chatbar actions
Introduce a new type BusyAction that wraps Kirigami.Action with the added isBusy property. This makes QML a bit happier
2025-08-14 20:56:26 +00:00
Tobias Fella
ec36d519b1 Cleanup and fix GlobalMenu 2025-08-14 22:47:42 +02:00
Tobias Fella
45b02ae34e Cleanup buttons
Mostly removing the usage of the action property, since there's no point in using it. Also add some translation contexts and some other minor cleanup
2025-08-14 20:46:46 +00:00
Kai Uwe Broulik
9b763daf52 notificationsmanager: Don't draw room avatar if it is null
Ideally, we painted the avatar with initials here but for now
it's better to not paint anything than an awkward white circle.
2025-08-14 11:28:18 +00:00
Tobias Fella
6e8ed5b341 Fix qml warnings in NewPollDialog 2025-08-14 09:23:04 +00:00
Tobias Fella
63a3c3e58a Fix build with kio 2025-08-14 09:22:16 +00:00
Tobias Fella
aadd9b0189 Fix qmllint warnings in QuickFormatBar 2025-08-14 09:20:28 +00:00
l10n daemon script
39f595e45d GIT_SILENT Sync po/docbooks with svn 2025-08-14 01:46:59 +00:00
Tobias Fella
823b2d3747 Add translation context 2025-08-13 22:13:59 +02:00
Tobias Fella
eb268576da Fix some warnings 2025-08-13 22:13:53 +02:00
Tobias Fella
8e51f3ec8e Fix some type warnings 2025-08-13 21:52:36 +02:00
Tobias Fella
de03e1ce2b Remove unused import 2025-08-13 21:52:22 +02:00
Tobias Fella
21b1258b8d Only allow opening QuickSwitcher if there is an active connection 2025-08-13 21:46:43 +02:00
Tobias Fella
becad8c127 Fix qml warnings in QuickSwitcher 2025-08-13 21:45:15 +02:00
Tobias Fella
4044048352 Add translation context 2025-08-13 21:44:34 +02:00
Tobias Fella
7d6bd7ab4c Add contexts to string 2025-08-13 21:37:05 +02:00
Tobias Fella
209ae00f8f Remove unused imports 2025-08-13 21:36:45 +02:00
Tobias Fella
f64c860453 Register dependency on KSyntaxHighlighting 2025-08-13 21:36:14 +02:00
Tobias Fella
36fccaffe6 Fix warnings 2025-08-13 21:32:45 +02:00
Tobias Fella
13deb2d928 Fix qmllint warnings 2025-08-13 21:26:24 +02:00
Tobias Fella
14fe71b556 Fix most warnings in AccountSwitchDialog 2025-08-13 19:37:21 +02:00
Tobias Fella
ecb900994b Remove unused import 2025-08-13 19:30:14 +02:00
Tobias Fella
55b97b469e Fix some qmllint warnings 2025-08-13 19:20:45 +02:00
Tobias Fella
1d594b492d Remove unused imports 2025-08-13 19:18:26 +02:00
Tobias Fella
3902293de7 Add translation contexts 2025-08-13 19:14:54 +02:00
Tobias Fella
a8bc51667c Fix various warnings and add translation contexts 2025-08-13 19:07:06 +02:00
Tobias Fella
0bcf6e74c0 Fix warnings in SearchPage 2025-08-13 18:56:37 +02:00
Tobias Fella
78b218fef3 Fix some qml type warnings 2025-08-13 17:43:51 +02:00
Tobias Fella
964bcfd5f5 Register dependency on KConfig to qml 2025-08-13 17:41:32 +02:00
Tobias Fella
fc733f9ba1 Fix some unqualified access warnings 2025-08-13 17:41:23 +02:00
l10n daemon script
e7e83fa789 GIT_SILENT Sync po/docbooks with svn 2025-08-13 01:41:50 +00:00
Tobias Fella
ef4f11546f Fix copying images 2025-08-12 14:20:57 +00:00
l10n daemon script
648796b9e0 GIT_SILENT Sync po/docbooks with svn 2025-08-12 01:42:57 +00:00
Tobias Fella
3b5da2473d Add missing copyright statement 2025-08-11 23:18:56 +02:00
Tobias Fella
9a04ae3e02 Fix opening files externally 2025-08-11 23:05:57 +02:00
James Graham
9ed5224470 Have ChatDocumentHandler update ChatBarCache text
Have ChatDocumentHandler update ChatBarCache text so all text operations in the ChatBar go through it
2025-08-11 19:33:58 +01:00
James Graham
bc82ceeb5f Simpify the API for ChatDocumentHandler
Simpify the API for ChatDocumentHandler by taking the text item and grabbing everything else needed from there
2025-08-11 19:23:40 +01:00
Tobias Fella
5f7ff209d3 Add .contextProperties.ini
Tells qmlls to ignore i18n context properties
2025-08-11 17:35:02 +02:00
Tobias Fella
35b363fdce Update room versions in security settings
We need to come up with a better way of testing the versions here, but that's for different patch
2025-08-11 10:07:19 +00:00
Tobias Fella
a74931e794 Refactor qml 2025-08-11 12:04:10 +02:00
Tobias Fella
6698bbcf79 Fix warning 2025-08-11 12:04:10 +02:00
Tobias Fella
8c78992b1a Remove broken and duplicate code 2025-08-11 12:04:08 +02:00
l10n daemon script
04e3b88e8c GIT_SILENT Sync po/docbooks with svn 2025-08-11 01:42:20 +00:00
l10n daemon script
9b8b13e98e GIT_SILENT made messages (after extraction) 2025-08-11 00:42:48 +00:00
l10n daemon script
e6dd6aec7f GIT_SILENT Sync po/docbooks with svn 2025-08-10 01:44:51 +00:00
l10n daemon script
c6f0879c9c GIT_SILENT Sync po/docbooks with svn 2025-08-09 01:39:31 +00:00
Joshua Goins
9e7ae37add Partially revert recent RoomMedia change to make it work again
This reverts part of f288367653 which
touches this file. I'm not entirely sure why it was changed, it looks
like a piece of refactoring that isn't complete yet, but MessageDelegate
still depends on a required room property.
2025-08-08 08:39:08 -04:00
Tobias Fella
0c727237ee Remove Edit menu
This menu providers a few text editing related functions, like undo/redo, copy, paste, etc.
As far as I can tell, it never worked in a useful way, though: It operates on the activeFocusItem,
but as soon as one clicks on the menu, this becomes null. It is somewhat useable through shortcuts,
but the text fields have these shortcuts natively.
2025-08-08 08:25:51 -04:00
Joshua Goins
bf66118355 Change "Copy Room Address" action to "Copy Room Link"
This is way more useful for sharing, as you can use this link in and
outside Matrix rooms. It also matches behavior with other clients like
Element Web.

I also cut some places where this shows unnecessarily, such as direct
chats and invite-only private rooms.
2025-08-08 08:25:34 -04:00
Tobias Fella
aacb097650 Add libQuotient qml module dependencies
The module doesn't exist upstream yet; this will just be ignored in that case
2025-08-08 13:42:24 +02:00
Tobias Fella
ce5d60fc5d Fix QML warning 2025-08-08 13:42:00 +02:00
Tobias Fella
96a0b86c33 Fix some unqualified access warnings 2025-08-08 13:39:06 +02:00
Tobias Fella
279b611754 Remove unused prison 2025-08-08 10:42:38 +02:00
Tobias Fella
f7c74a60cd Port away from applicationWindow() 2025-08-08 10:29:48 +02:00
l10n daemon script
3465fc7d39 GIT_SILENT Sync po/docbooks with svn 2025-08-07 19:27:58 +00:00
l10n daemon script
6381f06acb GIT_SILENT made messages (after extraction) 2025-08-07 18:30:01 +00:00
l10n daemon script
a7c7a5c72d GIT_SILENT Sync po/docbooks with svn 2025-08-07 01:42:12 +00:00
l10n daemon script
c8ded65e46 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-08-07 01:33:59 +00:00
l10n daemon script
729b46fc71 GIT_SILENT made messages (after extraction) 2025-08-07 00:42:54 +00:00
Tobias Fella
e63e04aa57 Fix QML warning 2025-08-07 00:21:10 +02:00
Tobias Fella
ae88879651 Remove unused kitemmodels imports 2025-08-07 00:19:18 +02:00
Tobias Fella
dfd106258b Fix some warnings around pagestack calls 2025-08-07 00:05:52 +02:00
Tobias Fella
4d29b9fd57 Fix qml warning 2025-08-06 23:53:04 +02:00
Tobias Fella
3de7ad237a Fix qml warning 2025-08-06 23:51:56 +02:00
Tobias Fella
7d4e589894 Remove some unused includes 2025-08-06 23:49:27 +02:00
Tobias Fella
4bbd127fe8 Fix some unqualified access 2025-08-06 23:47:28 +02:00
Tobias Fella
f2a0a66b01 Fix connection 2025-08-06 23:47:28 +02:00
James Graham
143c685045 Fix creating a new thread locally
Fix creating a new thread locally. We need to listen for the newThread signal and update the thread root event mode content
2025-08-06 18:10:03 +01:00
Tobias Fella
b8fa6f0690 Fix font sizes 2025-08-06 15:34:33 +02:00
Joshua Goins
93b6c53c82 Remind the user if they're trying to reply to someone who has left
This happens semi-frequently to me and others - we reply to a user who
has left the room. Sometimes this is useful (for example, bringing up a
previous topic) but in most cases this is accidental, and almost
guaranteed to happen if you turned off join/leave events.

So I added a reminder when replying - which manifests as a small label
in the chatbar - that the user has left and can't be notified of your
reply.
2025-08-06 08:13:49 -04:00
l10n daemon script
6822a1ef08 GIT_SILENT Sync po/docbooks with svn 2025-08-06 01:47:29 +00:00
Justin Zobel
12b7c25395 Flatpak: Update kunifiedpush project ID and URL as it is now in KDE Gear and update the version 2025-08-05 09:30:29 +00:00
Justin Zobel
42c0060122 Flatpak: Upgrade libsecret 2025-08-05 09:30:29 +00:00
Justin Zobel
b30ee55a81 Flatpak: Add cleanup 2025-08-05 09:30:29 +00:00
Tobias Fella
9d2ef838bb Don't crash when trying to open a context menu on a pending event 2025-08-05 09:17:13 +00:00
Tobias Fella
b5351e48dd Add tooltip to "remove server" button 2025-08-05 09:16:27 +00:00
l10n daemon script
7b437d91e1 GIT_SILENT Sync po/docbooks with svn 2025-08-05 01:54:03 +00:00
Nicolas Fella
a9d39353ab Add XDG activation support to rooms runner 2025-08-04 21:49:05 +02:00
Tobias Fella
5f778dbd81 Add some translation contexts 2025-08-04 21:27:28 +02:00
Nate Graham
b01286eae3 plasma-runner-neochat: remove unnecessary minimum letter count
Users generally expect search results to start appearing with the first
character typed; we shouldn't be setting minimum letter counts without
a very good reason. Excessive minimum letter counts are also bad
specifically for Chinese, which is highly information-dense.

Get rid of the minimum letter count.

CCBUG: 490972
2025-08-04 09:18:18 -06:00
Tobias Fella
4688802628 Cleanup CommonRoomsModel
- Remove unused include
- Remove roles that don't make sense
2025-08-04 17:14:32 +02:00
l10n daemon script
0282f2c7aa GIT_SILENT Sync po/docbooks with svn 2025-08-04 01:47:41 +00:00
Tobias Fella
53d0fd1663 Improve spacing in mobile UserInfo 2025-08-03 17:26:39 +02:00
l10n daemon script
24bdb7d651 GIT_SILENT Sync po/docbooks with svn 2025-08-03 01:41:12 +00:00
James Graham
dc32f2f947 Update ChatDocumentHandler so RoomManger is no longer required for saving text
Update `ChatDocumentHandler` so `RoomManger` is no longer required for saving the text in the chatbar between room switches. This is achieved by allowing `ChatDocumentHandler` to get the correct `ChatBarChache` itself rather than having to have it passed from `ChatBar.qml`. This avoids any race conditions.
2025-08-02 10:56:20 +01:00
l10n daemon script
401cf29ca8 GIT_SILENT Sync po/docbooks with svn 2025-08-02 01:39:43 +00:00
Nate Graham
f16dea85ed flatpak: add org.kde.kuiserver talk permission
This lets it use the notification infrastructure, e.g. when uploading or
downloading files.
2025-08-01 18:57:28 -06:00
James Graham
b4e1740cad Separate out a base MessageContentModel.
Separate out a base `MessageContentModel` that can be extended to get the component types from different places. This is used currently for `EventMessageContentModel` but will be used later as part of the rich chat bar.

All display text is now in the text component so it never needs special casing. This also cleans up some of the model parameters so more things come from attributes including location and file data (which was already a qvariantmap anyway).

Also cleaned up the itinerary and file enhancement views,
2025-08-01 12:15:51 +01:00
Tobias Fella
501f14fead Fix account switching on logout 2025-08-01 12:27:35 +02:00
Tobias Fella
d14466451d Fix loading 2025-08-01 12:11:35 +02:00
Tobias Fella
f7cd4bd2fb Don't set initial properties that don't exist 2025-08-01 11:43:09 +02:00
Tobias Fella
7742c6d4b0 Fix common crash during login 2025-08-01 09:26:31 +00:00
l10n daemon script
4f6dd50320 GIT_SILENT Sync po/docbooks with svn 2025-08-01 01:44:41 +00:00
Heiko Becker
92f77860dd GIT_SILENT Update Appstream for new release
(cherry picked from commit 699026fc2f)
2025-08-01 00:54:12 +02:00
l10n daemon script
03035b735d GIT_SILENT Sync po/docbooks with svn 2025-07-31 01:38:18 +00:00
Wang Yu
4e0b295f66 Replace duplicate beginResetModel with endResetModel
The initialize method was calling beginResetModel twice without
a corresponding endResetModel call. This could cause model state
inconsistencies.
2025-07-30 16:31:56 +08:00
l10n daemon script
8cbd3f5e0f GIT_SILENT Sync po/docbooks with svn 2025-07-30 01:40:03 +00:00
Joshua Goins
a0b3e484f5 Send thumbnails when uploading videos
NeoChat will now generate thumbnails from the first frame of the video,
since we are loading it anyway. This makes the experience of exchanging
videos in NeoChat much nicer!
2025-07-29 16:41:33 -04:00
Tobias Fella
8ff83ca6df Cleanup QML code for AccountMenu 2025-07-29 20:16:14 +00:00
Joshua Goins
5fe28cb183 Suggest verifying your own device in Devices settings
This button is currently hidden under the user menu, but it should be
shown more prominently - especially under the Devices settings. The
button under the user menu and this new one is now hidden when your
device is already verified.
2025-07-29 15:02:30 -04:00
l10n daemon script
17af4dfddb GIT_SILENT Sync po/docbooks with svn 2025-07-29 01:40:25 +00:00
Tobias Fella
43c6349359 Add translation context 2025-07-28 22:37:28 +02:00
Tobias Fella
265494ee44 Remove unused import 2025-07-28 22:37:05 +02:00
Tobias Fella
a9e4996191 Remove broken code 2025-07-28 22:35:30 +02:00
Tobias Fella
6e3276826d Fix positioning of mobile accounts popup 2025-07-28 22:35:15 +02:00
Tobias Fella
bfe976c438 Remove some unused includes 2025-07-28 21:50:41 +02:00
James Graham
f288367653 Add a dev setting to allow relations to be sent to any message
Add a dev setting to allow relations to be sent to any message using the right click delegate menu. This shouldn't be a main feature, but since it's technically allowed in matrix this will help any future debugging
2025-07-28 19:47:29 +01:00
Tobias Fella
6082bc89b0 Remove "NeoChat" menu item in global menu
BUG: 504429
2025-07-28 15:24:51 +00:00
l10n daemon script
4d3791250b GIT_SILENT Sync po/docbooks with svn 2025-07-28 01:37:39 +00:00
l10n daemon script
04472dae4f GIT_SILENT Sync po/docbooks with svn 2025-07-27 01:40:53 +00:00
Tobias Fella
aa40fc84ea Adapt to power level changes in room version 12 2025-07-26 22:19:07 +00:00
Tobias Fella
24e43d063a Fix test 2025-07-27 00:18:37 +02:00
l10n daemon script
c5caffcdf9 GIT_SILENT Sync po/docbooks with svn 2025-07-25 01:40:12 +00:00
l10n daemon script
95d334ad86 GIT_SILENT Sync po/docbooks with svn 2025-07-23 01:40:50 +00:00
Tobias Fella
602ac5c55f Fix opening EditStateDialog 2025-07-22 21:02:51 +02:00
l10n daemon script
247423bf83 GIT_SILENT Sync po/docbooks with svn 2025-07-22 01:42:47 +00:00
l10n daemon script
24d35b3eae GIT_SILENT Sync po/docbooks with svn 2025-07-21 01:41:16 +00:00
l10n daemon script
8bcd9f7469 GIT_SILENT Sync po/docbooks with svn 2025-07-20 01:41:17 +00:00
Tobias Fella
edf5d55da4 Prepare for new RoomId format
See MSC4291
2025-07-19 13:45:23 +00:00
l10n daemon script
976af783e2 GIT_SILENT Sync po/docbooks with svn 2025-07-19 06:16:11 +00:00
l10n daemon script
d87954838e GIT_SILENT Sync po/docbooks with svn 2025-07-18 01:38:52 +00:00
Joshua Goins
e757331dce Fix reference to Security & Safety settings on the invite screen
It's now called "Security & Safety", not just "Security". I also added a
note for translators to ensure they keep their strings consistent here.
2025-07-17 19:02:37 -04:00
l10n daemon script
bf4f6f5728 GIT_SILENT Sync po/docbooks with svn 2025-07-17 01:38:43 +00:00
Joshua Goins
c73bc8fc29 Redesign the enable encryption room setting
Clients like Element and ours show the room encryption mode as a toggle,
which in my opinion doesn't make sense. It's an irreversible operation,
so it should be a button!

When encryption is already used in the room, the button turns into a
non-interactive card.
2025-07-16 18:23:58 -04:00
Joshua Goins
211a08db68 Add ellipses to various settings actions that have confirm dialogs 2025-07-16 18:23:46 -04:00
Joshua Goins
38987e6d4c Add ellipses to the Report message action
This has a dialog to enter a message associated with the report, so as
suggested by the HIG it should have ellipses.
2025-07-16 18:23:46 -04:00
Joshua Goins
9d76e7e30b Show ellipses for leaving rooms and space actions, and always confirm
The HIG suggests using ellipses for actions that have a confirmation,
and leaving a space or room is one such cases. Otherwise, the user has
no idea if leaving is an immediate, irreversible action.

It turns out there *was* some cases where pressing this button
(especially for spaces) would actually do it without confirmation, which
is now fixed.
2025-07-16 18:23:46 -04:00
Joshua Goins
4c1a8d3657 Show the user's id or a room's canonical alias (if set) in invites
This should prevent the easiest way of masquerading, and provide an \
important identifier for rooms. Note that *only* the room canonical
alias is shown, if it's not set then it's just the display name. This is
intentional as regular users rarely interact with room IDs, but they can
still check it elsewhere in NeoChat.
2025-07-16 18:08:36 -04:00
James Graham
7a5de25885 Further refactor MessageContentModel
Further refactor MessageContentModel. Move away from special casing certian MessageContentTypes. Use forEachComponentOfType more
2025-07-16 18:27:03 +01:00
l10n daemon script
a17aa2c6fa GIT_SILENT Sync po/docbooks with svn 2025-07-16 01:56:47 +00:00
Tobias Fella
207a7876b6 Improve visualization of replies to non-message events 2025-07-15 19:55:35 +00:00
Tobias Fella
4c638a740e Set object ownership for NeoChatRoomMembers 2025-07-15 19:54:24 +00:00
Joshua Goins
0ee89e1b2b Show unable to decrypt events in the room list
These were previously (unintentionally) filtered, but I wanted to add
them because without showing and caching them my DMs look extremely out
of order. And assuming that you get an unexpected "unable to decrypt"
event, you would want that room to "shoot to the top" anyway.
2025-07-15 19:14:54 +00:00
Joshua Goins
4af42a57f4 Fix undefined QML reference in TimelineView
markAllMessagesAsRead() has moved it seems, and this specific case
wasn't changed.
2025-07-15 19:14:41 +00:00
Joshua Goins
34f2c2dabc Stop a room's lastActiveTime from changing because of hidden events
This was just a missed oversight during the refactoring, we need to pass
the hidden filter into NeoChatRoom::lastEvent so it doesn't pick up on
hidden events and push rooms to the top for no discernible reason.

Also, I simplified the function to take out the cached event handling,
because lastEvent is already doing that. The function is const now, too
and has some nicer comments.
2025-07-15 14:44:23 -04:00
l10n daemon script
9ff942915a GIT_SILENT Sync po/docbooks with svn 2025-07-15 01:44:46 +00:00
Tobias Fella
10123abc5b Fix maximizing replied-to media
The previous index-based handling opened the wrong media, as it used the wrong index.
2025-07-14 20:16:34 +00:00
l10n daemon script
ad993d4340 GIT_SILENT Sync po/docbooks with svn 2025-07-14 01:49:19 +00:00
l10n daemon script
ddc0a66d5b GIT_SILENT Sync po/docbooks with svn 2025-07-13 01:40:25 +00:00
l10n daemon script
e8981bdc0f GIT_SILENT Sync po/docbooks with svn 2025-07-12 01:42:05 +00:00
l10n daemon script
c42486a061 GIT_SILENT Sync po/docbooks with svn 2025-07-11 01:40:27 +00:00
l10n daemon script
64d82b8d2a GIT_SILENT Sync po/docbooks with svn 2025-07-10 01:40:11 +00:00
l10n daemon script
677abee890 GIT_SILENT Sync po/docbooks with svn 2025-07-09 01:42:04 +00:00
Joshua Goins
3a25a62350 Refer to global notification settings as "Default Settings" instead
This changes references to "global notification settings" to "default
settings", which should hopefully make it clearer what these actually
are.

Also, a information tip has been added to the settings page to clarify
what these settings do and imply that the per-room settings take
precedence.
2025-07-08 17:17:04 -04:00
l10n daemon script
bc7b480c41 GIT_SILENT Sync po/docbooks with svn 2025-07-08 01:40:16 +00:00
Tobias Fella
d9b495356d Fix roomlist positioning when changing rooms
The room list is supposed to center the new room, but, since the current room as stored in SortFilterRoomTreeModel is only updated after we're jumping to the room, mostly just jumped to the old room.
2025-07-07 18:31:40 +00:00
Tobias Fella
ce82606e6e Fix jumping to replied-to event 2025-07-07 14:20:02 +00:00
l10n daemon script
07837c2e64 GIT_SILENT Sync po/docbooks with svn 2025-07-07 01:38:29 +00:00
Tobias Fella
1738253e6f Don't crash when calling RoomManager::viewEventMenu with empty event id 2025-07-06 23:14:28 +02:00
James Graham
17fa2246da Refine MessageContentModel further
Create some standard functions that should hopefully make it easier to manage the model contents. Used for file stuff for now.
2025-07-06 21:16:17 +01:00
l10n daemon script
4f5e096e7e GIT_SILENT Sync po/docbooks with svn 2025-07-06 01:37:58 +00:00
Albert Astals Cid
b125c284bd GIT_SILENT Upgrade release service version to 25.11.70. 2025-07-05 11:59:46 +02:00
l10n daemon script
87213903b1 GIT_SILENT Sync po/docbooks with svn 2025-07-05 01:39:27 +00:00
James Graham
3d9d211d25 Allow the condition for when messages are automatically marked as read to be configurable.
Title this adds a number of options for when messages should be automatically marked as read for the user to choose from.

![image](/uploads/cef95f8c6c77bfdcabb7a8a309bc1fd2/image.png){width=480 height=262}
2025-07-04 14:36:36 +01:00
James Graham
d5d291396d MessageContentModel improvements
Remove the hack for DelegateChooser and minimise the amount of updates when data changes
2025-07-04 10:28:50 +01:00
l10n daemon script
d7cd38b5e5 GIT_SILENT Sync po/docbooks with svn 2025-07-04 01:39:05 +00:00
l10n daemon script
1a25cfc0e3 GIT_SILENT Sync po/docbooks with svn 2025-07-03 07:51:46 +00:00
l10n daemon script
8d9669e033 GIT_SILENT Sync po/docbooks with svn 2025-07-02 11:07:52 +00:00
l10n daemon script
690d2e2b12 GIT_SILENT Sync po/docbooks with svn 2025-07-02 08:01:01 +00:00
l10n daemon script
2208b1b2b3 GIT_SILENT Sync po/docbooks with svn 2025-07-02 01:40:33 +00:00
Joshua Goins
67da272ccd Add feature flag for the new calls feature
This will be experimental for some time, so we need a way to turn it
on/off for future stable builds.
2025-07-01 14:52:46 -04:00
Joshua Goins
171930fb1d Show a message when failing to download a file
And optionally show an error message, if libQuotient gets one. This
helps in certain rare situations where you just *Cannot* download
something. Before, NeoChat would just reset the image or video without
telling you anything but now there's a nice big error message telling
you what's wrong.
2025-07-01 06:53:59 -04:00
l10n daemon script
2bd59fa16f GIT_SILENT Sync po/docbooks with svn 2025-07-01 01:40:14 +00:00
Joshua Goins
d0d0384bdb Improve the look of loading videos
The video thumbnail is no longer hidden while loading. This makes it
look less buggy (like NeoChat forgot what the thumbnail was) while
loading a large video. To help with contrast, a slight background tint
is added.
2025-06-30 17:21:40 -04:00
James Graham
680573de61 Make sure that thread replies aren't hidden when threads are off 2025-06-30 19:34:27 +01:00
Heiko Becker
ba7f866cf5 GIT_SILENT Update Appstream for new release
(cherry picked from commit 7e87d415ae)
2025-06-30 18:57:23 +02:00
Tobias Fella
57a95ae972 Show nicer text for call member events 2025-06-30 17:17:47 +02:00
Marco Martin
de97275a38 Use new Kirigami builtin column resize handle
Use the new Kirigami.ColumnView.interactiveResizeEnabled attached property
which allows to have a built in resize handle for ColumnView pages
removing the need for a custom handle with resize logic

depends from https://invent.kde.org/frameworks/kirigami/-/merge_requests/1795
2025-06-30 11:25:28 +02:00
l10n daemon script
d08181f56d GIT_SILENT Sync po/docbooks with svn 2025-06-30 01:41:18 +00:00
James Graham
f6e8491bf1 Split message content into its own module
This is laying some groundwork for the rich text chatbar.
2025-06-29 12:43:48 +01:00
l10n daemon script
a1447ebd6f GIT_SILENT Sync po/docbooks with svn 2025-06-29 01:44:03 +00:00
l10n daemon script
3b67e4deaf GIT_SILENT Sync po/docbooks with svn 2025-06-28 01:39:59 +00:00
James Graham
e55afd0402 Move ImageEditorPage to Chatbar module 2025-06-27 16:25:57 +01:00
James Graham
7c0710d445 ActionToolbar is no longer needed now there is only 1 button 2025-06-27 16:08:30 +01:00
James Graham
61f9cd41f7 Initial Account
Restore the functionality where clicking on "edit this account" from the AccountMenu opens setting to the account rather than pushing as its own window
2025-06-27 16:08:14 +01:00
l10n daemon script
6c7a7a7be5 GIT_SILENT Sync po/docbooks with svn 2025-06-27 01:41:18 +00:00
James Graham
63125d97c3 Fix hiding down button on timeline when at the bottom 2025-06-26 14:38:16 +01:00
James Graham
cb4e7d4943 Fix room list header when collapsed 2025-06-26 14:34:18 +01:00
l10n daemon script
b9d5ed699e GIT_SILENT Sync po/docbooks with svn 2025-06-26 01:41:04 +00:00
l10n daemon script
75fe5c8970 GIT_SILENT Sync po/docbooks with svn 2025-06-25 01:42:54 +00:00
Aleix Pol
91da2d01b7 UserInfo: Move the switch user action to the menu
It doesn't clutter the main UI.
It allows us to see the keyboard shortcut.
2025-06-23 23:19:05 +00:00
l10n daemon script
4bade72ce4 GIT_SILENT Sync po/docbooks with svn 2025-06-23 01:39:47 +00:00
l10n daemon script
9c05e2feed GIT_SILENT Sync po/docbooks with svn 2025-06-22 01:45:29 +00:00
l10n daemon script
ba94098411 GIT_SILENT Sync po/docbooks with svn 2025-06-21 01:41:18 +00:00
James Graham
43c691160e Update the RoomTreeSection to use standard Kirigami.ListSectionHeader 2025-06-20 07:42:43 +01:00
James Graham
e2daa091e8 Switch section delegate for standard Kirigami.ListSectionHeader
Switch section delegate for standard Kirigami.ListSectionHeader, SectionDelegate is no longer required so removed.
2025-06-20 07:42:14 +01:00
l10n daemon script
f026414b1a GIT_SILENT Sync po/docbooks with svn 2025-06-20 01:39:04 +00:00
l10n daemon script
fd640a4bd0 GIT_SILENT Sync po/docbooks with svn 2025-06-19 01:39:06 +00:00
l10n daemon script
dd433d7f99 GIT_SILENT Sync po/docbooks with svn 2025-06-18 01:39:30 +00:00
James Graham
3183e00acc Fix Use After Free MessageDelegateBase
Make sure that a `MessageDelegateBase` is not used after free by a `MessageObjectIncubator` callback by tracking them and cleaning them up on deletion of a `MessageDelegateBase`
2025-06-17 17:19:38 +01:00
James Graham
1860de12ea RoomPage cleanup
This does some further cleanup of RoomPage, mostly removing all the vestigial bits from when we could have multiple windows. But also stuff is moved to TimelineView where possible.

The loading placeholder is removed as TimelineModel already has this built in.

TimelineView now gets room from it's model. This is to ensure we're always using the same room as it which may not be true momentarily when RoomManager.currentRoom changes as the model does it's own reset sequence. This will prevent some race conditions in future (and which I already hit creating other MRs)
2025-06-17 16:41:38 +01:00
l10n daemon script
277f1ab252 GIT_SILENT Sync po/docbooks with svn 2025-06-17 01:40:15 +00:00
Thiago Sueto
324b332fa6 Add confirmation prompt to reset all configuration
Resetting all configuration is destructive, it should require a confirmation prompt instead of triggering immediately.
2025-06-16 18:00:06 +00:00
James Graham
33d29f6b02 Fix jump to last message button trigger, the list view is no longer root. 2025-06-16 18:04:25 +01:00
l10n daemon script
419aed6375 GIT_SILENT Sync po/docbooks with svn 2025-06-16 01:39:23 +00:00
Aleix Pol
7bb26ac3be main: Add a replace option
Much like many other unique KDE components, add --replace so the running
instance can be replaced by the new one. Useful for development.
2025-06-15 23:07:40 +02:00
Aleix Pol
7654333ec1 ImageComponent: Can't make a qreal undefined
Using -1 because MediaSizeHelper is already handling negative numbers as
such.
2025-06-15 23:07:40 +02:00
Aleix Pol
d3a2da391d flatpak: Use the Qt 6.9 runtime
It's all the fresh!!
2025-06-15 23:07:40 +02:00
Aleix Pol
f19f59c37f EmojiPicker: Fix delegate for stickers
There's no avatarUrl in ImagePacksModel, it's just url.
2025-06-15 23:07:40 +02:00
Aleix Pol
94e53e14a3 UserInfo: Use "Account" naming consistently
We use "Account" in the settings so we better use the same concept
across the UI.
2025-06-15 23:06:15 +02:00
Manuel Alcaraz Zambrano
d2b3788872 Remove QCoro from Flatpak
QCoro is already available on the runtime.
2025-06-15 12:07:24 +02:00
l10n daemon script
8095062db2 GIT_SILENT Sync po/docbooks with svn 2025-06-15 01:40:14 +00:00
Tobias Fella
89beaa9316 Remove stray warning 2025-06-15 00:29:34 +02:00
Carl Schwan
dfaffd043e Cleanup craft config
Don't build master version of some modules
2025-06-14 12:13:31 +00:00
Tobias Fella
a8c200222f Don't parent PollHandler to room
PollHandlers are stored in a QCache, which takes over ownership of the object. At the same time, PollHandler currently relies on its parent being the room.
Somehow, this didn't explode entirely, but only leads to minor problems like crashes on shutdown.
2025-06-14 12:13:16 +00:00
Tobias Fella
598a1c28ac Add button for hiding images and videos 2025-06-14 12:12:01 +00:00
Tobias Fella
b972703f34 Refactor and fix opening file-based content
The relevant fix is checking whether the the event is file-*based* (i.e., image, video, audio, file), not whether it *is* a file
2025-06-14 12:07:56 +00:00
Tobias Fella
d30fdc67c6 Remove file logging
This hasn't proven to be as useful as i had hoped:
- My arcane logic for determining logging categories is apparently broken
- It won't work with the logging by the new crypto sdk
- I never actually ended up looking at my own logs, or anyone else's
- It seems to cause crashes
2025-06-14 11:57:23 +00:00
James Graham
6a5a2e6144 Refactor TimelineView
Refactor TimelineView to make it more reliable and prepare for read marker choice. This is done by creating signalling from the mode when reset which can be used to move the scrollbar to the newest meassage.

Some of the spaghetti is also removed so there is no need for ChatBar and TimelineView to talk directly.

The code to mark messages as read if they are all visible after 10s has been removed infour of just marking as read on entry if all are visible. This is temporary until a follow up providing user options is finished (although it will be one of the options)
2025-06-14 12:16:39 +01:00
Kai Uwe Broulik
235143528c MaximizeComponent: Fix download location
No idea where "saveFolder" was supposed to be coming from but this
makes it use the last location or default to downloads, consistent
with the other save dialogs.
2025-06-14 10:08:12 +02:00
l10n daemon script
33ca2b8d09 GIT_SILENT Sync po/docbooks with svn 2025-06-14 01:40:03 +00:00
Aleix Pol
50176cfafb ImageComponent: Do not set undefined to int
An int property cannot be undefined, make it var.
2025-06-13 15:46:30 +00:00
Aleix Pol
a67fb36a84 SpaceHierarchyDelegate: Fix opening rooms
RoomManager is in org.kde.neochat and needs importing
2025-06-13 15:46:30 +00:00
Aleix Pol
aea9984187 DevicesModel: Do not connect until the value is initialised
When the DevicesModel is open, we get a nullptr instance. I guess it's
obvious that it's null because it hasn't been set yet. Move the connect
to the setter.
2025-06-13 15:46:30 +00:00
Freya Lupen
7a6c234b40 macOS: Set app bundle properties
This fixes the app's name being blank in the system.
2025-06-13 08:25:45 +00:00
Freya Lupen
4cdaa513d3 Use Breeze style on macOS, as on other platforms 2025-06-13 08:25:45 +00:00
l10n daemon script
aaf655ea5b GIT_SILENT Sync po/docbooks with svn 2025-06-13 01:39:47 +00:00
Tobias Fella
207c824ec7 Use QML safe member for invitations
Has the sideeffect of making sure that the avatar colors are the same between the invitation view and the sidebar
2025-06-12 12:49:01 +00:00
Tobias Fella
29abe0bacb Use ImageComponent for replies
This automatically gives us the image hiding safety feature

BUG: 503442
2025-06-12 09:04:52 +00:00
Tobias Fella
d14eda2ca0 Hide avatar of inviting user when images are hidden by default
While this reuses the setting for a slightly different purpose, in practice, these safety feature really belong together and it makes sense to have them both under a single option.
In the future, we might want to rephrase the options description
2025-06-11 22:42:49 +02:00
l10n daemon script
0425cf41d0 GIT_SILENT Sync po/docbooks with svn 2025-06-11 01:45:08 +00:00
l10n daemon script
4ff014f288 GIT_SILENT Sync po/docbooks with svn 2025-06-09 01:42:48 +00:00
l10n daemon script
2e24500866 GIT_SILENT Sync po/docbooks with svn 2025-06-08 01:41:02 +00:00
Joshua Goins
f13a256150 Refactor receiveRichPlainUrl text handler test to be data-based
I was originally going to add another test case to this, turns out I
didn't need to. Might as well ship this anyway, as it will make it
trivial to add more in the future.
2025-06-07 07:30:28 -04:00
l10n daemon script
c958e8cba9 GIT_SILENT Sync po/docbooks with svn 2025-06-06 01:38:09 +00:00
Joshua Goins
7668da68d3 Allow accepting reason dialogs with Ctrl+Enter
The original suggestion was the Enter key, but this won't work well as
reasons can take multiple lines. I also made sure the reason control was
focused by default, and that the "Cancel" button has an icon.

BUG: 500990
2025-06-05 12:03:37 +00:00
Heiko Becker
c478a0c0fc GIT_SILENT Update Appstream for new release
(cherry picked from commit 11ebe5316f)
2025-06-02 23:46:47 +02:00
l10n daemon script
fe60a08817 GIT_SILENT Sync po/docbooks with svn 2025-06-02 01:39:15 +00:00
James Graham
dec5369a8f Actually position the view at the end
It turns out that for whatever reason ListView.posiitionViewAtEnd() ignores any whitespace. This means that when we use the function it goes to the last delegate. This is no good as we have some padding at the bottom to make space for the typing indicator.

So the fix for this is stupid and involves adding a "spacer" delegate to the timeline beginning model which is completely invisible but qml see as a delegate so we can both leave the space and properly position the view at the end.

BUG: 501075
2025-06-01 10:37:30 +01:00
Joshua Goins
5ec0b9393e Fix showPassiveNotification call in Account Editor
I hate this thing, I think the standalone account editor was fine, but
it didn't work when I went through account settings. Now that should be
fixed.
2025-06-01 04:51:18 +02:00
362 changed files with 89077 additions and 67695 deletions

2
.contextProperties.ini Normal file
View File

@@ -0,0 +1,2 @@
[General]
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"

View File

@@ -2,7 +2,5 @@
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/frameworks/extra-cmake-modules.version=master
kde/unreleased/kirigami-addons.version=master
kde/applications/neochat.packageAppx=True
libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.8",
"runtime-version": "6.9",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -20,8 +20,16 @@
"--talk-name=org.kde.kwalletd5",
"--talk-name=org.kde.StatusNotifierWatcher",
"--talk-name=org.freedesktop.secrets",
"--talk-name=org.kde.kuiserver",
"--own-name=org.kde.StatusNotifierItem-2-2"
],
"cleanup": [
"/include",
"/lib/*.a",
"/lib/cmake",
"/lib/pkgconfig",
"/share/ndk-modules"
],
"modules": [
{
"name": "kirigamiaddons",
@@ -36,6 +44,22 @@
}
]
},
{
"name": "opencv",
"config-opts": [
"-DBUILD_TESTS=OFF",
"-DWITH_GTK=OFF",
"-DBUILD_LIST=core,imgproc"
],
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://github.com/opencv/opencv"
}
],
"builddir": true
},
{
"name": "kquickimageeditor",
"config-opts": [
@@ -82,8 +106,8 @@
"sources": [
{
"type": "archive",
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.7.tar.xz",
"sha256": "6b452e4750590a2b5617adc40026f28d2f4903de15f1250e1d1c40bfd68ed55e",
"x-checker-data": {
"type": "gnome",
"name": "libsecret",
@@ -150,40 +174,23 @@
"builddir": true
},
{
"name": "qcoro",
"name": "kunifiedpush",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
"-DENABLE_TESTING=OFF",
"-DKUNIFIEDPUSH_CLIENT_ONLY=ON"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
"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": 236236,
"project-id": 8763,
"stable-only": true,
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
}
}
]
},
{
"name": "kunifiedpush",
"buildsystem": "cmake-ninja",
"builddir": true,
"sources": [
{
"type": "archive",
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf",
"x-checker-data": {
"type": "anitya",
"project-id": 375055,
"stable-only": true,
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz"
"url-template": "https://download.kde.org/stable/release-service/$version/src/kunifiedpush-$version.tar.xz"
}
}
]
@@ -199,6 +206,14 @@
{
"type": "dir",
"path": "."
},
{
"type": "patch",
"path": "patches/0001-Revert-Bump-KF6-dependency-version.patch"
},
{
"type": "patch",
"path": "patches/0001-Revert-Use-new-Kirigami-builtin-column-resize-handle.patch"
}
]
}

View File

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

View File

@@ -8,14 +8,14 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MINOR "11")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "6.12")
set(QT_MIN_VERSION "6.5")
set(KF_MIN_VERSION "6.17")
set(QT_MIN_VERSION "6.8")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)

View File

@@ -88,3 +88,9 @@ path = "memorytests/memtest-sync.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ".contextProperties.ini"
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"

View File

@@ -92,3 +92,15 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME actionstest
)
ecm_add_test(
servernoticestest.cpp
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME servernoticestest
)
ecm_add_test(
roommanagertest.cpp
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME roommanagertest
)

View File

@@ -6,6 +6,7 @@
#include <QObject>
#include <QTest>
#include <QSignalSpy>
#include <Quotient/roommember.h>
#include <Quotient/syncdata.h>
#include <qtestcase.h>
@@ -32,6 +33,7 @@ private Q_SLOTS:
void noRoom();
void badParent();
void reply();
void replyMissingUser();
void edit();
void attachment();
};
@@ -102,6 +104,33 @@ void ChatBarCacheTest::reply()
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);
}
void ChatBarCacheTest::replyMissingUser()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s);
chatBarCache->setAttachmentPath(u"some/path"_s);
chatBarCache->setReplyId(u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
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
room->syncNewEvents(u"test-min-sync-extra-sync.json"_s);
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
}
void ChatBarCacheTest::edit()

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

@@ -130,7 +130,8 @@ void EventHandlerTest::timeString()
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::UTC)).toString(u"hh:mm"_s));
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);

View File

@@ -10,7 +10,7 @@
#include <Quotient/roommember.h>
#include <Quotient/syncdata.h>
#include "models/messagecontentmodel.h"
#include "models/eventmessagecontentmodel.h"
#include "neochatconnection.h"
#include "testutils.h"
@@ -39,17 +39,17 @@ void MessageContentModelTest::initTestCase()
void MessageContentModelTest::missingEvent()
{
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto model1 = MessageContentModel(room, u"$153456789:example.org"_s);
auto model1 = EventMessageContentModel(room, u"$153456789:example.org"_s);
QCOMPARE(model1.rowCount(), 1);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
auto model2 = MessageContentModel(room, u"$153456789:example.org"_s, true);
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
QCOMPARE(model2.rowCount(), 1);
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
room->syncNewEvents(u"test-min-sync.json"_s);
QCOMPARE(model1.rowCount(), 2);

View File

@@ -9,7 +9,7 @@
#include <Quotient/events/roommessageevent.h>
#include "models/messagecontentmodel.h"
#include "models/eventmessagecontentmodel.h"
#include "testutils.h"
using namespace Quotient;
@@ -21,7 +21,7 @@ class ReactionModelTest : public QObject
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
MessageContentModel *parentModel;
EventMessageContentModel *parentModel;
private Q_SLOTS:
void initTestCase();
@@ -34,7 +34,7 @@ void ReactionModelTest::initTestCase()
{
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
parentModel = new MessageContentModel(room, "123456"_L1);
parentModel = new EventMessageContentModel(room, "123456"_L1);
}
void ReactionModelTest::basicReaction()

View File

@@ -0,0 +1,141 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <QVariantList>
#include "accountmanager.h"
#include "models/actionsmodel.h"
#include "roommanager.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
class RoomManagerTest : public QObject
{
Q_OBJECT
private:
NeoChatConnection *connection = nullptr;
NeoChatRoom *room = nullptr;
Server server;
private Q_SLOTS:
void initTestCase();
void testMaximizeMedia();
void testResolveMatrixLinks();
};
void RoomManagerTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
QVERIFY(connection);
auto roomId = server.createRoom(u"@user:localhost:1234"_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);
RoomManager::instance().setConnection(connection);
QSignalSpy roomSpy(&RoomManager::instance(), &RoomManager::currentRoomChanged);
RoomManager::instance().resolveResource(room->id());
QVERIFY(roomSpy.size() > 0);
}
void RoomManagerTest::testMaximizeMedia()
{
QSignalSpy spy(&RoomManager::instance(), &RoomManager::showMaximizedMedia);
QSignalSpy syncSpy(connection, &Connection::syncDone);
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for empty event id");
RoomManager::instance().maximizeMedia(QString());
QVERIFY(!spy.wait(10));
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for unknown event id \"Doesn't exist\"");
RoomManager::instance().maximizeMedia(u"Doesn't exist"_s);
QVERIFY(!spy.wait(10));
const auto eventWithoutMedia = server.sendEvent(room->id(),
u"m.room.message"_s,
QJsonObject({
{u"body"_s, u"Foo"_s},
{u"format"_s, u"org.matrix.custom.html"_s},
{u"formatted_body"_s, u"Foo"_s},
{u"msgtype"_s, u"m.text"_s},
}));
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(eventWithoutMedia).toLatin1().data());
RoomManager::instance().maximizeMedia(eventWithoutMedia);
QVERIFY(!spy.wait(10));
// NOTE: This is supposed to test that maximizing pending media works correctly. This probably doesn't work in the UI yet, but at least the backend supports
// it. If the server ever learns how to process events, this becomes pointless and we need to find a way of preventing *these* events from arriving
auto pendingEventWithoutMedia = room->postText(u"Hello"_s);
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(pendingEventWithoutMedia).toLatin1().data());
RoomManager::instance().maximizeMedia(pendingEventWithoutMedia);
QVERIFY(!spy.wait(10));
const auto eventWithMedia = server.sendEvent(room->id(),
u"m.room.message"_s,
QJsonObject({
{u"body"_s, u"Foo"_s},
{u"filename"_s, u"foo.jpg"_s},
{u"info"_s,
QJsonObject{
{u"h"_s, 1000},
{u"w"_s, 2000},
{u"size"_s, 10000},
{u"mimetype"_s, u"image/png"_s},
}},
{u"msgtype"_s, u"m.image"_s},
{u"url"_s, u"mxc://foo.bar/asdf"_s},
}));
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
RoomManager::instance().maximizeMedia(eventWithMedia);
QVERIFY(spy.size() == 1);
QVERIFY(spy[0][0] == 0);
auto pendingEventWithMedia = room->postJson(u"m.room.message"_s,
QJsonObject({
{u"body"_s, u"Foo"_s},
{u"filename"_s, u"foo.jpg"_s},
{u"info"_s,
QJsonObject{
{u"h"_s, 1000},
{u"w"_s, 2000},
{u"size"_s, 10000},
{u"mimetype"_s, u"image/png"_s},
}},
{u"msgtype"_s, u"m.image"_s},
{u"url"_s, u"mxc://foo.bar/asdf"_s},
}));
RoomManager::instance().maximizeMedia(pendingEventWithMedia);
QVERIFY(spy.size() == 2);
QVERIFY(spy[1][0] == 0);
}
void RoomManagerTest::testResolveMatrixLinks()
{
// Test if resolving a non-joined room will bring up the confirmation dialog.
const QSignalSpy askToJoinSpy(&RoomManager::instance(), &RoomManager::askJoinRoom);
RoomManager::instance().resolveResource(QStringLiteral("matrix:r/testbuild:matrix.org"), QStringLiteral("join"));
QTRY_COMPARE(askToJoinSpy.size(), 1);
}
QTEST_MAIN(RoomManagerTest)
#include "roommanagertest.moc"

View File

@@ -109,98 +109,20 @@ void Server::start()
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
QHttpServerRequest::Method::Post,
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
Changes changes;
changes.invitations += Changes::InviteUser{
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
.roomId = roomId,
};
m_state += changes;
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
QMap<QString, QJsonArray> stateEvents;
for (const auto &[roomId, matrixId] : m_roomsToCreate) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, matrixId},
{u"state_key"_s, QString()},
{u"type"_s, u"m.room.create"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, matrixId},
{u"state_key"_s, matrixId},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
m_roomsToCreate.clear();
for (const auto &roomId : m_invitedUsers.keys()) {
const auto &values = m_invitedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_invitedUsers.clear();
for (const auto &roomId : m_bannedUsers.keys()) {
const auto &values = m_bannedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_bannedUsers.clear();
for (const auto &roomId : m_joinedUsers.keys()) {
const auto &values = m_joinedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_joinedUsers.clear();
QJsonObject rooms;
for (const auto &roomId : stateEvents.keys()) {
rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
}
responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
QSslConfiguration config;
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
key.open(QFile::ReadOnly);
void(key.open(QFile::ReadOnly));
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
m_sslServer.setSslConfiguration(config);
@@ -214,22 +136,239 @@ void Server::start()
QString Server::createRoom(const QString &matrixId)
{
auto roomId = generateRoomId();
m_roomsToCreate += {roomId, matrixId};
const auto roomId = generateRoomId();
Changes changes;
changes.newRooms += Changes::NewRoom{
.initialMembers = {matrixId},
.roomId = {roomId},
.tags = {},
};
m_state += changes;
return roomId;
}
void Server::inviteUser(const QString &roomId, const QString &matrixId)
{
m_invitedUsers[roomId] += matrixId;
Changes changes;
changes.invitations += Changes::InviteUser{
.userId = matrixId,
.roomId = roomId,
};
m_state += changes;
}
void Server::banUser(const QString &roomId, const QString &matrixId)
{
m_bannedUsers[roomId] += matrixId;
Changes changes;
changes.bans += Changes::BanUser{
.userId = matrixId,
.roomId = roomId,
};
m_state += changes;
}
void Server::joinUser(const QString &roomId, const QString &matrixId)
{
m_joinedUsers[roomId] += matrixId;
Changes changes;
changes.joins += Changes::JoinUser{
.userId = matrixId,
.roomId = roomId,
};
m_state += changes;
}
QString Server::createServerNoticesRoom(const QString &matrixId)
{
const auto roomId = generateRoomId();
Changes changes;
changes.newRooms += Changes::NewRoom{
.initialMembers = {matrixId},
.roomId = {roomId},
.tags = {u"m.server_notice"_s},
};
m_state += changes;
return roomId;
}
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
{
Changes changes;
const auto eventId = generateEventId();
changes.events += Changes::Event{
.fullJson = 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}},
};
m_state += changes;
return eventId;
}
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
{
QJsonObject joinRooms;
auto token = request.query().queryItemValue(u"since"_s).toInt();
for (const auto &change : m_state.mid(token)) {
for (const auto &newRoom : change.newRooms) {
QJsonArray stateEvents;
stateEvents += QJsonObject{
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, newRoom.roomId},
{u"sender"_s, newRoom.initialMembers[0]},
{u"state_key"_s, QString()},
{u"type"_s, u"m.room.create"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
for (const auto &member : newRoom.initialMembers) {
stateEvents += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, newRoom.roomId},
{u"sender"_s, member},
{u"state_key"_s, member},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
QJsonArray roomAccountData;
QJsonObject tags;
for (const auto &tag : newRoom.tags) {
tags[tag] = QJsonObject();
}
if (!tags.empty()) {
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
}
if (roomAccountData.size() > 0) {
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
}
joinRooms[newRoom.roomId] = room;
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &invitation : change.invitations) {
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
stateEvents += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, invitation.roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, invitation.userId},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
if (joinRooms.contains(invitation.roomId)) {
auto room = joinRooms[invitation.roomId].toObject();
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
joinRooms[invitation.roomId] = room;
} else {
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
QJsonObject{
{u"events"_s, stateEvents},
}}};
}
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &ban : change.bans) {
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
stateEvents += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, ban.roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, ban.userId},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
if (joinRooms.contains(ban.roomId)) {
auto room = joinRooms[ban.roomId].toObject();
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
joinRooms[ban.roomId] = room;
} else {
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
QJsonObject{
{u"events"_s, stateEvents},
}}};
}
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &join : change.joins) {
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
stateEvents += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, join.roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, join.userId},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
if (joinRooms.contains(join.roomId)) {
auto room = joinRooms[join.roomId].toObject();
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
joinRooms[join.roomId] = room;
} else {
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
QJsonObject{
{u"events"_s, stateEvents},
}}};
}
}
}
for (const auto &change : m_state.mid(token)) {
for (const auto &event : change.events) {
// TODO the room might be in a different join state.
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
timeline += event.fullJson;
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
} else {
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
};
}
}
}
QJsonObject syncData = {
// {u"account_data"_s, QJsonObject {}},
// {u"presence"_s, QJsonObject {}},
{u"next_batch"_s, QString::number(m_state.size())},
};
QJsonObject rooms;
if (!joinRooms.isEmpty()) {
rooms[u"join"_s] = joinRooms;
}
if (!rooms.empty()) {
syncData[u"rooms"_s] = rooms;
}
qWarning() << syncData;
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
}

View File

@@ -2,10 +2,51 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
#include <QHttpServer>
#include <QJsonObject>
#include <QSslServer>
class Server
struct Changes {
struct NewRoom {
QStringList initialMembers;
QString roomId;
QStringList tags;
};
QList<NewRoom> newRooms;
struct InviteUser {
QString userId;
QString roomId;
};
QList<InviteUser> invitations;
struct BanUser {
QString userId;
QString roomId;
};
QList<BanUser> bans;
struct JoinUser {
QString userId;
QString roomId;
};
QList<JoinUser> joins;
struct Event {
QJsonObject fullJson;
};
QList<Event> events;
};
struct RoomData {
QStringList members;
QString id;
QStringList tags;
};
class Server : public QObject
{
Q_OBJECT
public:
Server();
@@ -21,13 +62,17 @@ public:
void banUser(const QString &roomId, const QString &matrixId);
void joinUser(const QString &roomId, const QString &matrixId);
/**
* Create a server notices room.
*/
QString createServerNoticesRoom(const QString &matrixId);
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
private:
QHttpServer m_server;
QSslServer m_sslServer;
QHash<QString, QList<QString>> m_invitedUsers;
QHash<QString, QList<QString>> m_bannedUsers;
QHash<QString, QList<QString>> m_joinedUsers;
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
QList<std::pair<QString, QString>> m_roomsToCreate;
QList<Changes> m_state;
};

View File

@@ -0,0 +1,87 @@
// 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 <QObject>
#include <QSignalSpy>
#include <QTest>
#include <KLocalizedString>
#include <Quotient/connection.h>
#include <Quotient/eventstats.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "accountmanager.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
class ServerNoticesTest : public QObject
{
Q_OBJECT
private:
NeoChatConnection *connection = nullptr;
Server server;
private Q_SLOTS:
void initTestCase();
void test();
};
void ServerNoticesTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
QVERIFY(connection);
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
RoomManager::instance().setConnection(connection);
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());
auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
}
void ServerNoticesTest::test()
{
auto roomTreeModel = RoomManager::instance().roomTreeModel();
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 0);
auto sortFilterRoomTreeModel = RoomManager::instance().sortFilterRoomTreeModel();
const auto roomId = server.createServerNoticesRoom(u"@user:localhost:1234"_s);
QSignalSpy syncSpy(connection, &Connection::syncDone);
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
const auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(connection->room(roomId)->isServerNoticeRoom());
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 1);
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
server.sendEvent(roomId,
u"m.room.message"_s,
QJsonObject{
{u"body"_s, u"Foo"_s},
{u"format"_s, u"org.matrix.custom.html"_s},
{u"formatted_body"_s, u"Foo"_s},
{u"msgtype"_s, u"m.text"_s},
});
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
sortFilterRoomTreeModel->invalidate();
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 0);
room->markAllMessagesAsRead();
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
}
QTEST_GUILESS_MAIN(ServerNoticesTest)
#include "servernoticestest.moc"

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QTest>
#include <Quotient/events/event.h>
#include <Quotient/syncdata.h>
@@ -32,7 +33,7 @@ public:
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
update(std::move(roomData));
@@ -46,7 +47,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
if (!eventFileName.isEmpty()) {
QFile testEventFile;
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly);
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson);
}

View File

@@ -34,6 +34,10 @@ private Q_SLOTS:
void stripDisallowedTags();
void stripDisallowedAttributes();
void emptyCodeTags();
void addStyle_data();
void addStyle();
void dontAddStyle_data();
void dontAddStyle();
void sendSimpleStringCase();
void sendSingleParaMarkup();
@@ -61,6 +65,7 @@ private Q_SLOTS:
void receiveRichStrikethrough();
void receiveRichtextIn();
void receiveRichMxcUrl();
void receiveRichPlainUrl_data();
void receiveRichPlainUrl();
void receiveRichEdited_data();
void receiveRichEdited();
@@ -70,6 +75,9 @@ private Q_SLOTS:
void componentOutput_data();
void componentOutput();
void updateSpoiler_data();
void updateSpoiler();
};
void TextHandlerTest::initTestCase()
@@ -88,21 +96,26 @@ void TextHandlerTest::initTestCase()
void TextHandlerTest::allowedAttributes()
{
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
const QString testOutputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
const QString testOutputString1S = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
const QString testOutputString1R = u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
theme->alternateBackgroundColor().name());
// Handle urls where the href has either single (') or double (") quotes.
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
const QString testOutputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
const QString testOutputString2S = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
const QString testOutputString2R =
u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a><a href='https://kde.org' style=\"text-decoration: none;\">link</a>"_s;
TextHandler testTextHandler;
testTextHandler.setData(testInputString1);
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
QCOMPARE(testTextHandler.handleSendText(), testOutputString1S);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1R);
testTextHandler.setData(testInputString2);
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
QCOMPARE(testTextHandler.handleSendText(), testOutputString2S);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2R);
}
void TextHandlerTest::stripDisallowedTags()
@@ -145,6 +158,56 @@ void TextHandlerTest::emptyCodeTags()
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
void TextHandlerTest::addStyle_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a>"_s;
QTest::newRow("table")
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
<< u"<table style=\"width: 100%; border-collapse: collapse; border: 1px; border-style: solid;\"><tr><th style=\"border: 1px solid black; padding: 3px;\">Company</th><th style=\"border: 1px solid black; padding: 3px;\">Contact</th><th style=\"border: 1px solid black; padding: 3px;\">Country</th></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Alfreds Futterkiste</td><td style=\"border: 1px solid black; padding: 3px;\">Maria Anders</td><td style=\"border: 1px solid black; padding: 3px;\">Germany</td></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Centro comercial Moctezuma</td><td style=\"border: 1px solid black; padding: 3px;\">Francisco Chang</td><td style=\"border: 1px solid black; padding: 3px;\">Mexico</td></tr></table>"_s;
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
theme->alternateBackgroundColor().name());
}
void TextHandlerTest::addStyle()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
void TextHandlerTest::dontAddStyle_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\">link</a>"_s;
QTest::newRow("table")
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s;
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
<< u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
}
void TextHandlerTest::dontAddStyle()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::sendSimpleStringCase()
{
const QString testInputString = u"This data should just be left alone."_s;
@@ -337,7 +400,8 @@ void TextHandlerTest::receiveRichInPlainOut()
void TextHandlerTest::receivePlainTextIn()
{
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
const QString testOutputStringRich = u"&lt;plain text in tag bracket&gt;<br>Test link <a href=\"https://kde.org\">https://kde.org</a>."_s;
const QString testOutputStringRich =
u"&lt;plain text in tag bracket&gt;<br>Test link <a href=\"https://kde.org\" style=\"text-decoration: none;\">https://kde.org</a>."_s;
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
// Make sure quotes are maintained in a plain string.
@@ -407,7 +471,7 @@ void TextHandlerTest::receivePlainStripMarkup()
void TextHandlerTest::receiveRichUserPill()
{
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s;
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>"_s;
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\" style=\"text-decoration: none;\">@alice:example.org</a></b>"_s;
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
@@ -450,6 +514,34 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
}
void TextHandlerTest::receiveRichPlainUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
QTest::addRow("link 1")
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\" style=\"text-decoration: none;\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\" style=\"text-decoration: none;\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was broken into 3 but needs to
// be just single link.
QTest::addRow("link 2")
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\" style=\"text-decoration: none;\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QTest::addRow("email")
<< uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
<< uR"(<a href="mailto:email@example.com" style="text-decoration: none;">email@example.com</a> <a href="mailto:email@example.com" style="text-decoration: none;">Link already rich</a>)"_s;
QTest::addRow("mxid")
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">Link already rich</a></b>"_s;
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s
<< u"a <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> b"_s;
}
/**
* For when your rich input string has a plain text url left in.
*
@@ -458,46 +550,13 @@ void TextHandlerTest::receiveRichMxcUrl()
*/
void TextHandlerTest::receiveRichPlainUrl()
{
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
const QString testInputStringLink1 =
u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
const QString testOutputStringLink1 =
u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was been broken into 3 but needs to
// be just single link.
const QString testInputStringLink2 = u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s;
const QString testOutputStringLink2 =
u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QString testInputStringEmail = uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testOutputStringEmail = uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testInputStringMxId = u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s;
QString testOutputStringMxId =
u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QString testInputStringMxIdWithPrefix = u"a @user:kde.org b"_s;
QString testOutputStringMxIdWithPrefix = u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
QFETCH(QString, input);
QFETCH(QString, output);
TextHandler testTextHandler;
testTextHandler.setData(testInputStringLink1);
testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
testTextHandler.setData(testInputStringLink2);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
testTextHandler.setData(testInputStringEmail);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
testTextHandler.setData(testInputStringMxId);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
testTextHandler.setData(testInputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output);
}
void TextHandlerTest::receiveRichEdited_data()
@@ -602,5 +661,35 @@ void TextHandlerTest::componentOutput()
QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents);
}
void TextHandlerTest::updateSpoiler_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
QTest::addColumn<bool>("spoilerRevealed");
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
QTest::newRow("same length") << u"<span data-mx-spoiler style=\"color: #123456; background: #123456;\">Test<span>"_s
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
theme->alternateBackgroundColor().name())
<< false;
QTest::newRow("different length") << u"<span data-mx-spoiler style=\"color: short; background: looooooooooong;\">Test<span>"_s
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
theme->alternateBackgroundColor().name())
<< false;
QTest::newRow("spoiler revealed")
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(theme->alternateBackgroundColor().name())
<< u"<span data-mx-spoiler style=\"color: %1; background: %2;\">Test<span>"_s.arg(theme->textColor().name(), theme->alternateBackgroundColor().name())
<< true;
}
void TextHandlerTest::updateSpoiler()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
QFETCH(bool, spoilerRevealed);
QCOMPARE(TextHandler::updateSpoilerText(this, testInputString, spoilerRevealed), testOutputString);
}
QTEST_MAIN(TextHandlerTest)
#include "texthandlertest.moc"

View File

@@ -57,7 +57,7 @@ void TimelineMessageModelTest::switchEmptyRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -77,7 +77,7 @@ void TimelineMessageModelTest::switchSyncedRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
// different every time.
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
testSyncFile.open(QIODevice::ReadOnly);
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
auto root = testSyncJson.object();
auto timeline = root["timeline"_L1].toObject();
@@ -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->eventIdToRow(u"$153456789:example.org"_s), 0);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
}
void TimelineMessageModelTest::cleanup()

View File

@@ -3,12 +3,12 @@
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
qt_add_executable(timeline-memtest
qt_add_executable(timeline_memtest
main.cpp
)
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
target_link_libraries(timeline-memtest PUBLIC
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
target_link_libraries(timeline_memtest PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
@@ -16,14 +16,13 @@ target_link_libraries(timeline-memtest PUBLIC
Qt::QuickControls2
Qt::Widgets
KF6::I18nQml
KF6::Kirigami
QuotientQt6
LibNeoChat
Timeline
)
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
ecm_add_qml_module(timeline_memtest URI org.kde.neochat.timeline_memtest GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline_memtest
QML_FILES
Main.qml
SOURCES

View File

@@ -28,7 +28,7 @@ int main(int argc, char **argv)
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
return app.exec();
}

View File

@@ -37,7 +37,11 @@ public:
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
auto ok = testSyncFile.open(QIODevice::ReadOnly);
if (!ok) {
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
}
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
auto timelineJson = testSyncJson["timeline"_L1].toObject();
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);

View File

@@ -49,7 +49,6 @@
<name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="tr">NeoChat</name>
<name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name>
<name xml:lang="zh-TW">NeoChat</name>
<summary>Chat on Matrix</summary>
@@ -75,6 +74,7 @@
<summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
@@ -82,7 +82,6 @@
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary>
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
<description>
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
@@ -109,16 +108,16 @@
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. 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.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التعمية من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير تطبيق للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p>
@@ -142,16 +141,16 @@
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. 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.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يوفر نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p>
@@ -175,6 +174,7 @@
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
@@ -182,13 +182,12 @@
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
<ul>
<li>Polls - MSC3381</li>
<li xml:lang="ar">التصويت - MSC3381</li>
<li xml:lang="ca">Enquestes - MSC3381</li>
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
<li xml:lang="ca">Votacions - MSC3381</li>
<li xml:lang="ca-valencia">Votacions - MSC3381</li>
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
<li xml:lang="en-GB">Polls - MSC3381</li>
<li xml:lang="eo">Enketoj - MSC3381</li>
@@ -209,6 +208,7 @@
<li xml:lang="nn">Avstemmingar  MSC3381</li>
<li xml:lang="pl">Ankiety - MSC3381</li>
<li xml:lang="pt">Inquéritos - MSC3381</li>
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
<li xml:lang="ru">Голосования — MSC3381</li>
<li xml:lang="sa">मतदान - MSC3381</li>
<li xml:lang="sl">Polls - MSC3381</li>
@@ -216,7 +216,6 @@
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler — MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
@@ -242,6 +241,7 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
@@ -249,7 +249,6 @@
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
<li>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
@@ -275,6 +274,7 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
@@ -282,7 +282,6 @@
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul>
</description>
@@ -299,8 +298,8 @@
<keyword>Matrix</keyword>
<keyword>Kirigami</keyword>
</keywords>
<developer id="kde.org">
<name>The KDE Community</name>
<developer id="org.kde">
<name translate="no">KDE</name>
<url>https://kde.org</url>
</developer>
<metadata_license>CC0-1.0</metadata_license>
@@ -344,6 +343,7 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -351,7 +351,6 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot type="default">
@@ -380,6 +379,7 @@
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
@@ -387,7 +387,6 @@
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
</screenshot>
<!--
@@ -424,6 +423,7 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -431,7 +431,6 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot environment="windows">
@@ -462,6 +461,7 @@
<caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="pt-BR">Tela de login</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
@@ -469,7 +469,6 @@
<caption xml:lang="ta">நுழைவுத் திரை</caption>
<caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption>
</screenshot>
</screenshots>
@@ -477,6 +476,10 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.08.1" date="2025-09-11"/>
<release version="25.08.0" date="2025-08-14"/>
<release version="25.04.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/>

View File

@@ -44,7 +44,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
GenericName=Matrix Client
@@ -88,7 +87,6 @@ GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி
GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix
@@ -121,7 +119,6 @@ Comment[sv]=Chatta på Matrix
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
Comment[zh_CN]=在 Matrix 上聊天
Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix;

View File

@@ -0,0 +1,28 @@
SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: BSD-2-Clause
From dbd1cefd0f07a6942aef450f8f3e082aa3b1cc25 Mon Sep 17 00:00:00 2001
From: Tobias Fella <tobias.fella@kde.org>
Date: Sun, 17 Aug 2025 20:04:04 +0200
Subject: [PATCH] Revert "Bump KF6 dependency version"
This reverts commit 18a6ea98232b3a734905fb18eebba9cf39bf5325.
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 10fe66daa..cd063113d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
-set(KF_MIN_VERSION "6.17")
+set(KF_MIN_VERSION "6.12")
set(QT_MIN_VERSION "6.8")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
--
2.50.1

View File

@@ -0,0 +1,123 @@
SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: BSD-2-Clause
From ca72345b8ee550be2172d8ac5e5dc9e4c2b508c9 Mon Sep 17 00:00:00 2001
From: Tobias Fella <tobias.fella@kde.org>
Date: Sun, 17 Aug 2025 20:00:08 +0200
Subject: [PATCH] Revert "Use new Kirigami builtin column resize handle"
This reverts commit de97275a387abcbca6fcb185bcbd1b69c30f5c66.
---
src/app/qml/Main.qml | 1 -
src/rooms/RoomListPage.qml | 70 +++++++++++++++++++++++++++++---------
2 files changed, 54 insertions(+), 17 deletions(-)
diff --git a/src/app/qml/Main.qml b/src/app/qml/Main.qml
index ea8955674..6eed271c1 100644
--- a/src/app/qml/Main.qml
+++ b/src/app/qml/Main.qml
@@ -45,7 +45,6 @@ Kirigami.ApplicationWindow {
showExisting: true
onConnectionChosen: root.load()
}
- columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
globalToolBar.canContainHandles: true
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar
diff --git a/src/rooms/RoomListPage.qml b/src/rooms/RoomListPage.qml
index 2ac211fd5..f5586d789 100644
--- a/src/rooms/RoomListPage.qml
+++ b/src/rooms/RoomListPage.qml
@@ -17,22 +17,13 @@ import org.kde.neochat
Kirigami.Page {
id: root
- Kirigami.ColumnView.interactiveResizeEnabled: true
- Kirigami.ColumnView.minimumWidth: _private.collapsedSize + spaceDrawer.width + 1
- Kirigami.ColumnView.maximumWidth: _private.defaultWidth + spaceDrawer.width + 1
- Kirigami.ColumnView.onInteractiveResizingChanged: {
- if (!Kirigami.ColumnView.interactiveResizing && collapsed) {
- Kirigami.ColumnView.preferredWidth = root.Kirigami.ColumnView.minimumWidth;
- }
- }
- Kirigami.ColumnView.preferredWidth: _private.currentWidth + spaceDrawer.width + 1
- Kirigami.ColumnView.onPreferredWidthChanged: {
- if (width > _private.collapseWidth) {
- NeoChatConfig.collapsed = false;
- } else if (Kirigami.ColumnView.interactiveResizing) {
- NeoChatConfig.collapsed = true;
- }
- }
+ /**
+ * @brief The current width of the room list.
+ *
+ * @note Other objects can access the value but the private function makes sure
+ * that only the internal members can modify it.
+ */
+ readonly property int currentWidth: _private.currentWidth + spaceDrawer.width + 1
required property NeoChatConnection connection
@@ -40,6 +31,10 @@ Kirigami.Page {
signal search
+ onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth
+ Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth
+
+
onCollapsedChanged: {
if (collapsed) {
RoomManager.sortFilterRoomTreeModel.filterText = "";
@@ -252,6 +247,49 @@ Kirigami.Page {
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
}
+ MouseArea {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ parent: applicationWindow().overlay.parent
+
+ x: root.currentWidth - width / 2
+ width: Kirigami.Units.smallSpacing * 2
+ z: root.z + 1
+ enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
+ visible: enabled
+ cursorShape: Qt.SplitHCursor
+
+ property int _lastX
+
+ onPressed: mouse => {
+ _lastX = mouse.x;
+ }
+ onPositionChanged: mouse => {
+ if (_lastX == -1) {
+ return;
+ }
+ if (mouse.x > _lastX) {
+ // we moved to the right
+ if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
+ // Here we get back directly to a more wide mode.
+ _private.currentWidth = _private.defaultWidth;
+ NeoChatConfig.collapsed = false;
+ } else if (_private.currentWidth >= _private.collapseWidth) {
+ // Increase page width
+ _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
+ }
+ } else if (mouse.x < _lastX) {
+ const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
+ if (tmpWidth < _private.collapseWidth) {
+ _private.currentWidth = Qt.binding(() => _private.collapsedSize);
+ NeoChatConfig.collapsed = true;
+ } else {
+ _private.currentWidth = tmpWidth;
+ }
+ }
+ }
+ }
+
Component {
id: userInfo
UserInfo {
--
2.50.1

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

@@ -0,0 +1,122 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Russian "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Руководство пользователя NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>man-страница NeoChat.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Клиент для взаимодействия с протоколом обмена сообщениями Matrix</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Описание</title>
<para
><command
>neochat</command
> — приложение для настольных и мобильных устройств, позволяющее общаться в чатах с помощью протокола Matrix. </para>
</refsect1>
<refsect1 id="options"
><title
>Параметры</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>URI-адрес пользователя или комнаты в Matrix, например: matrix:u/user:example.org и matrix:r/root:example.org. NeoChat попытается открыть указанную комнату или беседу. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Отчёты об ошибках</title>
<para
>Сообщать об ошибках и отправлять предложения по улучшению можно по адресу <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Смотрите также</title>
<simplelist>
<member
>Список наиболее часто задаваемых вопросов о Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Авторские права</title>
<para
>Авторские права &copy; Tobias Fella, 20202022 </para>
<para
>Авторские права &copy; Carl Schwan, 20202022 </para>
<para
>Лицензия: стандартная общественная лицензия GNU версии 3 или любой более поздней версии &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

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

@@ -9,6 +9,7 @@ add_subdirectory(libneochat)
add_subdirectory(login)
add_subdirectory(rooms)
add_subdirectory(roominfo)
add_subdirectory(messagecontent)
add_subdirectory(timeline)
add_subdirectory(spaces)
add_subdirectory(chatbar)

View File

@@ -20,8 +20,6 @@ add_library(neochat STATIC
windowcontroller.h
models/serverlistmodel.cpp
models/serverlistmodel.h
logger.cpp
logger.h
models/notificationsmodel.cpp
models/notificationsmodel.h
proxycontroller.cpp
@@ -50,6 +48,8 @@ if(ANDROID OR WIN32)
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
else()
set(EXTRA_IMPORTS org.kde.purpose)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
@@ -61,9 +61,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/RoomPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
@@ -108,23 +106,27 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
DEPENDENCIES
QtCore
QtQuick
io.github.quotient_im.libquotient
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.roominfo
org.kde.neochat.messagecontent
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
org.kde.neochat.devtools
org.kde.neochat.login
org.kde.neochat.chatbar
org.kde.config
org.kde.syntaxhighlighting
${EXTRA_IMPORTS}
)
if(NOT ANDROID AND NOT WIN32)
qt_target_qml_sources(neochat QML_FILES
qml/ShareAction.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES
@@ -180,7 +182,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin 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
@@ -205,6 +207,7 @@ target_link_libraries(neochat PUBLIC
QuotientQt6
Login
Rooms
MessageContent
Spaces
)
@@ -340,14 +343,7 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
@@ -359,3 +355,10 @@ install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()
if (APPLE)
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
endif ()

View File

@@ -5,7 +5,6 @@
#include "controller.h"
#include <Quotient/connection.h>
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
@@ -14,17 +13,16 @@
#include <signal.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/events/roommemberevent.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "accountmanager.h"
#include "enums/roomsortparameter.h"
#include "general_logging.h"
#include "mediasizehelper.h"
#include "models/actionsmodel.h"
#include "models/messagemodel.h"
#include "models/pushrulemodel.h"
#include "models/roomlistmodel.h"
#include "models/roomtreemodel.h"
#include "neochatconfig.h"
@@ -40,14 +38,6 @@
#include "trayicon_sni.h"
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
@@ -136,8 +126,10 @@ Controller::Controller(QObject *parent)
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
#ifndef Q_OS_ANDROID
delete m_trayIcon;
#endif
NeoChatConfig::self()->save();
});
@@ -203,13 +195,15 @@ void Controller::setAccountManager(AccountManager *manager)
m_accountManager = manager;
if (m_accountManager) {
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
if (!m_accountManager) {
return;
}
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
}
void Controller::initConnection(NeoChatConnection *connection)
@@ -265,8 +259,8 @@ bool Controller::supportSystemTray() const
#ifdef Q_OS_ANDROID
return false;
#else
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
return de != u"GNOME"_s && de != u"Pantheon"_s;
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
#endif
}
@@ -276,11 +270,8 @@ void Controller::setQuitOnLastWindowClosed()
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
} else {
if (m_trayIcon) {
delete m_trayIcon;
m_trayIcon = nullptr;
}
} else if (m_trayIcon) {
delete m_trayIcon;
}
#endif
}
@@ -337,30 +328,7 @@ void Controller::clearInvitationNotification(const QString &roomId)
void Controller::updateBadgeNotificationCount(int count)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(count, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_L1] = counterSlice;
dbusUnityProperties["count-visible"_L1] = true;
} else {
dbusUnityProperties["count-visible"_L1] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(count);
#endif // QT_VERSION_CHECK(6, 6, 0)
}
bool Controller::isFlatpak() const
@@ -381,7 +349,10 @@ QString Controller::loadFileContent(const QString &path) const
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QFile::ReadOnly);
if (!file.open(QFile::ReadOnly)) {
qCWarning(GENERAL) << "Failed to open file" << path;
return {};
}
return QString::fromLatin1(file.readAll());
}
@@ -407,4 +378,9 @@ void Controller::markImageShown(const QString &eventId)
m_shownImages.append(eventId);
}
void Controller::markImageHidden(const QString &eventId)
{
m_shownImages.removeAll(eventId);
}
#include "moc_controller.cpp"

View File

@@ -99,6 +99,7 @@ public:
Q_INVOKABLE bool isImageShown(const QString &eventId);
Q_INVOKABLE void markImageShown(const QString &eventId);
Q_INVOKABLE void markImageHidden(const QString &eventId);
private:
explicit Controller(QObject *parent = nullptr);
@@ -109,8 +110,9 @@ private:
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr;
#ifndef Q_OS_ANDROID
QPointer<TrayIcon> m_trayIcon;
#endif
QString m_endpoint;
QStringList m_shownImages;

View File

@@ -1,223 +0,0 @@
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "logger.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QMutex>
#include <QStandardPaths>
using namespace Qt::StringLiterals;
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
static QtMessageHandler oldHandler = nullptr;
static bool e2eeDebugEnabled = false;
class FileDebugStream : public QIODevice
{
Q_OBJECT
public:
FileDebugStream()
: mType(QtCriticalMsg)
{
open(WriteOnly);
}
bool isSequential() const override
{
return true;
}
qint64 readData(char *, qint64) override
{
return 0;
}
qint64 readLineData(char *, qint64) override
{
return 0;
}
qint64 writeData(const char *data, qint64 len) override
{
if (!mFileName.isEmpty()) {
QFile outputFile(mFileName);
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
outputFile.write(data, len);
outputFile.putChar('\n');
outputFile.close();
}
return len;
}
void setFileName(const QString &fileName)
{
mFileName = fileName;
}
void setType(QtMsgType type)
{
mType = type;
}
private:
QString mFileName;
QtMsgType mType;
};
class DebugPrivate
{
public:
DebugPrivate()
: origHandler(nullptr)
{
}
~DebugPrivate()
{
qInstallMessageHandler(origHandler);
file.close();
}
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker locker(&mutex);
QByteArray buf;
QTextStream str(&buf);
str << QDateTime::currentDateTime().toString(Qt::ISODate) << u" ["_s;
switch (type) {
case QtDebugMsg:
str << u"DEBUG"_s;
break;
case QtInfoMsg:
str << u"INFO "_s;
break;
case QtWarningMsg:
str << u"WARN "_s;
break;
case QtFatalMsg:
str << u"FATAL"_s;
break;
case QtCriticalMsg:
str << u"CRITICAL"_s;
break;
}
str << u"] "_s << context.category << u": "_s;
if (context.file && *context.file && context.line) {
str << context.file << u":"_s << context.line << u": "_s;
}
if (context.function && *context.function) {
str << context.function << u": "_s;
}
str << message << u"\n"_s;
str.flush();
file.write(buf.constData(), buf.size());
file.flush();
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
oldHandler(type, context, message);
}
}
void setName(const QString &appName)
{
name = appName;
if (file.isOpen()) {
file.close();
}
const auto &filePath = u"%1%2%3"_s.arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
auto entryList = dir.entryList({appName + u".*"_s});
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
auto leftIndex = left.split(u"."_s).last().toInt();
auto rightIndex = right.split(u"."_s).last().toInt();
return leftIndex > rightIndex;
});
for (const auto &entry : entryList) {
bool ok = false;
const auto index = entry.split(u"."_s).last().toInt(&ok);
if (!ok) {
continue;
}
QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
if (info.exists()) {
QFile file(info.absoluteFilePath());
if (index > 50) {
file.remove();
continue;
}
const auto &newName = u"%1.%2"_s.arg(filePath, QString::number(index + 1));
const auto success = file.copy(newName);
if (success) {
file.remove();
} else {
qFatal("Cannot rename log file '%s' to '%s': %s",
qUtf8Printable(file.fileName()),
qUtf8Printable(newName),
qUtf8Printable(file.errorString()));
}
}
}
QFileInfo finfo(filePath);
if (!finfo.absoluteDir().exists()) {
QDir().mkpath(finfo.absolutePath());
}
file.setFileName(filePath + u".0"_s);
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
}
void setOrigHandler(QtMessageHandler origHandler_)
{
origHandler = origHandler_;
}
QMutex mutex;
QFile file;
QString name;
QtMessageHandler origHandler;
QByteArray loggingCategory;
};
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
case QtWarningMsg:
case QtCriticalMsg:
sInstance()->log(type, context, message);
break;
case QtFatalMsg:
sInstance()->log(QtInfoMsg, context, message);
}
}
void filter(QLoggingCategory *category)
{
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
category->setEnabled(QtDebugMsg, true);
} else if (oldCategoryFilter) {
oldCategoryFilter(category);
}
}
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);
sInstance->setName(u"neochat.log"_s);
}
#include "logger.moc"

View File

@@ -1,9 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
*/
void initLogging();

View File

@@ -49,7 +49,6 @@
#include "blurhashimageprovider.h"
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "login.h"
#include "registration.h"
#include "roommanager.h"
@@ -138,6 +137,11 @@ int main(int argc, char *argv[])
font.setHintingPreference(QFont::PreferNoHinting);
app.setFont(font);
#endif
#ifdef Q_OS_MACOS
QApplication::setStyle(u"breeze"_s);
#endif
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QGuiApplication::setOrganizationName("KDE"_L1);
@@ -177,8 +181,6 @@ int main(int argc, char *argv[])
KCrash::initialize();
#endif
initLogging();
Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true);
@@ -195,6 +197,9 @@ int main(int argc, char *argv[])
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
QCommandLineOption replaceOption({QStringLiteral("replace")}, i18nc("command line description", "Replace an existing instance"));
parser.addOption(replaceOption);
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(testOption);
@@ -231,7 +236,7 @@ int main(int argc, char *argv[])
#endif
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
KDBusService service(KDBusService::Unique | (parser.isSet(replaceOption) ? KDBusService::Replace : KDBusService::StartupOption(0)));
#endif
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
@@ -239,13 +244,6 @@ int main(int argc, char *argv[])
LoginHelper::instance().setAccountManager(accountManager.get());
Registration::instance().setAccountManager(accountManager.get());
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_roomsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);

View File

@@ -7,7 +7,6 @@
#include <QQmlEngine>
#include "neochatconnection.h"
#include "neochatroom.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/roommember.h>
@@ -25,11 +24,7 @@ class CommonRoomsModel : public QAbstractListModel
public:
enum Roles {
TextRole = Qt::DisplayRole,
LongitudeRole,
LatitudeRole,
AssetRole,
AuthorRole,
RoomIdRole = Qt::DisplayRole,
};
Q_ENUM(Roles)

View File

@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
QHash<int, QByteArray> NotificationsModel::roleNames() const
{
return {
{TextRole, "text"},
{TextRole, "notificationText"},
{RoomIdRole, "roomId"},
{AuthorName, "authorName"},
{AuthorAvatar, "authorAvatar"},

View File

@@ -176,7 +176,7 @@ void ServerListModel::initialize()
true,
false,
});
beginResetModel();
endResetModel();
}
#include "moc_serverlistmodel.cpp"

View File

@@ -42,7 +42,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
DesktopEntry=org.kde.neochat
@@ -87,7 +86,6 @@ Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokolle
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端
@@ -134,7 +132,6 @@ Name[sv]=Nytt meddelande
Name[ta]=புதிய செய்தி
Name[tr]=Yeni İleti
Name[uk]=Нове повідомлення
Name[x-test]=xxNew messagexx
Name[zh_CN]=新消息
Name[zh_TW]=新訊息
Comment=There is a new message
@@ -177,7 +174,6 @@ Comment[sv]=Det finns ett nytt meddelande
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
Comment[tr]=Yeni bir ileti var
Comment[uk]=Надійшло нове повідомлення
Comment[x-test]=xxThere is a new messagexx
Comment[zh_CN]=有新消息
Comment[zh_TW]=有新的訊息
Action=Popup
@@ -222,7 +218,6 @@ Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ்
Name[tr]=Yeni Davet
Name[uk]=Нове запрошення
Name[x-test]=xxNew Invitationxx
Name[zh_CN]=新邀请
Name[zh_TW]=新邀請
Comment=There is a new invitation to a room
@@ -264,7 +259,6 @@ Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
Comment[tr]=Bir odaya yeni bir davetiye var
Comment[uk]=У кімнаті нове запрошення
Comment[x-test]=xxThere is a new invitation to a roomxx
Comment[zh_CN]=有新的聊天室邀请
Comment[zh_TW]=有新的加入聊天室邀請
Action=Popup
@@ -303,7 +297,6 @@ Name[sv]=Dela
Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_CN]=分享
Name[zh_TW]=分享
Comment=The result of sharing a piece of content
@@ -338,7 +331,6 @@ Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果
Action=Popup

View File

@@ -78,6 +78,12 @@
<label>Use a compact room list layout</label>
<default>false</default>
</entry>
<entry name="MarkReadCondition" type="Enum">
<label>The sort order for the rooms in the list.</label>
<choices name="::TimelineMarkReadCondition::Condition">
</choices>
<default>2</default>
</entry>
<entry name="ShowStateEvent" type="bool">
<label>Show state events in the timeline</label>
<default>true</default>
@@ -183,6 +189,10 @@
<label>Don't hide any events in the timeline</label>
<default>false</default>
</entry>
<entry name="RelateAnyEvent" type="bool">
<label>Send relations to any event, including state events and events normally hidden.</label>
<default>false</default>
</entry>
<entry name="AlwaysVerifyDevice" type="bool">
<label>Always allow device verification</label>
<default>false</default>
@@ -205,6 +215,10 @@
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>
</entry>
<entry name="Calls" type="bool">
<label>Enable audio and video calling</label>
<default>false</default>
</entry>
</group>
<group name="Security">
<entry name="RejectUnknownInvites" type="bool">

View File

@@ -60,9 +60,8 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
}
if (!m_connActiveJob.contains(connection->user()->id())) {
auto job = connection->callApi<GetNotificationsJob>();
m_connActiveJob.append(connection->user()->id());
connect(job, &BaseJob::success, this, [this, job, connection]() {
connection->callApi<GetNotificationsJob>().onResult([this, connection](const auto &job) {
m_connActiveJob.removeAll(connection->user()->id());
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
});
@@ -433,7 +432,7 @@ QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoCha
if (room != nullptr) {
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
if (icon != roomAvatar) {
if (!roomAvatar.isNull() && icon != roomAvatar) {
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
painter.setBrush(Qt::white);

View File

@@ -43,7 +43,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
Comment=Find rooms in NeoChat
@@ -83,7 +82,6 @@ Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
Comment[tr]=NeoChatte odalar bulun
Comment[uk]=Пошук кімнат у NeoChat
Comment[x-test]=xxFind rooms in NeoChatxx
Comment[zh_CN]=在 NeoChat 查找聊天室
Comment[zh_TW]=在 NeoChat 尋找聊天室
X-KDE-ServiceTypes=Plasma/Runner
@@ -93,4 +91,3 @@ X-Plasma-API=DBus
X-Plasma-DBusRunner-Service=org.kde.neochat
X-Plasma-DBusRunner-Path=/RoomRunner
X-Plasma-Request-Actions-Once=true
X-Plasma-Runner-Min-Letter-Count=3

View File

@@ -1,16 +1,16 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.settings
import org.kde.neochat.devtools
KirigamiComponents.ConvergentContextMenu {
id: root
@@ -18,44 +18,45 @@ KirigamiComponents.ConvergentContextMenu {
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow window
QQC2.Action {
Kirigami.Action {
text: i18nc("@action:button", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
(Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: "https://matrix.to/#/" + root.connection.localUser.id,
title: root.connection.localUser.displayName,
subtitle: root.connection.localUser.id,
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
});
if (typeof root.closeDialog === "function") {
root.closeDialog();
}
qrMax.open();
}) as QrCodeMaximizeComponent).open();
}
}
QQC2.Action {
text: i18n("Edit This Account")
icon.name: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
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, {
connection: root.connection
}, {
title: i18n("Account editor")
})
}) as Kirigami.Dialog).open();
}
QQC2.Action {
text: i18n("Notification Settings")
Kirigami.Action {
text: i18nc("@action:inmenu", "Edit This Account")
icon.name: "document-edit"
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Notification Settings")
icon.name: "notifications"
onTriggered: {
NeoChatSettingsView.open('notifications');
}
}
QQC2.Action {
text: i18n("Devices")
Kirigami.Action {
text: i18nc("@action:inmenu", "Devices")
icon.name: "computer-symbolic"
onTriggered: {
NeoChatSettingsView.open('devices');
@@ -63,10 +64,10 @@ KirigamiComponents.ConvergentContextMenu {
}
Kirigami.Action {
text: i18n("Open Developer Tools")
text: i18nc("@action:inmenu", "Open Developer Tools")
icon.name: "tools"
visible: NeoChatConfig.developerTools
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
connection: root.connection
}, {
title: i18nc("@title:window", "Developer Tools"),
@@ -84,9 +85,10 @@ KirigamiComponents.ConvergentContextMenu {
})
}
QQC2.Action {
Kirigami.Action {
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
visible: !root.connection.isVerifiedSession
onTriggered: {
root.connection.startSelfVerification();
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
@@ -95,19 +97,17 @@ KirigamiComponents.ConvergentContextMenu {
standardButtons: Kirigami.Dialog.Ok
})
dialog.open();
root.connection.onNewKeyVerificationSession.connect(() => {
root.connection.newKeyVerificationSession.connect(() => {
dialog.close();
});
}
}
QQC2.Action {
text: i18n("Logout")
Kirigami.Action {
text: i18nc("@action:inmenu", "Logout")
icon.name: "im-kick-user"
onTriggered: confirmLogoutDialogComponent.createObject(root).open()
}
readonly property Component confirmLogoutDialogComponent: ConfirmLogoutDialog {
connection: root.connection
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection
}) as Kirigami.Dialog).open()
}
}

View File

@@ -1,7 +1,10 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@@ -16,8 +19,6 @@ Kirigami.Dialog {
required property NeoChatConnection connection
parent: applicationWindow().overlay
leftPadding: 0
rightPadding: 0
topPadding: 0
@@ -25,7 +26,7 @@ Kirigami.Dialog {
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")
onVisibleChanged: if (visible) {
@@ -53,8 +54,8 @@ Kirigami.Dialog {
}
text: i18nc("@button: login to or register a new account.", "Add Account")
contentItem: Delegates.SubtitleContentItem {
itemDelegate: parent
subtitle: i18n("Log in or create a new account")
itemDelegate: addDelegate
subtitle: i18nc("@info", "Log in or create a new account")
labelItem.textFormat: Text.PlainText
subtitleItem.textFormat: Text.PlainText
}
@@ -94,8 +95,8 @@ Kirigami.Dialog {
accountView.decrementCurrentIndex();
}
}
Keys.onEnterPressed: accountView.currentItem.clicked()
Keys.onReturnPressed: accountView.currentItem.clicked()
Keys.onEnterPressed: (accountView.currentItem as Delegates.RoundedItemDelegate).clicked()
Keys.onReturnPressed: (accountView.currentItem as Delegates.RoundedItemDelegate).clicked()
onVisibleChanged: {
for (let i = 0; i < accountView.count; i++) {

View File

@@ -6,8 +6,6 @@ import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Dialog {
id: root
@@ -20,7 +18,7 @@ Kirigami.Dialog {
title: i18nc("@title:dialog", "Start a chat")
contentItem: QQC2.Label {
text: i18n("Do you want to start a chat with %1?", root.user.displayName)
text: i18nc("@info", "Do you want to start a chat with %1?", root.user.displayName)
textFormat: Text.PlainText
wrapMode: Text.Wrap
horizontalAlignment: Qt.AlignHCenter

View File

@@ -33,7 +33,7 @@ ColumnLayout {
}
QQC2.ToolButton {
id: editImageButton
visible: hasImage
visible: root.hasImage
icon.name: "document-edit"
text: i18n("Edit")
display: QQC2.AbstractButton.IconOnly
@@ -46,9 +46,9 @@ ColumnLayout {
}
onClicked: {
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
let imageEditor = (Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(imageEditorPage);
imageEditor.newPathChanged.connect(function (newPath) {
applicationWindow().pageStack.layers.pop();
imageEditor.closeDialog();
root.attachmentPath = newPath;
});
}
@@ -58,14 +58,18 @@ ColumnLayout {
QQC2.ToolButton {
id: cancelAttachmentButton
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18n("Cancel sending attachment")
icon.name: "dialog-close"
onTriggered: attachmentCancelled()
shortcut: "Escape"
}
text: i18nc("@action:button", "Cancel sending attachment")
icon.name: "dialog-close"
onClicked: root.attachmentCancelled()
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Kirigami.Action {
shortcut: "Escape"
onTriggered: cancelAttachmentButton.clicked()
}
}
}
@@ -75,8 +79,8 @@ ColumnLayout {
asynchronous: true
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
source: hasImage ? root.attachmentPath : ""
visible: hasImage
source: root.hasImage ? root.attachmentPath : ""
visible: root.hasImage
fillMode: Image.PreserveAspectFit
onSourceChanged: {
@@ -114,11 +118,11 @@ ColumnLayout {
id: mimetypeIcon
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
source: attachmentMimetype.iconName
source: root.attachmentMimetype.iconName
}
QQC2.Label {
id: fileLabel
text: baseFileName
text: root.baseFileName
}
}
}

View File

@@ -3,12 +3,9 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
Delegates.RoundedItemDelegate {
id: root

View File

@@ -48,7 +48,7 @@ Components.AbstractMaximizeComponent {
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
name: root.author.name ?? root.author.displayName
name: root.author.displayName
source: root.author.avatarUrl
color: root.author.color
}
@@ -57,7 +57,7 @@ Components.AbstractMaximizeComponent {
QQC2.Label {
id: userLabel
text: root.author.name ?? root.author.displayName
text: root.author.displayName
color: root.author.color
font.weight: Font.Bold
elide: Text.ElideRight

View File

@@ -8,7 +8,6 @@ import QtQml.Models
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kitemmodels
import org.kde.neochat

View File

@@ -1,86 +0,0 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import Qt.labs.platform as Labs
import QtQuick
import QtQuick.Layouts
Labs.Menu {
id: root
required property Item field
Labs.MenuItem {
enabled: root.field !== null && root.field.canUndo
text: i18nc("text editing menu action", "Undo")
shortcut: StandardKey.Undo
onTriggered: {
root.field.undo();
root.close();
}
}
Labs.MenuItem {
enabled: root.field !== null && root.field.canRedo
text: i18nc("text editing menu action", "Redo")
shortcut: StandardKey.Redo
onTriggered: {
root.field.undo();
root.close();
}
}
Labs.MenuSeparator {}
Labs.MenuItem {
enabled: root.field !== null && root.field.selectedText
text: i18nc("text editing menu action", "Cut")
shortcut: StandardKey.Cut
onTriggered: {
root.field.cut();
root.close();
}
}
Labs.MenuItem {
enabled: root.field !== null && root.field.selectedText
text: i18nc("text editing menu action", "Copy")
shortcut: StandardKey.Copy
onTriggered: {
root.field.copy();
root.close();
}
}
Labs.MenuItem {
enabled: root.field !== null && root.field.canPaste
text: i18nc("text editing menu action", "Paste")
shortcut: StandardKey.Paste
onTriggered: {
root.field.paste();
root.close();
}
}
Labs.MenuItem {
enabled: root.field !== null && root.field.selectedText !== ""
text: i18nc("text editing menu action", "Delete")
shortcut: ""
onTriggered: {
root.field.remove(root.field.selectionStart, root.field.selectionEnd);
root.close();
}
}
Labs.MenuSeparator {}
Labs.MenuItem {
enabled: root.field !== null
text: i18nc("text editing menu action", "Select All")
shortcut: StandardKey.SelectAll
onTriggered: {
root.field.selectAll();
root.close();
}
}
}

View File

@@ -6,16 +6,15 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
ColumnLayout {
id: root
property alias emoji: emojiLabel.text
property alias description: descriptionLabel.text
required property string emoji
required property string description
QQC2.Label {
id: emojiLabel
text: root.emoji
Layout.fillWidth: true
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
@@ -25,7 +24,7 @@ ColumnLayout {
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
}
QQC2.Label {
id: descriptionLabel
text: root.description
Layout.fillWidth: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter

View File

@@ -17,9 +17,6 @@ RowLayout {
Repeater {
id: repeater
delegate: EmojiItem {
emoji: modelData.emoji
description: modelData.description
}
delegate: EmojiItem {}
}
}

View File

@@ -5,7 +5,6 @@ import Qt.labs.platform as Labs
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@@ -16,12 +15,50 @@ Labs.MenuBar {
id: root
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow appWindow
Labs.Menu {
title: i18nc("menu", "NeoChat")
title: i18nc("menu", "File")
Labs.MenuItem {
enabled: pageStack.layers.currentItem.title !== i18n("Configure NeoChat…")
icon.name: "list-add-user"
text: i18nc("@action:inmenu", "Find your Friends")
enabled: root.connection
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
Labs.MenuItem {
icon.name: "system-users-symbolic"
text: i18nc("@action:inmenu", "Create a Room…")
enabled: root.connection
shortcut: StandardKey.New
onTriggered: {
Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root.appWindow, {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
}).open();
}
}
Labs.MenuItem {
icon.name: "compass-symbolic"
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");
});
}
}
Labs.MenuItem {
text: i18nc("menu", "Configure NeoChat…")
shortcut: StandardKey.Preferences
@@ -34,58 +71,15 @@ Labs.MenuBar {
onTriggered: Qt.quit()
}
}
Labs.Menu {
title: i18nc("menu", "File")
Labs.MenuItem {
icon.name: "list-add-user"
text: i18nc("@action:inmenu", "Find your Friends")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
Labs.MenuItem {
icon.name: "system-users-symbolic"
text: i18nc("@action:inmenu", "Create a Room…")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
shortcut: StandardKey.New
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}
}
Labs.MenuItem {
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu", "Explore Rooms")
onTriggered: {
let dialog = 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");
});
}
}
}
EditMenu {
title: i18nc("menu", "Edit")
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
}
Labs.Menu {
title: i18nc("menu", "View")
Labs.MenuItem {
icon.name: "search-symbolic"
enabled: root.connection
text: i18nc("@action:inmenu opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Search Rooms")
onTriggered: quickSwitcher.open()
onTriggered: (root.appWindow as Main).quickSwitcher.open()
}
}
Labs.Menu {
@@ -93,8 +87,8 @@ Labs.MenuBar {
Labs.MenuItem {
icon.name: "view-fullscreen-symbolic"
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
text: root.appWindow.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
onTriggered: root.appWindow.visibility === Window.FullScreen ? root.appWindow.showNormal() : root.appWindow.showFullScreen()
}
}
Labs.Menu {
@@ -103,12 +97,12 @@ Labs.MenuBar {
Labs.MenuItem {
icon.name: "help-about-symbolic"
text: i18nc("menu", "About NeoChat")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
}
Labs.MenuItem {
icon.name: "kde-symbolic"
text: i18nc("menu", "About KDE")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
}
}
}

View File

@@ -3,8 +3,10 @@
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.neochat
Item {
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow appWindow
}

View File

@@ -3,29 +3,54 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.Control {
RowLayout {
id: root
property string text
visible: !root.text.startsWith("https://matrix.to/") && root.text.length > 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
z: 20
Accessible.ignored: true
contentItem: QQC2.Label {
text: root.text.startsWith("https://matrix.to/") ? "" : root.text
elide: Text.ElideRight
Accessible.description: i18nc("@info screenreader", "The currently selected link")
onTextChanged: {
// This is done so the text doesn't disappear for a split second while in the opacity transition
if (root.text.length > 0) {
urlLabel.text = root.text
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
z: 99
spacing: 0
opacity: (!root.text.startsWith("https://matrix.to/") && root.text.length > 0) ? 1 : 0
visible: opacity > 0
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
QQC2.Control {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Accessible.ignored: true
contentItem: QQC2.Label {
id: urlLabel
elide: Text.ElideRight
}
background: Kirigami.ShadowedRectangle {
corners.topRightRadius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
border {
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
width: 1
}
}
}
}

View File

@@ -2,6 +2,8 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
@@ -16,7 +18,7 @@ ColumnLayout {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
readonly property var invitingMember: currentRoom.qmlSafeMember(currentRoom.invitingUserId)
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
spacing: Kirigami.Units.smallSpacing
@@ -33,7 +35,7 @@ ColumnLayout {
Layout.fillWidth: true
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
}
@@ -52,6 +54,15 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.currentRoom && root.currentRoom.canonicalAlias
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
}
Kirigami.Heading {
text: root.currentRoom.displayName
@@ -70,7 +81,14 @@ ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
text: root.currentRoom.displayName
text: root.invitingMember.displayName
Layout.alignment: Qt.AlignHCenter
}
QQC2.Label {
text: root.invitingMember.id
color: Kirigami.Theme.disabledTextColor
Layout.alignment: Qt.AlignHCenter
}
@@ -159,7 +177,7 @@ ColumnLayout {
QQC2.Label {
color: Kirigami.Theme.disabledTextColor
text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
text: xi18nc("@info:label Ensure you are referring to the same translation used for that settings page", "You can reject invitations from unknown users under the <interface>Security & Safety</interface> settings.")
wrapMode: Text.WordWrap
// + 5 to prevent it from wrapping unnecessarily

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