Compare commits

...

290 Commits

Author SHA1 Message Date
Joshua Goins
2803a865b2 Preserve mx-reply in the edited message if it exists
(cherry picked from commit fa57db8e83)
2024-04-24 15:29:52 -04:00
Heiko Becker
86e8dc2e40 GIT_SILENT Update Appstream for new release 2024-04-08 17:57:16 +02:00
Heiko Becker
8839c71d97 GIT_SILENT Upgrade release service version to 24.02.2. 2024-04-08 17:01:48 +02:00
l10n daemon script
1663ef41dc GIT_SILENT Sync po/docbooks with svn 2024-03-31 03:22:50 +00:00
l10n daemon script
3bdd2c123e GIT_SILENT Sync po/docbooks with svn 2024-03-28 03:25:22 +00:00
l10n daemon script
404fc37174 GIT_SILENT Sync po/docbooks with svn 2024-03-27 11:28:50 +00:00
l10n daemon script
6de8bf90c0 GIT_SILENT Sync po/docbooks with svn 2024-03-27 03:25:15 +00:00
l10n daemon script
ca9be5aea3 GIT_SILENT Sync po/docbooks with svn 2024-03-26 03:27:24 +00:00
Tobias Fella
429e20417c Fix crash when visiting user
We're adding the "join" action so that rooms are joined.
libQuotient doesn't like it when we the action is join and the uri is for a user.

BUG: 483744
2024-03-23 20:55:24 +01:00
Julius Künzel
f1397a4742 Add back Appimage job, it is fixed now 2024-03-21 22:40:23 +00:00
l10n daemon script
9be2c0513c GIT_SILENT Sync po/docbooks with svn 2024-03-21 03:00:02 +00:00
Albert Astals Cid
5ffe6ec59f Remove craft-appimage-qt6
It has been failing for more than 4 weeks
2024-03-19 23:32:09 +01:00
l10n daemon script
add48f88ce GIT_SILENT Sync po/docbooks with svn 2024-03-19 03:01:18 +00:00
Joshua Goins
09d0dd2b7a Fix the quick format bar not actually doing anything
(cherry picked from commit 5109b4fcd1)
2024-03-18 16:17:52 -04:00
Joshua Goins
498f6d9e64 Exclude lonely question marks from the linkify regex
Many URLs we see in the KDE rooms end with a question mark, without a
space. The linkify regex for plain URLs incorrectly considered them as
part of the link, which usually breaks them when opened in a web
browser. Now the regex excludes these, unless they are accompanied by
another character (so links like kde.org/realurl?is=true will still
work.)

(cherry picked from commit 1b7f482d0b)
2024-03-18 16:17:52 -04:00
l10n daemon script
f33780e996 GIT_SILENT Sync po/docbooks with svn 2024-03-16 03:42:32 +00:00
Heiko Becker
f731877519 GIT_SILENT Update Appstream for new release 2024-03-15 22:12:49 +01:00
Heiko Becker
5e48d5cb25 GIT_SILENT Upgrade release service version to 24.02.1. 2024-03-15 21:09:19 +01:00
Joshua Goins
afba8430f7 Don't destroy formatting when editing previous messages
Adds a few new methods to grab the markdown/slightly rich text from the
message, and will intelligently re-insert user mentions as needed.

(cherry picked from commit e2eb6ab33c)
2024-03-15 15:10:29 -04:00
Joshua Goins
18d14446bf Prevent collision between KUnifiedPush DBus and KRunner DBus
These share the same D-Bus service name (org.kde.neochat) which comes
with a fun little addition: KRunner activation! While this is not a
problem while NeoChat is running - since it's already registered - this
becomes an issue while searching for NeoChat in something like the
Kickoff. The Kickoff (and consequently, KRunner) tries to activate the
NeoChat D-Bus service which runs our unified push parts.

This introduces a "FakeRunner" which watches closely for calls to the
KRunner interface while we're in unified push mode (or directly called
from D-Bus but not running) so it quits immediately.

(cherry picked from commit 35b08d085c)
2024-03-15 14:48:00 -04:00
l10n daemon script
84c2173a04 GIT_SILENT Sync po/docbooks with svn 2024-03-14 03:36:36 +00:00
l10n daemon script
7f3112f53d GIT_SILENT Sync po/docbooks with svn 2024-03-13 02:54:10 +00:00
l10n daemon script
371016f977 GIT_SILENT made messages (after extraction) 2024-03-13 02:23:20 +00:00
Ingo Klöcker
35efe9693d This is no longer needed now that ECM 6.0.0 is available 2024-03-12 16:38:17 +01:00
Ingo Klöcker
586cf3fc6c Create an APPX package for NeoChat 24.02 2024-03-12 16:38:17 +01:00
l10n daemon script
a88b5d6af9 GIT_SILENT Sync po/docbooks with svn 2024-03-12 02:51:19 +00:00
Nicolas Fella
35aa08b279 Remove manual window toggling for system tray icon
KStatusNotifierItem automatically does this for us
since we associate our window with it

Doing it again causes the window to be toggled again, which means
it won't be shown

BUG: 479721

BUG: 482779
(cherry picked from commit 550dc43dc0)
2024-03-11 11:43:57 +01:00
l10n daemon script
a75072d069 GIT_SILENT Sync po/docbooks with svn 2024-03-11 02:51:12 +00:00
Tobias Fella
4177ade7a0 Don't link KDBusAddons on windows
(cherry picked from commit ad6c7dbd1f)
2024-03-10 14:31:39 +01:00
l10n daemon script
8c1eab76cf GIT_SILENT Sync po/docbooks with svn 2024-03-09 03:00:03 +00:00
l10n daemon script
4d72ace337 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"
2024-03-09 02:56:34 +00:00
l10n daemon script
d575323651 GIT_SILENT Sync po/docbooks with svn 2024-03-07 02:50:29 +00:00
Ingo Klöcker
28fdf28a23 Don't require kirigami-addons master
The default version of kirigami-addons (0.11.90) should be sufficient
for 24.02. And the Qt version is already set by the CraftConfig used for
Qt 6 builds.
2024-03-06 14:52:45 +00:00
Albert Astals Cid
1032ae7ca7 flatpak: Switch to non-preview runtime
(cherry picked from commit 5b007129e3)
2024-03-06 00:33:51 +01:00
l10n daemon script
84960385f8 GIT_SILENT Sync po/docbooks with svn 2024-03-04 02:51:12 +00:00
l10n daemon script
baa1486657 GIT_SILENT Sync po/docbooks with svn 2024-03-03 03:04:25 +00:00
l10n daemon script
1bfbfa51b4 GIT_SILENT made messages (after extraction) 2024-03-03 02:32:39 +00:00
l10n daemon script
e575a86252 GIT_SILENT Sync po/docbooks with svn 2024-03-02 02:56:51 +00:00
Tobias Fella
6f6aebcada Fix (un)ignoring unknown users
(cherry picked from commit 943f6c762c)
2024-03-01 09:33:06 +00:00
l10n daemon script
7a9695e3ee GIT_SILENT Sync po/docbooks with svn 2024-02-29 02:59:21 +00:00
l10n daemon script
9e589d71ff GIT_SILENT Sync po/docbooks with svn 2024-02-28 02:54:05 +00:00
l10n daemon script
5de5fa3053 GIT_SILENT Sync po/docbooks with svn 2024-02-25 03:07:03 +00:00
Tobias Fella
abcb5a0334 Fix saving images from maximize component
Fixes #643
2024-02-21 16:53:12 +01:00
l10n daemon script
b901ea6e2a GIT_SILENT Sync po/docbooks with svn 2024-02-21 02:56:16 +00:00
James Graham
8cfd515db2 Add feature flag for reply in thread
Currently the ability to reply in threads was added but not the ability to actually view threads so this doesn't currently make much sense to just have enabled int he main build.

Note: I want to cherrypick this so it's just the flag. I'll add a feature flag page to dev tools for master soon.


(cherry picked from commit 864f9b8f74)
2024-02-20 20:11:01 +00:00
l10n daemon script
3e5181d64e GIT_SILENT Sync po/docbooks with svn 2024-02-20 02:59:05 +00:00
Carl Schwan
ae53bf5df2 Add neochat 24.02 release note
(cherry picked from commit dc5366e924)
2024-02-19 12:33:21 +00:00
l10n daemon script
d2ed304672 GIT_SILENT Sync po/docbooks with svn 2024-02-19 02:58:48 +00:00
l10n daemon script
4924bd05a8 GIT_SILENT Sync po/docbooks with svn 2024-02-18 02:57:59 +00:00
l10n daemon script
9aa7553a1f GIT_SILENT Sync po/docbooks with svn 2024-02-16 03:02:48 +00:00
Heiko Becker
1c910165c1 GIT_SILENT Update Appstream for new release 2024-02-16 00:48:30 +01:00
Heiko Becker
05a84da722 GIT_SILENT Upgrade release service version to 24.02.0. 2024-02-16 00:01:13 +01:00
Carl Schwan
1ab8b85f06 Fix reaction update event when the event is not there anymore
Happens when interacting witht Mjonir quite often


(cherry picked from commit 6d3839dd42)
2024-02-15 20:13:28 +00:00
Carl Schwan
05883bcb71 Fix reaction delegate sizing for text reaction
(cherry picked from commit 755a060e12)
2024-02-15 20:13:01 +00:00
l10n daemon script
20cb6dc864 GIT_SILENT Sync po/docbooks with svn 2024-02-15 02:52:41 +00:00
l10n daemon script
b6cf60acdb GIT_SILENT made messages (after extraction) 2024-02-15 02:21:10 +00:00
Tobias Fella
d4a6a41981 Skip Welcome screen when there's only one connection and it's loaded
If the connection is stuck, we can still log in to a different one that way.

(cherry picked from commit 7150445f8e)
2024-02-14 18:16:07 +01:00
Tobias Fella
8c2682c943 Allow dropping connections from the welcome page
This is the last piece required to make sure that we can recover from broken connections, e.g., when the access token is invalid.

(cherry picked from commit b02bdd22dd)
2024-02-14 18:14:20 +01:00
Heiko Becker
9c56561853 GIT_SILENT Update Appstream for new release
(cherry picked from commit 0cd0a6a672)
2024-02-14 14:39:59 +01:00
l10n daemon script
ab9410cc03 GIT_SILENT Sync po/docbooks with svn 2024-02-14 03:00:27 +00:00
Tobias Fella
43fae7af04 Show custom emoji reactions as per MSC4027
(cherry picked from commit ca57732871)
2024-02-12 16:02:48 +01:00
Tobias Fella
0cf19d21f2 Fix AudioDelegate playback
(cherry picked from commit b909cb2db8)
2024-02-10 23:06:44 +01:00
l10n daemon script
ce448bd027 GIT_SILENT Sync po/docbooks with svn 2024-02-09 03:07:07 +00:00
l10n daemon script
056e91df9f GIT_SILENT Sync po/docbooks with svn 2024-02-05 03:32:35 +00:00
l10n daemon script
258815ca10 GIT_SILENT Sync po/docbooks with svn 2024-02-02 02:54:20 +00:00
l10n daemon script
174373fb15 GIT_SILENT Sync po/docbooks with svn 2024-01-31 03:10:56 +00:00
l10n daemon script
aa0790d7fd GIT_SILENT Sync po/docbooks with svn 2024-01-30 02:58:53 +00:00
l10n daemon script
e6c589c6ac GIT_SILENT Sync po/docbooks with svn 2024-01-29 02:59:49 +00:00
James Graham
4b1805bdaa Fix copying selected text from a message
(cherry picked from commit 48502480df)
2024-01-28 10:06:27 +00:00
l10n daemon script
6055460bff GIT_SILENT Sync po/docbooks with svn 2024-01-28 02:58:51 +00:00
l10n daemon script
0642685874 GIT_SILENT Sync po/docbooks with svn 2024-01-27 02:57:15 +00:00
l10n daemon script
e2b7e6778e GIT_SILENT Sync po/docbooks with svn 2024-01-26 02:59:18 +00:00
l10n daemon script
22bf9b8a59 GIT_SILENT Sync po/docbooks with svn 2024-01-24 02:56:01 +00:00
l10n daemon script
85fc1a1f46 GIT_SILENT Sync po/docbooks with svn 2024-01-23 03:02:41 +00:00
l10n daemon script
624123407c GIT_SILENT Sync po/docbooks with svn 2024-01-22 03:38:56 +00:00
James Graham
7bad41739f Cherrypick 24.02 Clip QuickSwitcher
Clip QuickSwitcher to stop the delegates overlapping the dialog


(cherry picked from commit 8e8105d04d)
2024-01-17 17:19:40 +00:00
l10n daemon script
ee16504aa0 GIT_SILENT Sync po/docbooks with svn 2024-01-17 02:57:22 +00:00
Ingo Klöcker
cf308bcdce Require master of ECM
We need the fix for APK packaging with Android NDK r25


(cherry picked from commit 21d9e69712)
2024-01-16 14:21:07 +00:00
l10n daemon script
6fbfa48c77 GIT_SILENT Sync po/docbooks with svn 2024-01-16 02:57:45 +00:00
l10n daemon script
67d71cb590 GIT_SILENT Sync po/docbooks with svn 2024-01-15 02:56:34 +00:00
l10n daemon script
c5817df2c9 GIT_SILENT Sync po/docbooks with svn 2024-01-14 03:43:35 +00:00
Joshua Goins
b94fcd6858 Make the search message dialog header way prettier, like it is in KCMs
I think I've heard of this before...

(cherry picked from commit 2247a2a7af)
2024-01-13 20:37:48 -05:00
Joshua Goins
8a8874fcb6 Add missing thread roles in SearchModel
This fixes the message search so it works again!

(cherry picked from commit 08a0fbfd6b)
2024-01-13 20:35:25 -05:00
James Graham
b593f7321b Cherrypick 24.02 Readonly Room
Add readonly property to a room and use it to decide whether to show chatbar, replies and edits

BUG: 479590


