Compare commits

...

168 Commits

Author SHA1 Message Date
Tobias Fella
565fe1975b Remove duplicated corrosion 2025-03-22 17:55:30 +01:00
Tobias Fella
635b77d03e Add licenses 2025-03-22 17:45:08 +01:00
Tobias Fella
4d7b788160 Fix rust flatpak 2025-03-22 17:38:41 +01:00
Tobias Fella
9e24c855b6 Add corrosion 2025-03-22 17:28:34 +01:00
Tobias Fella
18bea02fa0 WIP 2025-03-22 17:17:39 +01:00
Tobias Fella
073e756364 Port to Integral 2025-03-22 17:10:33 +01:00
l10n daemon script
f153e57fdb GIT_SILENT Sync po/docbooks with svn 2025-03-12 01:39:11 +00:00
l10n daemon script
bac93e778e GIT_SILENT Sync po/docbooks with svn 2025-03-11 01:37:29 +00:00
James Graham
2aeed10429 Message attached property
Create Message attached property to propagate parameters like room, timeline, index and maxContentWidth down to the message content avoiding lots of boilerplate
2025-03-10 18:28:42 +00:00
l10n daemon script
ea6ad902a7 GIT_SILENT Sync po/docbooks with svn 2025-03-10 01:41:40 +00:00
James Graham
f9c53ee3b0 ThreePIDModel Updates
There is no need for NeochatConnection to depend on ThreePIdModel and also this means it's not in memory when not needed.

Also a little cleanup to make sure only a single job can run at a time.
2025-03-09 12:31:14 +00:00
l10n daemon script
3f0843647c GIT_SILENT Sync po/docbooks with svn 2025-03-09 01:36:22 +00:00
Albert Astals Cid
a50df870e7 GIT_SILENT Upgrade release service version to 25.07.70. 2025-03-08 19:26:23 +01:00
James Graham
716ae11941 Fix Viewing Encrypted Events
Turns out I forgot that an encrypted event is not a roommessage event so we need to handle this when grabbing the content model for the message.
2025-03-08 14:42:48 +00:00
l10n daemon script
4ff16ff402 GIT_SILENT Sync po/docbooks with svn 2025-03-08 01:43:38 +00:00
l10n daemon script
bd598b9c44 GIT_SILENT Sync po/docbooks with svn 2025-03-07 01:39:06 +00:00
Joshua Goins
f1253e4ede Make joining remote rooms more reliable
When joining remote rooms we have to specify another homeserver (that is in the
room) to help us join. The matrix documentation is a little unclear what to do
in this scenario, so instead of giving up let's at least brute force it with the
server in the alias or room id.

This *does* work and allows my server to join rooms in NeoChat I otherwise
couldn't.

BUG: 487253
CCBUG: 491359
FIXED-IN: 24.12.3
2025-03-06 15:39:32 +00:00
Joshua Goins
79a3da3358 Stop emojis from destroying your message
This is easy to reproduce in the following scenario with a bunch of
half-completed emojis: ":a :a :a :a". Trying to complete anything but the last
one starts replacing parts of the message because it only considers the last
colon to the current completion identifier.

This change fixes that and said scenario can no longer cause a message
massacare. This bug doesn't seem to affect the other completions because their
searching in the string was correct, but I made sure they all share the same
index now.

BUG: 479587
FIXED-IN: 24.12.3
2025-03-06 14:47:27 +00:00
l10n daemon script
5a6bdfbbba GIT_SILENT Sync po/docbooks with svn 2025-03-06 01:39:29 +00:00
Joshua Goins
3d663be506 Move the "Explore rooms" button from the hamburger to the space drawer
I think we should put this feature in a more obvious place (and similar to other
chat applications.) Instead of it being buried underneath a menu, joining
spaces/rooms should have the space promenience as creating them - you're going
to be more likely to access this dialog more anyway.
2025-03-05 22:03:54 +00:00
Joshua Goins
0ada4cdebe Add a dialog explaining what to do next when tapping "Verify this device"
The menu that has this action is now more discoverable, and this menu item is
plagued with a bad UX - when you tap on it nothing obvious happens! To people
not familiar with device verification they will think this is a bug, but in fact
all they need to do is open another verified session on another device.

So now there's a dialog explaining that the next step is to do just that. This
dialog also closes once the verification session starts, but the user has the
option to close it in the meantime.
2025-03-05 22:03:38 +00:00
Balló György
d103de96aa Don't create tray icon if system tray is not supported
This fixes the problem that the tray icon is created in GNOME if it was
enabled in other desktop environment previously.
2025-03-05 20:35:18 +00:00
l10n daemon script
f248b04834 GIT_SILENT Sync po/docbooks with svn 2025-03-05 01:54:04 +00:00
l10n daemon script
9572f20682 GIT_SILENT Sync po/docbooks with svn 2025-03-04 01:37:41 +00:00
Justin Zobel
409cec08fc CI - Flatpak Updates 2025-03-03 02:18:43 +00:00
l10n daemon script
51f330eae9 GIT_SILENT Sync po/docbooks with svn 2025-03-03 01:37:33 +00:00
l10n daemon script
51750267e5 GIT_SILENT Sync po/docbooks with svn 2025-03-02 01:35:42 +00:00
l10n daemon script
99948d5151 GIT_SILENT Sync po/docbooks with svn 2025-03-01 01:48:27 +00:00
l10n daemon script
030726e6fb GIT_SILENT Sync po/docbooks with svn 2025-02-28 01:36:36 +00:00
Heiko Becker
1fad54272f GIT_SILENT Update Appstream for new release
(cherry picked from commit a85af258fe)
2025-02-27 21:59:20 +01:00
James Graham
4af4bfd55f Improve the handling of switching link preivews on and off.
First make sure that the global setting is tied into the room setting, previously it was a bit of a patchwork that worked more by luck than judgement. The two levels of global and room level are properly tied together in a hierarchy.

Add a message in the room when global notifcations re turned off. This has caused confusion in the past when people don't realise there are 2 levels.
2025-02-27 16:37:33 +00:00
l10n daemon script
77cedef5bb GIT_SILENT Sync po/docbooks with svn 2025-02-27 01:35:42 +00:00
Joshua Goins
db36f187dc Don't show the "Settings" button when adding a new account
As much as I like opening Settings while I'm in Settings, this
doesn't make much sense.
2025-02-26 23:59:05 +00:00
Joshua Goins
2861eb9c60 Add new message action to pin and unpin messages in rooms
Self-explanatory, now you can manage pinned messages in NeoChat alone.
2025-02-26 23:53:38 +00:00
l10n daemon script
9811c0d97a GIT_SILENT Sync po/docbooks with svn 2025-02-26 01:37:50 +00:00
l10n daemon script
e9c21373ed GIT_SILENT Sync po/docbooks with svn 2025-02-25 01:35:40 +00:00
l10n daemon script
bda23ec54a GIT_SILENT Sync po/docbooks with svn 2025-02-24 10:28:45 +00:00
l10n daemon script
e23641375b GIT_SILENT Sync po/docbooks with svn 2025-02-24 01:35:59 +00:00
l10n daemon script
024d54345a GIT_SILENT Sync po/docbooks with svn 2025-02-23 01:34:15 +00:00
James Graham
59fd4d3916 Make sure the thread dev setting is actually obeyed 2025-02-22 19:48:56 +00:00
Joshua Goins
88d684b6c1 Don't allow long-pressing on non-touchscreen devices
It isn't the right kind of interaction on a computer with a mouse or
trackpad, it should be relegated to touchscreen only. This should
hopefully cover everything from room list delegates to messages.
2025-02-22 18:50:39 +00:00
Joshua Goins
94fdf777cb Early exit if we're checking mutual rooms with yourself
This is rejected by servers too, so don't even bother doing as it
doesn't make sense.
2025-02-22 13:05:30 -05:00
Joshua Goins
dea70152e4 Improve discoverability of the account menu
I figure that not many users know there's a secret and super useful
account menu. Right-clicking or long-pressing opens this menu.
Additionally, tapping your avatar brings up the "Accounts" settings for
some reason. Worse, there's no indication of any of this functionality
or why we're hiding two separate functions here.

