Compare commits

...

134 Commits

Author SHA1 Message Date
Heiko Becker
11ebe5316f GIT_SILENT Update Appstream for new release 2025-06-02 23:46:38 +02:00
Heiko Becker
9b79580c29 GIT_SILENT Upgrade release service version to 25.04.2. 2025-06-02 22:02:15 +02:00
Joshua Goins
5327ece769 Fix space switching on mobile
We forgot to connect to onTapped on the touchscreen-specific TapHandler.

BUG: 504166

(cherry picked from commit 5c8ba7e29e)
2025-06-01 08:55:07 +02:00
l10n daemon script
58658d1aa2 GIT_SILENT Sync po/docbooks with svn 2025-05-25 03:56:50 +00:00
Joshua Goins
611fb87376 Improve the UX for rooms that don't have a canonical alias
This was discovered none other than Bug Catcher Nate, while in the TWiKS
room. That room doesn't have a canonical alias set (yet) and that
exposed some UX problems around aliases in NeoChat.

First, the non-canonical alias isn't shown in the info drawer despite
being the only alias available. This is something that Element actually
does, and now NeoChat does too.

Second, NeoChat will try to copy the room's internal Matrix ID (which is
not that great) to the clipboard because it looks for the canonical
alias. Surprisingly, Element also does this but now NeoChat doesn't.

(cherry picked from commit 86fd2e8e1e)
2025-05-24 17:34:12 -04:00
Joshua Goins
823700eb8e Fix GlobalMenu initialization on other platforms
Cherry-pick of 76919a13b8
2025-05-24 09:42:44 -04:00
l10n daemon script
fbefaa0638 GIT_SILENT Sync po/docbooks with svn 2025-05-22 03:22:29 +00:00
Joshua Goins
85262c70c3 Fix issues with global menu
Smaller version of b5fcad3db0 that doesn't
have string changes.
2025-05-21 16:36:23 -04:00
Joshua Goins
c88e843a34 Add padding around file progress indicator, move it to the center
Before it stuck to the top-left corner, and looked bad. Now it's
properly centered inside of the chat bar.

(cherry picked from commit de8c9f4878)
2025-05-21 16:35:02 -04:00
Joshua Goins
702d17bc4d Add duration to voice message delegate
Otherwise you have to download the audio file before you can tell how
large it is.

(cherry picked from commit f029cf842a)
2025-05-21 16:35:02 -04:00
l10n daemon script
e015cbea07 GIT_SILENT Sync po/docbooks with svn 2025-05-21 03:19:54 +00:00
Albert Astals Cid
ccf3976a6b CI: Remove Linux Next (Qt 6.10) job
It has been failing for 4 consecutive weeks.

Master passes with Qt 6.10 so hopefully no one will be using this
version with 6.10...
2025-05-20 20:36:02 +00:00
Akseli Lahtinen
bfa1f9f053 Load stickers properly
This fixes stickers not loading, similar to the patch at
0cc14f710d
2025-05-20 15:14:35 +03:00
l10n daemon script
9c4dce53b2 GIT_SILENT Sync po/docbooks with svn 2025-05-20 03:18:03 +00:00
l10n daemon script
baaea57cd9 GIT_SILENT Sync po/docbooks with svn 2025-05-16 03:23:59 +00:00
Joshua Goins
61bb1c5db1 Check if RoomEvent is null before trying to insert a notification
As seen in the bug report, roomEvent could be null and then we get a
nice and simple null-access error. We should be careful before trying to
use it.

BUG: 502687
(cherry picked from commit a1513b30cd)
2025-05-15 13:43:02 -04:00
Joshua Goins
9b8c7714d4 Allow performing shortcuts while the completion menu is open
This prevents doing actions like zooming in your screen (Meta+Equals)
because it will tripper shortcutOverride and close the menu. Instead, we
shouldn't block shortcuts but still allow closing when Escape is
pressed.

This *does* block all NeoChat shortcuts when the completion menu is
open, but personally I think this is fine. If you have this open, you
really should finish your message first!

(cherry picked from commit fda84a6aac)
2025-05-15 13:42:52 -04:00
Joshua Goins
a020db2e04 Give completion popup a minimum size when editing an existing message
This was previously fixed to the size of the text area itself, which
could be small. We also have a bug where the text area *itself* is like
one character big, so this helps there too.

The popup is also given a margin of 0 so it is always positioned within
the bounds of the window.

BUG: 503483

(cherry picked from commit 142312d87d)
2025-05-15 13:42:52 -04:00
Joshua Goins
457b4ee3be Fix crash when trying to edit your message on a wide screen
This only seems to happen when the window is maximized, specifically
the height of the window. This seems to be a weird TextArea issue (or
something else in our TimelineView) but until that's found out, a
Qt.callLater will prevent this annoying crash.

BUG: 503846

(cherry picked from commit 906fb97259)
2025-05-15 13:42:15 -04:00
Joshua Goins
0237bf048b Fix undefined reference to webShortcutModel
This was incorrectly capitalized, so I just went ahead and properly
capitalized it so we stop making this mistake!

(cherry picked from commit 80923a2025)
2025-05-15 13:41:46 -04:00
Joshua Goins
f6183a4689 Fix the room list header margin in Kirigami >6.14
They changed how margins behave when there's no navigation buttons in 7b02df40381b28de97fab1dd8d97182778c242df
so we are actually adding more padding ontop of the existing one.


(cherry picked from commit d51e68e9e2)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2025-05-15 13:38:35 -04:00
Joshua Goins
2a2d469d7d Make sure that ChatDocumentHandler can handle the document being changed to a nullptr
Make sure that ChatDocumentHandler can handle the document being changed to a nullptr.

BUG: 501950


(cherry picked from commit 97d5be9d81)

3f287b20 Make sure that ChatDocumentHandler can handle the document being changed to a nullptr.

Co-authored-by: James Graham <james.h.graham@protonmail.com>
2025-05-14 18:11:09 -04:00
l10n daemon script
2212dc4d5e GIT_SILENT Sync po/docbooks with svn 2025-05-13 03:26:15 +00:00
l10n daemon script
6e179562f0 GIT_SILENT Sync po/docbooks with svn 2025-05-11 03:16:58 +00:00
Aleix Pol Gonzalez
b75f8b527c Fixes a null pointer call
It seems like the case is possible as we already are treating the case
in isUserBanned. Doesn't seem ideal as it shows "" where the username
should be but it's better than a crash.


(cherry picked from commit df9a7292b9)