(cherry picked from commit ec4aa73e37)
2024-01-13 12:06:00 +00:00
Albert Astals Cid
5002258e34 GIT_SILENT Upgrade release service version to 24.01.95. 2024-01-11 20:53:22 +01:00
Yifan Zhu
f75c194e7c Call signals instead of signal handlers
Directly calling signals is the supported way to send signals.
Calling signal handlers worked in the past, but will be phased out in
the future (https://bugreports.qt.io/browse/QTBUG-120573).
2024-01-11 17:23:08 +00:00
Tobias Fella
ecf93de006 Update copyright year 2024-01-11 18:21:44 +01:00
l10n daemon script
7feb02c7d8 GIT_SILENT Sync po/docbooks with svn 2024-01-11 01:17:30 +00:00
Hannah von Reth
0764fe0dd9 Use craft default targets 2024-01-10 11:47:48 +00:00
Hannah von Reth
50a7138633 Add craft ci targets 2024-01-10 11:47:48 +00:00
l10n daemon script
96a477f91c GIT_SILENT Sync po/docbooks with svn 2024-01-10 02:13:57 +00:00
Tobias Fella
5a6b0f756d Remove broken network error checks
Fixes #529
2024-01-09 19:59:23 +01:00
l10n daemon script
dbbf975f7a GIT_SILENT Sync po/docbooks with svn 2024-01-09 02:10:55 +00:00
l10n daemon script
e06c2d2f93 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"
2024-01-09 02:06:01 +00:00
Albert Astals Cid
5dea17b616 GIT_SILENT Upgrade release service version to 24.01.90. 2024-01-09 00:47:19 +01:00
Tobias Fella
63b6d7ebe0 Remove unused includes 2024-01-08 20:41:59 +01:00
l10n daemon script
652106e6a1 GIT_SILENT Sync po/docbooks with svn 2024-01-08 02:16:01 +00:00
Joshua Goins
3e158b3e60 Stop log spam because subtitleText was called without a valid event
It's up to the call site to check if the event is valid before calling
this function, and it prevents tons of log spam because we didn't check
yet.
2024-01-07 21:43:26 +00:00
Tobias Fella
b9ec33dd94 Remove activeConnection from Config
It's not used anymore
2024-01-07 21:25:55 +01:00
Tobias Fella
69bd1202ba Remove another comment 2024-01-07 21:20:04 +01:00
Tobias Fella
f75c09e130 Remove comment 2024-01-07 21:19:25 +01:00
Tobias Fella
683d216f44 Mark ReactionModel as uncreatable 2024-01-07 19:47:19 +01:00
Tobias Fella
c10bcf1764 Move userConsentRequired to NeoChatConnection 2024-01-07 19:44:37 +01:00
Tobias Fella
9e2bf0da26 Port away from Controller::saveWindowGeometry 2024-01-07 19:39:18 +01:00
James Graham
51f7de117d Refactor LinkPreviewer
Refactor `LinkPreviewer` to take an event and put the functions for getting the link in the class itself. This means the functions in `EventHandler` are no longer required.

This mr also sets up `LinkPreviewer` so that it is automatically updated when an event is edited. This includes changing the link if edited, and it can handle a message having a previous link removed or a one added when one didn't exist before.

Also adds test suite.
2024-01-07 18:07:13 +00:00
Tobias Fella
f361f4e2d8 Don't always html-escape user-specified input when serializing a state event body
We don't want this for the room list subtitle
2024-01-07 16:12:45 +00:00
l10n daemon script
acfb5ab834 GIT_SILENT Sync po/docbooks with svn 2024-01-07 02:42:26 +00:00
James Graham
8bc4500ce1 Fix by allowing RoomPage to access the interactive property of the timeline 2024-01-06 19:49:20 +00:00
Joshua Goins
b96d3dde46 Remove useless interactive set when maximizing images
This doesn't seem to do anything, and it's not easy to turn it back when
the popup is closed
2024-01-06 19:49:20 +00:00
James Graham
fad381c36f Refactor reactions
Currently we effectively create the reactions list in EventHandler then pass that data into a model. This reworks the model so that we just pass in a room and an event and it grabs it's own data. This means that:
- the functions in event handler are no longer required
- the model can update itself to add/remove reactions so no need to handle that in MessageEventModel
- MessageEventModel only needs to create new ReactionModels or remove old ones when no reactions exist anymore

A basic test suite has also been created for the ReactionModel
2024-01-06 17:50:32 +00:00
Tobias Fella
ad083f64b1 Ensure that only one RoomManager exists 2024-01-06 17:46:28 +01:00
Tobias Fella
5c78b23cc2 Slightly fix QML formatting 2024-01-06 17:34:56 +01:00
Tobias Fella
2202063641 Remove stray console log 2024-01-06 17:34:10 +01:00
Tobias Fella
5be15ffa6a Show user display names as plaintext in InviteUserPage 2024-01-06 17:33:39 +01:00
Tobias Fella
a0bafe53a0 Use Plaintext for user displaynames in startchatpage 2024-01-06 17:31:57 +01:00
l10n daemon script
1a39ce9585 GIT_SILENT Sync po/docbooks with svn 2024-01-06 02:11:50 +00:00
Tobias Fella
4a809d57f7 Fix crash when accepting/declining already accepted/declined invite
BUG: 475502
2024-01-05 14:16:21 +01:00
l10n daemon script
c01e42c972 GIT_SILENT Sync po/docbooks with svn 2024-01-05 02:13:27 +00:00
l10n daemon script
6c56f7f4ef GIT_SILENT Sync po/docbooks with svn 2024-01-04 02:16:55 +00:00
l10n daemon script
6d8d5a82c2 GIT_SILENT Sync po/docbooks with svn 2024-01-03 02:14:00 +00:00
Nicolas Fella
daa27b0333 Fix Android build 2024-01-03 01:40:14 +01:00
Nicolas Fella
f6edf0e4cc Add missing include 2024-01-03 01:29:07 +01:00
James Graham
356e8eefe0 Refactor PollHandler
Refactor PollHandler to make it more reliable. This ended up with much more code than I expected as the original intent was just to stop a crash when switching rooms.
- Using a event string was flaky, changing to using an event reference is more reliable.
- Since we're only creating them from NeoChatRoom there is no need to to be able to set properties from QML so only read properties.
- Pass from the MessageEventModel rather than an invokable method.
- Create a basic test suite
- Create properties in PollHandler to remove the need to use content in PollDelegate, this means content is no longer a required role.
2024-01-02 21:22:08 +00:00
Laurent Montel
7ad362225f Use --socket=fallback-x11 in .flatpak-manifest.json 2024-01-02 17:59:31 +01:00
Tobias Fella
d623a8c826 Don't HTML-escape invite notification title 2024-01-02 07:48:52 +00:00
l10n daemon script
b3f0d110d9 GIT_SILENT Sync po/docbooks with svn 2024-01-02 02:09:10 +00:00
James Graham
612b5d7f47 Move all the enums for push rules into their own header file 2024-01-01 16:15:40 +00:00
James Graham
7e9f206348 Clear the emoji picker search when the dialog is closed
Clear the emoji picker search when the dialog is closed

BUG: 472873
2024-01-01 16:15:29 +00:00
l10n daemon script
d5f4a3dd64 GIT_SILENT Sync po/docbooks with svn 2024-01-01 02:13:41 +00:00
James Graham
e807ad9908 Improve the unread marker behaviour
The fixes include:
- improving the timer to make it more reliable
- making sure a read marker is added when changin rooms, this is needed when the messages have already been loaded.
- increase the default timer to 10s to avoid the read marker disappearing and being re-added when a message arrive in quick succession. 

BUG: 465300
2023-12-31 17:47:27 +00:00
Tobias Fella
2d8ad834a7 Fix showing stickers 2023-12-31 18:15:43 +01:00
l10n daemon script
d82dfc7a5b GIT_SILENT Sync po/docbooks with svn 2023-12-31 02:43:10 +00:00
l10n daemon script
1e1e54d4bd 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"
2023-12-31 02:27:14 +00:00
l10n daemon script
9043b1c7a1 GIT_SILENT made messages (after extraction) 2023-12-31 01:54:25 +00:00
Tobias Fella
0739f4c661 Re-Enable Flatpak CI 2023-12-27 22:00:29 +00:00
James Graham
29321aeaf3 WindowController test
Create a WindowController test suite.

Also make sure that the class handles m_window being nullptr.
2023-12-24 15:37:56 +00:00
l10n daemon script
9fe44515a7 GIT_SILENT Sync po/docbooks with svn 2023-12-24 02:48:11 +00:00
Tobias Fella
4c3d7ab011 Move Controller::toggleWindow to WindowController 2023-12-23 14:50:36 +00:00
Tobias Fella
d02eee6daa Port away from and remove Controller::initiated 2023-12-23 13:39:41 +00:00
Tobias Fella
a613baa148 Don't crash when invited room doesn't have member state for the current room
This smells illegal, but nothing stops a server from sending that to us so we shouldn't crash.

BUG: 478903
2023-12-23 11:55:22 +00:00
James Graham
4e141e05f0 Cleanup leftover issues from moving ReplyComponent away from GridLayout
Cleanup leftover issues from moving ReplyComponent away from GridLayout.
- Remove leftover GridLayout properties or references to them or convert as required
- Remove unneeded Item wrapper
2023-12-23 11:37:01 +00:00
l10n daemon script
134f1d2ae5 GIT_SILENT Sync po/docbooks with svn 2023-12-23 02:30:01 +00:00
Tobias Fella
9ec3e5b2cc Remove more unused signals 2023-12-23 00:45:00 +01:00
Tobias Fella
15d066b3ed Remove unused variable 2023-12-23 00:39:08 +01:00
Tobias Fella
7694f6c464 Remove unused signal 2023-12-23 00:36:53 +01:00
Marius P
46da4f9777 org.kde.neochat.appdata.xml use https://bugs.kde.org/enter_bug.cgi?product=NeoChat
https://github.com/ximion/appstream/docs/xml/metainfo-component.xml says
"bugtracker - Should point to the software's bug tracking system,
for users to report new bugs.".
2023-12-22 13:10:55 +00:00
l10n daemon script
44e0ef43ac GIT_SILENT Sync po/docbooks with svn 2023-12-22 02:23:21 +00:00
l10n daemon script
5b79371c0a GIT_SILENT made messages (after extraction) 2023-12-22 01:39:20 +00:00
Joshua Goins
3caa5ad2ed Prevent KUnifiedPush-activated daemon from sticking around forever
Erroneous activations of the D-Bus service could cause the daemon to be
launched without any messageReceived signals being called (which then
hooks up the notifications to quit the app.) Now there's a five-second
timeout to prevent it from living too long.
2023-12-21 19:41:41 +00:00
l10n daemon script
a74d78439c GIT_SILENT Sync po/docbooks with svn 2023-12-21 02:47:51 +00:00
l10n daemon script
0b82aeb5cc GIT_SILENT Sync po/docbooks with svn 2023-12-19 02:13:10 +00:00
Albert Astals Cid
bc7aa4e272 GIT_SILENT Upgrade release service version to 24.01.85. 2023-12-18 23:19:45 +01:00
Akseli Lahtinen
d1f421994d Add config import to ReactionDelegate
Forgot to add this in https://invent.kde.org/network/neochat/-/merge_requests/1484
2023-12-18 21:16:47 +00:00
Carl Schwan
b043cbedc9 Make BanSheet frameless
Similar to https://invent.kde.org/network/neochat/-/merge_requests/1487
2023-12-18 19:24:31 +00:00
Carl Schwan
5a469ed126 Make ReportSheet frameless
Similar to https://invent.kde.org/network/neochat/-/merge_requests/1487
2023-12-18 16:45:34 +00:00
Akseli Lahtinen
8d209a5424 Make it easier to spot reaction buttons
In dark themes it can be very hard to spot the reaction
buttons when using compact mode. 

Before:

![image](/uploads/f499f5fd16be353c5fef9b1a08e99522/image.png)

After:

![image](https://invent.kde.org/network/neochat/uploads/86e6cecc546d73481dff79debdcadc3f/image.png)

Also this uses the given colorscheme so it will work everywhere 😄
2023-12-18 16:23:00 +00:00
l10n daemon script
a0937bdcac GIT_SILENT Sync po/docbooks with svn 2023-12-18 02:46:11 +00:00
Carl Schwan
df4eba0191 Make the RemoveSheet frameless 2023-12-17 22:26:05 +00:00
l10n daemon script
9f83cf1c52 GIT_SILENT Sync po/docbooks with svn 2023-12-17 02:11:31 +00:00
James Graham
ddc0bfe786 Make sure that a nullptr connection is not accessed in AccountEmoticonModel
I don't think it possible to open settings without an account connected anymore, however just to make sure I've made sure that a nullptr connection is handled in `AccountEmoticonsModel`.

BUG: 478024
2023-12-13 18:34:59 +00:00
l10n daemon script
09796b6e0b GIT_SILENT Sync po/docbooks with svn 2023-12-13 02:12:01 +00:00
l10n daemon script
73c34c68b7 GIT_SILENT Sync po/docbooks with svn 2023-12-12 02:14:39 +00:00
l10n daemon script
76ab96ef3e GIT_SILENT made messages (after extraction) 2023-12-12 01:37:09 +00:00
l10n daemon script
5a580dd16c GIT_SILENT Sync po/docbooks with svn 2023-12-11 02:11:45 +00:00
l10n daemon script
918887525b 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"
2023-12-11 02:06:11 +00:00
l10n daemon script
500ebbecbf GIT_SILENT made messages (after extraction) 2023-12-11 01:35:18 +00:00
James Graham
8062dface6 ActionsHandler test
Add basic test suite to make sure that ActionsHandler rejects attempts to handleMessage if either m_room or chatBarCacher are nullptr.
2023-12-10 18:14:51 +00:00
James Graham
8d717b78ac Move the function to get a subtitle string to eventhandler 2023-12-10 13:19:13 +00:00
l10n daemon script
4751ac6acc GIT_SILENT Sync po/docbooks with svn 2023-12-10 02:46:07 +00:00
James Graham
1d8ffb22ee TestUtils
Create TestUtils with TestRoom and use this for all tests which require it
2023-12-09 19:32:48 +00:00
James Graham
a035d6542d Fix spoilers in roomlist subtitles
Make sure spoilers aren't revealed in SubtitleRole like in LastEventRole then remove LastEventRole because it is no longer used
2023-12-09 15:30:19 +00:00
Laurent Montel
d7230022f6 Port [=] operator 2023-12-09 15:47:26 +01:00
James Graham
d25340bc31 Fix inline custom emojis in codeblocks
Make sure that custom emojis in inline code blocks are not turned into images.

By calling preprocess text in `texthandler` the whole function can be simplified as it will now never be called on any text inside any code block (which was the reason for all the split stuff previously).

BUG: 477512
2023-12-09 14:28:00 +00:00
l10n daemon script
c396024a26 GIT_SILENT Sync po/docbooks with svn 2023-12-08 02:12:32 +00:00
James Graham
65b94890ab Add test for pending events into the MessageEventModelTest 2023-12-06 20:07:18 +00:00
l10n daemon script
682004d9a2 GIT_SILENT Sync po/docbooks with svn 2023-12-06 02:18:06 +00:00
Tobias Fella
decf797180 Don't HTML-escape display names in room list subtitles 2023-12-05 16:58:24 +00:00
l10n daemon script
552b6dd913 GIT_SILENT Sync po/docbooks with svn 2023-12-05 02:10:40 +00:00
l10n daemon script
e6358eca16 GIT_SILENT Sync po/docbooks with svn 2023-12-04 02:12:32 +00:00
James Graham
f3bacad460 Add more tests to MessageEventModelTest 2023-12-03 17:58:17 +00:00
Tobias Fella
862f4363a9 Use HTML-escaped display name in avatar tooltip 2023-12-03 16:40:09 +00:00
Tobias Fella
9b3a3dc562 Always clip roomlist
The current behavior is a leftover from some previous component
2023-12-03 14:59:08 +01:00
Tobias Fella
c2641a5fc2 Cleanup error reporting 2023-12-03 14:39:31 +01:00
Tobias Fella
1213ba5916 Don't show notifications view button when room list is collapsed
Looks broken
2023-12-03 14:23:33 +01:00
Tobias Fella
0521ff2c4d Move PasswordStatus out of Controller 2023-12-03 14:09:41 +01:00
Tobias Fella
65c892787e Don't suggest kicking or banning users with a power level too high for us to kick 2023-12-03 13:56:41 +01:00
l10n daemon script
7541c192bf GIT_SILENT Sync po/docbooks with svn 2023-12-03 02:11:34 +00:00
l10n daemon script
c692d7e679 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"
2023-12-03 02:06:11 +00:00
Joshua Goins
44a9a491af Add more missing icons on Android 2023-12-02 17:27:56 +00:00
Emir SARI
f8cae7c143 Fix i18n regression 2023-12-02 17:09:19 +00:00
Tobias Fella
4f2b559126 Show author name when previous message was deleted
This will cause the name to show up more often than necessary, but that's better than not showing it at all, which leads to messages being attributed to the wrong person.
The root cause here is a mismatch in event visibility between EventStatus::Hidden and filterAcceptsRow in MessageFilterModel, which will be fixed in a future patch.
2023-12-02 17:12:02 +01:00
James Graham
46fd288a95 Add some clarification around around offical and canonical parents 2023-12-02 13:21:26 +00:00
Joshua Goins
3a1cac6c99 Remove ffmpeg from Android build
This doesn't work on Android right now, and should allow the apk to at least launch.
2023-12-02 03:53:55 +00:00
l10n daemon script
225092a193 GIT_SILENT Sync po/docbooks with svn 2023-12-01 02:13:35 +00:00
Heiko Becker
2fcb17621c GIT_SILENT Update Appstream for new release
(cherry picked from commit 17b14aa556)
2023-11-30 18:35:03 +01:00
l10n daemon script
69e3ee862f GIT_SILENT Sync po/docbooks with svn 2023-11-30 02:08:31 +00:00
James Graham
47c12cb582 MessageEventModel test
Add basic test suite for MessageEventModel
2023-11-29 19:06:43 +00:00
l10n daemon script
94cf75af68 GIT_SILENT Sync po/docbooks with svn 2023-11-29 02:12:57 +00:00
ivan tkachenko
259b9884c7 Set WrapMode on all TextArea components, and fix up some of the Labels
Upstream styles do not provide any wrapping by default, and thus we
should not rely on deceptively convenient override in qqc2-desktop-style.

See https://invent.kde.org/frameworks/qqc2-desktop-style/-/merge_requests/331
2023-11-28 22:26:57 +00:00
l10n daemon script
2980dc49e4 GIT_SILENT Sync po/docbooks with svn 2023-11-28 02:11:26 +00:00
Tobias Fella
5e12f50899 Wrap text in poll delegates
BUG: 477629
2023-11-27 20:44:19 +00:00
James Graham
79940f707f By default only show leave/join state events
As discussed in network/neochat#609
2023-11-27 18:15:48 +00:00
l10n daemon script
e7024a270c GIT_SILENT Sync po/docbooks with svn 2023-11-27 02:12:56 +00:00
Tobias Fella
1a561f6649 Fix linking on windows 2023-11-26 17:40:50 +01:00
Tobias Fella
2cbb6ed65c Enable windows qt6 CI 2023-11-26 15:22:26 +00:00
James Graham
ab4639926a Fix isEdit Check MessageEditComponent
Stop the console being pinged with qrc:/org/kde/neochat/qml/MessageEditComponent.qml:158: TypeError: Cannot read property 'isEditing' of undefined.

Fixes network/neochat#629
2023-11-26 15:21:18 +00:00
Tobias Fella
89b6c54f25 Fix state delegate spacing 2023-11-26 15:06:35 +01:00
James Graham
27c9c62564 ResolveResource
Use ResolveResource rather than calling individual functions like visit user and room
2023-11-26 12:23:28 +00:00
l10n daemon script
bb8ffb02d1 GIT_SILENT Sync po/docbooks with svn 2023-11-26 02:48:22 +00:00
James Graham
201aa82c04 Remove controller::joinRoom
Remove controller::joinRoom in favour of using RoomManager::OpenResource
2023-11-25 15:07:15 +00:00
l10n daemon script
704430db7a GIT_SILENT Sync po/docbooks with svn 2023-11-25 02:08:53 +00:00
Albert Astals Cid
dcbbbd9296 GIT_SILENT Upgrade release service version to 24.01.80. 2023-11-25 00:51:46 +01:00
Tobias Fella
410258c478 Join rooms when visiting them
BUG: 477261
2023-11-24 19:11:15 +01:00
Tobias Fella
6baf2e4888 Fix crash for invalid media uris
BUG: 476698
2023-11-24 18:50:30 +01:00
Tobias Fella
dd6eaac556 Don't crash when sticker pack room doesn't exist
BUG: 476923
2023-11-24 16:43:16 +01:00
l10n daemon script
840903128c GIT_SILENT Sync po/docbooks with svn 2023-11-24 02:10:11 +00:00
l10n daemon script
68f0ca96da GIT_SILENT Sync po/docbooks with svn 2023-11-23 02:13:47 +00:00
l10n daemon script
faf7af06fe GIT_SILENT Sync po/docbooks with svn 2023-11-22 02:09:37 +00:00
l10n daemon script
4ef67c3e0d GIT_SILENT Sync po/docbooks with svn 2023-11-21 02:09:50 +00:00
James Graham
5efd17d370 Loading and End of Timeline Delegates
Add delegate for showing the user a loading indicator and for the beginning of the timeline.

BUG: 455045
BUG: 465285
2023-11-20 17:10:56 +00:00
l10n daemon script
0dbef58ff2 GIT_SILENT Sync po/docbooks with svn 2023-11-20 02:44:00 +00:00
Tobias Fella
6a3df8baf4 Fix initial visual focus in welcomepage 2023-11-19 14:09:08 +01:00
l10n daemon script
2fc973f218 GIT_SILENT Sync po/docbooks with svn 2023-11-19 02:26:37 +00:00
l10n daemon script
7d16999c44 GIT_SILENT Sync po/docbooks with svn 2023-11-18 02:09:37 +00:00
Laurent Montel
f9ba31f2dc Remove unused variables 2023-11-17 10:23:58 +00:00
Laurent Montel
d4ad773ff1 Remove unused "this" argument 2023-11-17 09:36:35 +00:00
Laurent Montel
822a4dc500 Use nullptr 2023-11-17 07:34:46 +00:00
l10n daemon script
bfd1d431c1 GIT_SILENT Sync po/docbooks with svn 2023-11-17 02:12:16 +00:00
Carl Schwan
94bf2481f0 Fix spacing in login screen 2023-11-16 22:43:45 +01:00
Tobias Fella
5c32520c35 Add appium test for opening the user details sheet and fix some accessibility problems 2023-11-16 19:47:13 +01:00
l10n daemon script
de47597f6e GIT_SILENT Sync po/docbooks with svn 2023-11-16 02:21:25 +00:00
l10n daemon script
7780a72888 GIT_SILENT Sync po/docbooks with svn 2023-11-15 02:13:48 +00:00
Carl Schwan
1f8c07fedf Make room timeline non interactible
Currently trying to scroll with this scrollbar is probablematic and it's
unlikely we will find an easy solution to fix it, so make it non interactible.
2023-11-14 19:16:02 +00:00
Tobias Fella
877575b4d3 Fix kirigami name 2023-11-14 19:18:02 +01:00
l10n daemon script
1181df4db2 GIT_SILENT Sync po/docbooks with svn 2023-11-14 02:12:52 +00:00
Joshua Goins
5be2113b32 Add matrix: URL to push notifications 2023-11-13 20:02:07 +00:00
l10n daemon script
7ad7af56e8 GIT_SILENT Sync po/docbooks with svn 2023-11-13 02:12:13 +00:00
Joshua Goins
d3148f8c8b Don't start the entire NeoChat backend when receiving push notifications
This adds a dedicated "set up for push notifications only" function in
Controller, which only sets up the KUnifiedPush connector for receiving
the message and then quitting right afterward. If the user tries to
open a notification, then it will quit and open the main client.
2023-11-12 17:08:15 -05:00
Joshua Goins
100f595026 Add handler for KUnifiedPush messageReceived events 2023-11-12 21:46:33 +00:00
Joshua Goins
15ba6d58e2 Only attempt to setup push notifications on the first syncDone 2023-11-12 15:53:15 -05:00
James Graham
61ad892732 Handle multiple line names
Add function to get the display name for an author on a single line as nothing stops there being linebreaks.

BUG:476731
2023-11-12 18:46:04 +00:00
James Graham
2a3e1dfcd7 Read Marker Hidden
Only show the read marker if there is a non-hidden event earlier in the timeline

fixes network/neochat#615
2023-11-12 16:32:08 +00:00
Joshua Goins
d1dc6fc4ed Fix typo preventing APK from building 2023-11-12 15:36:38 +00:00
James Graham
6dc30a9ca7 Fix hover action position when wide
Fix the positioning of the hover actions when the window is wide by using the content x pos
2023-11-12 14:20:46 +00:00
James Graham
ae0c5ffaef Improve State Text Translatability
Make the state state strings less ambiguous for the purpose of translation.

BUG: 476358
2023-11-12 14:08:36 +00:00
Tobias Fella
fc546d4a43 Add notifications view 2023-11-12 13:16:09 +00:00
Tobias Fella
96e62e3ebe Disable system tray integration by default 2023-11-12 12:46:16 +00:00
Joshua Goins
d979cd2fbc Add Snap Store link in README 2023-11-12 04:01:26 +00:00
l10n daemon script
22298181cb GIT_SILENT Sync po/docbooks with svn 2023-11-12 02:22:14 +00:00
Tobias Fella
1312fde470 Cleanup code 2023-11-12 01:46:23 +01:00
Tobias Fella
7fe2feb1e4 Make NeoChat not shut down when disabling close-to-tray 2023-11-12 01:46:06 +01:00
Ingo Klöcker
67fb5d0824 Add option used on Binary Factory 2023-11-11 22:06:59 +00:00
Ingo Klöcker
359114bd3d Tell Craft that we need Qt 6 and master of the dependencies 2023-11-11 22:06:59 +00:00
Ingo Klöcker
3db9f1198b Add CI template for building APKs 2023-11-11 22:06:59 +00:00
Tobias Fella
2435a6b953 Fix opening account editor 2023-11-11 23:06:26 +01:00
Tobias Fella
e6c8b3fa4b Simplify some android code 2023-11-11 22:59:27 +01:00
Tobias Fella
2d55dca508 Don't allow changing the power level for users with a power level higher or equal to ours
The server won't allow it anyway
2023-11-11 18:20:54 +00:00
Tobias Fella
bbb0bc3092 Improve and fix powerlevels dialog 2023-11-11 17:37:27 +00:00
James Graham
4065aa6a2e Fix linkpreview tooltip
Fix linkpreview tooltip so that the correct text is always shown.

BUG: 467106
2023-11-11 17:29:15 +00:00
Tobias Fella
5942eac5ed Force plaintext in permissions settings 2023-11-11 18:09:03 +01:00
Tobias Fella
85c2b7dada Fix crash on shutdown 2023-11-11 16:03:35 +01:00
Carl Schwan
86ef921cdb Fix alignment user info
Otherwise we end up with two times largeSpacing as right margin for the
user info.
2023-11-11 15:40:15 +01:00
James Graham
aab69c5bae Suggested rooms spaces
Add the ability to set and show suggested rooms for spaces. 

This is just adding the basic functionality, we can do more things with it later like sort/filter the space home for example.
2023-11-11 13:32:19 +00:00
Carl Schwan
624578ec77 Improve welcome page
- Don't use card for welcome message and instead use same layout as
  kwordquiz
- Add separator between login and register button
- Add type annotation in functions
2023-11-11 13:12:56 +00:00
l10n daemon script
197ff984fd GIT_SILENT Sync po/docbooks with svn 2023-11-11 02:19:28 +00:00
James Graham
a26337d5f4 Fix ServerListModel
The server list needs to be populated on connection change as this is instantiated from QML
2023-11-10 20:32:51 +00:00
Joshua Goins
31b4eefadd Move closeDialog signal from Loading to LoginStep
This prevents errors because we try to connect to a non-existent signal
if the step is anything but Loading.
2023-11-10 18:40:37 +00:00
Joshua Goins
b8e592f8ba Fix wrong argument being passed into maximize component creation
It's supposed to refer to the attached property on QQC2.Overlay, not
QQC2.ApplicationWindow
2023-11-10 18:36:49 +00:00
Joshua Goins
555d23863e Port to QEvent::ApplicationPaletteChange
The paletteChanged on QGuiApplication is deprecated in Qt6
2023-11-10 18:30:27 +00:00
Joshua Goins
ffa2d5dc0e Fix push notification registration
Now it should regularly happen after login
2023-11-10 18:08:52 +00:00
Joshua Goins
9987edbaf2 Note that profileTag is intentionally left empty 2023-11-10 18:08:52 +00:00
Joshua Goins
d90298392d Only run setupPushNotification once for new accounts 2023-11-10 18:08:52 +00:00
Joshua Goins
600dbd0603 Remove unnecessary activeConnection() call in setupPushNotifications 2023-11-10 18:08:52 +00:00
Joshua Goins
2179e2cc35 Fix QCoro headers 2023-11-10 18:08:52 +00:00
Joshua Goins
8119ea3ccb Use auto in NeoChatConnection::setupPushNotifications 2023-11-10 18:08:52 +00:00
Joshua Goins
3fc125a798 Move push notification setup to NeoChatConnection 2023-11-10 18:08:52 +00:00
Joshua Goins
99f6778df4 Register for push notifications
This sets it up so the homeserver will give us push notifications, but
we aren't handling them yet.
2023-11-10 18:08:52 +00:00
Joshua Goins
f7bd24db34 Depend on QCoro::Network
For awaiting on QNetworkReply
2023-11-10 18:08:52 +00:00
Joshua Goins
41c2f9c4d5 Re-arrange main.cpp and add no gui option when DBus activated
We don't want the QML engine initialized at all when launching on
notifications, it'll be useless there.
2023-11-10 18:08:52 +00:00
Joshua Goins
56d5f3036b Change name of notify argument to dbus-activated 2023-11-10 18:08:52 +00:00
Tobias Fella
21bb7dce21 Fix background color of UserInfo
Previously, this would change when the window lost focus, which looks strange
2023-11-10 16:16:29 +01:00
l10n daemon script
11081719a7 GIT_SILENT Sync po/docbooks with svn 2023-11-10 02:22:55 +00:00
Joshua Goins
980de7d85d Use go.kde.org for the Matrix link, mention Matrix wiki page
I moved NeoChat up next to Element on go.kde.org, so it's good marketing
;-)
2023-11-09 16:53:36 -05:00
Joshua Goins
7735313b0c Add DBus service for NeoChat and link to KUnifiedPush if found 2023-11-09 21:40:09 +00:00
Joshua Goins
1a3055df86 Add optional KUnifiedPush dependency 2023-11-09 21:40:09 +00:00
Tobias Fella
84cad630cd Refactor and fix ChatBox layouting
BUG: 474616
2023-11-09 20:19:09 +00:00
James Graham
0beb5df08d Hide the search field for room members in a direct chat.
Hide the search field for room members in a direct chat as there are no members to search.
2023-11-09 20:11:38 +00:00
Tobias Fella
4ef44b8e93 Track online status per connection 2023-11-09 19:38:43 +01:00
l10n daemon script
59153be006 GIT_SILENT Sync po/docbooks with svn 2023-11-09 02:12:54 +00:00
l10n daemon script
50be96f762 GIT_SILENT Sync po/docbooks with svn 2023-11-08 09:27:31 +00:00
225 changed files with 33292 additions and 24561 deletions

5
.craft.ini Normal file
View File

@@ -0,0 +1,5 @@
; SPDX-FileCopyrightText: None
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/applications/neochat.packageAppx = True

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "5.15-22.08",
"runtime-version": "6.6",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -12,7 +12,7 @@
"finish-args": [
"--share=network",
"--share=ipc",
"--socket=x11",
"--socket=fallback-x11",
"--socket=wayland",
"--device=dri",
"--filesystem=xdg-download",
@@ -31,6 +31,7 @@
},
{
"name": "kquickimageeditor",
"config-opts": [ "-DBUILD_WITH_QT6=ON" ],
"buildsystem": "cmake-ninja",
"sources": [
{
@@ -85,8 +86,8 @@
"sources": [
{
"type": "archive",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/v0.13.2.tar.gz",
"sha256": "20beeb32de7c4eb0af9039b21e18370faf847ac8697ab3045906076afbc4caa5",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
"sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
"x-checker-data": {
"type": "anitya",
"project-id": 4138,
@@ -96,6 +97,7 @@
}
],
"config-opts": [
"-DBUILD_WITH_QT6=ON",
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO"
@@ -113,6 +115,7 @@
}
],
"config-opts": [
"-DBUILD_WITH_QT6=ON",
"-DQuotient_ENABLE_E2EE=ON",
"-DBUILD_TESTING=OFF"
]

View File

@@ -2,9 +2,14 @@
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/reuse-lint.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml

View File

@@ -27,10 +27,10 @@ Dependencies:
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux']
'require':

View File

@@ -45,3 +45,7 @@ License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0

View File

@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "01")
set(RELEASE_SERVICE_VERSION_MICRO "75")
set(RELEASE_SERVICE_VERSION_MINOR "02")
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})
@@ -58,12 +58,12 @@ set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
set_package_properties(KF6Kirigami2 PROPERTIES
set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
@@ -102,8 +102,7 @@ set_package_properties(QuotientQt6 PROPERTIES
PURPOSE "Talk with matrix server"
)
# The android part is just for CI. We do NOT support any builds without E2EE
if (NOT TARGET Olm::Olm AND NOT ANDROID)
if (NOT TARGET Olm::Olm)
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
endif()
@@ -130,7 +129,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments"
)
find_package(QCoro6 0.4 COMPONENTS Core REQUIRED)
find_package(QCoro6 0.4 COMPONENTS Core Network REQUIRED)
qcoro_enable_coroutines()
@@ -140,6 +139,13 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL
)
find_package(KUnifiedPush QUIET)
set_package_properties(KUnifiedPush PROPERTIES
TYPE OPTIONAL
PURPOSE "Push notification support"
URL "https://invent.kde.org/libraries/kunifiedpush"
)
if(ANDROID)
find_package(Sqlite3)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)