Instead, let's make it a ToolButton but keep the general appearance the
same. That makes it act and feel more like a regular button, and
pressing on it will open the account menu. The shortcut to the accounts
settings is removed, there's plenty of other ways to get there.
2025-02-22 17:31:00 +00:00
Joshua Goins
614caf5ca0 Add ellipses to "Remove" message action
This doesn't apply instantly, it opens a dialog to confirm with you and
optionally add a message. According to the HIG (and just a good idea in
general) it should be marked with ellipses.
2025-02-22 17:30:25 +00:00
Joshua Goins
25dbae37fb Change "Copy Message Link" icon to "link-symbolic"
Instead of sharing the same icon as the "Copy Text" action, this make it
even clearer.
2025-02-22 17:30:12 +00:00
Joshua Goins
e060032e6a Improve the notification setting description
The current text has invited a lot of confusion around how notifications
work in NeoChat, because it mentions "push notifications". Some users
take it to mean that somehow the notifications appear in the background,
but that's only supported if built with KUnifiedPush.

To make it super clear, let's change the description dynamically based
on whether:
1. NeoChat is built with KUnifiedPush support.
2. We were able to connect with the KUnifiedPush daemon and your server
has a push gateway.
2025-02-22 10:33:52 -05:00
l10n daemon script
4725410c0f GIT_SILENT Sync po/docbooks with svn 2025-02-22 01:35:48 +00:00
Scarlett Moore
20488ee400 snapcraft: Move to core24 2025-02-21 04:25:07 -07:00
l10n daemon script
b1c0619af5 GIT_SILENT Sync po/docbooks with svn 2025-02-21 01:35:30 +00:00
l10n daemon script
ade730179a GIT_SILENT Sync po/docbooks with svn 2025-02-20 01:36:40 +00:00
Joshua Goins
9264ad26d6 Make the Notifications window non-modal
Tapping on a notification here doesn't close the window. Additionally,
you need to tap it several times for NeoChat to scroll up in a room.

Considering all of this, it would make more sense for this window to be
non-modal for now so you can have the Notifications window open while
using the main NeoChat window.
2025-02-19 13:21:16 +00:00
Joshua Goins
9020e2c7cb Remove word puzzle in the new invitation page 2025-02-18 20:52:13 -05:00
Joshua Goins
0f51c34b24 Add dedicated invitation subtitle text to rooms
Instead of displaying the message event - or usually nothing at all -
show a label like "user has invited you".
2025-02-19 01:45:02 +00:00
Joshua Goins
f6a427e865 Add user information to the invitation page
Currently the invite page kinda sucks. If someone invites you to a room,
you have no idea who from the UI - which is a safety issue.

Now the invite page shows you who invited you, and it has a slightly
different layout & text for one-on-one chats and room invites.

Also the buttons on this page are improved with fixed capitalization
and icons!
2025-02-19 01:45:02 +00:00
l10n daemon script
9b95930463 GIT_SILENT Sync po/docbooks with svn 2025-02-19 01:37:15 +00:00
l10n daemon script
cb96b4991e GIT_SILENT Sync po/docbooks with svn 2025-02-17 01:34:35 +00:00
l10n daemon script
cde7a51cde GIT_SILENT Sync po/docbooks with svn 2025-02-16 01:38:19 +00:00
l10n daemon script
046d611f56 GIT_SILENT Sync po/docbooks with svn 2025-02-15 01:48:20 +00:00
Albert Astals Cid
d7b3748159 CI: Add linux-qt6-next build 2025-02-13 08:14:42 +00:00
l10n daemon script
188c9fc726 GIT_SILENT Sync po/docbooks with svn 2025-02-13 01:43:18 +00:00
l10n daemon script
dbc735e63b 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-02-13 01:33:08 +00:00
James Graham
8750486f7b Move to upstream JoinRuleEvent 2025-02-12 18:04:59 +00:00
l10n daemon script
6dc4baeeb5 GIT_SILENT Sync po/docbooks with svn 2025-02-12 01:34:39 +00:00
l10n daemon script
ff28828a2e GIT_SILENT Sync po/docbooks with svn 2025-02-11 01:38:11 +00:00
l10n daemon script
e28452dfd1 GIT_SILENT Sync po/docbooks with svn 2025-02-10 01:33:12 +00:00
James Graham
5d7cb5c28f Move the reaction delegate into the bubble
Move the reaction delegate into the bubble so it can be instantiated by the Content model. This aims to make sure we only instantiate it when needed rather than for every event. You can now hover the event to show the ReactionComponent with a button to add a reaction if none are currently present

Added bonus ReactionModel no longer needs an event pointer, the event ID is enough to get reaction from the room so things are less likely to blow up.
2025-02-09 19:07:53 +00:00
James Graham
08b29f7081 Make sure that a blank entry is never added to the message model store
title
2025-02-09 18:18:15 +00:00
l10n daemon script
c9e034b5b3 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-02-09 01:27:43 +00:00
l10n daemon script
d9f0ff466f GIT_SILENT Sync po/docbooks with svn 2025-02-08 01:33:49 +00:00
Joshua Goins
6b4b895102 Fixup higher power levels not being displayed correctly
This is is a fix or 4d1c82a623, I was
coercing integer values to PowerLevel (the enum.)
2025-02-05 20:36:04 +00:00
l10n daemon script
0c7e02e7c9 GIT_SILENT Sync po/docbooks with svn 2025-02-05 01:36:34 +00:00
Joshua Goins
4d1c82a623 Add power level to the user details dialog
The only way to check a user's power level is to haul yourself over to
the member list, which is cumbersome but also hard to parse - especially
if a room has lots of members.