Co-authored-by: Aleix Pol <aleixpol@kde.org>
2025-05-04 14:46:32 +00:00
Heiko Becker
0a7fccaa5c GIT_SILENT Update Appstream for new release 2025-05-03 00:31:30 +02:00
Heiko Becker
dd3488efbe GIT_SILENT Upgrade release service version to 25.04.1. 2025-05-02 23:04:43 +02:00
l10n daemon script
617eb25bbb GIT_SILENT Sync po/docbooks with svn 2025-04-28 03:16:05 +00:00
l10n daemon script
7cf20a473c GIT_SILENT Sync po/docbooks with svn 2025-04-23 03:14:41 +00:00
l10n daemon script
749b59e5eb GIT_SILENT Sync po/docbooks with svn 2025-04-22 03:14:28 +00:00
l10n daemon script
c8170f5a96 GIT_SILENT Sync po/docbooks with svn 2025-04-21 03:12:41 +00:00
l10n daemon script
142adbeb00 GIT_SILENT Sync po/docbooks with svn 2025-04-18 03:11:51 +00:00
Joshua Goins
a1c213dc46 Fix two (edited) strings appearing in edited quotes
This is because we only check if the last message component != Text,
because that handles it's own edit strings. Quote components do that
too, so if we don't exclude it there ends up being two (edited) strings
in one message.

(cherry picked from commit 5ef4ab0756)
2025-04-17 09:44:56 +00:00
Joshua Goins
f642b1488a Fix multi-line quotes being cut off
This was a mistake in the code that was designed to remove the outside
paragraphs, which seems to be to make way for the quotation marks we
add around the text. Instead of doing that (which turns out, is very
brittle and breaks on multiple paragraphs) insert the quotation marks
*inside* of the paragraph tags.

A test case is added for this as well.

(cherry picked from commit 704505958e)
2025-04-17 09:44:56 +00:00
Joshua Goins
3e683eac77 LinkPreviewComponent: Fix a few bugs, restore the image preview
We realized that images don't display in link previews anymore, because
QML is terrible and this property is a QUrl, so when we call .length it
silently fails and never loads the image. This is easily fixed by
calling .toString().

There's also another bug where the title ie elided way too greedily, but
we can simplify the elision check and fix the bug at the same time.
No more "Hom..."!

(cherry picked from commit 2546d79f26)
2025-04-17 09:44:56 +00:00
Joshua Goins
299d8e9c40 Set the pointing hand cursor shape on link previews
Our HIG explicitly says we can do this for links, and these components
are literally giant links. This might also help differentiate these from
similar looking components like quotes for desktop users.

(cherry picked from commit c3404936fd)
2025-04-17 09:44:56 +00:00
Joshua Goins
45f9aeb5bf Elide the hover link indicator
For long links, this ends up going "off" the window (not really of
course.) One good example is Bugzilla filter URLs which are ungodly
huge. So eliding these just makes it look better, browsers do this too.

(cherry picked from commit ec635d7de3)
2025-04-17 09:44:56 +00:00
l10n daemon script
6961442090 GIT_SILENT Sync po/docbooks with svn 2025-04-17 03:11:11 +00:00
l10n daemon script
f4b31838bc GIT_SILENT Sync po/docbooks with svn 2025-04-15 03:12:49 +00:00
l10n daemon script
8e3ffd3e9d GIT_SILENT Sync po/docbooks with svn 2025-04-14 03:12:29 +00:00
l10n daemon script
02cfb0df9c GIT_SILENT Sync po/docbooks with svn 2025-04-13 03:07:06 +00:00
l10n daemon script
c95778b3ac GIT_SILENT Sync po/docbooks with svn 2025-04-10 03:11:40 +00:00
l10n daemon script
8e66f635fd 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-04-09 03:08:06 +00:00
l10n daemon script
355b5a33b7 GIT_SILENT made messages (after extraction) 2025-04-09 02:32:07 +00:00
l10n daemon script
e40d8230c0 GIT_SILENT Sync po/docbooks with svn 2025-04-08 03:20:39 +00:00
l10n daemon script
a76d4496da 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-04-08 03:15:18 +00:00
Heiko Becker
4ad1a628b6 GIT_SILENT Update Appstream for new release 2025-04-07 23:59:22 +02:00
Heiko Becker
87ea0ef5b2 GIT_SILENT Upgrade release service version to 25.04.0. 2025-04-07 22:47:49 +02:00
Scarlett Moore
7ed43044ce snapcraft: Sync fixes into stable. 2025-04-05 14:48:52 +00:00
l10n daemon script
074e5d4c5a GIT_SILENT Sync po/docbooks with svn 2025-04-05 03:20:22 +00:00
l10n daemon script
12ce1e460e GIT_SILENT Sync po/docbooks with svn 2025-04-04 03:21:02 +00:00
l10n daemon script
74a6cd28eb GIT_SILENT Sync po/docbooks with svn 2025-04-03 03:12:31 +00:00
l10n daemon script
53307f9358 GIT_SILENT Sync po/docbooks with svn 2025-04-02 03:12:46 +00:00
l10n daemon script
c683df2b40 GIT_SILENT Sync po/docbooks with svn 2025-03-31 03:11:46 +00:00
l10n daemon script
3b1e19c740 GIT_SILENT Sync po/docbooks with svn 2025-03-29 03:12:15 +00:00
l10n daemon script
4a5c4559bf GIT_SILENT Sync po/docbooks with svn 2025-03-28 03:16:51 +00:00
Albert Astals Cid
7d2dc0f9ac GIT_SILENT Upgrade release service version to 25.03.90. 2025-03-27 11:14:06 +01:00
l10n daemon script
f5a7fb0d13 GIT_SILENT Sync po/docbooks with svn 2025-03-27 03:17:45 +00:00
l10n daemon script
3e09099958 GIT_SILENT made messages (after extraction) 2025-03-27 02:37:16 +00:00
l10n daemon script
4474efd03f GIT_SILENT Sync po/docbooks with svn 2025-03-26 03:13:42 +00:00
l10n daemon script
8fe4cf3f89 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-03-26 03:09:53 +00:00
l10n daemon script
e25060c12d GIT_SILENT Sync po/docbooks with svn 2025-03-24 03:12:20 +00:00
l10n daemon script
c56ec01637 GIT_SILENT Sync po/docbooks with svn 2025-03-21 03:20:49 +00:00
l10n daemon script
5e07a07f38 GIT_SILENT Sync po/docbooks with svn 2025-03-20 03:20:33 +00:00
l10n daemon script
dcc184a45f GIT_SILENT Sync po/docbooks with svn 2025-03-18 03:17:51 +00:00
l10n daemon script
790ab54d0f GIT_SILENT Sync po/docbooks with svn 2025-03-17 03:09:17 +00:00
l10n daemon script
606f8a1c99 GIT_SILENT Sync po/docbooks with svn 2025-03-16 03:15:39 +00:00
l10n daemon script
46c706e364 GIT_SILENT Sync po/docbooks with svn 2025-03-15 03:16:18 +00:00
l10n daemon script
32bea56a6d GIT_SILENT Sync po/docbooks with svn 2025-03-13 03:13:58 +00:00
l10n daemon script
661cf22667 GIT_SILENT Sync po/docbooks with svn 2025-03-10 03:49:36 +00:00
l10n daemon script
ad1254fb71 GIT_SILENT Sync po/docbooks with svn 2025-03-09 03:11:01 +00:00
Albert Astals Cid
7514a8a6f7 GIT_SILENT Upgrade release service version to 25.03.80. 2025-03-08 18:43:32 +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
121 changed files with 20620 additions and 11574 deletions