View File

@@ -11,6 +11,7 @@ A Qt/QML based Matrix client.
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://snapcraft.io/static/images/badges/en/snap-store-black.svg'/></a>
## Introduction
@@ -95,7 +96,7 @@ As is the case throughout the KDE ecosystem contributions are welcome from all.
## Contact
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org).
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://go.kde.org/matrix/#/#neochat:kde.org). See [Matrix](https://community.kde.org/Matrix) for more details.
## Acknowledgement

View File

@@ -92,6 +92,7 @@ android {
exclude 'lib/*/*_imageformats_qtga_*'
exclude 'lib/*/*_imageformats_qtiff_*'
exclude 'lib/*/*_qmltooling_*'
exclude 'lib/*/*_multimedia_ffmpeg*' // temporary qt6 android fix
}
aaptOptions {

View File

@@ -21,3 +21,8 @@ add_test(
NAME logintest
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
)
add_test(
NAME openuserdetailstest
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/openuserdetailstest.py
)

View File

@@ -0,0 +1,3 @@
{
"next_batch": "batch1234"
}

View File

@@ -0,0 +1,50 @@
{
"next_batch": "batch1234",
"rooms": {
"join": {
"!room_id_1234:localhost:1234": {
"state": {
"events": [
{
"type": "m.room.member",
"state_key": "@user:localhost:1234",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_0:localhost:1234",
"room_id": "!room_id_1234:localhost:1234",
"content": {
"avatar_url": "",
"displayname": "A Display Name",
"membership": "join",
"reason": "Nothing"
},
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"type": "m.room.message",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!room_id_1234:localhost:1234",
"content": {
"body": "This is a message",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
"msgtype": "m.text"
},
"unsigned": {
"age": 1234
}
}
]
}
}
}
}
}

View File

@@ -1,10 +1,12 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import json
from flask import Flask, request, abort
import os
app = Flask(__name__)
@app.route("/_matrix/client/v3/login", methods=["GET"])
def login_get():
result = dict()
@@ -12,6 +14,13 @@ def login_get():
result["flows"][0]["type"] = "m.login.password"
return result
@app.route("/_matrix/client/v3/account/whoami", methods=["GET"])
def whoami():
result = dict()
result["device_id"] = "device_id_1234"
result["user_id"] = "@user:localhost:1234"
return result
@app.route("/_matrix/client/v3/login", methods=["POST"])
def login_post():
data = request.get_json()
@@ -19,15 +28,22 @@ def login_post():
abort(403)
print(data)
result = dict()
result["access_token"] = "token_1234"
result["access_token"] = "token_login"
result["device_id"] = "device_1234"
result["user_id"] = "@user:localhost:1234"
return result
def load_json(name):
parts = __file__.split("/")
parts.pop()
datadir = "/".join(parts)
return json.loads(open(f"{datadir}/data/{name}.json").read())
@app.route("/_matrix/client/r0/sync")
def sync():
result = dict()
result["next_batch"] = "batch1234"
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
return result
@app.route("/.well-known/matrix/client")
@@ -37,6 +53,18 @@ def well_known():
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
return reply
@app.route("/_matrix/client/v3/profile/<id>")
def profile(id):
reply = dict()
reply["avatar_url"] = "mxc://localhost:1234/asdf1234"
reply["displayname"] = "User123"
return reply
@app.route("/_matrix/client/v3/keys/upload", methods=["POST"])
def upload_keys():
reply = dict()
return reply
if __name__ == "__main__":
app.run(ssl_context='adhoc', port=1234)

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import os
import subprocess
import sys
import unittest
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class OpenUserDetailsTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(cls):
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors --test")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
def setUp(self):
pass
def tearDown(self):
if not self._outcome.result.wasSuccessful():
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
@classmethod
def tearDownClass(self):
self.mockServerProcess.terminate()
self.driver.quit()
def test_open_sheet(self):
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
self.driver.find_element(by=AppiumBy.NAME, value="Empty room (!room_id_1234:localhost:1234)").click()
self.driver.find_element(by=AppiumBy.NAME, value="A Display Name").click()
self.driver.find_element(by=AppiumBy.NAME, value="Account Details")
if __name__ == '__main__':
unittest.main()

View File

@@ -46,3 +46,39 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatdocumenthandlertest
)
ecm_add_test(
messageeventmodeltest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME messageeventmodeltest
)
ecm_add_test(
actionshandlertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME actionshandlertest
)
ecm_add_test(
windowcontrollertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME windowcontrollertest
)
ecm_add_test(
pollhandlertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME pollhandlertest
)
ecm_add_test(
reactionmodeltest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME reactionmodeltest
)
ecm_add_test(
linkpreviewertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME linkpreviewertest
)

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QTest>
#include "actionshandler.h"
#include "chatbarcache.h"
#include "testutils.h"
class ActionsHandlerTest : public QObject
{
Q_OBJECT
private:
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
ActionsHandler *actionsHandler = new ActionsHandler(this);
private Q_SLOTS:
void nullObject();
};
void ActionsHandlerTest::nullObject()
{
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(nullptr);
auto chatBarCache = new ChatBarCache(this);
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(chatBarCache);
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
actionsHandler->setRoom(room);
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(nullptr);
// The final one should throw no warning so we make sure.
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(chatBarCache);
}
QTEST_GUILESS_MAIN(ActionsHandlerTest)
#include "actionshandlertest.moc"