This adds the user's power level to the existing details dialog. For
example, this makes it easier to identify someone as a moderator if they
sent a message in the room.
2025-02-04 20:14:58 +00:00
Joshua Goins
8d33fe6221 StateKeys: Fix opening a specific state key member 2025-02-04 20:13:09 +00:00
l10n daemon script
9d27651411 GIT_SILENT Sync po/docbooks with svn 2025-02-04 01:34:04 +00:00
Joshua Goins
268975bc3b Fix crash when trying to view Security settings in an invited room
This state event doesn't exist (or is inaccessible) to us, and tries to
access nullptr.
2025-02-04 01:02:25 +00:00
James Graham
66343ba11e Fix new MessageModel
Make sure that we initialise the MessageContentModel for nwe and historical events after they have been added to the timeline
2025-02-03 17:16:40 +00:00
l10n daemon script
684cd85a7a GIT_SILENT Sync po/docbooks with svn 2025-02-02 01:38:47 +00:00
Heiko Becker
ef9a80e76f GIT_SILENT Update Appstream for new release
(cherry picked from commit 96b03082e3)
2025-01-31 01:44:04 +01:00
l10n daemon script
fbb5f02379 GIT_SILENT Sync po/docbooks with svn 2025-01-29 01:35:45 +00:00
James Graham
5f4bde96e9 Max Width Threads
Since threads are a conversation where both the local user and others take part always make them span the full available width
2025-01-28 18:13:56 +00:00
l10n daemon script
f8c8a68840 GIT_SILENT Sync po/docbooks with svn 2025-01-28 01:36:42 +00:00
Tobias Fella
bf6f4a951e Mark MessageModel as uncreatable 2025-01-27 16:07:30 +01:00
l10n daemon script
58c9366548 GIT_SILENT Sync po/docbooks with svn 2025-01-27 01:37:35 +00:00
l10n daemon script
f410ecac2b GIT_SILENT Sync po/docbooks with svn 2025-01-26 01:35:04 +00:00
Max Buchholz
1d1a43ade2 Appdata: add display size 2025-01-25 17:57:21 +00:00
James Graham
37adb56233 Thread fetch more button
Changes threads so there is a button to fetch more events. Also adds a separator between the thread root and the rest of the events.
2025-01-25 16:50:29 +00:00
Justin Zobel
aca0669bf6 CI: Add JSON, XML and YML linting 2025-01-25 09:33:05 -05:00
Justin Zobel
b33ab76ff8 YAML formatting 2025-01-25 09:33:04 -05:00
Gary Wang
38a391b7fa CI: add frameworks/kiconthemes to .kde-ci.yaml 2025-01-25 14:14:19 +00:00
Gary Wang
82434fe87c fix: no icon under Windows
See also:

- https://invent.kde.org/frameworks/kiconthemes/-/issues/3
- https://planet.kde.org/christoph-cullmann-2024-05-11-kde-applications-icons/
2025-01-25 14:14:19 +00:00
Tobias Fella
8bf7c36249 Improve verification method choosing 2025-01-25 11:56:10 +01:00
l10n daemon script
cff3557a24 GIT_SILENT Sync po/docbooks with svn 2025-01-25 01:36:39 +00:00
Carl Schwan
2c476c4351 Fix double separator in RoomDrawer 2025-01-24 12:36:50 +00:00
l10n daemon script
82c8ab511d GIT_SILENT Sync po/docbooks with svn 2025-01-24 01:36:28 +00:00
l10n daemon script
486ed6edd2 GIT_SILENT Sync po/docbooks with svn 2025-01-23 01:33:32 +00:00
l10n daemon script
a4b0a9ed36 GIT_SILENT Sync po/docbooks with svn 2025-01-22 01:34:28 +00:00
l10n daemon script
bba9c37ba5 GIT_SILENT Sync po/docbooks with svn 2025-01-21 01:35:12 +00:00
Joshua Goins
febc7d1630 Add UI to set a custom display name for specific rooms
This is the same functionality that /myroomnick does, but it's now
exposed in a much more accessible place in the UI. A new page to the
room settings is added to configure your profile in the room. It's
currently limited to a display name.
2025-01-19 21:33:07 -05:00
l10n daemon script
1ed071949b GIT_SILENT Sync po/docbooks with svn 2025-01-20 01:39:21 +00:00
l10n daemon script
3878c264ef 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-01-20 01:28:38 +00:00
Joshua Goins
21daf0b664 Fix "Configure Web Shortcuts" not doing anything, again 2025-01-19 19:52:37 +00:00
l10n daemon script
5efaa72cea GIT_SILENT Sync po/docbooks with svn 2025-01-19 01:35:00 +00:00
Mark Penner
191cd7cbba add missing icons on Android 2025-01-18 13:00:31 -06:00
Joshua Goins
c583e31b16 Add how many rooms you have in common to the user detail dialog
Eventually this will be expanded into an actual list you can look
through, but this can at least give you an idea the
number of rooms this user shares with you. If the user doesn't share any
rooms with you (e.g. they left) then the label is hidden.
2025-01-18 17:40:26 +00:00
Joshua Goins
590fba7deb Always open the user details dialog in the focused window
This fixes some odd UX where you tap on someone's user in a search or
pinned messages window, but it opens in the NeoChat main window instead.

Fixes #681
2025-01-18 14:41:34 +00:00
Joshua Goins
a8f22003cb Adapt to new libQuotient API changes
postPlainText is gone
2025-01-18 13:28:39 +00:00
l10n daemon script
54596e3fe6 GIT_SILENT Sync po/docbooks with svn 2025-01-18 12:28:26 +00:00
l10n daemon script
2ee4d110a0 GIT_SILENT Sync po/docbooks with svn 2025-01-18 01:41:35 +00:00
James Graham
7a949dccbb Refactor threads
The focus here is to make threads use the standard message content system rather than having a special implementation.

To achieve this the threadroot content model will now get a thread body component which will visualise the thread model with all the other messages. The latest message in the thread will then just ask for the thread root content model and show that.

Note: in order to stop a cyclical dependency with MessageComponentChooser and new base version has been added which is just missing ThreadBodyComponent and and the main version is now inherited from that with ThreadBodyComponent added.
2025-01-17 19:18:44 +00:00
Tobias Fella
111a45ab38 Use ellipsis character instead of ... 2025-01-17 16:50:56 +01:00
l10n daemon script
2bcf59c225 GIT_SILENT Sync po/docbooks with svn 2025-01-17 01:34:40 +00:00
Joshua Goins
d542033125 Don't spam pending invites every time NeoChat is started
Currently the way we show invite notifications is sub-optimal. We did it
during the initial room state load, which meant it shows an invite
notification *every time* you opened NeoChat. This gets annoying very
quickly if you have any pending invitations you don't want to take
action on just yet.

Instead, let's handle this in NotificationsManager directly, and also
remove some scaffolding now that it isn't plumbed through
NeoChatRoom/NeoChatConnection.
2025-01-16 20:35:42 +00:00
Tobias Fella
44c72828e1 Fixes for password changing 2025-01-16 18:21:35 +01:00
l10n daemon script
99d3ee32fa GIT_SILENT Sync po/docbooks with svn 2025-01-16 01:35:29 +00:00
l10n daemon script
7df0ff309e GIT_SILENT Sync po/docbooks with svn 2025-01-15 01:35:33 +00:00
James Graham
856a751fcb Fix Getting Member Objects
It seems that there are no guarantees that we will have a room member event available when a message has arrived especially early on after room load so we should create member object unconditionally and make it the responsibility of the caller to only ask for real senders.

BUG: 498649
2025-01-14 18:54:34 +00:00
l10n daemon script
da99bcae5d GIT_SILENT Sync po/docbooks with svn 2025-01-14 01:36:26 +00:00
Joshua Goins
1b0c6c2847 Add button to view pinned messages in a room
BUG: 497427
2025-01-13 20:48:35 +00:00
Joshua Goins
c315e817b2 Add an option to disable encryption in new chats
Right now NeoChat (or more technically, libQuotient) decides to use
encryption by default in new chats. Some users may not prefer or need
this, so a new option is added under Security to change this behavior.

BUG: 498375
2025-01-13 20:34:00 +00:00
l10n daemon script
6fde07a20d GIT_SILENT Sync po/docbooks with svn 2025-01-13 01:38:22 +00:00
James Graham
8a86159fd7 Fix getting content models for old events in a search model
Fix getting content models for old events in a search model by allowing for calling using the event. This gets past the intial checks and the content model itself can load the event from the server.