View File

@@ -25,13 +25,23 @@
"modules": [
{
"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": [
{
@@ -43,17 +53,19 @@
{
"name": "olm",
"buildsystem": "cmake-ninja",
"config-opts": [ "-DOLM_TESTS=OFF" ],
"config-opts": [
"-DOLM_TESTS=OFF"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git",
"tag": "3.2.10",
"tag": "3.2.16",
"x-checker-data": {
"type": "git",
"tag-pattern": "^([\\d.]+)$"
},
"commit": "9908862979147a71dc6abaecd521be526ae77be1"
"commit": "7e0c8277032e40308987257b711b38af8d77cc69"
}
]
},
@@ -65,13 +77,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 +98,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,7 +112,8 @@
"-DBUILD_WITH_QT6=ON",
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO"
"-DBUILD_TRANSLATIONS=NO",
"-DBUILD_TESTING=OFF"
]
},
{
@@ -123,29 +136,31 @@
{
"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 +173,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

@@ -10,6 +10,7 @@ include:
- /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

View File

@@ -8,8 +8,8 @@ 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_MICRO "70")
set(RELEASE_SERVICE_VERSION_MINOR "04")
set(RELEASE_SERVICE_VERSION_MICRO "2")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

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

@@ -530,6 +530,9 @@ void TextHandlerTest::componentOutput_data()
QTest::newRow("quote") << u"<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}};
QTest::newRow("multiple paragraph quote") << u"<blockquote>\n<p>blockquote</p>\n<p>next paragraph</p>\n</blockquote>"_s
<< QList<MessageComponent>{
MessageComponent{MessageComponentType::Quote, u"<p>“blockquote</p>\n<p>next paragraph”</p>"_s, {}}};
QTest::newRow("no tag first paragraph") << u"Text\n<p>Text</p>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};

View File

@@ -70,10 +70,12 @@
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
<summary xml:lang="ko">Matrix에서 대화하기</summary>
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary>
<summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
<summary xml:lang="sv">Chatta på Matrix</summary>
@@ -102,6 +104,7 @@
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
<p xml:lang="ko">NeoChat은 Matrix 네트워크를 사용하는 채팅 앱입니다. 텍스트 메시지, 동영상, 오디오 파일을 가족, 친구, 동료와 안전하게 공유할 수 있습니다.</p>
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
@@ -372,6 +375,7 @@
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
<caption xml:lang="ko">Matrix 스페이스에서 새로운 커뮤니티 탐험</caption>
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
@@ -473,6 +477,10 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/>

View File

@@ -109,9 +109,12 @@ Comment[hu]=Csevegés Matrixon
Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[ko]=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[ru]=Общение в Matrix
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
Comment[sl]=Klepet na Matrixu
Comment[sv]=Chatta på Matrix

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

6167
po/he/neochat.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
# SPDX-FileCopyrightText: 2024 Scarlett Moore <sgmoore@kde.org>
# SPDX-FileCopyrightText: 2024-2025 Scarlett Moore <sgmoore@kde.org>
#
# SPDX-License-Identifier: CC0-1.0
---
name: neochat
base: core22
base: core24
adopt-info: neochat
grade: stable
confinement: strict
@@ -24,6 +24,9 @@ apps:
- network-manager-observe
- password-manager-service
- accounts-service
environment:
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
QML_IMPORT_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml"
compression: lzo
@@ -41,11 +44,12 @@ parts:
olm:
source: https://gitlab.matrix.org/matrix-org/olm.git
source-depth: 1
source-tag: '3.2.12'
source-tag: '3.2.16'
plugin: cmake
cmake-parameters:
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
prime:
- -usr/include
- -usr/lib/*/pkgconfig
@@ -81,8 +85,7 @@ 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
@@ -106,8 +109,6 @@ parts:
build-snaps:
- cmake
build-packages:
- gcc-13
- g++-13
- libssl-dev
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
@@ -115,11 +116,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
@@ -132,6 +128,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
@@ -142,17 +140,32 @@ parts:
- -usr/lib/*/pkgconfig
- -usr/lib/*/cmake
kunifiedpush:
source: https://invent.kde.org/libraries/kunifiedpush.git
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
- -DBUILD_TESTING=OFF
neochat:
after:
- qtkeychain
- libquotient
- kquickimageeditor
- kunifiedpush
parse-info:
- usr/share/metainfo/org.kde.neochat.appdata.xml
source: .
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
@@ -175,3 +188,13 @@ parts:
- libcmark0.30.2
prime:
- usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

@@ -107,8 +107,6 @@ add_library(neochat STATIC
models/imagepacksmodel.h
events/imagepackevent.cpp
events/imagepackevent.h
events/joinrulesevent.cpp
events/joinrulesevent.h
models/reactionmodel.cpp
models/reactionmodel.h
delegatesizehelper.cpp
@@ -208,6 +206,9 @@ if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
@@ -322,7 +323,10 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
qt_target_qml_sources(neochat QML_FILES
qml/ShareActionStub.qml
qml/GlobalMenuStub.qml
)
endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)

View File

@@ -283,12 +283,12 @@ QQC2.Control {
if (quickFormatBar.visible && selectedText.length > 0) {
quickFormatBar.close();
}
} else if (event.key === Qt.Key_Escape && completionMenu.visible) {
completionMenu.close();
}
}
Keys.onShortcutOverride: event => {
if (completionMenu.visible) {
completionMenu.close();
} else if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
_private.chatBarCache.attachmentPath = "";
_private.chatBarCache.replyId = "";
_private.chatBarCache.threadId = "";
@@ -315,11 +315,13 @@ QQC2.Control {
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: modelData.trigger()
padding: Kirigami.Units.smallSpacing
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: modelData.tooltip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
PieProgressBar {
contentItem: PieProgressBar {
visible: modelData.isBusy
progress: root.currentRoom.fileUploadingProgress
}

View File

@@ -117,6 +117,10 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
});
});
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
if (!m_document) {
m_highlighter->setDocument(nullptr);
return;
}
m_highlighter->setDocument(m_document->textDocument());
});
connect(this, &ChatDocumentHandler::cursorPositionChanged, this, [this]() {
@@ -222,11 +226,14 @@ void ChatDocumentHandler::complete(int index)
return;
}
// Ensure we only search for the beginning of the current completion identifier
const auto fromIndex = qMax(completionStartIndex(), 0);
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
auto text = getText();
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
auto at = text.indexOf(QLatin1Char('@'), fromIndex);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -239,7 +246,7 @@ void ChatDocumentHandler::complete(int index)
} 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('/'));
auto at = text.indexOf(QLatin1Char('/'), fromIndex);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -247,7 +254,7 @@ void ChatDocumentHandler::complete(int index)
} 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);
auto at = text.indexOf(QLatin1Char('#'), fromIndex);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -260,7 +267,7 @@ void ChatDocumentHandler::complete(int index)
} 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(':'));
auto at = text.indexOf(QLatin1Char(':'), fromIndex);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);

View File

@@ -294,7 +294,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 {

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

View File

@@ -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

@@ -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

@@ -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"
@@ -265,6 +266,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"

View File

@@ -239,6 +239,7 @@ 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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -121,6 +121,10 @@ void NotificationsModel::loadData()
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl();
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
if (!roomEvent) {
continue;
}
beginInsertRows({}, m_notifications.length(), m_notifications.length());
m_notifications += Notification{
.roomId = notification.roomId,

View File

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

View File

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

View File

@@ -348,6 +348,12 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(room);
}
if (role == SubtitleTextRole) {
if (room->isInvite()) {
if (room->isDirectChat()) {
return i18nc("@info:label", "Invited you to chat");
}
return i18nc("@info:label", "%1 invited you", room->member(room->invitingUserId()).displayName());
}
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return QString();
}

View File

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

View File

@@ -32,42 +32,6 @@ class TimelineMessageModel : public MessageModel
QML_ELEMENT
public:
/**
* @brief Defines the model roles.
*/
enum EventRoles {
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
EventIdRole, /**< The matrix event ID of the event. */
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
SectionRole, /**< The date of the event as a string. */
AuthorRole, /**< The author of the event. */
HighlightRole, /**< Whether the event should be highlighted. */
SpecialMarksRole, /**< Whether the event is hidden or not. */
ProgressInfoRole, /**< Progress info when downloading files. */
GenericDisplayRole, /**< A generic string based upon the message type. */
MediaInfoRole, /**< The media info for the event. */
ContentModelRole, /**< The MessageContentModel for the event. */
IsThreadedRole, /**< Whether the message is in a thread. */
ThreadRootRole, /**< The Matrix ID of the thread root message, if any . */
ShowSectionRole, /**< Whether the section header should be shown. */
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
ReactionRole, /**< List model for this event. */
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
IsRedactedRole, /**< Whether an event has been deleted. */
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
IsEditableRole, /**< Whether the event can be edited by the user. */
LastRole, // Keep this last
};
Q_ENUM(EventRoles)
explicit TimelineMessageModel(QObject *parent = nullptr);
/**

View File

@@ -290,6 +290,7 @@ Name[hu]=Megosztás
Name[ia]=Comparti
Name[it]=Condivisione
Name[ka]=გაზიარება
Name[ko]=공유
Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
@@ -303,6 +304,7 @@ Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_CN]=分享
Name[zh_TW]=分享
Comment=The result of sharing a piece of content
Comment[ar]=نتيجة مشاركة محتوى
@@ -323,6 +325,7 @@ Comment[hu]=Tartalom megosztásának eredménye
Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[ko]=콘텐츠 공유 결과
Comment[lv]=Satura kopīgošanas rezultāts
Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald
@@ -336,5 +339,6 @@ Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவ
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果
Action=Popup

View File

@@ -104,6 +104,7 @@
</entry>
<entry name="ShowLinkPreview" type="bool">
<label>Show preview of the links in the chat messages</label>
<default>true</default>
</entry>
<entry name="SystemTray" type="bool">
<label>Close NeoChat to system tray</label>

View File

@@ -145,6 +145,10 @@ void NeoChatConnection::connectSignals()
connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] {
setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
});
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
setGlobalUrlPreviewEnabled(NeoChatConfig::showLinkPreview());
});
}
int NeoChatConnection::badgeNotificationCount() const
@@ -167,6 +171,25 @@ void NeoChatConnection::refreshBadgeNotificationCount()
}
}
bool NeoChatConnection::globalUrlPreviewEnabled()
{
return m_globalUrlPreviewEnabled;
}
void NeoChatConnection::setGlobalUrlPreviewEnabled(bool newState)
{
if (m_globalUrlPreviewEnabled == newState) {
return;
}
m_globalUrlPreviewEnabled = newState;
if (!m_globalUrlPreviewEnabled) {
m_linkPreviewers.clear();
}
NeoChatConfig::setShowLinkPreview(m_globalUrlPreviewEnabled);
Q_EMIT globalUrlPreviewEnabledChanged();
}
void NeoChatConnection::logout(bool serverSideLogout)
{
SettingsGroup(u"Accounts"_s).remove(userId());
@@ -472,9 +495,12 @@ QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
false);
qInfo() << "Registered for push notifications";
m_pushNotificationsEnabled = true;
} else {
qWarning() << "There's no gateway, not setting up push notifications.";
m_pushNotificationsEnabled = false;
}
Q_EMIT enablePushNotificationsChanged();
#else
Q_UNUSED(endpoint)
co_return;
@@ -502,7 +528,7 @@ QString NeoChatConnection::accountDataJsonString(const QString &type) const
LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
{
if (!NeoChatConfig::showLinkPreview()) {
if (!m_globalUrlPreviewEnabled) {
return nullptr;
}
@@ -536,4 +562,18 @@ bool NeoChatConnection::canEraseData() const
return m_canEraseData;
}
bool NeoChatConnection::pushNotificationsAvailable() const
{
#ifdef HAVE_KUNIFIEDPUSH
return true;
#else
return false;
#endif
}
bool NeoChatConnection::enablePushNotifications() const
{
return m_pushNotificationsEnabled;
}
#include "moc_neochatconnection.cpp"

View File

@@ -32,6 +32,11 @@ class NeoChatConnection : public Quotient::Connection
*/
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
/**
* @brief Whether URL previews are enabled globally.
*/
Q_PROPERTY(bool globalUrlPreviewEnabled READ globalUrlPreviewEnabled WRITE setGlobalUrlPreviewEnabled NOTIFY globalUrlPreviewEnabledChanged)
/**
* @brief The model with the account's 3PIDs.
*/
@@ -90,6 +95,16 @@ class NeoChatConnection : public Quotient::Connection
*/
Q_PROPERTY(bool canEraseData READ canEraseData NOTIFY canEraseDataChanged)
/**
* @brief Whether this build of NeoChat supports push notifications via KUnifiedPush.
*/
Q_PROPERTY(bool pushNotificationsAvailable READ pushNotificationsAvailable CONSTANT)
/**
* @brief Whether we successfully set up push notifications with the server.
*/
Q_PROPERTY(bool enablePushNotifications READ enablePushNotifications NOTIFY enablePushNotificationsChanged)
public:
/**
* @brief Defines the status after an attempt to change the password on an account.
@@ -172,17 +187,24 @@ public:
int badgeNotificationCount() const;
void refreshBadgeNotificationCount();
bool globalUrlPreviewEnabled();
void setGlobalUrlPreviewEnabled(bool newState);
bool directChatInvites() const;
// note: this is intentionally a copied QString because
// the reference could be destroyed before the task is finished
QCoro::Task<void> setupPushNotifications(QString endpoint);
bool pushNotificationsAvailable() const;
bool enablePushNotifications() const;
bool isOnline() const;
LinkPreviewer *previewerForLink(const QUrl &link);
Q_SIGNALS:
void globalUrlPreviewEnabledChanged();
void labelChanged();
void identityServerChanged();
void directChatNotificationsChanged();
@@ -196,6 +218,7 @@ Q_SIGNALS:
void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
void canCheckMutualRoomsChanged();
void canEraseDataChanged();
void enablePushNotificationsChanged();
/**
* @brief Request a message be shown to the user of the given type.
@@ -216,9 +239,11 @@ private:
void connectSignals();
int m_badgeNotificationCount = 0;
bool m_globalUrlPreviewEnabled = true;
QCache<QUrl, LinkPreviewer> m_linkPreviewers;
bool m_canCheckMutualRooms = false;
bool m_canEraseData = false;
bool m_pushNotificationsEnabled = false;
};

View File

@@ -40,10 +40,10 @@
#include "chatbarcache.h"
#include "clipboard.h"
#include "eventhandler.h"
#include "events/joinrulesevent.h"
#include "events/pollevent.h"
#include "filetransferpseudojob.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroommember.h"
#include "roomlastmessageprovider.h"
#include "spacehierarchycache.h"
@@ -133,7 +133,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
Q_EMIT canEncryptRoomChanged();
Q_EMIT parentIdsChanged();
Q_EMIT canonicalParentChanged();
Q_EMIT joinRuleChanged();
Q_EMIT readOnlyChanged();
});
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
@@ -157,6 +156,10 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
Q_EMIT childrenHaveHighlightNotificationsChanged();
}
});
const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
Q_ASSERT(neochatconnection);
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
}
bool NeoChatRoom::visible() const
@@ -605,63 +608,12 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
doDeleteMessagesByUser(user, reason);
}
QString NeoChatRoom::joinRule() const
{
auto joinRulesEvent = currentState().get<JoinRulesEvent>();
if (!joinRulesEvent) {
return {};
}
return joinRulesEvent->joinRule();
}
void NeoChatRoom::setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces)
{
if (!canSendState("m.room.join_rules"_L1)) {
qWarning() << "Power level too low to set join rules";
return;
}
auto actualRule = joinRule;
if (joinRule == "restricted"_L1 && allowedSpaces.isEmpty()) {
actualRule = "private"_L1;
}
QJsonArray allowConditions;
if (actualRule == "restricted"_L1) {
for (auto allowedSpace : allowedSpaces) {
allowConditions += QJsonObject{{"type"_L1, "m.room_membership"_L1}, {"room_id"_L1, allowedSpace}};
}
}
QJsonObject content;
content.insert("join_rule"_L1, joinRule);
if (!allowConditions.isEmpty()) {
content.insert("allow"_L1, allowConditions);
}
qWarning() << content;
setState("m.room.join_rules"_L1, {}, content);
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
}
QList<QString> NeoChatRoom::restrictedIds() const
{
auto joinRulesEvent = currentState().get<JoinRulesEvent>();
if (!joinRulesEvent) {
return {};
}
if (joinRulesEvent->joinRule() != "restricted"_L1) {
return {};
}
QList<QString> roomIds;
for (auto allow : joinRulesEvent->allow()) {
roomIds += allow.toObject().value("room_id"_L1).toString();
}
return roomIds;
}
QString NeoChatRoom::historyVisibility() const
{
return currentState().get("m.room.history_visibility"_L1)->contentJson()["history_visibility"_L1].toString();
if (auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
return stateEvent->contentJson()["history_visibility"_L1].toString();
}
return {};
}
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
@@ -728,6 +680,9 @@ void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
bool NeoChatRoom::urlPreviewEnabled() const
{
if (!static_cast<NeoChatConnection *>(connection())->globalUrlPreviewEnabled()) {
return false;
}
if (hasAccountData("org.matrix.room.preview_urls"_L1)) {
return !accountData("org.matrix.room.preview_urls"_L1)->contentJson()["disable"_L1].toBool();
} else {
@@ -1716,7 +1671,11 @@ void NeoChatRoom::cleanupExtraEvent(const QString &eventId)
}
QString NeoChatRoom::invitingUserId() const
{
return currentState().get<RoomMemberEvent>(connection()->userId())->senderId();
auto event = currentState().get<RoomMemberEvent>(connection()->userId());
if (!event) {
return {};
}
return event->senderId();
}
void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content)
@@ -1767,7 +1726,9 @@ MessageContentModel *NeoChatRoom::contentModelForEvent(const Quotient::RoomEvent
auto eventId = event->id();
const auto txnId = event->transactionId();
if (!m_eventContentModels.contains(eventId) && !m_eventContentModels.contains(txnId)) {
return m_eventContentModels.emplace(eventId, std::make_unique<MessageContentModel>(this, eventId.isEmpty() ? txnId : eventId, false, eventId.isEmpty()))
return m_eventContentModels
.emplace(eventId.isEmpty() ? txnId : eventId,
std::make_unique<MessageContentModel>(this, eventId.isEmpty() ? txnId : eventId, false, eventId.isEmpty()))
.first->second.get();
}
@@ -1802,4 +1763,23 @@ ThreadModel *NeoChatRoom::modelForThread(const QString &threadRootId)
return m_threadModels[threadRootId].get();
}
void NeoChatRoom::pinEvent(const QString &eventId)
{
auto eventIds = pinnedEventIds();
eventIds.push_back(eventId);
setPinnedEvents(eventIds);
}
void NeoChatRoom::unpinEvent(const QString &eventId)
{
auto eventIds = pinnedEventIds();
eventIds.removeAll(eventId);
setPinnedEvents(eventIds);
}
bool NeoChatRoom::isEventPinned(const QString &eventId) const
{
return pinnedEventIds().contains(eventId);
}
#include "moc_neochatroom.cpp"

View File

@@ -134,22 +134,6 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(bool readOnly READ readOnly NOTIFY readOnlyChanged)
/**
* @brief The current join rule for the room as a QString.
*
* Possible values are [public, knock, invite, private, restricted].
*
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomjoin_rules
*/
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
/**
* @brief The space IDs that members of can join this room.
*
* Empty if the join rule is not restricted.
*/
Q_PROPERTY(QList<QString> restrictedIds READ restrictedIds NOTIFY joinRuleChanged)
/**
* @brief Get the maximum room version that the server supports.
*
@@ -420,25 +404,6 @@ public:
bool readOnly() const;
[[nodiscard]] QString joinRule() const;
/**
* @brief Set the join rule for the room.
*
* Will fail if the user doesn't have the required privileges.
*
* @param joinRule the join rule [public, knock, invite, private, restricted].
* @param allowedSpaces only used when the join rule is restricted. This is a
* list of space Matrix IDs that members of can join without an invite.
* If the rule is restricted and this list is empty it is treated as a join
* rule of private instead.
*
* @sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
*/
Q_INVOKABLE void setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces = {});
QList<QString> restrictedIds() const;
int maxRoomVersion() const;
/**
@@ -636,6 +601,23 @@ public:
*/
Q_INVOKABLE ThreadModel *modelForThread(const QString &threadRootId);
/**
* @brief Pin a message in the room.
* @param eventId The id of the event to pin.
*/
Q_INVOKABLE void pinEvent(const QString &eventId);
/**
* @brief Unpin a message in the room.
* @param eventId The id of the event to unpin.
*/
Q_INVOKABLE void unpinEvent(const QString &eventId);
/**
* @return True if @p eventId is pinned in the room.
*/
Q_INVOKABLE bool isEventPinned(const QString &eventId) const;
private:
bool m_visible = false;
@@ -693,7 +675,6 @@ Q_SIGNALS:
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void canEncryptRoomChanged();
void joinRuleChanged();
void historyVisibilityChanged();
void defaultUrlPreviewStateChanged();
void urlPreviewEnabledChanged();

View File

@@ -22,6 +22,7 @@
"Name[ia]": "Tobias Fella",
"Name[it]": "Tobias Fella",
"Name[ka]": "Tobias Fella",
"Name[ko]": "Tobias Fella",
"Name[lv]": "Tobias Fella",
"Name[nl]": "Tobias Fella",
"Name[nn]": "Tobias Fella",
@@ -35,6 +36,7 @@
"Name[tr]": "Tobias Fella",
"Name[uk]": "Tobias Fella",
"Name[x-test]": "xxTobias Fellaxx",
"Name[zh_CN]": "Tobias Fella",
"Name[zh_TW]": "Tobias Fella"
}
],
@@ -58,6 +60,7 @@
"Description[ia]": "Comparti via NeoChat",
"Description[it]": "Condividi tramite NeoChat",
"Description[ka]": "გააზიარეთ NeoChat-ით",
"Description[ko]": "NeoChat으로 공유",
"Description[lv]": "Kopīgot ar „NeoChat“",
"Description[nl]": "Delen via NeoChat",
"Description[nn]": "Del via NeoChat",
@@ -70,6 +73,7 @@
"Description[tr]": "NeoChat ile Paylaş",
"Description[uk]": "Оприлюднити за допомогою NeoChat",
"Description[x-test]": "xxShare via NeoChatxx",
"Description[zh_CN]": "通过 NeoChat 分享",
"Description[zh_TW]": "透過 NeoChat 分享",
"Icon": "org.kde.neochat.tray",
"License": "GPL",
@@ -94,6 +98,7 @@
"Name[ia]": "Neochat",
"Name[it]": "NeoChat",
"Name[ka]": "NeoChat",
"Name[ko]": "NeoChat",
"Name[lv]": "NeoChat",
"Name[nl]": "NeoChat",
"Name[nn]": "NeoChat",
@@ -107,6 +112,7 @@
"Name[tr]": "NeoChat",
"Name[uk]": "NeoChat",
"Name[x-test]": "xxNeoChatxx",
"Name[zh_CN]": "NeoChat",
"Name[zh_TW]": "NeoChat",
"X-Purpose-ActionDisplay": "NeoChat"
},