View File

@@ -12,26 +12,17 @@
#include "chatbarcache.h"
#include "neochatroom.h"
#include "testutils.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class ChatBarCacheTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
@@ -47,14 +38,7 @@ private Q_SLOTS:
void ChatBarCacheTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
room->update(std::move(roomData));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
}
void ChatBarCacheTest::empty()

View File

@@ -135,6 +135,21 @@
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
"membership": "join"
},
"event_id": "$143273582443PhrSh:example.org",
"origin_server_ts": 1432735824659,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@newline:example.org",
"state_key": "@newline:example.org",
"type": "m.room.member",
"unsigned": {
"age": 12345
}
}
]
},
@@ -373,6 +388,20 @@
"unsigned": {
"age": 1238
}
},
{
"content": {
"body": "A message from someone who thought it was a good idea to put newlines in their display name.",
"msgtype": "m.text"
},
"event_id": "$153456889:example.org",
"origin_server_ts": 14327358246589,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@newline:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1230
}
}
],
"limited": true,

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://matrix.to/#/@alice:example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "testhttps://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,24 @@
{
"timeline": {
"events": [
{
"content": {
"body": "https://kde.org",
"format": "org.matrix.custom.html",
"formatted_body": "https://kde.org",
"msgtype": "m.text"
},
"origin_server_ts": 1704648567967,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 112
},
"event_id": "$validlink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,35 @@
{
"timeline": {
"events": [
{
"content": {
"body": "* ",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"m.new_content": {
"body": "",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"msgtype": "m.text"
},
"m.relates_to": {
"event_id": "$validlink:example.org",
"rel_type": "m.replace"
},
"msgtype": "m.text",
"type": "m.room.message"
},
"origin_server_ts": 1704648614969,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 65
},
"event_id": "$nolink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,105 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"displayname": "Example",
"membership": "join"
},
"event_id": "$exampleMember:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@example:example.org"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"displayname": "Example Changed",
"membership": "join"
},
"event_id": "$exampleMemberChnage:example.org",
"origin_server_ts": 2,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$exampleMember:example.org",
"prev_content": {
"displayname": "Example",
"membership": "join"
},
"prev_sender": "@example:example.org",
"age": 1234
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,22 @@
{
"timeline": {
"events": [
{
"content": {
"body": "New plain message",
"msgtype": "m.text"
},
"event_id": "$pendingmerge:example.org",
"origin_server_ts": 123456,
"room_id":"#myroom:kde.org",
"sender":"@bob:kde.org",
"type":"m.room.message",
"unsigned": {
"transaction_id": "17017181543521"
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,38 @@
{
"timeline": {
"events": [
{
"content": {
"org.matrix.msc1767.text": "test\n1. option1\n2. option 2",
"org.matrix.msc3381.poll.start": {
"answers": [
{
"id": "option1",
"org.matrix.msc1767.text": "option1"
},
{
"id": "option2",
"org.matrix.msc1767.text": "option2"
}
],
"kind": "org.matrix.msc3381.poll.disclosed",
"max_selections": 1,
"question": {
"body": "test",
"msgtype": "m.text",
"org.matrix.msc1767.text": "test"
}
}
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "org.matrix.msc3381.poll.start",
"unsigned": {
"age": 1232
}
}
]
}
}

View File

@@ -0,0 +1,44 @@
{
"timeline": {
"events": [
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545183,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159121
},
"event_id": "$163456790:example.org",
"age": 390159121
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "😆",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545184,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159122
},
"event_id": "$163456791:example.org",
"age": 390159122
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,204 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
},
{
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@bob:example.com": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@jeff:example.com": {
"ts": 1436451550455
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tina:example.com": {
"ts": 1436451550456
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@sally:example.com": {
"ts": 1436451550457
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@fred:example.com": {
"ts": 1436451550458
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
"membership": "join"
},
"event_id": "$143273582443PhrSh:example.org",
"origin_server_ts": 1432735824659,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@newline:example.org",
"state_key": "@newline:example.org",
"type": "m.room.member",
"unsigned": {
"age": 12345
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545182,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$163456789:example.org",
"age": 390159120
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,16 @@
{
"content": {
"body": "[Rich Link](https://kde.org)",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -18,26 +18,17 @@
#include "neochatroom.h"
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class EventHandlerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
TestUtils::TestRoom *room = nullptr;
EventHandler eventHandler;
EventHandler emptyHandler;
EventHandler noEventHandler;
@@ -55,6 +46,8 @@ private Q_SLOTS:
void nullAuthor();
void authorDisplayName();
void nullAuthorDisplayName();
void singleLineSidplayName();
void nullSingleLineDisplayName();
void time();
void nullTime();
void timeString();
@@ -68,12 +61,11 @@ private Q_SLOTS:
void genericBody_data();
void genericBody();
void nullGenericBody();
void markdownBody();
void subtitle();
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void linkPreviewer();
void nullLinkPreviewer();
void reactions();
void nullReactions();
void hasReply();
void nullHasReply();
void replyId();
@@ -99,14 +91,7 @@ private Q_SLOTS:
void EventHandlerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testEventHandlerSyncFile;
testEventHandlerSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-eventhandler-sync.json"));
testEventHandlerSyncFile.open(QIODevice::ReadOnly);
const auto testEventHandlerSyncJson = QJsonDocument::fromJson(testEventHandlerSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testEventHandlerSyncJson.object());
room->update(std::move(roomData));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
eventHandler.setRoom(room);
noEventHandler.setRoom(room);
@@ -203,6 +188,23 @@ void EventHandlerTest::nullAuthorDisplayName()
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
}
void EventHandlerTest::singleLineSidplayName()
{
auto event = room->messageEvents().at(11).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
}
void EventHandlerTest::nullSingleLineDisplayName()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
}
void EventHandlerTest::time()
{
auto event = room->messageEvents().at(0).get();
@@ -345,6 +347,32 @@ void EventHandlerTest::nullGenericBody()
QCOMPARE(noEventHandler.getGenericBody(), QString());
}
void EventHandlerTest::markdownBody()
{
eventHandler.setEvent(room->messageEvents().at(0).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
}
void EventHandlerTest::subtitle()
{
auto event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is an example text message"));
event = room->messageEvents().at(2).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
}
void EventHandlerTest::nullSubtitle()
{
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
QCOMPARE(noEventHandler.subtitleText(), QString());
}
void EventHandlerTest::mediaInfo()
{
auto event = room->messageEvents().at(4).get();
@@ -377,45 +405,6 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::linkPreviewer()
{
auto event = room->messageEvents().at(2).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::nullLinkPreviewer()
{
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::reactions()
{
auto event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getReactions()->rowCount(), 1);
}
void EventHandlerTest::nullReactions()
{
QTest::ignoreMessage(QtWarningMsg, "getReactions called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReactions(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReactions called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReactions(), nullptr);
}
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();

View File

@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QTest>
#include "linkpreviewer.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class LinkPreviewerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
void editedLink();
};
void LinkPreviewerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
}
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
}
void LinkPreviewerTest::linkPreviewsMatch()
{
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
}
void LinkPreviewerTest::linkPreviewsReject()
{
QFETCH(QString, eventSource);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
void LinkPreviewerTest::editedLink()
{
room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto linkPreviewer = LinkPreviewer(room, event);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
QTEST_MAIN(LinkPreviewerTest)
#include "linkpreviewertest.moc"

View File

@@ -0,0 +1,215 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <Quotient/connection.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "enums/delegatetype.h"
#include "models/messageeventmodel.h"
#include "neochatroom.h"
#include "testutils.h"
using namespace Quotient;
class MessageEventModelTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
MessageEventModel *model = nullptr;
private Q_SLOTS:
void initTestCase();
void init();
void switchEmptyRoom();
void switchSyncedRoom();
void simpleTimeline();
void syncNewEvents();
void pendingEvent();
void disconnect();
void idToRow();
void cleanup();
};
void MessageEventModelTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
}
void MessageEventModelTest::init()
{
QCOMPARE(model, nullptr);
model = new MessageEventModel;
}
// Make sure that basic empty rooms can be switched without crashing.
void MessageEventModelTest::switchEmptyRoom()
{
auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"));
auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"));
QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
QCOMPARE(spy.count(), 1);
QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
model->setRoom(secondRoom);
QCOMPARE(spy.count(), 2);
QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
model->setRoom(nullptr);
QCOMPARE(spy.count(), 3);
QCOMPARE(model->room(), nullptr);
}
// Make sure that rooms with some events can be switched without crashing
void MessageEventModelTest::switchSyncedRoom()
{
auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
QCOMPARE(spy.count(), 1);
QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
model->setRoom(secondRoom);
QCOMPARE(spy.count(), 2);
QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
model->setRoom(nullptr);
QCOMPARE(spy.count(), 3);
QCOMPARE(model->room(), nullptr);
}
void MessageEventModelTest::simpleTimeline()
{
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
model->setRoom(room);
QCOMPARE(model->rowCount(), 2);
QCOMPARE(model->data(model->index(0), MessageEventModel::DelegateTypeRole), DelegateType::State);
QCOMPARE(model->data(model->index(0)), QStringLiteral("changed their display name to Example Changed"));
QCOMPARE(model->data(model->index(1)), QStringLiteral("<b>This is an example<br>text message</b>"));
QCOMPARE(model->data(model->index(1), MessageEventModel::DelegateTypeRole), DelegateType::Message);
QCOMPARE(model->data(model->index(1), MessageEventModel::PlainText), QStringLiteral("This is an example\ntext message"));
QCOMPARE(model->data(model->index(1), MessageEventModel::EventIdRole), QStringLiteral("$153456789:example.org"));
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
QCOMPARE(model->data(model->index(-1)), QVariant());
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
QCOMPARE(model->data(model->index(model->rowCount())), QVariant());
}
// Sync some events into the MessageEventModel's current room and don't crash.
void MessageEventModelTest::syncNewEvents()
{
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
QSignalSpy spy(room, SIGNAL(aboutToAddNewMessages(Quotient::RoomEventsRange)));
model->setRoom(room);
QCOMPARE(model->rowCount(), 0);
room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
QCOMPARE(model->rowCount(), 2);
QCOMPARE(spy.count(), 1);
}
// Check the adding of pending events to the room doesn't cause any issues in the model.
void MessageEventModelTest::pendingEvent()
{
QSignalSpy spyInsert(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex, const QModelIndex, const QList<int> &)));
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
model->setRoom(room);
QCOMPARE(model->rowCount(), 0);
auto txnId = room->postPlainText("New plain message"_ls);
QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 1);
room->discardMessage(txnId);
QCOMPARE(model->rowCount(), 0);
QCOMPARE(spyRemove.count(), 1);
txnId = room->postPlainText("New plain message"_ls);
QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 2);
// We need to manually set the transaction ID of the new message as it will be
// different every time.
QFile testSyncFile;
testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-pending-sync.json"));
testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
auto root = testSyncJson.object();
auto timeline = root["timeline"_ls].toObject();
auto events = timeline["events"_ls].toArray();
auto firstEvent = events[0].toObject();
firstEvent.insert(QLatin1String("unsigned"), QJsonObject{{QLatin1String("transaction_id"), txnId}});
events[0] = firstEvent;
timeline.insert("events"_ls, events);
root.insert("timeline"_ls, timeline);
testSyncJson.setObject(root);
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testSyncJson.object());
room->update(std::move(roomData));
QCOMPARE(model->rowCount(), 1);
// The model will throw multiple data changed signals we need the one that refreshes
// the IsPendingRole.
QCOMPARE(spyChanged.count() > 0, true);
auto isPendingChanged = false;
for (auto signal : spyChanged) {
auto roles = signal.at(2).toList();
if (roles.contains(MessageEventModel::IsPendingRole)) {
isPendingChanged = true;
}
}
QCOMPARE(isPendingChanged, true);
}
// Make sure that the signals are disconnecting correctly when a room is switched.
void MessageEventModelTest::disconnect()
{
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
model->setRoom(room);
QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
model->setRoom(nullptr);
room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
QCOMPARE(spy.count(), 0);
}
void MessageEventModelTest::idToRow()
{
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
model->setRoom(room);
QCOMPARE(model->eventIdToRow(QStringLiteral("$153456789:example.org")), 0);
}
void MessageEventModelTest::cleanup()
{
delete model;
model = nullptr;
QCOMPARE(model, nullptr);
}
QTEST_MAIN(MessageEventModelTest)
#include "messageeventmodeltest.moc"

View File

@@ -5,55 +5,30 @@
#include <QSignalSpy>
#include <QTest>
#include "neochatroom.h"
#include <Quotient/connection.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "testutils.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class NeoChatRoomTest : public QObject {
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void subtitleTextTest();
void eventTest();
};
void NeoChatRoomTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
room->update(std::move(roomData));
}
void NeoChatRoomTest::subtitleTextTest()
{
QCOMPARE(room->timelineSize(), 1);
QCOMPARE(room->lastEventToString(), QStringLiteral("@example:example.org: This is an example\ntext message"));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-min-sync.json"_ls);
}
void NeoChatRoomTest::eventTest()

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <Quotient/connection.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "events/pollevent.h"
#include "pollhandler.h"
#include "testutils.h"
using namespace Quotient;
class PollHandlerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void nullObject();
void poll();
};
void PollHandlerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-pollhandlerstart-sync.json"_ls);
}
// Basically don't crash.
void PollHandlerTest::nullObject()
{
auto pollHandler = PollHandler();
QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), QString());
QCOMPARE(pollHandler.options(), QJsonArray());
QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(pollHandler.kind(), QString());
}
void PollHandlerTest::poll()
{
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
auto pollHandler = PollHandler(room, startEvent);
auto options = QJsonArray{QJsonObject{{"id"_ls, "option1"_ls}, {"org.matrix.msc1767.text"_ls, "option1"_ls}},
QJsonObject{{"id"_ls, "option2"_ls}, {"org.matrix.msc1767.text"_ls, "option2"_ls}}};
QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), QStringLiteral("test"));
QCOMPARE(pollHandler.options(), options);
QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(pollHandler.kind(), QStringLiteral("org.matrix.msc3381.poll.disclosed"));
}
QTEST_GUILESS_MAIN(PollHandlerTest)
#include "pollhandlertest.moc"