Requires network/neochat!2110 to fix the showauthor issue
2025-01-12 15:14:48 +00:00
Joshua Goins
1d532a1fc1 Move showAuthor role to MessageModel, so it's available for all models
This fixes features like the search model, where no message delegates
could be created because it's missing showAuthor.
2025-01-12 13:48:31 +00:00
Tobias Fella
a67ce75924 Improve ssss result reporting 2025-01-12 14:33:42 +01:00
l10n daemon script
b3d845ea32 GIT_SILENT Sync po/docbooks with svn 2025-01-12 01:40:23 +00:00
Carl Schwan
008e12cb42 Port AccountMenu to ConvergentContextMenu 2025-01-11 17:07:17 +00:00
Carl Schwan
ceaed8be51 Simplify ExploreComponent
Remove mobile mode support as this is handled by ExploreComponentMobile
2025-01-11 17:07:17 +00:00
Carl Schwan
f0e0979366 Port DelegateContextMenu to ConvergentContextMenu 2025-01-11 17:07:17 +00:00
Carl Schwan
c43563a804 Port space menu to ConvergentContextMenu 2025-01-11 17:07:17 +00:00
Carl Schwan
8ec3b2d05d Port to ConvergentContextMenu 2025-01-11 17:07:17 +00:00
Tobias Fella
39a95c727f Remove unused functions for getting crypto keys 2025-01-11 17:47:30 +01:00
James Graham
a2f5a585e3 Use the libQuotient function to get the user power level as it is now equivalent 2025-01-11 16:38:20 +00:00
Tobias Fella
aa95bc62bd Use 6.8 flatpak runtime 2025-01-11 15:47:45 +01:00
James Graham
ae7bfa5bcb Move the storage of thread models to the room 2025-01-11 13:22:51 +00:00
James Graham
37de1ec583 Move the storage of MessageContentModels to the room
Move the storage of MessageContentModels to the room in the same manner as memeber objects to prevent duplication but mainly to make the system easier to maintain going forward with things like threads for example. This requires the creation of a MessageContentFilterModel as the same model may be used in multiple places, sometimes with the author showning sometimes not.
2025-01-11 13:16:14 +00:00
l10n daemon script
bb8f0eae1b GIT_SILENT Sync po/docbooks with svn 2025-01-11 01:33:52 +00:00
l10n daemon script
571d3c14c8 GIT_SILENT Sync po/docbooks with svn 2025-01-10 10:24:58 +00:00
Joshua Goins
d7202ae0a7 Implement request for user data erasure
This adds UI for MSC4025 to the account deactivation dialog, if the
server supports it. We also switch away from our
customDeactivateAccountJob to libQuotient's.

Fixes #670.
2025-01-09 16:37:09 -05:00
Carl Schwan
2a9c75e24f Improve handling of DonwloadAction
Set the progress the download action only when the currentProgressInfo
changed, otherwise we sometimes end up in a data race.
2025-01-09 16:41:21 +00:00
Carl Schwan
7231662f94 Fix right clicking on NeoChatMaximizedComponent
The api of RoomManager.viewEventMenu changed and now require also
passing the author.
2025-01-09 16:41:21 +00:00
Carl Schwan
df83927ed7 Expose ProgressInfoRole also for other type of attachments 2025-01-09 16:41:21 +00:00
Carl Schwan
f14dfc5de8 Set explicitely parent in MaximizeComponent
Currently it uses applicationWindow().overlay which works but is
not ideal for multiple reasons:

- This as a tendency to breaks unexpectedly
- It can't be optimized by the qml compiler

So we are trying to move away from these construct everywhere.
2025-01-09 16:41:21 +00:00
l10n daemon script
188d0c9d5c GIT_SILENT Sync po/docbooks with svn 2025-01-09 01:36:14 +00:00
l10n daemon script
8d68c64fdf GIT_SILENT Sync po/docbooks with svn 2025-01-08 01:36:51 +00:00
Carl Schwan
d796ab350e Use Component.onCompleted
as we don't yet depends on KF 6.10
2025-01-07 16:33:31 +01:00
Carl Schwan
334a1b5bef Revert "Require Kirigami master"
This reverts commit 3304ee0985.
2025-01-07 16:32:31 +01:00
Joshua Goins
3304ee0985 Require Kirigami master
This is needed for emptyHelpfulAction, which is only in the currently unreleased KF 6.10.
This should fix the KDE Android Nightly.
2025-01-07 08:17:44 +00:00
l10n daemon script
d7b3523544 GIT_SILENT Sync po/docbooks with svn 2025-01-07 01:35:44 +00:00
l10n daemon script
1a8d346064 GIT_SILENT Sync po/docbooks with svn 2025-01-06 01:37:20 +00:00
James Graham
42f9b36667 Get rid of the m_memberObjects store in MessageModel
Get rid of the m_memberObjects store in MessageModel which was missed in the last mr. Also clean up the code for NeoChatMembers
2025-01-05 18:52:18 +00:00
James Graham
c21e9f2114 Store NeochatRoomMember objects in the room
Store NeochatRoomMember objects in the room so we don't duplicate them unnecessarily. This also adds a visible property for a room which is set true when shown by MessageEventModel and false when not, triggering the deletion of member objects. This mechanism will be used for other object types in the future.
2025-01-05 11:06:42 +00:00
l10n daemon script
3dbe605de8 GIT_SILENT Sync po/docbooks with svn 2025-01-05 01:35:48 +00:00
James Graham
41a6dd6175 Timeline Memory Test App
Create a simple app that puts a number of events into a simple timeline which can be used to verify future memory optimisations

When Neochat is built with tests on you should find the app at ~/kde/usr/bin/timeline-memtest assuming ~/kde is your kdesrc-build directory
2025-01-04 15:29:40 +00:00
l10n daemon script
09af6fe0a7 GIT_SILENT Sync po/docbooks with svn 2025-01-04 01:34:01 +00:00
Kai Uwe Broulik
3b8c3afa3e Support canceling file transfer from pseudo job
Allows to click cancel on the job popup rather than only from
within NeoChat.
2025-01-03 13:56:33 +01:00
Kai Uwe Broulik
7b7f4d264c Handle transfer job being canceled
Set KilledJobError to indicate it was canceled by the user
to avoid a bogus "finished" notification.

Sadly, fileTransferCanceled has been removed from libQuotient
so this lambda botch checking transfer status needs to be done.
2025-01-03 13:55:56 +01:00
Kai Uwe Broulik
c454a4942e MessageDelegateContextMenu: Fix "Edit" action not showing
There's no such thing as Emote or Message anymore, it's all "Text".
Matches IsEditableRole of MessageEventModel.