View File

@@ -87,7 +87,18 @@ KirigamiComponents.ConvergentContextMenu {
QQC2.Action {
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
onTriggered: root.connection.startSelfVerification()
onTriggered: {
root.connection.startSelfVerification();
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
title: i18nc("@title", "Verification Request Sent"),
subtitle: i18nc("@info:label", "To proceed, accept the verification request on another device."),
standardButtons: Kirigami.Dialog.Ok
})
dialog.open();
root.connection.onNewKeyVerificationSession.connect(() => {
dialog.close();
});
}
enabled: Controller.csSupported
}

View File

@@ -34,9 +34,8 @@ Delegates.RoundedItemDelegate {
Keys.onSpacePressed: selected()
Keys.onEnterPressed: selected()
onPressAndHold: root.contextMenuRequested()
TapHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton | Qt.LeftButton
onTapped: (eventPoint, button) => {
if (button === Qt.RightButton) {
@@ -47,6 +46,12 @@ Delegates.RoundedItemDelegate {
}
}
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onTapped: root.selected()
onLongPressed: root.contextMenuRequested()
}
contentItem: AvatarNotification {
id: avatarNotification
source: root.source

View File

@@ -75,7 +75,7 @@ KirigamiComponents.ConvergentContextMenu {
component RemoveMessageAction: Kirigami.Action {
visible: author.isLocalMember || currentRoom.canSendState("redact")
text: i18nc("@action:button", "Remove")
text: i18nc("@action:button", "Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: {
@@ -132,6 +132,15 @@ KirigamiComponents.ConvergentContextMenu {
}
}
component PinMessageAction: Kirigami.Action {
readonly property bool pinned: currentRoom.isEventPinned(root.eventId)
visible: currentRoom.canSendState("m.room.pinned_events")
text: pinned ? i18nc("@action:button 'Unpin' as in 'Unpin this message'", "Unpin") : i18nc("@action:button 'Pin' as in 'Pin the message in the room'", "Pin")
icon.name: pinned ? "window-unpin-symbolic" : "pin-symbolic"
onTriggered: pinned ? currentRoom.unpinEvent(root.eventId) : currentRoom.pinEvent(root.eventId)
}
headerContentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing

View File

@@ -24,10 +24,6 @@ RowLayout {
*/
signal textChanged(string newText)
Item {
Layout.preferredWidth: Kirigami.Units.largeSpacing
}
Kirigami.Heading {
Layout.fillWidth: true
visible: !root.collapsed
@@ -78,21 +74,6 @@ RowLayout {
Component {
id: menu
QQC2.Menu {
QQC2.MenuItem {
text: i18n("Explore rooms")
icon.name: "compass"
onTriggered: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});
}
}
QQC2.MenuItem {
text: i18n("Find your friends")
icon.name: "list-add-user"

View File

@@ -71,7 +71,7 @@ DelegateContextMenu {
Kirigami.Action {
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
text: i18n("Remove")
text: i18n("Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
onTriggered: {
@@ -90,6 +90,8 @@ DelegateContextMenu {
}
}
DelegateContextMenu.PinMessageAction {}
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}

View File

@@ -15,7 +15,7 @@ import org.kde.neochat.settings
Labs.MenuBar {
id: root
property NeoChatConnection connection
required property NeoChatConnection connection
Labs.Menu {
title: i18nc("menu", "NeoChat")
@@ -40,7 +40,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "Find your friends")
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
onTriggered: pushReplaceLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
@@ -51,8 +51,11 @@ Labs.MenuBar {
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
shortcut: StandardKey.New
onTriggered: {
const dialog = createRoomDialog.createObject(root.overlay);
dialog.open();
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}
}
Labs.MenuItem {

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import org.kde.neochat
Item {
required property NeoChatConnection connection
}

View File

@@ -19,6 +19,11 @@ ColumnLayout {
*/
required property NeoChatRoom room
/**
* @brief The canonical alias of the room, if it exists. Otherwise falls back to the first available alias.
*/
readonly property var roomAlias: room.aliases[0]
Layout.fillWidth: true
RowLayout {
@@ -73,8 +78,8 @@ ColumnLayout {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
visible: root.room && root.roomAlias
text: root.room && root.roomAlias ? root.roomAlias : ""
color: Kirigami.Theme.disabledTextColor
}
}

View File

@@ -21,6 +21,7 @@ QQC2.Control {
contentItem: QQC2.Label {
text: root.text.startsWith("https://matrix.to/") ? "" : root.text
elide: Text.ElideRight
Accessible.description: i18nc("@info screenreader", "The currently selected link")
}

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
@@ -6,22 +7,89 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
Kirigami.PlaceholderMessage {
ColumnLayout {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId())
text: i18n("Accept this invitation?")
explanation: root.currentRoom.connection.canCheckMutualRooms ? i18n("You can reject invitations from unknown users under Security settings.") : ""
RowLayout {
spacing: Kirigami.Units.smallSpacing
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
color: root.invitingMember.color
}
Loader {
active: !root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
text: i18nc("@info:label", "%1 has invited you to join", root.invitingMember.displayName)
Layout.alignment: Qt.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
}
}
Loader {
active: root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
QQC2.Label {
text: i18nc("@info:label", "This user is inviting you to chat.")
Layout.alignment: Qt.AlignHCenter
}
}
}
QQC2.Label {
color: Kirigami.Theme.disabledTextColor
text: i18n("You can reject invitations from unknown users under Security settings.")
visible: root.currentRoom.connection.canCheckMutualRooms
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.mediumSpacing
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
text: i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and ignore user")
text: i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and Ignore User")
icon.name: "list-remove-symbolic"
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
@@ -30,18 +98,18 @@ Kirigami.PlaceholderMessage {
}
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
text: i18n("Reject")
icon.name: "cards-block-symbolic"
text: i18nc("@action:button", "Reject")
onClicked: RoomManager.leaveRoom(root.currentRoom)
}
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
text: i18n("Accept")
icon.name: "dialog-ok-symbolic"
text: i18nc("@action:button", "Accept")
onClicked: {
root.currentRoom.acceptInvitation();
}
onClicked: root.currentRoom.acceptInvitation()
}
}
}

View File

@@ -73,9 +73,8 @@ Kirigami.ApplicationWindow {
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
sourceComponent: Qt.createComponent("org.kde.neochat", "GlobalMenu")
onActiveChanged: if (active) {
item.connection = root.connection;
sourceComponent: GlobalMenu {
connection: root.connection
}
}
@@ -292,8 +291,12 @@ Kirigami.ApplicationWindow {
HoverLinkIndicator {
id: linkIndicator
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
rightMargin: Kirigami.Units.largeSpacing
}
}
Shortcut {

View File

@@ -78,7 +78,7 @@ DelegateContextMenu {
}
QQC2.Action {
text: i18nc("@action:inmenu", "Copy Message Link")
icon.name: "edit-copy"
icon.name: "link-symbolic"
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
}
@@ -86,6 +86,7 @@ DelegateContextMenu {
Kirigami.Action {
separator: true
}
DelegateContextMenu.PinMessageAction {}
DelegateContextMenu.ReportMessageAction {}
DelegateContextMenu.ShowUserAction {}
Kirigami.Action {
@@ -104,20 +105,20 @@ DelegateContextMenu {
Kirigami.Action {
id: webShortcutModelAction
text: i18n("Search for '%1'", webshortcutModel.trunkatedSearchText)
text: i18n("Search for '%1'", webShortcutModel.trunkatedSearchText)
icon.name: "search-symbolic"
visible: webshortcutModel.enabled
visible: webShortcutModel.enabled
readonly property Instantiator instantiator: Instantiator {
model: WebShortcutModel {
id: webshortcutModel
id: webShortcutModel
selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText
onOpenUrl: url => RoomManager.resolveResource(url.toString())
}
delegate: QQC2.Action {
text: model.display
icon.name: model.decoration
onTriggered: webshortcutModel.trigger(model.edit)
onTriggered: webShortcutModel.trigger(model.edit)
}
onObjectAdded: (index, object) => webShortcutModelAction.children.push(object)
}
@@ -126,7 +127,7 @@ DelegateContextMenu {
Kirigami.Action {
text: i18n("Configure Web Shortcuts…")
icon.name: "configure"
visible: !Controller.isFlatpak && webshortcutModel.enabled
onTriggered: webshortcutModel.configureWebShortcuts()
visible: !Controller.isFlatpak && webShortcutModel.enabled
onTriggered: webShortcutModel.configureWebShortcuts()
}
}

View File

@@ -125,12 +125,17 @@ KirigamiComponents.ConvergentContextMenu {
QQC2.Action {
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy"
onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(room.canonicalAlias);
onTriggered: {
// The canonical alias (if it exists) otherwise the first available alias
const firstAlias = room.aliases[0];
if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (!firstAlias) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(firstAlias);
}
}
}

View File

@@ -42,8 +42,6 @@ Delegates.RoundedItemDelegate {
}
}
onPressAndHold: createRoomListContextMenu()
Keys.onSpacePressed: clicked()
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
@@ -54,6 +52,11 @@ Delegates.RoundedItemDelegate {
onTapped: (eventPoint, button) => root.createRoomListContextMenu()
}
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onLongPressed: root.createRoomListContextMenu()
}
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing

View File

@@ -9,6 +9,8 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as Components
import Quotient
import org.kde.neochat
Kirigami.Dialog {
@@ -42,7 +44,7 @@ Kirigami.Dialog {
ids.push(spaceGroup.buttons[i].modelData.id);
}
}
root.room.setJoinRule("restricted", ids);
root.room.setJoinRule(JoinRule.Restricted, ids);
}
QQC2.ButtonGroup {

View File

@@ -65,7 +65,8 @@ QQC2.Control {
onSelected: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'NotificationsView'), {
connection: root.connection
}, {
title: i18nc("@title", "Notifications")
title: i18nc("@title", "Notifications"),
modality: Qt.NonModal
})
}
@@ -274,6 +275,31 @@ QQC2.Control {
title: i18nc("@title", "Create a Space")
})
}
AvatarTabButton {
Layout.fillWidth: true
Layout.preferredHeight: width - Kirigami.Units.smallSpacing
Layout.maximumHeight: width - Kirigami.Units.smallSpacing
text: i18nc("@action:button", "Explore rooms")
contentItem: Kirigami.Icon {
source: "compass"
}
activeFocusOnTab: true
onSelected: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection,
keyword: RoomManager.sortFilterRoomTreeModel.filterText
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});
}
}
}
}
}

View File

@@ -116,6 +116,16 @@ Kirigami.Dialog {
}
}
Kirigami.Chip {
visible: root.room
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
closable: false
checkable: false
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
}
Kirigami.Separator {
Layout.fillWidth: true
}

View File

@@ -27,6 +27,7 @@ RowLayout {
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 3 : -1
onVisibleChanged: {
@@ -34,59 +35,74 @@ RowLayout {
accountsPopup.close();
}
}
KirigamiComponents.AvatarButton {
QQC2.ToolButton {
id: accountButton
readonly property url avatarUrl: root.connection.localUser.avatarUrl
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.leftMargin: Kirigami.Units.largeSpacing
down: accountMenu.opened || pressed
text: i18n("Edit this account")
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(avatarUrl) : ""
name: root.connection.localUser.displayName
onClicked: accountMenu.popup()
activeFocusOnTab: true
onClicked: {
NeoChatSettingsView.open("accounts")
}
onPressAndHold: accountMenu.popup();
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: accountMenu.popup()
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth: Math.round(root.width * 0.55)
visible: !root.collapsed
spacing: 0
QQC2.Label {
id: displayNameLabel
Layout.fillWidth: true
text: root.connection.localUser.displayName
textFormat: Text.PlainText
elide: Text.ElideRight
Layout.fillHeight: true
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: i18nc("@info:tooltip", "Manage Account")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
readonly property url avatarUrl: root.connection.localUser.avatarUrl
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.leftMargin: Kirigami.Units.largeSpacing
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(avatarUrl) : ""
name: root.connection.localUser.displayName
}
ColumnLayout {
Layout.fillWidth: true
Layout.maximumWidth: Math.round(root.width * 0.55)
visible: !root.collapsed
spacing: 0
QQC2.Label {
id: displayNameLabel
Layout.fillWidth: true
text: root.connection.localUser.displayName
textFormat: Text.PlainText
elide: Text.ElideRight
}
QQC2.Label {
id: idLabel
Layout.fillWidth: true
text: (root.connection.label.length > 0 ? (root.connection.label + " ") : "") + root.connection.localUser.id
font.pointSize: displayNameLabel.font.pointSize * 0.8
opacity: 0.7
textFormat: Text.PlainText
elide: Text.ElideRight
}
}
}
QQC2.Label {
id: idLabel
Layout.fillWidth: true
text: (root.connection.label.length > 0 ? (root.connection.label + " ") : "") + root.connection.localUser.id
font.pointSize: displayNameLabel.font.pointSize * 0.8
opacity: 0.7
textFormat: Text.PlainText
elide: Text.ElideRight
AccountMenu {
id: accountMenu
connection: root.connection
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow
}
}
Kirigami.ActionToolBar {
alignment: Qt.AlignRight
display: QQC2.Button.IconOnly
Layout.fillWidth: true
Layout.preferredWidth: maximumContentWidth
actions: [
Kirigami.Action {
text: i18n("Switch User")
@@ -106,12 +122,6 @@ RowLayout {
]
}
AccountMenu {
id: accountMenu
y: root.bottomEdge ? -height : accountButton.height
connection: root.connection
window: accountButton.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow
}
Component {
id: accountSwitchDialog
AccountSwitchDialog {}

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