View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include "models/reactionmodel.h"
#include <Quotient/events/roommessageevent.h>
#include "testutils.h"
using namespace Quotient;
class ReactionModelTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void nullModel();
void basicReaction();
void newReaction();
};
void ReactionModelTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-reactionmodel-sync.json"));
}
void ReactionModelTest::nullModel()
{
auto model = ReactionModel(nullptr, nullptr);
QCOMPARE(model.rowCount(), 0);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
}
void ReactionModelTest::basicReaction()
{
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = ReactionModel(event, room);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>"));
QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍"));
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
auto authorList = QVariantList{room->getUser(room->user(QStringLiteral("@alice:matrix.org")))};
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
}
void ReactionModelTest::newReaction()
{
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = new ReactionModel(event, room);
QCOMPARE(model->rowCount(), 1);
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
QSignalSpy spy(model, SIGNAL(modelReset()));
room->syncNewEvents(QLatin1String("test-reactionmodel-extra-sync.json"));
QCOMPARE(model->rowCount(), 2);
QCOMPARE(spy.count(), 2); // Once for each of the 2 new reactions.
QCOMPARE(model->data(model->index(1), ReactionModel::ReactionRole), QStringLiteral("😆"));
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
QStringLiteral("@alice:matrix.org and @bob:example.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
delete model;
}
QTEST_MAIN(ReactionModelTest)
#include "reactionmodeltest.moc"

55
autotests/testutils.h Normal file
View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <Quotient/events/event.h>
#include <Quotient/syncdata.h>
#include "neochatroom.h"
namespace Quotient
{
class Connection;
}
namespace TestUtils
{
class TestRoom : public NeoChatRoom
{
public:
TestRoom(Quotient::Connection *connection, const QString &roomName, const QString &syncFileName = {})
: NeoChatRoom(connection, roomName, Quotient::JoinState::Join)
{
syncNewEvents(syncFileName);
}
void update(Quotient::SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
void syncNewEvents(const QString &syncFileName)
{
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
update(std::move(roomData));
}
}
};
template<Quotient::EventClass EventT>
inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFileName)
{
if (!eventFileName.isEmpty()) {
QFile testEventFile;
testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson);
}
return nullptr;
}
}