While at it, also clear threadId, which is what the Edit quick
button also does.
2025-01-03 10:21:36 +00:00
l10n daemon script
44cd52af6c GIT_SILENT Sync po/docbooks with svn 2025-01-03 01:34:59 +00:00
Heiko Becker
0d01339b02 GIT_SILENT Update Appstream for new release
(cherry picked from commit a5b37a78a0)
2025-01-02 15:06:37 +01:00
Carl Schwan
e5b4ca53f8 Use nightly kirigami addons in craft builds 2025-01-02 15:05:36 +01:00
l10n daemon script
703b03b33c GIT_SILENT Sync po/docbooks with svn 2025-01-02 01:33:44 +00:00
l10n daemon script
73cdad66ac GIT_SILENT Sync po/docbooks with svn 2025-01-01 01:34:36 +00:00
James Graham
4b1afdbe2d Fix Thread Chatbars
Since the new thread API was released in 0.9.2 update the if defs to include it. This will solve the double chat bar in thread for anyone on 0.9.2 or above
2024-12-31 16:13:27 +00:00
James Graham
64bfc0f29a Add more detail to the MessageModel documentation
Because when I was making a new one these are the things I forgot
2024-12-31 14:52:49 +00:00
Carl Schwan
28c4c0b48c Use symbolic icon for purpose plugin 2024-12-31 14:05:05 +00:00
l10n daemon script
40ccde9f06 GIT_SILENT Sync po/docbooks with svn 2024-12-31 01:36:00 +00:00
l10n daemon script
baa7d02ba5 GIT_SILENT Sync po/docbooks with svn 2024-12-30 01:41:01 +00:00
Kai Uwe Broulik
a391df1e67 QuickSwitcher: Suggest to explore rooms when there are no search results
My go to place for doing anything with rooms, is the search button.
However, I cannot join/find new rooms from there. Therefore this adds
an "Expore rooms" button as helpful action when there are no matching
rooms.
2024-12-29 18:56:50 +00:00
Kai Uwe Broulik
1f26485208 ChatBar: Remove explicit Keys.onDeletePressed handler
Key-specific handlers, such as Key.onDeletePressed implicitly accept
the event. This means that the entire logic for the delete key must
be reimplemented, and e.g. Ctrl+Delete to delete the previous *word*
was missed.

Since all it has to do is handle the typing notification and format bar,
just use the already existing Keys.onPressed handler (which does *not*
accept the event) and add a case for Delete alongside Backspace.
2024-12-29 11:02:36 +01:00
217 changed files with 45038 additions and 30649 deletions

View File

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

View File

@@ -5,13 +5,17 @@ include:
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/reuse-lint.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/windows-qt6.yml
# - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
# - /gitlab-templates/android-qt6.yml
# - /gitlab-templates/linux-qt6.yml
# - /gitlab-templates/linux-qt6-next.yml
# - /gitlab-templates/windows-qt6.yml
# - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml
# - /gitlab-templates/snap-snapcraft-lxd.yml
# - /gitlab-templates/craft-android-qt6-apks.yml
# - /gitlab-templates/craft-appimage-qt6.yml
# - /gitlab-templates/craft-windows-x86-64-qt6.yml
# - /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -2,42 +2,43 @@
# SPDX-License-Identifier: BSD-2-Clause
Dependencies:
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
'require':
'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6'
'frameworks/kirigami': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6'
'frameworks/prison': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows', 'Linux', 'FreeBSD']
'require':
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
'require':
'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6'
'frameworks/kirigami': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6'
'frameworks/prison': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows', 'Linux', 'FreeBSD']
'require':
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
- 'on': ['Linux']
'require':
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
- 'on': ['Linux']
'require':
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
Options:
per-test-timeout: 90
require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD', 'Windows' ]
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']

View File

@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
@@ -49,8 +49,6 @@ if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX NEOCHAT
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
@@ -66,7 +64,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
@@ -107,13 +105,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
find_package(QuotientQt6 0.9)
set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API"
URL "https://github.com/quotient-im/libQuotient/"
PURPOSE "Talk with matrix server"
)
find_package(Integral 0.1 REQUIRED)
find_package(cmark)
set_package_properties(cmark PROPERTIES
@@ -174,6 +166,9 @@ if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests)
add_subdirectory(appiumtests)
if (NOT ANDROID)
add_subdirectory(memorytests)
endif()
endif()
if(KF6DocTools_FOUND)

View File

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

View File

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

View File

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

View File

@@ -137,7 +137,11 @@ void TimelineMessageModelTest::pendingEvent()
model->setRoom(room);
QCOMPARE(model->rowCount(), 0);
#if Quotient_VERSION_MINOR > 9
auto txnId = room->postText("New plain message"_L1);
#else
auto txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 1);
@@ -145,7 +149,11 @@ void TimelineMessageModelTest::pendingEvent()
QCOMPARE(model->rowCount(), 0);
QCOMPARE(spyRemove.count(), 1);
#if Quotient_VERSION_MINOR > 9
txnId = room->postText("New plain message"_L1);
#else
txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 2);

30
flatpak/corrosion.json Normal file
View File

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

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
qt_add_executable(timeline-memtest
main.cpp
)
target_link_libraries(timeline-memtest PRIVATE neochatplugin timelineplugin)
target_link_libraries(timeline-memtest PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::QuickControls2
KF6::Kirigami
QuotientQt6
neochat
)
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
memtesttimelinemodel.cpp
memtesttimelinemodel.h
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat
)

33
memorytests/Main.qml Normal file
View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
QQC2.ApplicationWindow {
id: root
title: "Timeline Memory Test"
minimumWidth: Kirigami.Units.gridUnit * 30
minimumHeight: Kirigami.Units.gridUnit * 30
visible: true
QQC2.ScrollView {
width: root.width
height: root.height
contentItem: ListView {
model: messageFilterModel
delegate: EventDelegate {
room: memTestTimelineModel.room
}
}
}
}

30
memorytests/main.cpp Normal file
View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <KLocalizedString>
#include "memtesttimelinemodel.h"
#include "models/messagefiltermodel.h"
using namespace Qt::StringLiterals;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QQmlApplicationEngine engine;
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
return app.exec();
}

View File