View File

@@ -10,28 +10,21 @@
#include <Quotient/syncdata.h>
#include <qnamespace.h>
#include "models/customemojimodel.h"
#include "neochatconnection.h"
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class TextHandlerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
@@ -48,6 +41,9 @@ private Q_SLOTS:
void sendBadLinks();
void sendEscapeCode();
void sendCodeClass();
void sendCustomEmoji();
void sendCustomEmojiCode_data();
void sendCustomEmojiCode();
void receiveStripReply();
void receivePlainTextIn();
@@ -68,24 +64,20 @@ private Q_SLOTS:
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
};
void TextHandlerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
connection->setAccountData("im.ponies.user_emotes"_ls,
QJsonObject{{"images"_ls,
QJsonObject{{"test"_ls,
QJsonObject{{"body"_ls, "Test custom emoji"_ls},
{"url"_ls, "mxc://example.org/test"_ls},
{"usage"_ls, QJsonArray{"emoticon"_ls}}}}}}});
CustomEmojiModel::instance().setConnection(static_cast<NeoChatConnection *>(connection));
QFile testTextHandlerSyncFile;
testTextHandlerSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-texthandler-sync.json"));
testTextHandlerSyncFile.open(QIODevice::ReadOnly);
const auto testTextHandlerSyncJson = QJsonDocument::fromJson(testTextHandlerSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testTextHandlerSyncJson.object());
room->update(std::move(roomData));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-texthandler-sync.json"));
}
void TextHandlerTest::allowedAttributes()
@@ -234,6 +226,39 @@ void TextHandlerTest::sendCodeClass()
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::sendCustomEmoji()
{
const QString testInputString = QStringLiteral(":test:");
const QString testOutputString = QStringLiteral(
"<p><img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" /></p>");
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::sendCustomEmojiCode_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<p><code>:test:</code></p>");
QTest::newRow("block") << QStringLiteral("```\n:test:\n```") << QStringLiteral("<pre><code>:test:\n</code></pre>");
}
// Custom emojis in code blocks should be left alone.
void TextHandlerTest::sendCustomEmojiCode()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::receiveStripReply()
{
const QString testInputString = QStringLiteral(
@@ -493,53 +518,6 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
}
void TextHandlerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
<< QList<QUrl>({
QUrl("https://kde.org"_ls),
QUrl("www.example.org"_ls),
});
}
void TextHandlerTest::linkPreviewsMatch()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
}
void TextHandlerTest::linkPreviewsReject()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("<code>https://kde.org</code>");

View File

@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QQmlApplicationEngine>
#include <QTest>
#include <QWindow>
#include <KConfig>
#include <KSharedConfig>
#include <KWindowConfig>
#include "windowcontroller.h"
class WindowControllerTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void nullWindow();
void geometry();
void showAndRaise();
void toggle();
void cleanup();
};
// Basically don't crash when no window is set.
void WindowControllerTest::nullWindow()
{
auto &instance = WindowController::instance();
QCOMPARE(instance.window(), nullptr);
instance.restoreGeometry();
instance.saveGeometry();
instance.showAndRaiseWindow({});
instance.toggleWindow();
}
void WindowControllerTest::geometry()
{
auto &instance = WindowController::instance();
QWindow window;
window.setGeometry(0, 0, 200, 200);
instance.setWindow(&window);
QCOMPARE(instance.window(), &window);
instance.saveGeometry();
const auto stateConfig = KSharedConfig::openStateConfig();
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
window.setGeometry(0, 0, 400, 400);
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
instance.restoreGeometry();
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
}
void WindowControllerTest::showAndRaise()
{
auto &instance = WindowController::instance();
QWindow window;
instance.setWindow(&window);
QCOMPARE(window.isVisible(), false);
instance.showAndRaiseWindow({});
QCOMPARE(window.isVisible(), true);
}
void WindowControllerTest::cleanup()
{
auto &instance = WindowController::instance();
instance.setWindow(nullptr);
QCOMPARE(instance.window(), nullptr);
}
void WindowControllerTest::toggle()
{
auto &instance = WindowController::instance();
QWindow window;
instance.setWindow(&window);
QCOMPARE(window.isVisible(), false);
instance.toggleWindow();
QCOMPARE(window.isVisible(), true);
instance.toggleWindow();
QCOMPARE(window.isVisible(), false);
// A window is classed as visible by qt when minimized but to the user this is not visible.
// So in this case we expect to show it even though visibility is technically true.
window.setVisibility(QWindow::Minimized);
QCOMPARE(window.windowState(), Qt::WindowMinimized);
QCOMPARE(window.isVisible(), true);
instance.toggleWindow();
QCOMPARE(window.windowState(), Qt::WindowNoState);
QCOMPARE(window.isVisible(), true);
instance.toggleWindow();
QCOMPARE(window.windowState(), Qt::WindowNoState);
QCOMPARE(window.isVisible(), false);
}
QTEST_MAIN(WindowControllerTest)
#include "windowcontrollertest.moc"

View File

@@ -47,17 +47,20 @@
<name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name>
<name xml:lang="zh-TW">NeoChat</name>
<summary>Chat with your friends on matrix</summary>
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary>
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
<summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
<summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
@@ -67,9 +70,11 @@
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="tr">Matrixte arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
<summary xml:lang="zh-CN">在 Matrix 上与朋友聊天</summary>
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
<description>
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
to provide a convergent experience across multiple platforms.</p>
@@ -83,6 +88,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxaría instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
<p xml:lang="hu">A NeoChat egy kliens a Matrixhoz, az azonnali üzenetküldés decentralizált komunikációs protokolljához. Szöveges üzeneteket, videókat és hangfájlokat küldhet családjának, kollégáinak és barátainak. A KDE keretrendszert használja, a Kirigaminak köszönhetően konvergens élményt nyújt több platformon is.</p>
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
@@ -96,6 +102,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="tr">NeoChat, anlık iletileşme için merkezi olmayan iletişim protokolü olan Matrix için bir istemcidir. Ailenize, iş arkadaşlarınıza ve arkadaşlarınıza metin iletiler, videolar ve ses dosyaları göndermenize olanak tanır. Birden çok platformda yakınsak bir deneyim sağlamak için KDE Frameworks ve en önemlilerinden Kirigami'yi kullanır.</p>
<p xml:lang="uk">NeoChat — клієнт Matrix, децентралізованого протоколу спілкування для миттєвого обміну повідомленнями. За його допомогою ви можете надсилати текстові повідомлення, відео та звукові файли вашій родин, колегами та друзям. У програмі використано бібліотеки KDE, зокрема Kirigami, для надання однорідного середовища на декількох програмних та апаратних платформах.</p>
<p xml:lang="x-test">xxNeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.xx</p>
<p xml:lang="zh-TW">NeoChat 是去中心化即時通訊協定 Matrix 的一個用戶端。它讓您可以傳送文字訊息、影片、音訊檔案給您的家人、同事或朋友。NeoChat 使用 KDE frameworks尤其是 Kirigami來提供跨平台的響應式體驗。</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
@@ -107,6 +114,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
@@ -117,9 +125,10 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
@@ -131,6 +140,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
<p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p>
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
@@ -145,6 +155,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
<ul>
<li>Polls - MSC3381</li>
<li xml:lang="ar">التصويت - MSC3381</li>
@@ -157,6 +168,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Kyselyt MSC3381</li>
<li xml:lang="fr">Sondages - MSC3381</li>
<li xml:lang="gl">Enquisas — MSC3381</li>
<li xml:lang="hu">Szavazások - MSC3381</li>
<li xml:lang="ia">Inquestas - MSC3381</li>
<li xml:lang="it">Sondaggi - MSC3381</li>
<li xml:lang="ka">Polls - MSC3381</li>
@@ -168,9 +180,10 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Polls - MSC3381</li>
<li xml:lang="sv">Polls - MSC3381</li>
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler - MSC3381</li>
<li xml:lang="tr">Anketler MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
@@ -182,6 +195,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Tarrapakkaukset MSC2545</li>
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
<li xml:lang="hu">Matricacsomagok - MSC2545</li>
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
@@ -193,9 +207,10 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
<li xml:lang="tr">Yapışkan Paketleri MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
<li>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
@@ -207,6 +222,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Sijaintitapahtumat MSC3488</li>
<li xml:lang="fr">Événements de lieu - MSC3488</li>
<li xml:lang="gl">Localización de eventos — MSC3488</li>
<li xml:lang="hu">Események helyadatai - MSC3488</li>
<li xml:lang="ia">Eventos de Location - MSC3488</li>
<li xml:lang="it">Località eventi - MSC3488</li>
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
@@ -218,13 +234,14 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Location Events - MSC3488</li>
<li xml:lang="sv">Location Events - MSC3488</li>
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul>
</description>
<url type="homepage">https://apps.kde.org/neochat/</url>
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&amp;product=NeoChat</url>
<url type="homepage">https://apps.kde.org/neochat</url>
<url type="bugtracker">https://bugs.kde.org/enter_bug.cgi?product=NeoChat</url>
<categories>
<category>Network</category>
</categories>
@@ -265,6 +282,7 @@ to provide a convergent experience across multiple platforms.</p>
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
<developer_name xml:lang="zh-TW">KDE 社群</developer_name>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license>
<custom>
@@ -296,6 +314,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
@@ -310,6 +329,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot environment="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
@@ -317,12 +337,14 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="ar">شاشة الدخول</caption>
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
<caption xml:lang="cs">Přihlašovací obrazovka</caption>
<caption xml:lang="eo">Ensaluta ekrano</caption>
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
<caption xml:lang="fr">Écran de connexion</caption>
<caption xml:lang="gl">Pantalla de identificación.</caption>
<caption xml:lang="hu">Bejelentkező képernyő</caption>
<caption xml:lang="ia">Schermo de accesso</caption>
<caption xml:lang="it">Schermata di accesso</caption>
<caption xml:lang="ka">შესვლის ეკრანი</caption>
@@ -337,12 +359,34 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/>
<release version="24.02.0" date="2024-02-28">
<url>https://kde.org/announcements/megarelease/6/#neochat</url>
<description>
<p>In the newest version, when launching the app, you will get a welcome page that lets you choose which account you want to use and lets you log in to other accounts. The welcome screen will also warn you when NeoChat cannot load an account.</p>
<p>NeoChat will also let you register a new account directly from the app itself. Deactivating your Matrix account is also possible from within NeoChat.</p>
<p>Spaces are a relatively new feature of Matrix that let you group chat channels together. This is used to improve the discoverability of rooms, manage large communities, or just tidy all the channels you are in. You can now do all this without leaving NeoChat.</p>
<p>NeoChat won't let you miss any new notifications anymore. We added a new page that includes all your recent notifications and when NeoChat is closed, you will still be able to receive push notifications. The main timeline will let you know when more messages are loading and when you reach the end of it.</p>
<p>More NeoChat Goodies</p>
<ul>
<li>QR Codes to share contacts</li>
<li>Improved room upgrades</li>
<li>Added button to reject invitation and ignore user</li>
<li>Display device security details</li>
<li>Added room security settings</li>
</ul>
</description>
</release>
<release version="23.08.5" date="2024-02-15"/>
<release version="23.08.4" date="2023-12-07"/>
<release version="23.08.3" date="2023-11-09"/>
<release version="23.08.2" date="2023-10-12"/>
<release version="23.08.0" date="2023-08-24">

View File

@@ -42,6 +42,7 @@ Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
GenericName=Matrix Client
GenericName[ar]=عميل ماتركس
GenericName[az]=Matrix Müştərisi
@@ -81,6 +82,7 @@ GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端
Comment=Client for the Matrix protocol
Comment[ar]=عميل لميفاق ماتركس
Comment[az]=Matrix protokolu üçün müştəri
@@ -119,6 +121,7 @@ Comment[tr]=Matrix protokolü için istemci
Comment[uk]=Клієнт протоколу Matrix
Comment[x-test]=xxClient for the Matrix protocolxx
Comment[zh_CN]=为 Matrix 协议打造的客户端
Comment[zh_TW]=Matrix 通訊協定的用戶端
MimeType=x-scheme-handler/matrix;
Exec=neochat %u
Terminal=false

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -139,6 +139,11 @@ add_library(neochat STATIC
chatbarcache.h
colorschemer.cpp
colorschemer.h
models/notificationsmodel.cpp
models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
enums/pushrule.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -173,7 +178,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBox.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
@@ -290,11 +294,19 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/Security.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/LoadingDelegate.qml
qml/TimelineEndDelegate.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
ecm_qt_declare_logging_category(neochat
HEADER "messageeventmodel_logging.h"
IDENTIFIER "MessageEvent"
@@ -353,6 +365,10 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
@@ -365,7 +381,7 @@ target_link_libraries(neochat PUBLIC
Qt::Network
Qt::QuickControls2
KF6::I18n
KF6::Kirigami2
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
@@ -376,6 +392,7 @@ target_link_libraries(neochat PUBLIC
QuotientQt6
cmark::cmark
QCoro::Core
QCoro::Network
)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
@@ -463,8 +480,22 @@ if(ANDROID)
"computer-symbolic"
"gps"
"system-users-symbolic"
"map-flat"
"documentinfo"
"view-list-details"
"go-previous"
"mail-forward-symbolic"
"dialog-warning-symbolic"
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR/android})
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
@@ -474,7 +505,7 @@ if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons)
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
@@ -483,9 +514,18 @@ if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()

View File

@@ -12,9 +12,9 @@
#include <QStringBuilder>
#include "models/actionsmodel.h"
#include "models/customemojimodel.h"
#include "neochatconfig.h"
#include "texthandler.h"
#include "utils.h"
using namespace Quotient;
@@ -40,7 +40,8 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
{
if (!chatBarCache) {
if (!m_room || !chatBarCache) {
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
return;
}
@@ -61,10 +62,6 @@ void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
{
if (!m_room) {
return QString();
}
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
@@ -135,7 +132,6 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
}
}
handledText = CustomEmojiModel::instance().preprocessText(handledText);
TextHandler textHandler;
textHandler.setData(handledText);
handledText = textHandler.handleSendText();
@@ -149,6 +145,26 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
return;
}
// We want to add back the <mx-reply> if it's in the original message but not in the edit, to preserve the reply.
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
QString originalString;
if (event->content()) {
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
} else {
originalString = event->plainBody();
}
const QRegularExpression exp(TextRegex::removeRichReply);
const auto match = exp.match(originalString);
if (match.hasCaptured(0) && !handledText.contains(TextRegex::removeRichReply)) {
handledText.prepend(match.captured(0));
}
}
}
}
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
}

View File

@@ -177,7 +177,7 @@ uint8_t *decode(const char *blurhash, int width, int height, int punch, int nCha
uint8_t *pixelArray = createByteArray(bytesPerRow * height);
if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) {
return NULL;
return nullptr;
}
return pixelArray;
}

View File

@@ -3,6 +3,7 @@
#include "chatbarcache.h"
#include "chatdocumenthandler.h"
#include "eventhandler.h"
#include "neochatroom.h"
@@ -118,7 +119,7 @@ QString ChatBarCache::relationMessage() const
eventhandler.setRoom(room);
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
return eventhandler.getMarkdownBody();
}
return {};
}
@@ -164,6 +165,54 @@ QList<Mention> *ChatBarCache::mentions()
return &m_mentions;
}
void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler)
{
documentHandler->setDocument(document);
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (m_relationId.isEmpty()) {
return;
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(QStringLiteral(R"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"));
m_mentions.clear();
int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
while (matches.hasNext()) {
const QRegularExpressionMatch match = matches.next();
if (match.hasMatch()) {
const QString id = match.captured(1);
const QString name = match.captured(2);
const int position = match.capturedStart(0) - linkSize;
const int end = position + name.length();
linkSize += match.capturedLength(0) - name.length();
QTextCursor cursor(documentHandler->document()->textDocument());
cursor.setPosition(position);
cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
}
}
}
}
}
QString ChatBarCache::savedText() const
{
return m_savedText;

View File

@@ -5,8 +5,11 @@
#include <QObject>
#include <QQmlEngine>
#include <QQuickTextDocument>
#include <QTextCursor>
class ChatDocumentHandler;
/**
* @brief Defines a user mention in the current chat or edit text.
*/
@@ -174,6 +177,11 @@ public:
*/
QList<Mention> *mentions();
/**
* @brief Update the mentions in @p document when editing a message.
*/
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler);
/**
* @brief Get the saved chat bar text.
*/

View File

@@ -132,11 +132,7 @@ int ChatDocumentHandler::completionStartIndex() const
return 0;
}
#if !defined(Q_OS_ANDROID)
const long long cursor = cursorPosition();
#else
const auto cursor = cursorPosition();
#endif
const qsizetype cursor = cursorPosition();
const auto &text = getText();
auto start = std::min(cursor, text.size()) - 1;

View File

@@ -158,7 +158,6 @@ private:
QPointer<NeoChatRoom> m_room;
QPointer<ChatBarCache> m_chatBarCache;
bool completionVisible = false;
QColor m_mentionColor;
QColor m_errorColor;
@@ -172,7 +171,5 @@ private:
SyntaxHighlighter *m_highlighter = nullptr;
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
CompletionModel *m_completionModel = nullptr;
};

View File

@@ -7,10 +7,7 @@
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <KWindowConfig>
#include <QFile>
#include <QFileInfo>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
@@ -28,13 +25,11 @@
#include <Quotient/eventstats.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/user.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "roommanager.h"
#include "windowcontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -42,6 +37,8 @@
#include "trayicon_sni.h"
#endif
bool testMode = false;
using namespace Quotient;
Controller::Controller(QObject *parent)
@@ -56,9 +53,18 @@ Controller::Controller(QObject *parent)
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
if (!testMode) {
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
} else {
auto c = new NeoChatConnection(this);
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
connect(c, &Connection::connected, this, [c, this]() {
m_accountRegistry.add(c);
c->syncLoop();
});
}
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
@@ -66,8 +72,7 @@ Controller::Controller(QObject *parent)
});
#ifndef Q_OS_WINDOWS
// Setup Unix signal handlers
const auto unixExitHandler = [](int /*sig*/) -> void {
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
@@ -96,9 +101,27 @@ Controller::Controller(QObject *parent)
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
NotificationsManager::instance().handleNotifications(connection);
});
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
connection->setupPushNotifications(m_endpoint);
});
}
oldAccountCount = m_accountRegistry.size();
});
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
m_endpoint = endpoint;
for (auto &quotientConnection : m_accountRegistry) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
});
connector->registerClient(i18n("Receiving push notifications"));
m_endpoint = connector->endpoint();
#endif
}
Controller &Controller::instance()
@@ -107,23 +130,6 @@ Controller &Controller::instance()
return _instance;
}
void Controller::toggleWindow()
{
auto &instance = WindowController::instance();
auto window = instance.window();
if (window->isVisible()) {
if (window->windowStates() & Qt::WindowMinimized) {
window->showNormal();
window->requestActivate();
} else {
window->close();
}
} else {
instance.showAndRaiseWindow({});
instance.window()->requestActivate();
}
}
void Controller::addConnection(NeoChatConnection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
@@ -132,9 +138,7 @@ void Controller::addConnection(NeoChatConnection *c)
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
Q_EMIT syncDone();
connect(c, &NeoChatConnection::syncDone, this, [c] {
c->sync(30000);
c->saveState();
});
@@ -142,12 +146,6 @@ void Controller::addConnection(NeoChatConnection *c)
dropConnection(c);
});
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
c->sync();
Q_EMIT connectionAdded(c);
@@ -164,18 +162,13 @@ void Controller::dropConnection(NeoChatConnection *c)
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
}
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
@@ -185,41 +178,21 @@ void Controller::invokeLogin()
}
auto connection = new NeoChatConnection(account.homeserver());
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
m_connectionsLoading[accountId] = connection;
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
connection->loadState();
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
m_connectionsLoading.remove(accountId);
Q_EMIT accountsLoadingChanged();
if (connection->userId() == id) {
setActiveConnection(connection);
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
}
});
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
connection->logout(false);
} else if (error == "Connection closed"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
connection->logout(true);
}
Q_EMIT initiated();
});
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error));
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
});
connection->assumeIdentity(account.userId(), accessToken);
});
}
}
if (accounts.isEmpty()) {
Q_EMIT initiated();
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
@@ -236,17 +209,17 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
switch (job->error()) {
case QKeychain::EntryNotFound:
Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
Q_EMIT errorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
break;
case QKeychain::AccessDeniedByUser:
case QKeychain::AccessDenied:
Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
Q_EMIT errorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
break;
case QKeychain::NoBackendAvailable:
Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
Q_EMIT errorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
break;
case QKeychain::OtherError:
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), job->errorString());
Q_EMIT errorOccured(i18n("Unable to read access token"), job->errorString());
break;
default:
break;
@@ -292,17 +265,12 @@ void Controller::setQuitOnLastWindowClosed()
if (NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
connect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
} else {
if (m_trayIcon) {
disconnect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
delete m_trayIcon;
m_trayIcon = nullptr;
}
}
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
#else
return;
#endif
}
@@ -325,61 +293,43 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
}
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &NeoChatConnection::networkError, this, [this]() {
if (!m_isOnline) {
return;
}
m_isOnline = false;
Q_EMIT isOnlineChanged(false);
});
connect(connection, &NeoChatConnection::syncDone, this, [this] {
if (m_isOnline) {
return;
}
m_isOnline = true;
Q_EMIT isOnlineChanged(true);
});
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
} else {
NeoChatConfig::self()->setActiveConnection(QString());
}
NeoChatConfig::self()->save();
Q_EMIT activeConnectionChanged();
}
void Controller::saveWindowGeometry()
{
WindowController::instance().saveGeometry();
}
bool Controller::isOnline() const
{
return m_isOnline;
}
// TODO: Remove in favor of RoomManager::joinRoom
void Controller::joinRoom(const QString &alias)
{
if (!alias.contains(":"_ls)) {
Q_EMIT errorOccured(i18n("The room id you are trying to join is not valid"));
return;
}
const auto knownServer = alias.mid(alias.indexOf(":"_ls) + 1);
RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281
connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
auto timer = new QTimer();
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
NotificationsManager::instance().postPushNotification(data);
timer->stop();
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
// Otherwise, messageReceived is never activated, and this daemon could stick around forever.
timer->start(5000);
connector->registerClient(i18n("Receiving push notifications"));
#endif
}
void Controller::setApplicationProxy()
{
NeoChatConfig *cfg = NeoChatConfig::self();
@@ -425,3 +375,18 @@ AccountRegistry &Controller::accounts()
}
#include "moc_controller.cpp"
void Controller::setTestMode(bool test)
{
testMode = test;
}
void Controller::removeConnection(const QString &userId)
{
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
SettingsGroup("Accounts"_ls).remove(userId);
}
}

View File

@@ -9,19 +9,15 @@
#include "neochatconnection.h"
#include <Quotient/accountregistry.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/settings.h>
class NeoChatRoom;
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
class TrayIcon;
class QQuickTextDocument;
namespace Quotient
{
class Room;
class User;
}
namespace QKeychain
{
class ReadPasswordJob;
@@ -51,11 +47,6 @@ class Controller : public QObject
*/
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
/**
* @brief Whether NeoChat is currently able to connect to the server.
*/
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
/**
* @brief Whether NeoChat is running as a flatpak.
*
@@ -66,16 +57,6 @@ class Controller : public QObject
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
public:
/**
* @brief Defines the status after an attempt to change the password on an account.
*/
enum PasswordStatus {
Success, /**< The password was successfully changed. */
Wrong, /**< The current password entered was wrong. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(PasswordStatus)
static Controller &instance();
static Controller *create(QQmlEngine *engine, QJSEngine *)
{
@@ -101,15 +82,8 @@ public:
*/
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
/**
* @brief Join a room.
*/
Q_INVOKABLE void joinRoom(const QString &alias);
[[nodiscard]] bool supportSystemTray() const;
bool isOnline() const;
/**
* @brief Sets the QNetworkProxy for the application.
*
@@ -126,8 +100,18 @@ public:
*/
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
/**
* @brief Start listening for notifications in dbus-activated mode.
* These notifications will quit the application when closed.
*/
static void listenForNotifications();
Quotient::AccountRegistry &accounts();
static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId);
private:
explicit Controller(QObject *parent = nullptr);
@@ -138,35 +122,20 @@ private:
void loadSettings();
void saveSettings() const;
bool m_isOnline = true;
QMap<Quotient::Room *, int> m_notificationCounts;
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QMap<QString, QPointer<Quotient::Connection>> m_connectionsLoading;
QString m_endpoint;
private Q_SLOTS:
void invokeLogin();
void toggleWindow();
void setQuitOnLastWindowClosed();
Q_SIGNALS:
/// Error occurred because of user inputs
void errorOccured(const QString &error);
/// Error occurred because of server or bug in NeoChat
void globalErrorOccured(QString error, QString detail);
void syncDone();
void errorOccured(const QString &error, const QString &detail);
void connectionAdded(NeoChatConnection *connection);
void connectionDropped(NeoChatConnection *connection);
void initiated();
void quitOnLastWindowClosedChanged();
void unreadCountChanged();
void activeConnectionChanged();
void passwordStatus(Controller::PasswordStatus status);
void userConsentRequired(QUrl url);
void isOnlineChanged(bool isOnline);
void accountsLoadingChanged();
public Q_SLOTS:
void saveWindowGeometry();
};

View File

@@ -40,6 +40,8 @@ public:
Poll, /**< The initial event for a poll. */
Location, /**< A location event. */
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Loading, /**< A delegate to tell the user more messages are being loaded. */
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
Other, /**< Anything that cannot be classified as another type. */
};
Q_ENUM(Type);

186
src/enums/pushrule.h Normal file
View File