@@ -0,0 +1,655 @@
{
"ephemeral": {
"events": [
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@alice:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@bob:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tim:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@example:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@jeff:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tina:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@sally:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@fred:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582555PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Bob",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "bob:example.org",
"state_key": "@bob:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tim",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tim:example.org",
"state_key": "@tim:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Jeff",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "jeff:example.org",
"state_key": "@jeff:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tina",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tina:example.org",
"state_key": "@tina:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Sally",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "sally:example.org",
"state_key": "@sally:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Fred",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "fred:example.org",
"state_key": "@fred:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": "$1000000000000:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": "$1000000000001:example.org",
"origin_server_ts": 1000000000001,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000001:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000002,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000002:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000000:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000003,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000003:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1000000000004:example.org",
"origin_server_ts": 1000000000004,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": "$1000000000005:example.org",
"origin_server_ts": 1000000000005,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": "$1000000000006:example.org",
"origin_server_ts": 1000000000006,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": "$1000000000007:example.org",
"origin_server_ts": 1000000000007,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": "$1000000000008:example.org",
"origin_server_ts": 1000000000008,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": "$1000000000009:example.org",
"origin_server_ts": 1000000000009,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000009:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000010,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000010:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000008:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000011,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000011:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1000000000012:example.org",
"origin_server_ts": 1000000000012,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": "$1000000000013:example.org",
"origin_server_ts": 1000000000013,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": "$1000000000014:example.org",
"origin_server_ts": 1000000000014,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": "$1000000000015:example.org",
"origin_server_ts": 1000000000015,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": "$1000000000016:example.org",
"origin_server_ts": 1000000000016,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": "$1000000000017:example.org",
"origin_server_ts": 1000000000017,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000017:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000018,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000018:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000016:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000019,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000019:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1000000000020:example.org",
"origin_server_ts": 1000000000020,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": "$1000000000021:example.org",
"origin_server_ts": 1000000000021,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": "$1000000000022:example.org",
"origin_server_ts": 1000000000022,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": "$1000000000023:example.org",
"origin_server_ts": 1000000000023,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "memtesttimelinemodel.h"
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/roommessageevent.h>
using namespace Quotient;
MemTestTimelineModel::MemTestTimelineModel(QObject *parent)
: MessageModel(parent)
{
beginResetModel();
m_connection = Connection::makeMockConnection(u"@bob:example.org"_s);
m_room = new MemTestRoom(m_connection, u"#memtestroom:example.org"_s, u"memtest-sync.json"_s);
for (const auto &eventIt : m_room->messageEvents()) {
Q_EMIT newEventAdded(eventIt.event());
}
endResetModel();
}
std::optional<std::reference_wrapper<const RoomEvent>> MemTestTimelineModel::getEventForIndex(QModelIndex index) const
{
return *m_room->messageEvents().at(index.row()).event();
}
int MemTestTimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_room->messageEvents().size();
}
#include "moc_memtesttimelinemodel.cpp"

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/roomevent.h>
#include <Quotient/syncdata.h>
#include "models/messagemodel.h"
namespace Quotient
{
class Connection;
}
class NeoChatRoom;
class MemTestRoom : public NeoChatRoom
{
public:
MemTestRoom(Quotient::Connection *connection, const QString &roomName, const QString &syncFileName = {})
: NeoChatRoom(connection, roomName, Quotient::JoinState::Join)
{
syncNewEvents(syncFileName);
}
void update(Quotient::SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
void syncNewEvents(const QString &syncFileName)
{
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
update(std::move(roomData));
}
}
};
/**
* @class MemTestTimelineModel
*
* This is a special version of the MessageModel design to load an unchanging set
* of events from a json file so that timeline memory optimisations can be measured.
*/
class MemTestTimelineModel : public MessageModel
{
Q_OBJECT
QML_ELEMENT
public:
explicit MemTestTimelineModel(QObject *parent = nullptr);
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
QPointer<Quotient::Connection> m_connection;
std::vector<Quotient::RoomEventPtr> m_events;
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> getEventForIndex(QModelIndex index) const override;
};

View File

@@ -473,6 +473,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/>
@@ -648,6 +651,9 @@
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
</release>
</releases>
<requires>
<display_length compare="ge">360</display_length>
</requires>
<branding>
<color type="primary" scheme_preference="light">#a6e4f3</color>
<color type="primary" scheme_preference="dark">#235670</color>

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
# SPDX-License-Identifier: CC0-1.0
---
name: neochat
base: core22
base: core24
adopt-info: neochat
grade: stable
confinement: strict
@@ -28,8 +28,8 @@ apps:
compression: lzo
package-repositories:
- type: apt
ppa: ubuntu-toolchain-r/test
- type: apt
ppa: ubuntu-toolchain-r/test
slots:
session-dbus-interface:
@@ -66,7 +66,7 @@ parts:
- -Dcrypto=disabled
- -Dgtk_doc=false
build-packages:
- meson
- meson
- libglib2.0-dev
- libgcrypt20-dev
prime:
@@ -81,7 +81,8 @@ parts:
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PKG_CONFIG_PATH: $CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH
- PKG_CONFIG_PATH: "$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET\
/pkgconfig:$PKG_CONFIG_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
@@ -105,8 +106,6 @@ parts:
build-snaps:
- cmake
build-packages:
- gcc-13
- g++-13
- libssl-dev
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
@@ -114,9 +113,6 @@ parts:
- -DBUILD_TESTING=OFF
- -DQuotient_ENABLE_E2EE=ON
- -DBUILD_WITH_QT6=ON
override-build: |
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13
craftctl default
prime:
- -usr/include
- -usr/lib/*/pkgconfig
@@ -129,6 +125,8 @@ parts:
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
@@ -150,6 +148,8 @@ parts:
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
build-packages:
- cmark
- libcmark-dev
@@ -173,3 +173,12 @@ parts:
prime:
- usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

@@ -10,190 +10,192 @@ endif()
add_library(neochat STATIC
controller.cpp
controller.h
models/emojimodel.cpp
models/emojimodel.h
emojitones.cpp
emojitones.h
models/customemojimodel.cpp
models/customemojimodel.h
clipboard.cpp
clipboard.h
models/timelinemessagemodel.cpp
models/timelinemessagemodel.h
models/messagefiltermodel.cpp
models/messagefiltermodel.h
models/roomlistmodel.cpp
models/roomlistmodel.h
models/sortfilterspacelistmodel.cpp
models/sortfilterspacelistmodel.h
models/accountemoticonmodel.cpp
models/accountemoticonmodel.h
spacehierarchycache.cpp
spacehierarchycache.h
roommanager.cpp
roommanager.h
# models/emojimodel.cpp
# models/emojimodel.h
# emojitones.cpp
# emojitones.h
# models/customemojimodel.cpp
# models/customemojimodel.h
# clipboard.cpp
# clipboard.h
# models/timelinemessagemodel.cpp
# models/timelinemessagemodel.h
# models/messagefiltermodel.cpp
# models/messagefiltermodel.h
# models/roomlistmodel.cpp
# models/roomlistmodel.h
# models/sortfilterspacelistmodel.cpp
# models/sortfilterspacelistmodel.h
# models/accountemoticonmodel.cpp
# models/accountemoticonmodel.h
# spacehierarchycache.cpp
# spacehierarchycache.h
# roommanager.cpp
# roommanager.h
neochatroom.cpp
neochatroom.h
models/userlistmodel.cpp
models/userlistmodel.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/publicroomlistmodel.cpp
models/publicroomlistmodel.h
models/spacechildrenmodel.cpp
models/spacechildrenmodel.h
models/spacechildsortfiltermodel.cpp
models/spacechildsortfiltermodel.h
models/spacetreeitem.cpp
models/spacetreeitem.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
# models/publicroomlistmodel.cpp
# models/publicroomlistmodel.h
# models/spacechildrenmodel.cpp
# models/spacechildrenmodel.h
# models/spacechildsortfiltermodel.cpp
# models/spacechildsortfiltermodel.h
# models/spacetreeitem.cpp
# models/spacetreeitem.h
# models/userdirectorylistmodel.cpp
# models/userdirectorylistmodel.h
models/pushrulemodel.cpp
models/pushrulemodel.h
models/emoticonfiltermodel.cpp
models/emoticonfiltermodel.h
notificationsmanager.cpp
notificationsmanager.h
models/sortfilterroomlistmodel.cpp
models/sortfilterroomlistmodel.h
# models/emoticonfiltermodel.cpp
# models/emoticonfiltermodel.h
# notificationsmanager.cpp
# notificationsmanager.h
# models/sortfilterroomlistmodel.cpp
# models/sortfilterroomlistmodel.h
models/roomtreemodel.cpp
models/roomtreemodel.h
chatdocumenthandler.cpp
chatdocumenthandler.h
models/devicesmodel.cpp
models/devicesmodel.h
models/devicesproxymodel.cpp
filetype.cpp
filetype.h
login.cpp
login.h
models/webshortcutmodel.cpp
models/webshortcutmodel.h
# models/devicesmodel.cpp
# models/devicesmodel.h
# models/devicesproxymodel.cpp
# filetype.cpp
# filetype.h
# login.cpp
# login.h
# models/webshortcutmodel.cpp
# models/webshortcutmodel.h
blurhash.cpp
blurhash.h
blurhashimageprovider.cpp
blurhashimageprovider.h
models/mediamessagefiltermodel.cpp
models/mediamessagefiltermodel.h
urlhelper.cpp
urlhelper.h
# models/mediamessagefiltermodel.cpp
# models/mediamessagefiltermodel.h
# urlhelper.cpp
# urlhelper.h
windowcontroller.cpp
windowcontroller.h
linkpreviewer.cpp
linkpreviewer.h
# linkpreviewer.cpp
# linkpreviewer.h
models/completionmodel.cpp
models/completionmodel.h
models/completionproxymodel.cpp
models/completionproxymodel.h
models/actionsmodel.cpp
models/actionsmodel.h
models/serverlistmodel.cpp
models/serverlistmodel.h
models/statemodel.cpp
models/statemodel.h
models/statefiltermodel.cpp
models/statefiltermodel.h
filetransferpseudojob.cpp
filetransferpseudojob.h
models/searchmodel.cpp
models/searchmodel.h
texthandler.cpp
texthandler.h
# models/completionproxymodel.cpp
# models/completionproxymodel.h
# models/actionsmodel.cpp
# models/actionsmodel.h
# models/serverlistmodel.cpp
# models/serverlistmodel.h
# models/statemodel.cpp
# models/statemodel.h
# models/statefiltermodel.cpp
# models/statefiltermodel.h
# filetransferpseudojob.cpp
# filetransferpseudojob.h
# models/searchmodel.cpp
# models/searchmodel.h
# texthandler.cpp
# texthandler.h
logger.cpp
logger.h
models/stickermodel.cpp
models/stickermodel.h
models/imagepacksmodel.cpp
models/imagepacksmodel.h
events/imagepackevent.cpp
events/imagepackevent.h
events/joinrulesevent.cpp
events/joinrulesevent.h
models/reactionmodel.cpp
models/reactionmodel.h
# models/stickermodel.cpp
# models/stickermodel.h
# models/imagepacksmodel.cpp
# models/imagepacksmodel.h
# events/imagepackevent.cpp
# events/imagepackevent.h
# models/reactionmodel.cpp
# models/reactionmodel.h
delegatesizehelper.cpp
delegatesizehelper.h
models/livelocationsmodel.cpp
models/livelocationsmodel.h
models/locationsmodel.cpp
models/locationsmodel.h
locationhelper.cpp
locationhelper.h
events/pollevent.cpp
pollhandler.cpp
# models/livelocationsmodel.cpp
# models/livelocationsmodel.h
# models/locationsmodel.cpp
# models/locationsmodel.h
# locationhelper.cpp
# locationhelper.h
# events/pollevent.cpp
# pollhandler.cpp
utils.h
utils.cpp
registration.cpp
# registration.cpp
neochatconnection.cpp
neochatconnection.h
jobs/neochatdeactivateaccountjob.cpp
jobs/neochatdeactivateaccountjob.h
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h
# jobs/neochatgetcommonroomsjob.cpp
# jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp
mediasizehelper.h
eventhandler.cpp
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
# eventhandler.cpp
# enums/delegatetype.h
# roomlastmessageprovider.cpp
# roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
colorschemer.cpp
colorschemer.h
models/notificationsmodel.cpp
models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
# models/notificationsmodel.cpp
# models/notificationsmodel.h
# models/timelinemodel.cpp
# models/timelinemodel.h
enums/pushrule.h
models/itinerarymodel.cpp
models/itinerarymodel.h
# models/itinerarymodel.cpp
# models/itinerarymodel.h
proxycontroller.cpp
proxycontroller.h
models/linemodel.cpp
models/linemodel.h
events/locationbeaconevent.h
events/widgetevent.h
enums/messagecomponenttype.h
models/messagecontentmodel.cpp
models/messagecontentmodel.h
# events/locationbeaconevent.h
# events/widgetevent.h
# enums/messagecomponenttype.h
# models/messagecontentmodel.cpp
# models/messagecontentmodel.h
enums/neochatroomtype.h
models/sortfilterroomtreemodel.cpp
models/sortfilterroomtreemodel.h
mediamanager.cpp
mediamanager.h
models/statekeysmodel.cpp
models/statekeysmodel.h
sharehandler.cpp
sharehandler.h
# models/statekeysmodel.cpp
# models/statekeysmodel.h
# sharehandler.cpp
# sharehandler.h
models/roomtreeitem.cpp
models/roomtreeitem.h
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
identityserverhelper.cpp
identityserverhelper.h
enums/powerlevel.cpp
enums/powerlevel.h
models/permissionsmodel.cpp
models/permissionsmodel.h
threepidbindhelper.cpp
threepidbindhelper.h
models/readmarkermodel.cpp
models/readmarkermodel.h
neochatroommember.cpp
neochatroommember.h
models/threadmodel.cpp
models/threadmodel.h
enums/messagetype.h
messagecomponent.h
# models/threepidmodel.cpp
# models/threepidmodel.h
# threepidaddhelper.cpp
# threepidaddhelper.h
# identityserverhelper.cpp
# identityserverhelper.h
# enums/powerlevel.cpp
# enums/powerlevel.h
# models/permissionsmodel.cpp
# models/permissionsmodel.h
# threepidbindhelper.cpp
# threepidbindhelper.h
# models/readmarkermodel.cpp
# models/readmarkermodel.h
# neochatroommember.cpp
# neochatroommember.h
# models/threadmodel.cpp
# models/threadmodel.h
# enums/messagetype.h
# messagecomponent.h
enums/roomsortparameter.cpp
enums/roomsortparameter.h
models/roomsortparametermodel.cpp
models/roomsortparametermodel.h
models/messagemodel.cpp
models/messagemodel.h
# models/roomsortparametermodel.cpp
# models/roomsortparametermodel.h
# models/messagemodel.cpp
# models/messagemodel.h
# models/messagecontentfiltermodel.cpp
# models/messagecontentfiltermodel.h
# models/pinnedmessagemodel.cpp
# models/pinnedmessagemodel.h
# models/commonroomsmodel.cpp
# models/commonroomsmodel.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -213,7 +215,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
qml/ContextMenu.qml
qml/RoomContextMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml
qml/RoomListPage.qml
@@ -249,6 +251,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/MessageSourceSheet.qml
qml/ConfirmEncryptionDialog.qml
qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
qml/InvitationView.qml
@@ -293,6 +296,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/HoverLinkIndicator.qml
qml/AvatarNotification.qml
qml/ReasonDialog.qml
SOURCES
# messageattached.cpp
# messageattached.h
DEPENDENCIES
QtCore
QtQuick
@@ -392,10 +398,10 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp)
# target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
# target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
@@ -418,9 +424,10 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ColorScheme
KF6::ItemModels
QuotientQt6
Integral
cmark::cmark
QCoro::Core
QCoro::Network
@@ -491,6 +498,7 @@ if(ANDROID)
"network-connect"
"list-remove-user"
"org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
@@ -528,12 +536,16 @@ if(ANDROID)
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-high"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
"pin-symbolic"
"kt-restore-defaults-symbolic"
"user-symbolic"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()

View File

@@ -237,20 +237,6 @@ QQC2.Control {
onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
}
Keys.onDeletePressed: {
if (selectedText.length > 0) {
remove(selectionStart, selectionEnd);
} else {
remove(cursorPosition, cursorPosition + 1);
}
if (textField.text == selectedText || textField.text.length <= 1) {
root.currentRoom.sendTypingNotification(false);
repeatTimer.stop();
}
if (quickFormatBar.visible) {
quickFormatBar.close();
}
}
Keys.onEnterPressed: event => {
const controlIsPressed = event.modifiers & Qt.ControlModifier;
if (completionMenu.visible) {
@@ -289,7 +275,7 @@ QQC2.Control {
completionMenu.decrementIndex();
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex();
} else if (event.key === Qt.Key_Backspace) {
} else if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) {
if (textField.text == selectedText || textField.text.length <= 1) {
root.currentRoom.sendTypingNotification(false);
repeatTimer.stop();
@@ -358,13 +344,13 @@ QQC2.Control {
Item {
implicitWidth: replyComponent.implicitWidth
implicitHeight: replyComponent.implicitHeight
ReplyComponent {
id: replyComponent
replyEventId: _private.chatBarCache.replyId
replyAuthor: _private.chatBarCache.relationAuthor
replyContentModel: _private.chatBarCache.relationEventContentModel
maxContentWidth: paneLoader.item.width
}
// ReplyComponent {
// id: replyComponent
// replyEventId: _private.chatBarCache.replyId
// replyAuthor: _private.chatBarCache.relationAuthor
// replyContentModel: _private.chatBarCache.relationEventContentModel
// Message.maxContentWidth: paneLoader.item.width
// }
QQC2.Button {
id: cancelButton
@@ -512,23 +498,23 @@ QQC2.Control {
}
}
EmojiDialog {
id: emojiDialog
x: root.width - width
y: -implicitHeight
modal: false
includeCustom: true
closeOnChosen: false
currentRoom: root.currentRoom
onChosen: emoji => insertText(emoji)
onClosed: if (emojiAction.checked) {
emojiAction.checked = false;
}
}
// EmojiDialog {
// id: emojiDialog
//
// x: root.width - width
// y: -implicitHeight
//
// modal: false
// includeCustom: true
// closeOnChosen: false
//
// currentRoom: root.currentRoom
//
// onChosen: emoji => insertText(emoji)
// onClosed: if (emojiAction.checked) {
// emojiAction.checked = false;
// }
// }
function insertText(text) {
let initialCursorPosition = textField.cursorPosition;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,12 +50,17 @@ public:
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Encrypted, /**< An encrypted message that cannot be decrypted. */
Reply, /**< A component to show a replied-to message. */
Reaction, /**< A component to show the reactions to this message. */
LinkPreview, /**< A preview of a URL in the message. */
LinkPreviewLoad, /**< A loading dialog for a link preview. */
ChatBar, /**< A text edit for editing a message. */
ThreadRoot, /**< The root message of the thread. */
ThreadBody, /**< The other messages in the thread. */
ReplyButton, /**< A button to reply in the current thread. */
FetchButton, /**< A button to fetch more messages in the current thread. */
Verification, /**< A user verification session start message. */
Loading, /**< The component is loading. */
Separator, /**< A horizontal separator. */
Other, /**< Anything that cannot be classified as another type. */
};
Q_ENUM(Type);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ FileTransferPseudoJob::FileTransferPseudoJob(Operation operation, const QString
, m_eventId(eventId)
, m_operation(operation)
{
setCapabilities(KJob::Killable);
}
void FileTransferPseudoJob::fileTransferProgress(const QString &id, qint64 progress, qint64 total)
@@ -41,6 +42,15 @@ void FileTransferPseudoJob::fileTransferFailed(const QString &id, const QString
emitResult();
}
void FileTransferPseudoJob::fileTransferCanceled(const QString &id)
{
if (id != m_eventId) {
return;
}
setError(KJob::KilledJobError);
emitResult();
}
void FileTransferPseudoJob::start()
{
setTotalAmount(Unit::Files, 1);
@@ -49,3 +59,9 @@ void FileTransferPseudoJob::start()
{i18nc("The URL being downloaded/uploaded", "Source"), m_path},
{i18nc("The location being downloaded to", "Destination"), m_path});
}
bool FileTransferPseudoJob::doKill()
{
Q_EMIT cancelRequested(m_eventId);
return true;
}

View File

@@ -15,6 +15,7 @@
*/
class FileTransferPseudoJob : public KJob
{
Q_OBJECT
public:
enum Operation {
Download,
@@ -38,11 +39,22 @@ public:
*/
void fileTransferFailed(const QString &id, const QString &errorMessage = {});
/**
* @brief Set the file transfer as canceled.
*/
void fileTransferCanceled(const QString &id);
/**
* @brief Start the file transfer.
*/
void start() override;
protected:
bool doKill() override;
Q_SIGNALS:
void cancelRequested(const QString &id);
private:
QString m_path;
QString m_eventId;

View File

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

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatdeactivateaccountjob.h"
using namespace Quotient;
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
: BaseJob(HttpVerb::Post, u"DisableDeviceJob"_s, "_matrix/client/v3/account/deactivate")
{
QJsonObject data;
addParam<IfNotEmpty>(data, u"auth"_s, auth);
setRequestData(data);
}

View File

@@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
{
public:
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
};

View File

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

View File

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

View File

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

View File

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

View File

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

126
src/messageattached.cpp Normal file
View File

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

78
src/messageattached.h Normal file
View File

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

View File

@@ -136,7 +136,11 @@ QList<ActionsModel::Action> actions{
Action{
u"plain"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
#if Quotient_VERSION_MINOR > 9
room->postText(text.toHtmlEscaped());
#else
room->postPlainText(text.toHtmlEscaped());
#endif
return QString();
},
std::nullopt,

View File

@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "commonroomsmodel.h"
#include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication>
using namespace Quotient;
CommonRoomsModel::CommonRoomsModel(QObject *parent)
: QAbstractListModel(parent)
{
}
NeoChatConnection *CommonRoomsModel::connection() const
{
return m_connection;
}
void CommonRoomsModel::setConnection(NeoChatConnection *connection)
{
m_connection = connection;
Q_EMIT connectionChanged();
reload();
}
QString CommonRoomsModel::userId() const
{
return m_userId;
}
void CommonRoomsModel::setUserId(const QString &userId)
{
m_userId = userId;
Q_EMIT userIdChanged();
reload();
}
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{
Q_UNUSED(index)
Q_UNUSED(roleName)
return {};
}
int CommonRoomsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_commonRooms.size();
}
void CommonRoomsModel::reload()
{
if (!m_connection || m_userId.isEmpty()) {
return;
}
if (!m_connection->canCheckMutualRooms()) {
return;
}
// Checking if you have mutual rooms with yourself doesn't make sense and servers reject it too
if (m_connection->userId() == m_userId) {
return;
}
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
const auto &replyData = job->jsonData();
beginResetModel();
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
m_commonRooms.push_back(roomId.toString());
}
endResetModel();
Q_EMIT countChanged();
});
}
#include "moc_commonroomsmodel.cpp"

View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include "neochatconnection.h"
#include "neochatroom.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/roommember.h>
/**
* @brief Model to show the common or mutual rooms between you and another user.
*/
class CommonRoomsModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED)
Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
TextRole = Qt::DisplayRole,
LongitudeRole,
LatitudeRole,
AssetRole,
AuthorRole,
};
Q_ENUM(Roles)
explicit CommonRoomsModel(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString userId() const;
void setUserId(const QString &userId);
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
Q_SIGNALS:
void connectionChanged();
void userIdChanged();
void countChanged();
private:
void reload();
QPointer<NeoChatConnection> m_connection;
QString m_userId;
QList<QString> m_commonRooms;
};

View File

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

View File

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

View File

@@ -39,14 +39,6 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date();
return day != previousEventDay;
}
// Catch and force the author to be shown for all rows
if (role == TimelineMessageModel::ContentModelRole) {
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(TimelineMessageModel::ContentModelRole));
if (model != nullptr) {
model->setShowAuthor(true);
}
return QVariant::fromValue<MessageContentModel *>(model);
}
QVariantMap mediaInfo = mapToSource(index).data(TimelineMessageModel::MediaInfoRole).toMap();

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