@@ -0,0 +1,186 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
/**
* @class PushRuleKind
*
* A class with the Kind enum for push notifications and helper functions.
*
* The kind relates to the kinds of push rule defined in the matrix spec, see
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
*/
class PushRuleKind : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Defines the different kinds of push rule.
*/
enum Kind {
Override = 0, /**< The highest priority rules. */
Content, /**< These configure behaviour for messages that match certain patterns. */
Room, /**< These rules change the behaviour of all messages for a given room. */
Sender, /**< These rules configure notification behaviour for messages from a specific Matrix user ID. */
Underride, /**< These are identical to override rules, but have a lower priority than content, room and sender rules. */
};
Q_ENUM(Kind)
/**
* @brief Translate the Kind enum value to a human readable string.
*
* @sa Kind
*/
static QString kindString(Kind kind)
{
switch (kind) {
case Kind::Override:
return QLatin1String("override");
case Kind::Content:
return QLatin1String("content");
case Kind::Room:
return QLatin1String("room");
case Kind::Sender:
return QLatin1String("sender");
case Kind::Underride:
return QLatin1String("underride");
default:
return {};
}
};
};
/**
* @class PushRuleAction
*
* A class with the Action enum for push notifications.
*
* The action relates to the actions of push rule defined in the matrix spec, see
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
*/
class PushRuleAction : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Defines the global push notification actions.
*/
enum Action {
Unknown = 0, /**< The action has not yet been obtained from the server. */
Off, /**< No push notifications are to be sent. */
On, /**< Push notifications are on. */
Noisy, /**< Push notifications are on, also trigger a notification sound. */
Highlight, /**< Push notifications are on, also the event should be highlighted in chat. */
NoisyHighlight, /**< Push notifications are on, also trigger a notification sound and highlight in chat. */
};
Q_ENUM(Action)
};
/**
* @class PushNotificationState
*
* A class with the State enum for room push notification state.
*
* The state define whether the room adheres to the global push rule states for the
* account or is overridden for a room.
*
* @note This is different to the PushRuleAction which defines the type of notification
* for an individual rule.
*
* @sa PushRuleAction
*/
class PushNotificationState : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Describes the push notification state for the room.
*/
enum State {
Unknown, /**< The state has not yet been obtained from the server. */
Default, /**< The room follows the globally configured rules for the local user. */
Mute, /**< No notifications for messages in the room. */
MentionKeyword, /**< Notifications only for local user mentions and keywords. */
All, /**< Notifications for all messages. */
};
Q_ENUM(State)
};
/**
* @class PushRuleSection
*
* A class with the Section enum for push notifications and helper functions.
*
* @note This is different from the PushRuleKind and instead is used for sorting
* in the settings page which is not necessarily by Kind.
*
* @sa PushRuleKind
*/
class PushRuleSection : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Defines the sections to sort push rules into.
*/
enum Section {
Master = 0, /**< The master push rule */
Room, /**< Push rules relating to all rooms. */
Mentions, /**< Push rules relating to user mentions. */
Keywords, /**< Global Keyword push rules. */
RoomKeywords, /**< Keyword push rules that only apply to a specific room. */
Invites, /**< Push rules relating to invites. */
Unknown, /**< New default push rules that have not been added to the model yet. */
/**
* @brief Push rules that should never be shown.
*
* There are numerous rules that get set that shouldn't be shown in the general
* list e.g. The array of rules used to override global settings in individual
* rooms.
*
* This is specifically different to unknown which are just new default push
* rule that haven't been added to the model yet.
*/
Undefined,
};
Q_ENUM(Section)
/**
* @brief Translate the Section enum value to a human readable string.
*
* @sa Section
*/
static QString sectionString(Section section)
{
switch (section) {
case Section::Master:
return QLatin1String("Master");
case Section::Room:
return QLatin1String("Room Notifications");
case Section::Mentions:
return QLatin1String("@Mentions");
case Section::Keywords:
return QLatin1String("Keywords");
case Section::Invites:
return QLatin1String("Invites");
default:
return {};
}
};
};

View File

@@ -169,6 +169,28 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const
}
}
QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthorDisplayName called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthorDisplayName called with m_event set to nullptr.";
return {};
}
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
auto displayName = m_room->safeMemberName(author->id());
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />"), QStringLiteral(" "));
displayName.replace(u'\n', QStringLiteral(" "));
displayName.replace(u'\u2028', QStringLiteral(" "));
return displayName;
}
QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
{
if (m_event == nullptr) {
@@ -278,6 +300,27 @@ bool EventHandler::isHidden()
return false;
}
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
{
if (event.hasFileContent()) {
auto fileCaption = event.content()->fileInfo()->originalName;
if (fileCaption.isEmpty()) {
fileCaption = event.plainBody();
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
fileCaption = event.plainBody() + " | "_ls + fileCaption;
}
return fileCaption;
}
QString body;
if (event.hasTextContent() && event.content()) {
body = static_cast<const MessageEventContent::TextContent *>(event.content())->body;
} else {
body = event.plainBody();
}
return body;
}
QString EventHandler::getRichBody(bool stripNewlines) const
{
if (m_event == nullptr) {
@@ -296,6 +339,22 @@ QString EventHandler::getPlainBody(bool stripNewlines) const
return getBody(m_event, Qt::PlainText, stripNewlines);
}
QString EventHandler::getMarkdownBody() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr.";
return {};
}
if (!m_event->is<RoomMessageEvent>()) {
qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent.";
return {};
}
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
return roomMessageEvent->plainBody();
}
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
{
if (event->isRedacted()) {
@@ -333,7 +392,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.repeatsState()) {
auto text = i18n("reinvited %1 to the room", subjectName);
if (!e.reason().isEmpty()) {
text += i18nc("Optional reason for an invitation", ": %1") + e.reason().toHtmlEscaped();
text += i18nc("Optional reason for an invitation", ": %1") + (prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
return text;
}
@@ -357,7 +416,9 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (!e.newDisplayName()) {
text = i18nc("their refers to a singular user", "cleared their display name");
} else {
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
text = i18nc("their refers to a singular user",
"changed their display name to %1",
prettyPrint ? e.newDisplayName()->toHtmlEscaped() : *e.newDisplayName());
}
}
if (e.isAvatarUpdate()) {
@@ -393,7 +454,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.reason().isEmpty()) {
return i18n("banned %1 from the room", subjectName);
} else {
return i18n("banned %1 from the room: %2", subjectName, e.reason().toHtmlEscaped());
return i18n("banned %1 from the room: %2", subjectName, prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
} else {
return i18n("self-banned from the room");
@@ -409,8 +470,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const RoomCanonicalAliasEvent &e) {
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
},
[](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
[prettyPrint](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", prettyPrint ? e.name().toHtmlEscaped() : e.name());
},
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
return (e.topic().isEmpty()) ? i18n("cleared the topic")
@@ -425,14 +486,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const EncryptionEvent &) {
return i18n("activated End-to-End Encryption");
},
[](const RoomCreateEvent &e) {
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
[prettyPrint](const RoomCreateEvent &e) {
return e.isUpgrade()
? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()))
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()));
},
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[](const StateEvent &e) {
[prettyPrint](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -449,7 +511,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
return e.contentJson()["description"_ls].toString();
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
},
[](const PollStartEvent &e) {
return e.question();
@@ -623,6 +685,15 @@ QString EventHandler::getGenericBody() const
i18n("Unknown event"));
}
QString EventHandler::subtitleText() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "subtitleText called with m_event set to nullptr.";
return {};
}
return singleLineAuthorDisplayname() + (m_event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": ")) + getPlainBody(true);
}
QVariantMap EventHandler::getMediaInfo() const
{
if (m_room == nullptr) {
@@ -663,12 +734,12 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
QVariantMap mediaInfo;
// Get the mxc URL for the media.
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
mediaInfo["source"_ls] = QUrl();
} else {
QUrl source = m_room->makeMediaUrl(eventId, fileInfo->url());
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
if (source.isValid()) {
mediaInfo["source"_ls] = source;
} else {
mediaInfo["source"_ls] = QUrl();
@@ -743,97 +814,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
return mediaInfo;
}
QSharedPointer<LinkPreviewer> EventHandler::getLinkPreviewer() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
return nullptr;
}
QString text;
auto event = eventCast<const RoomMessageEvent>(m_event);
if (event->hasTextContent()) {
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
TextHandler textHandler;
textHandler.setData(text);
QList<QUrl> links = textHandler.getLinkPreviews();
if (links.size() > 0) {
return QSharedPointer<LinkPreviewer>(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
} else {
return nullptr;
}
}
QSharedPointer<ReactionModel> EventHandler::getReactions() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReactions called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReactions called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
qCWarning(EventHandling) << "getReactions called with on a non-message event.";
return nullptr;
}
auto eventId = m_event->id();
const auto &annotations = m_room->relatedEvents(eventId, EventRelation::AnnotationType);
if (annotations.isEmpty()) {
return nullptr;
};
QMap<QString, QList<User *>> reactions = {};
for (const auto &a : annotations) {
if (a->isRedacted()) { // Just in case?
continue;
}
if (const auto &e = eventCast<const ReactionEvent>(a)) {
reactions[e->key()].append(m_room->user(e->senderId()));
}
}
if (reactions.isEmpty()) {
return nullptr;
}
QList<ReactionModel::Reaction> res;
auto i = reactions.constBegin();
while (i != reactions.constEnd()) {
QVariantList authors;
for (const auto &author : i.value()) {
authors.append(m_room->getUser(author));
}
res.append(ReactionModel::Reaction{i.key(), authors});
++i;
}
if (res.size() > 0) {
return QSharedPointer<ReactionModel>(new ReactionModel(nullptr, res, m_room->localUser()));
} else {
return nullptr;
}
}
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {

View File

@@ -111,6 +111,17 @@ public:
*/
QString getAuthorDisplayName(bool isPending = false) const;
/**
* @brief Get the display name of the event author but with any newlines removed.
*
* Turns out you can put newlines in your display name so we need to handle that
* primarily for the room list subtitle.
*
* @param isPending whether the event is pending as this cannot be derived from
* just the event object.
*/
QString singleLineAuthorDisplayname(bool isPending = false) const;
/**
* @brief Return a QDateTime object for the event timestamp.
*/
@@ -148,6 +159,14 @@ public:
*/
bool isHidden();
/**
* @brief Output a string for the room message content without any formatting.
*
* This is the content of the formatted_body key if present or the body key if
* not.
*/
static QString rawMessageBody(const Quotient::RoomMessageEvent &event);
/**
* @brief Output a string for the message content ready for display in a rich text field.
*
@@ -180,6 +199,13 @@ public:
*/
QString getPlainBody(bool stripNewlines = false) const;
/**
* @brief Output the original body for the message content, useful for editing the original message.
*
* The event type must be a room message event.
*/
QString getMarkdownBody() const;
/**
* @brief Output a generic string for the message content ready for display.
*
@@ -195,6 +221,14 @@ public:
*/
QString getGenericBody() const;
/**
* @brief Output a string for the event to be used as a RoomList subtitle.
*
* The output includes the username followed by the plain message, all with no
* line breaks.
*/
QString subtitleText() const;
/**
* @brief Return the media info for the event.
*
@@ -212,25 +246,6 @@ public:
*/
QVariantMap getMediaInfo() const;
/**
* @brief Return a LinkPreviewer object for the event.
*
* A nullptr will be returned for any event that doesn't have any links so the
* return should be null checked and an empty LinkPreviewer provided if null.
*
* @sa LinkPreviewer
*/
QSharedPointer<LinkPreviewer> getLinkPreviewer() const;
/**
* @brief Return a ReactionModel object for the event.
*
* A nullptr will be returned for any event that doesn't have any links so the
* return should be null checked and an empty QVariantList (or other suitable
* empty mode) provided if null.
*/
QSharedPointer<ReactionModel> getReactions() const;
/**
* @brief Whether the event is a reply to another in the timeline.
*/

36
src/fakerunner.cpp Normal file
View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "fakerunner.h"
#include <QCoreApplication>
#include <QDBusMetaType>
Q_SCRIPTABLE RemoteActions FakeRunner::Actions()
{
QCoreApplication::quit();
return {};
}
Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm)
{
QCoreApplication::quit();
return {};
}
Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId)
{
QCoreApplication::quit();
}
FakeRunner::FakeRunner()
: QObject()
{
qDBusRegisterMetaType<RemoteMatch>();
qDBusRegisterMetaType<RemoteMatches>();
qDBusRegisterMetaType<RemoteAction>();
qDBusRegisterMetaType<RemoteActions>();
qDBusRegisterMetaType<RemoteImage>();
}
#include "moc_fakerunner.cpp"

31
src/fakerunner.h Normal file
View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QDBusContext>
#include "runner.h"
/**
* This is a close-to-identical copy of the regular Runner interface,
* only used when activated for push notifications. This stubs it out so
* Plasma Search and Kickoff doesn't accidentally activate the push notification
* service.
*
* @sa Runner
*/
class FakeRunner : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1")
public:
Q_SCRIPTABLE RemoteActions Actions();
Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm);
Q_SCRIPTABLE void Run(const QString &id, const QString &actionId);
FakeRunner();
};

View File

@@ -3,25 +3,37 @@
#include "linkpreviewer.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "utils.h"
using namespace Quotient;
LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
: QObject(parent)
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
: QObject(nullptr)
, m_currentRoom(room)
, m_event(event)
, m_loaded(false)
, m_url(url)
, m_url(linkPreview(event))
{
loadUrlPreview();
if (m_currentRoom) {
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
if (m_event != nullptr && m_currentRoom != nullptr) {
loadUrlPreview();
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
// Make sure that we react to edits
connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
if (m_event->id() == newEvent->id()) {
m_event = eventCast<const Quotient::RoomMessageEvent>(newEvent);
m_url = linkPreview(m_event);
Q_EMIT urlChanged();
loadUrlPreview();
}
});
}
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
}
@@ -51,15 +63,6 @@ QUrl LinkPreviewer::url() const
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url != m_url) {
m_url = url;
urlChanged();
loadUrlPreview();
}
}
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
@@ -98,4 +101,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
if (event == nullptr) {
return {};
}
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
auto data = text.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,6 +7,11 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -25,7 +30,7 @@ class LinkPreviewer : public QObject
/**
* @brief The URL to get the preview for.
*/
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
/**
* @brief Whether the preview information has been loaded.
@@ -55,18 +60,25 @@ class LinkPreviewer : public QObject
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QUrl imageSource() const;
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given event has at least 1 pre-viewable link.
*
* A link is only pre-viewable if it is http, https or something starting with www.
*/
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
private:
const NeoChatRoom *m_currentRoom = nullptr;
const NeoChatRoom *m_currentRoom;
const Quotient::RoomMessageEvent *m_event;
bool m_loaded;
QString m_title = QString();
@@ -76,6 +88,14 @@ private:
void loadUrlPreview();
/**
* @brief Return the link to be previewed from the given event.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www. The first valid link is returned.
*/
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
Q_SIGNALS:
void loadedChanged();
void titleChanged();

View File

@@ -82,7 +82,7 @@ void LoginHelper::init()
m_connection = nullptr;
});
connect(m_connection, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
m_isLoggingIn = false;
Q_EMIT isLoggingInChanged();
});
@@ -97,12 +97,11 @@ void LoginHelper::init()
});
connect(m_connection, &Connection::resolveError, this, [](QString error) {
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
});
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
Q_EMIT loaded();
Q_EMIT Controller::instance().initiated();
});
}

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