Compare commits

...

85 Commits

Author SHA1 Message Date
Thiago Sueto
67b54cc112 Increase top, left and right margins in RoomInformation drawer 2024-12-04 17:04:55 -03:00
l10n daemon script
39046632aa GIT_SILENT Sync po/docbooks with svn 2024-11-19 01:30:50 +00:00
Carl Schwan
fbb2afdb49 Remove layout attached properties
They don't do anything
2024-11-18 12:32:00 +00:00
James Graham
aff0402f71 Make sure the loading text for a new login wraps
Title

BUG: 493869
2024-11-18 08:42:19 +00:00
James Graham
cee9058c77 Fix Sed Edits
Make sure that for multiple sed edits we grab the eventID of the original message not the replacement

BUG: 496313
2024-11-18 08:41:52 +00:00
l10n daemon script
3f922b4c90 GIT_SILENT Sync po/docbooks with svn 2024-11-18 01:34:10 +00:00
l10n daemon script
02d2d31cf3 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-11-18 01:25:11 +00:00
Thiago Sueto
240cf6a0ed Make height of chatbar, userinfo bar and side tab bar the same
|Current state|With this MR|
|-|-|
|![Screenshot_20241115_171736](/uploads/858a8ca21a6f4024a20f6ba32225aece/Screenshot_20241115_171736.png)|![Screenshot_20241115_171650](/uploads/115f99c7bb2b93a542c42647f9cc25c7/Screenshot_20241115_171650.png)|
2024-11-17 18:47:45 +00:00
Tobias Fella
dcd9ee93de Escape display name in WelcomePage 2024-11-17 19:26:04 +01:00
Joshua Goins
2a8cd74ab1 Fix undefined access when loading stickers in chat
We need a check here, because stickers (and really, any images without a
tempSource) will try to access an undefined object.

This fixes the error:
"qrc:/qt/qml/org/kde/neochat/timeline/ImageComponent.qml:106: TypeError: Cannot read property 'source' of undefined"
2024-11-16 21:30:59 +00:00
Joshua Goins
63bc7055c2 Default to a more sensible sticker size
If we do not set the width/height for stickers (which don't have any)
then the height is okay, but the message has the maximum width which
looks odd.

Instead, let's limit all stickers to 256px and it makes them look much
nicer in chat.
2024-11-16 16:12:56 -05:00
Joshua Goins
1cca9733d6 Add a comment that these are not normal quotation marks
It can be hard to tell depending on which font you're viewing the code
with.
2024-11-16 16:02:12 -05:00
Joshua Goins
1104da5e2c TextHandler: Use the fancy Unicode quotation characters
As per our HIG, we should use these in quotations instead of the normal
quote characters.
2024-11-16 21:01:04 +00:00
Joshua Goins
3a9718c09d Limit the width of a user's QR code
This fixes the lopsided layout in the user details dialog.
2024-11-16 21:00:39 +00:00
Joshua Goins
55362c5573 RoomManager: Unify the resolveResource overloads
Every time I look at how resource resolving works, I always trip over
this unused overload. This behaves *different* than the other overload,
which has special cases for handling invalid Uris.

Now whether you pass in a Uri or a QString, it should behave the same.
2024-11-16 21:00:29 +00:00
Joshua Goins
0bba2299b3 Set the size of custom emoticons to the font height, and fix alignment
Currently custom emojis render weirdly in NeoChat. Not only are they
large, they're also in charge and like to mess up the layout of the
text.

Now that's fixed and they'll take up the same height as the surrounding
text. It's now centered in the text too.
2024-11-16 21:00:11 +00:00
Joshua Goins
45685af9e9 Add icons to the recommended space actions, fix spacing of the items 2024-11-16 20:59:57 +00:00
Joshua Goins
6c416a9338 Fix restoring the last used space on desktop
This was supposed to work, but it's done in the wrong order. We need to
set the current space first, and then select the room - otherwise
it doesn't get restored.
2024-11-16 20:59:45 +00:00
Joshua Goins
1b0027e1d2 Ensure it's not possible for the recommended space avatar to assert 2024-11-16 20:59:02 +00:00
Joshua Goins
2409adf516 Fix avatars not loading in the room completion model 2024-11-16 20:59:02 +00:00
Joshua Goins
554801dfe4 Make sure RoomInformation's source is type url 2024-11-16 20:59:02 +00:00
Joshua Goins
20c23917e9 Remove now unused NeoChatRoomMember::avatarMediaId() 2024-11-16 20:59:02 +00:00
Joshua Goins
ef953b7574 Remove more needless usages of makeMediaUrl
This is only really needed in specific cases, e.g. localUser which isn't
attached to a connection and thus needs a little help. Notes for when
this is needed is added for future readers.
2024-11-16 20:59:02 +00:00
Joshua Goins
6b79795229 Change how room avatars are passed, fix friend avatars not loading
The problem lies in how media URLs work, in this case it the old
NeoChatRoom::avatarMediaId could pass a mxc url *or* a path that can
be put into root.connection.makeMediaUrl. So normal rooms with avatars
loaded, but never friends because room members gave the mxc URL.

Instead, change everything to use avatarMediaUrl which corrects this
issue by always passing a mxc URL to QML. This also removes the need to
call makeMediaUrl.

Fixes #675
2024-11-16 20:59:02 +00:00
Joshua Goins
9cb7ec2348 Add ellipses to the "Forward" message action, because it opens a dialog 2024-11-16 18:53:53 +00:00
Joshua Goins
437c981d30 Don't show the file name underneath the image
This still keeps custom image descriptions, but no longer shows it for
images where it was the same as their filename.
2024-11-16 13:18:46 -05:00
Joshua Goins
0334cae4c8 Change the room alias text color to disabled
It's less important than the title, and this should reduce it's visual
prominence.
2024-11-16 16:40:57 +00:00
Joshua Goins
24c405d747 Add a separator between the report and copy message actions 2024-11-16 11:39:18 -05:00
Joshua Goins
a3f5962809 DelegateContextMenu: Add support for separators in the mobile menu too 2024-11-16 11:30:49 -05:00
Joshua Goins
0deb7495f0 Re-arrange the file and message context menus, add separators
Like the room context menu, this is a jumbled list of actions that could
use some organization. Also, the text for "Copy Text" and "Copy Link" is
clearer.
2024-11-16 11:23:39 -05:00
Joshua Goins
d34f89fc4b DelegateContextMenu: Add support for separator actions 2024-11-16 11:22:37 -05:00
Joshua Goins
a909ed498f Hide the category list in the emoji picker when there is none
This is easy to test if you have no stickers. It should no longer have
a weird empty space above the placeholder message.
2024-11-16 16:03:36 +00:00
Joshua Goins
16f4e17e8f Improve how stickers appear in the emoji picker
First, the fill mode for the sticker images shouldn't stretch them.
Also make sure there is enough padding in the category so the image
doesn't appear larger than the button. Finally, set the source size for
the images so Qt can smooth them out better.
2024-11-16 16:03:24 +00:00
Joshua Goins
0e9592a96c Settings: Use symbolic version of the NeoChat icon
To match the rest of the icons in this sidebar, we can reuse our tray
icon.
2024-11-16 16:03:09 +00:00
Joshua Goins
704ee6a53a Add placeholder icon when there's no emojis or stickers 2024-11-16 10:50:49 -05:00
Joshua Goins
5b9afbce9a Settings: Request symbolic versions of the icons
According to the HIG, we should be using symbolic versions of these
icons at this size. Not that we have symbolic versions for these icons
yet, but it's still safe to do as they'll fall back to the old ones.
2024-11-16 09:48:13 -05:00
Thiago Sueto
396cc8e8ef Make top margin consistent across Neochat settings
This standardizes on the same value used for KirigamiAddons pages like AboutKDE and About, namely largeSpacing * 4.

Now, when switching between settings pages you no longer have settings inconsistently changing heights willy nilly, header notwithstanding.

The only page that's missing is the Spellchecking page, as that needs to be fixed in Kirigami Addons' private Sonnet page.
2024-11-16 14:37:01 +00:00
Thiago Sueto
bf776b5c06 Fix inconsistent wording about leaving current space/room
For rooms, we already say "Leave this room".

When viewing a Space page, we have both "Leave the space" and "Leave this room". The "Leave the space" VS "Leave this space" was bothering me, and the Space page should say "Leave this space" instead of "Leave this room".
2024-11-16 14:36:30 +00:00
Joshua Goins
be319f88d3 "Save As" action should have ellipses
Because you have to interact with the save dialog before doing anything
else.
2024-11-16 14:25:47 +00:00
Joshua Goins
af40d555d4 Improve the layout and function of the room context menu
The room context menu is a jumbled mess of actions, so the first idea of
this commit is to organize them. The first item is "Mark as Read"
because let's be honest, you're going to be using that the most. Then
the next "group" of actions are what users can "do" with the room. This
is like "Notification settings", "Favorite" and etc. Then there's room
settings, and leave.

Secondly, the "Favorite" action now uses the same icon we use elsewhere.

Third, "Notification State" is a weird name for this action and renamed
to simply "Notifications".

Finally, the "Mark as Read" action is now disabled when there's nothing
else to read.
2024-11-16 14:25:12 +00:00
Joshua Goins
f802dbe686 Port from deprecated AboutKDE component to AboutKDEPage 2024-11-16 14:24:10 +00:00
Joshua Goins
2379e3d83b Don't scroll up when clicking on the same room over and over
If you try to click on your current room in the list, it scrolls up the
messages a bit. This is because in RoomManager::visitRoom it's being
called with an empty eventId and we will happily emit a goToEvent. This
is despite there being nothing to go to.

Fixes #677.
2024-11-16 14:18:32 +00:00
Joshua Goins
9e90ac0412 Add margins to the room drawer header to match Kirigami
Otherwise it sticks to the left of the drawer and looks kinda ugly.
2024-11-16 14:16:50 +00:00
Joshua Goins
c27948ca3c Change the leave button in the drawer to "Leave this space" if needed 2024-11-16 14:14:32 +00:00
Joshua Goins
c3b9d664df "Room Information" title should be capitalized 2024-11-16 14:13:32 +00:00
Joshua Goins
31ef0a5223 Make it so the filename is filled out by default when saving files
This was never ported from the Qt labs platform FileDialog, because
currentFile doesn't exist anymore. It's now called selectedFile.
2024-11-16 14:12:19 +00:00
Joshua Goins
14c58acea1 Improve the appearance of the welcome page user list
Before it only listed the user id, and nothing else. If you had multiple
accounts, it's a little difficult to tell them apart. Now the user
selection appears like how they are displayed elsewhere in NeoChat, with
the display name and avatar.

| Before | After |
| ------ | ------ |
|   ![Screenshot_20241115_221425](/uploads/3986e4c7bbb7dcdca67ee30bb529767e/Screenshot_20241115_221425.png){width=786 height=822}     |        ![Screenshot_20241115_221149](/uploads/57eb1a7e57ba5ae8c41dd922cbf39c62/Screenshot_20241115_221149.png){width=786 height=822} |
2024-11-16 14:11:55 +00:00
l10n daemon script
5dae20603e GIT_SILENT Sync po/docbooks with svn 2024-11-16 01:34:25 +00:00
Joshua Goins
3f6fa94289 Port from Kirigami Add-ons Banner to Kirigami InlineMessage 2024-11-15 17:29:56 +00:00
l10n daemon script
117615a8b0 GIT_SILENT Sync po/docbooks with svn 2024-11-15 01:32:04 +00:00
l10n daemon script
4a52773c7d GIT_SILENT Sync po/docbooks with svn 2024-11-14 01:31:26 +00:00
l10n daemon script
edfee495c6 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-11-14 01:24:49 +00:00
l10n daemon script
7d112df7c6 GIT_SILENT made messages (after extraction) 2024-11-14 00:40:15 +00:00
l10n daemon script
9acaaade45 GIT_SILENT Sync po/docbooks with svn 2024-11-13 01:30:31 +00:00
l10n daemon script
aaca28dbf6 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-11-13 01:24:06 +00:00
Paul Brown
d4ef5f9d4d Update org.kde.neochat.appdata.xml 2024-11-12 19:02:36 +00:00
James Graham
2095dea801 Add #if for patch to fix pendingEventAdded event ref 2024-11-12 16:25:23 +00:00
James Graham
a36f7ef10d Fix test 2024-11-12 16:25:23 +00:00
James Graham
9874962ee3 Make sure that the content model is loaded properly when a new event is set. This fixes seeing an unknown event message for all new events. Instead a loading symbol is briefly seen before switching to the actual content. 2024-11-12 16:25:23 +00:00
l10n daemon script
4b08022075 GIT_SILENT Sync po/docbooks with svn 2024-11-12 01:33:25 +00:00
l10n daemon script
dc3db3aec4 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-11-12 01:26:03 +00:00
l10n daemon script
0568c2a93d GIT_SILENT Sync po/docbooks with svn 2024-11-11 01:35:46 +00:00
l10n daemon script
7ab0a6fc9e 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-11-11 01:24:43 +00:00
Joshua Goins
d6b780762e PollHandler: Make sure it's not constructible from QML 2024-11-10 15:16:26 +00:00
Joshua Goins
5ef66b5cf6 PollHandler: Ensure that m_pollStartEvent is always initialized to null
Otherwise it may be undefined, and we DO create default-constructed
PollHandler. For example, one is used as a fallback poll object
in NeoChatRoom::poll.

This is blind fix for a pretty nasty poll-related crash we saw a few
months ago.

BUG: 493649
2024-11-10 15:16:26 +00:00
l10n daemon script
19e8cd5e48 GIT_SILENT Sync po/docbooks with svn 2024-11-10 01:35:11 +00:00
l10n daemon script
df5117892f 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-11-10 01:24:25 +00:00
l10n daemon script
aaa4216f55 GIT_SILENT made messages (after extraction) 2024-11-10 00:40:38 +00:00
Joshua Goins
85ee5084b6 Add m.room.create state events to sync_response
In case we need to access the creation state in an appium test in the
future.
2024-11-09 23:21:37 +00:00
Joshua Goins
bb9ce117de Hide rooms that have a defined room type
I have a room with a custom type that's only for holding data, and
doesn't need to be shown in the room list. Currently the spec is a bit
vague about what clients should do, but hiding them is probably fine
for now.
2024-11-09 23:21:37 +00:00
Carl Schwan
00c5aa26bb RoomGeneralPage: Add missing separator
And some other minor fixes
2024-11-09 23:11:19 +00:00
Joshua Goins
bae4de227c Make closing link previews instant, as it should be
We were missing a endResetModel() call, now with it added the removal
happens instantly.
2024-11-09 23:10:54 +00:00
Joshua Goins
253f891c5a Stop being able to crash NeoChat by pressing a button repeatedly
If you spam click the "Close link preview" button, it's possible to
crash NeoChat. This is because the index check is wrong for the array
size.

It's possible to even do this due to a bug causing the removal to be
reflected visually too slowly, that's fixed in the next commit.
2024-11-09 23:10:54 +00:00
Joshua Goins
6966159062 Improve clicking link previews
First of all, clicking on them actually works - because we were missing
an import for RoomManager. Secondly, we use a dedicated TapHandler
since onLinkActivated sucks. We want to be able to click anywhere on the
preview to go to the website/room anyway.
2024-11-09 23:10:40 +00:00
Joshua Goins
07d3b80c3e Don't set isThread on the message and file delegate context menus
It doesn't have a property called isThread, and I don't know where it
went - if it ever existed?
2024-11-09 23:10:31 +00:00
Joshua Goins
a41d0f3214 Make fullscreen images focused when they're opened
Otherwise keyboard shortcuts don't work until you tap the image, which
makes no sense.

BUG: 484322
2024-11-09 23:10:21 +00:00
Joshua Goins
1ee15de78b Fix viewing any kind of data in developer tools
Fix pageStack being undefined, so we're able to view event data again.
2024-11-09 23:10:08 +00:00
Carl Schwan
b044358970 Update checkbox of PollComponent
Use FormCheckDelegate instead of a CheckBox inside a RowLayout. This
increase the click area particularly on mobile.
2024-11-09 23:09:51 +00:00
Oliver Beard
d2e11bb3bb timeline: Round separators for replies and link previews 2024-11-09 23:09:33 +00:00
Joshua Goins
a55bac899c README: Change snap store badge to the one from apps.kde.org
It seems CORS is blocking access to the badge, but we have rehosted on
apps.kde.org.
2024-11-09 21:32:45 +00:00
Joshua Goins
c2380fb8df Update network proxy page with the improved version from Tokodon
This functions the same, but looks a bit nicer.
2024-11-09 17:11:11 +00:00
Joshua Goins
f31c644b13 Update desktop file and app description to match AppStream data
This was updated to "Chat on Matrix" but in other places it was never
switched from "Matrix client" and the like. Now it should be more
consistent.
2024-11-09 17:11:00 +00:00
Joshua Goins
26cd621d0e Clarify that sorting rooms by activity isn't the only thing it does
Recently, it also sorts rooms based on unread notification count and
importance. This adds a clarification to the setting so users (like me)
aren't confused why it isn't sorting only by activity.
2024-11-09 16:46:54 +00:00
l10n daemon script
4c58512c54 GIT_SILENT Sync po/docbooks with svn 2024-11-09 01:30:21 +00:00
Albert Astals Cid
04c1b47660 GIT_SILENT Upgrade release service version to 25.03.70. 2024-11-08 19:38:32 +01:00
118 changed files with 13701 additions and 11929 deletions

View File

@@ -7,8 +7,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 "11")
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")

View File

@@ -11,7 +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>
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://apps.kde.org/store_badges/snapstore/en.svg'/></a>
## Introduction

View File

@@ -5,6 +5,26 @@
"!room_id_1234:localhost:1234": {
"state": {
"events": [
{
"content": {
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "11"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234,
"membership": "join"
}
},
{
"type": "m.room.member",
"state_key": "@user:localhost:1234",
@@ -26,6 +46,26 @@
},
"timeline": {
"events": [
{
"content": {
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "11"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234,
"membership": "join"
}
},
{
"type": "m.room.message",
"sender": "@user:localhost:1234",

View File

@@ -535,7 +535,7 @@ void TextHandlerTest::componentOutput_data()
QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}};
QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Quote, QStringLiteral("\"blockquote\""), {}}};
MessageComponent{MessageComponentType::Quote, QStringLiteral("blockquote"), {}}};
QTest::newRow("no tag first paragraph") << QStringLiteral("Text\n<p>Text</p>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};

View File

@@ -58,6 +58,8 @@
<summary xml:lang="es">Charle en Matrix</summary>
<summary xml:lang="eu">Berriketa Matrix-en</summary>
<summary xml:lang="fr">Discuter sur Matrix</summary>
<summary xml:lang="gl">Charlar en Matrix</summary>
<summary xml:lang="hu">Csevegés Matrixon</summary>
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
@@ -286,6 +288,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet</value>
</custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots>

View File

@@ -87,47 +87,25 @@ 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
Comment[ca]=Client per al protocol Matrix
Comment[ca@valencia]=Client per al protocol Matrix
Comment[de]=Programm für das Matrix-Protokoll
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
Comment[en_GB]=Client for the Matrix protocol
Comment[eo]=Kliento por la Matrix-protokolo
Comment[es]=Cliente para el protocolo Matrix
Comment[eu]=Matrix protokolorako bezeroa
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
Comment[fr]=Client pour le protocole « Matrix »
Comment[gl]=Cliente para o protocolo Matrix.
Comment[he]=לקוח לפרוטוקול Matrix
Comment[hu]=Kliens a Matrix protokollhoz
Comment[ia]=Cliente per le protocollo de Matrix
Comment[id]=Klien untuk protokol Matrix
Comment[ie]=Un cliente del protocol Matrix
Comment[it]=Client per il protocollo Matrix
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
Comment[ko]=Matrix 프로토콜용 클라이언트
Comment[lt]=Matrix protokolo kliento programa
Comment[lv]=Klients „Matrix“ protokolam
Comment[nl]=Client voor het Matrix-protocol
Comment[nn]=Klient for Matrix-protokollen
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
Comment[pl]=Program obsługi protokołu Matriksa
Comment[pt]=Cliente para o protocolo Matrix
Comment[pt_BR]=Cliente para o protocolo Matrix
Comment[ro]=Client pentru protocolul Matrix
Comment[ru]=Клиент для протокола Matrix
Comment[sk]=Klient protokolu Matrix
Comment[sl]=Odjemalec za protokol Matrix
Comment[sv]=Klient för protokollet Matrix
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
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 通訊協定的用戶端
Comment=Chat on Matrix
Comment[ca]=Xat a Matrix
Comment[ca@valencia]=Xat a Matrix
Comment[en_GB]=Chat on Matrix
Comment[es]=Chat en Matrix
Comment[eu]=Berriketa Matrix-en
Comment[fr]=Clavarder sur Matrix
Comment[gl]=Charle en Matrix
Comment[hu]=Csevegés Matrixon
Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie
Comment[sl]=Klepet na Matrixu
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix Üzerinde Sohbet Et
Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -176,13 +176,14 @@ QQC2.Control {
RowLayout {
QQC2.ScrollView {
id: chatBarScrollView
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
@@ -320,12 +321,11 @@ QQC2.Control {
id: actionsRow
spacing: 0
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: Kirigami.Units.smallSpacing * 1.5
Layout.bottomMargin: Kirigami.Units.smallSpacing * 4
Repeater {
model: root.actions
delegate: QQC2.ToolButton {
Layout.alignment: Qt.AlignVCenter
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: modelData.trigger()
@@ -342,7 +342,6 @@ QQC2.Control {
}
}
}
DelegateSizeHelper {
id: chatBarSizeHelper
startBreakpoint: Kirigami.Units.gridUnit * 46

View File

@@ -43,6 +43,9 @@ QQC2.ItemDelegate {
anchors.fill: parent
visible: root.emoji.startsWith("mxc") || root.isImage
source: visible ? root.emoji : ""
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
}
}

View File

@@ -84,6 +84,7 @@ QQC2.ScrollView {
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
icon.name: root.stickers ? "stickers" : "preferences-desktop-emoticons"
text: root.stickers ? i18n("No stickers") : i18n("No emojis")
visible: emojis.count === 0
}

View File

@@ -66,6 +66,7 @@ ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
visible: categories.count !== 0
ListView {
id: categories
@@ -201,8 +202,13 @@ ColumnLayout {
width: root.categoryIconSize
height: width
checked: stickerModel.packIndex === model.index
padding: Kirigami.Units.largeSpacing
contentItem: Image {
source: model.avatarUrl
fillMode: Image.PreserveAspectFit
sourceSize.width: width
sourceSize.height: height
}
QQC2.ToolTip.text: model.name
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay

View File

@@ -4,6 +4,7 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
@@ -23,7 +24,7 @@ ColumnLayout {
model: root.connection.accountDataEventTypes
delegate: FormCard.FormButtonDelegate {
text: modelData
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
sourceText: root.connection.accountDataJsonString(modelData)
}, {
title: i18nc("@title:window", "Event Source"),

View File

@@ -3,6 +3,7 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
@@ -47,7 +48,7 @@ ColumnLayout {
model: root.room.accountDataEventTypes
delegate: FormCard.FormButtonDelegate {
text: modelData
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
sourceText: root.room.roomAcountDataJson(text)
}, {
title: i18n("Event Source"),
@@ -77,7 +78,7 @@ ColumnLayout {
if (model.eventCount === 1) {
openEventSource(model.type, model.stateKey);
} else {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
room: root.room,
eventType: model.type
}, {
@@ -89,7 +90,7 @@ ColumnLayout {
}
}
function openEventSource(type: string, stateKey: string): void {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateModel,
allowEdit: true,
room: root.room,

View File

@@ -13,6 +13,7 @@ LoginStep {
id: root
FormCard.FormTextDelegate {
textItem.wrapMode: Text.Wrap
text: i18n("Please wait while your messages are loaded from the server. This might take a little while.")
}
FormCard.AbstractFormDelegate {

View File

@@ -7,6 +7,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.settings
@@ -90,11 +91,27 @@ Kirigami.Page {
id: loadedAccounts
model: AccountRegistry
delegate: FormCard.FormButtonDelegate {
text: model.userId
id: delegate
required property string userId
required property NeoChatConnection connection
text: QmlUtils.escapeString(connection.localUser.displayName)
description: connection.localUser.id
leadingPadding: Kirigami.Units.largeSpacing
onClicked: {
Controller.activeConnection = model.connection;
Controller.activeConnection = delegate.connection;
root.connectionChosen();
}
leading: KirigamiComponents.Avatar {
id: avatar
name: delegate.text
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : ""
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
}
}
}
Repeater {

View File

@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
KAboutData about(QStringLiteral("neochat"),
i18n("NeoChat"),
QStringLiteral(NEOCHAT_VERSION_STRING),
i18n("Matrix client"),
i18n("Chat on Matrix"),
KAboutLicense::GPL_V3,
i18n("© 2018-2020 Black Hat, 2020-2024 KDE Community"));
about.addAuthor(i18n("Carl Schwan"),

View File

@@ -600,14 +600,19 @@ bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messa
} else {
originalString = event->plainBody();
}
QString replaceId = event->id();
const auto eventRelation = event->relatesTo();
if (eventRelation && eventRelation->type == "m.replace"_L1) {
replaceId = eventRelation->eventId;
}
if (flags == "/g"_L1) {
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, replaceId);
} else {
room->postHtmlMessage(messageText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
event->id());
replaceId);
}
return true;
}

View File

@@ -85,13 +85,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
}
if (role == IconNameRole) {
auto mediaId = m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
if (mediaId.isEmpty()) {
return QVariant();
}
if (m_room) {
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
}
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
}
}
if (m_autoCompletionType == Emoji) {

View File

@@ -34,7 +34,7 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
: QAbstractListModel(parent)
, m_room(room)
, m_eventId(eventId)
, m_isPending(isPending)
, m_currentState(isPending ? Pending : Unknown)
, m_isReply(isReply)
{
initializeModel();
@@ -45,19 +45,27 @@ void MessageContentModel::initializeModel()
Q_ASSERT(m_room != nullptr);
Q_ASSERT(!m_eventId.isEmpty());
connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent);
connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() {
if (m_room != nullptr && m_currentState == Unknown) {
initializeEvent();
updateReplyModel();
resetModel();
}
});
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
if (m_room != nullptr) {
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
beginResetModel();
m_isPending = false;
m_eventId = serverEvent->id();
initializeEvent();
endResetModel();
}
}
});
connect(m_room, &NeoChatRoom::pendingEventMerged, this, [this]() {
if (m_room != nullptr && m_currentState == Pending) {
initializeEvent();
updateReplyModel();
resetModel();
}
});
connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) {
if (m_room != nullptr) {
for (int i = fromIndex; i <= toIndex; i++) {
@@ -143,20 +151,33 @@ void MessageContentModel::initializeModel()
});
initializeEvent();
updateReplyModel();
if (m_currentState == Available || m_currentState == Pending) {
updateReplyModel();
}
resetModel();
}
void MessageContentModel::initializeEvent()
{
const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) {
Q_EMIT eventUnavailable();
if (m_currentState == UnAvailable) {
return;
}
const auto eventResult = m_room->getEvent(m_eventId);
if (eventResult.first == nullptr) {
if (m_currentState != Pending) {
getEvent();
}
return;
}
if (eventResult.second) {
m_currentState = Pending;
} else {
m_currentState = Available;
}
if (m_eventSenderObject == nullptr) {
auto senderId = event->senderId();
auto senderId = eventResult.first->senderId();
// A pending event might not have a sender ID set yet but in that case it must
// be the local member.
if (senderId.isEmpty()) {
@@ -172,7 +193,6 @@ void MessageContentModel::getEvent()
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
if (m_room != nullptr) {
if (eventId == m_eventId) {
m_notFound = false;
initializeEvent();
updateReplyModel();
resetModel();
@@ -184,7 +204,7 @@ void MessageContentModel::getEvent()
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
if (m_room != nullptr) {
if (eventId == m_eventId) {
m_notFound = true;
m_currentState = UnAvailable;
resetModel();
return true;
}
@@ -237,7 +257,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
const auto component = m_components[index.row()];
const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) {
if (event.first == nullptr) {
if (role == DisplayRole) {
if (m_isReply) {
return i18n("Loading reply");
@@ -252,7 +272,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
}
if (role == DisplayRole) {
if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) {
if (m_currentState == UnAvailable || m_room->connection()->isIgnored(m_eventSenderId)) {
Kirigami::Platform::PlatformTheme *theme =
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
@@ -276,7 +296,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
if (!component.content.isEmpty()) {
return component.content;
}
return EventHandler::richBody(m_room, event);
return EventHandler::richBody(m_room, event.first);
}
if (role == ComponentTypeRole) {
return component.type;
@@ -285,53 +305,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return component.attributes;
}
if (role == EventIdRole) {
return EventHandler::id(event);
return EventHandler::id(event.first);
}
if (role == TimeRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
return event->transactionId() == pendingEvent->transactionId();
return event.first->transactionId() == pendingEvent->transactionId();
});
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
return EventHandler::time(event, m_isPending, lastUpdated);
return EventHandler::time(event.first, m_currentState == Pending, lastUpdated);
}
if (role == TimeStringRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
return event->transactionId() == pendingEvent->transactionId();
return event.first->transactionId() == pendingEvent->transactionId();
});
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated);
return EventHandler::timeString(event.first, QStringLiteral("hh:mm"), m_currentState == Pending, lastUpdated);
}
if (role == AuthorRole) {
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
}
if (role == MediaInfoRole) {
return EventHandler::mediaInfo(m_room, event);
return EventHandler::mediaInfo(m_room, event.first);
}
if (role == FileTransferInfoRole) {
return QVariant::fromValue(m_room->cachedFileTransferInfo(event));
return QVariant::fromValue(m_room->cachedFileTransferInfo(event.first));
}
if (role == ItineraryModelRole) {
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
}
if (role == LatitudeRole) {
return EventHandler::latitude(event);
return EventHandler::latitude(event.first);
}
if (role == LongitudeRole) {
return EventHandler::longitude(event);
return EventHandler::longitude(event.first);
}
if (role == AssetRole) {
return EventHandler::locationAssetType(event);
return EventHandler::locationAssetType(event.first);
}
if (role == PollHandlerRole) {
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
}
if (role == ReplyEventIdRole) {
return EventHandler::replyId(event);
return EventHandler::replyId(event.first);
}
if (role == ReplyAuthorRole) {
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event));
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
}
if (role == ReplyContentModelRole) {
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
@@ -387,18 +407,17 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
void MessageContentModel::resetModel()
{
const auto event = m_room->getEvent(m_eventId);
beginResetModel();
m_components.clear();
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) {
if (m_room->connection()->isIgnored(m_eventSenderId) || m_currentState == UnAvailable) {
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
endResetModel();
return;
}
if (event == nullptr) {
const auto event = m_room->getEvent(m_eventId);
if (event.first == nullptr) {
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
endResetModel();
return;
@@ -431,19 +450,19 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
{
const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) {
if (event.first == nullptr) {
return {};
}
QList<MessageComponent> newComponents;
if (eventCast<const Quotient::RoomMessageEvent>(event)
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
if (eventCast<const Quotient::RoomMessageEvent>(event.first)
&& eventCast<const Quotient::RoomMessageEvent>(event.first)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
return newComponents;
}
if (event->isRedacted()) {
if (event.first->isRedacted()) {
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
return newComponents;
}
@@ -455,7 +474,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
if (isEditing) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
} else {
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event)));
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
}
if (m_room->urlPreviewEnabled()) {
@@ -463,7 +482,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
}
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
if (isThreading && !EventHandler::isThreaded(event)) {
if (isThreading && !EventHandler::isThreaded(event.first)) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
}
@@ -473,11 +492,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
void MessageContentModel::updateReplyModel()
{
const auto event = m_room->getEvent(m_eventId);
if (event == nullptr || m_isReply) {
if (event.first == nullptr || m_isReply) {
return;
}
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) {
if (!EventHandler::hasReply(event.first) || (EventHandler::isThreaded(event.first) && NeoChatConfig::self()->threads())) {
if (m_replyModel) {
delete m_replyModel;
}
@@ -488,7 +507,7 @@ void MessageContentModel::updateReplyModel()
return;
}
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this);
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event.first), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -498,13 +517,13 @@ void MessageContentModel::updateReplyModel()
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
{
const auto event = m_room->getEvent(m_eventId);
if (event == nullptr) {
if (event.first == nullptr) {
return {};
}
switch (type) {
case MessageComponentType::Text: {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
return TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
@@ -515,11 +534,11 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::File: {
QList<MessageComponent> components;
components += MessageComponent{MessageComponentType::File, QString(), {}};
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (m_emptyItinerary) {
if (!m_isReply) {
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
auto fileTransferInfo = m_room->cachedFileTransferInfo(event.first);
#ifndef Q_OS_ANDROID
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
@@ -567,17 +586,24 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::Image:
case MessageComponentType::Audio:
case MessageComponentType::Video: {
if (!event->is<StickerEvent>()) {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
QList<MessageComponent> components;
components += MessageComponent{type, QString(), {}};
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
components += TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
return components;
if (!event.first->is<StickerEvent>()) {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
const auto fileContent = roomMessageEvent->get<EventContent::FileContentBase>();
if (fileContent != nullptr) {
const auto fileInfo = fileContent->commonInfo();
const auto body = EventHandler::rawMessageBody(*roomMessageEvent);
// Do not attach the description to the image, if it's the same as the original filename.
if (fileInfo.originalName != body) {
QList<MessageComponent> components;
components += MessageComponent{type, QString(), {}};
components += TextHandler().textComponents(body,
EventHandler::messageBodyInputFormat(*roomMessageEvent),
m_room,
roomMessageEvent,
roomMessageEvent->isReplaced());
return components;
}
}
}
}
default:
@@ -635,7 +661,7 @@ QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageCompon
void MessageContentModel::closeLinkPreview(int row)
{
if (row < 0 || row > m_components.size()) {
if (row < 0 || row >= m_components.size()) {
qWarning() << "closeLinkPreview() called with row" << row << "which does not exist. m_components.size() =" << m_components.size();
return;
}
@@ -645,6 +671,7 @@ void MessageContentModel::closeLinkPreview(int row)
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
m_components.remove(row);
m_components.squeeze();
endResetModel();
resetContent();
}
}
@@ -652,13 +679,13 @@ void MessageContentModel::closeLinkPreview(int row)
void MessageContentModel::updateItineraryModel()
{
const auto event = m_room->getEvent(m_eventId);
if (m_room == nullptr || event == nullptr) {
if (m_room == nullptr || event.first == nullptr) {
return;
}
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
if (roomMessageEvent->has<EventContent::FileContent>()) {
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
auto filePath = m_room->cachedFileTransferInfo(event.first).localPath;
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
delete m_itineraryModel;
m_itineraryModel = nullptr;

View File

@@ -31,6 +31,14 @@ class MessageContentModel : public QAbstractListModel
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
public:
enum MessageState {
Unknown, /**< The message state is unknown. */
Pending, /**< The message is a new pending message which the server has not yet acknowledged. */
Available, /**< The message is available and acknowledged by the server. */
UnAvailable, /**< The message can't be retrieved either because it doesn't exist or is blocked. */
};
Q_ENUM(MessageState)
/**
* @brief Defines the model roles.
*/
@@ -98,7 +106,6 @@ public:
Q_SIGNALS:
void showAuthorChanged();
void eventUnavailable();
void eventUpdated();
private:
@@ -107,10 +114,9 @@ private:
QString m_eventSenderId;
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
bool m_isPending;
MessageState m_currentState = Unknown;
bool m_showAuthor = true;
bool m_isReply;
bool m_notFound = false;
void initializeModel();
void initializeEvent();

View File

@@ -160,12 +160,21 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
refreshLastUserEvents(i);
}
});
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
connect(m_currentRoom, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
m_initialized = true;
createEventObjects(event, true);
beginInsertRows({}, 0, 0);
endInsertRows();
});
#else
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
m_initialized = true;
createEventObjects(event);
createEventObjects(event, true);
beginInsertRows({}, 0, 0);
});
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
#endif
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
if (i == 0) {
@@ -618,7 +627,7 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
}
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, bool isPending)
{
if (event == nullptr) {
return;
@@ -641,7 +650,7 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId));
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId, false, isPending));
}
}

View File

@@ -136,7 +136,7 @@ private:
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
void moveReadMarker(const QString &toEventId);
void createEventObjects(const Quotient::RoomEvent *event);
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
bool m_initialized = false;

View File

@@ -212,7 +212,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return room->displayName().toHtmlEscaped();
}
if (role == AvatarRole) {
return room->avatarMediaId();
return room->avatarMediaUrl();
}
if (role == CanonicalAliasRole) {
return room->canonicalAlias();

View File

@@ -286,6 +286,7 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[IconRole] = "icon";
roles[AttentionRole] = "attention";
roles[FavouriteRole] = "favourite";
roles[RoomTypeRole] = "roomType";
return roles;
}
@@ -323,7 +324,7 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return room->displayName();
}
if (role == AvatarRole) {
return room->avatarMediaId();
return room->avatarMediaUrl();
}
if (role == CanonicalAliasRole) {
return room->canonicalAlias();
@@ -385,6 +386,11 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == FavouriteRole) {
return room->isFavourite();
}
if (role == RoomTypeRole) {
if (room->creation()) {
return room->creation()->contentPart<QString>("type"_L1);
}
}
return {};
}

View File

@@ -50,6 +50,7 @@ public:
IconRole,
AttentionRole, /**< Whether there are any notifications. */
FavouriteRole, /**< Whether the room is favourited. */
RoomTypeRole, /**< The room's type. */
};
Q_ENUM(EventRoles)
explicit RoomTreeModel(QObject *parent = nullptr);

View File

@@ -157,6 +157,11 @@ bool SortFilterRoomTreeModel::filterAcceptsRow(int source_row, const QModelIndex
return false;
}
// Hide rooms with defined types, assuming that data-holding rooms have a defined type
if (!sourceModel()->data(index, RoomTreeModel::RoomTypeRole).toString().isEmpty()) {
return false;
}
static auto config = NeoChatConfig::self();
if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
return acceptRoom;

View File

@@ -261,7 +261,7 @@ Action=Popup
Name=Share
Name[ar]=شارك
Name[ca]=Compartició
Name[ca@valencia]=Compartició
Name[ca@valencia]=Compartiu
Name[cs]=Sdílet
Name[de]=Teilen
Name[el]=Κοινοποίηση

View File

@@ -431,9 +431,9 @@ QDateTime NeoChatRoom::lastActiveTime()
return messageEvents().rbegin()->get()->originTimestamp();
}
QString NeoChatRoom::avatarMediaId() const
QUrl NeoChatRoom::avatarMediaUrl() const
{
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
if (const auto avatar = Room::avatarUrl(); !avatar.isEmpty()) {
return avatar;
}
@@ -441,7 +441,7 @@ QString NeoChatRoom::avatarMediaId() const
const auto directChatMembers = this->directChatMembers();
for (const auto member : directChatMembers) {
if (member != localMember()) {
return member.avatarMediaId();
return member.avatarUrl();
}
}
@@ -1749,25 +1749,31 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
});
}
const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const
std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString &eventId) const
{
if (eventId.isEmpty()) {
return nullptr;
return {};
}
const auto timelineIt = findInTimeline(eventId);
if (timelineIt != historyEdge()) {
return timelineIt->get();
return std::make_pair(timelineIt->get(), false);
}
const auto pendingIt = findPendingEvent(eventId);
auto pendingIt = findPendingEvent(eventId);
if (pendingIt != pendingEvents().end()) {
return pendingIt->event();
return std::make_pair(pendingIt->event(), true);
}
// findPendingEvent() searches by transaction ID, we also need to check event ID.
for (const auto &event : pendingEvents()) {
if (event->id() == eventId || event->transactionId() == eventId) {
return std::make_pair(event.event(), true);
}
}
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
return event->id() == eventId;
});
return extraIt != m_extraEvents.end() ? extraIt->get() : nullptr;
return std::make_pair(extraIt != m_extraEvents.end() ? extraIt->get() : nullptr, false);
}
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const

View File

@@ -69,9 +69,9 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
/**
* @brief The avatar image to be used for the room.
* @brief The avatar image to be used for the room, as a mxc:// URL.
*/
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
Q_PROPERTY(QUrl avatarMediaUrl READ avatarMediaUrl NOTIFY avatarChanged STORED false)
/**
* @brief Get a RoomMember object for the other person in a direct chat.
@@ -320,7 +320,7 @@ public:
[[nodiscard]] bool readMarkerLoaded() const;
[[nodiscard]] QString avatarMediaId() const;
[[nodiscard]] QUrl avatarMediaUrl() const;
NeochatRoomMember *directChatRemoteMember();
@@ -570,7 +570,7 @@ public:
*
* The result will be nullptr if not found so needs to be managed.
*/
const Quotient::RoomEvent *getEvent(const QString &eventId) const;
std::pair<const Quotient::RoomEvent *, bool> getEvent(const QString &eventId) const;
/**
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.

View File

@@ -153,15 +153,6 @@ QColor NeochatRoomMember::color() const
return m_room->member(m_memberId).color();
}
QString NeochatRoomMember::avatarMediaId() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).avatarMediaId();
}
QUrl NeochatRoomMember::avatarUrl() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {

View File

@@ -70,7 +70,6 @@ public:
int hue() const;
qreal hueF() const;
QColor color() const;
QString avatarMediaId() const;
QUrl avatarUrl() const;
Q_SIGNALS:

View File

@@ -30,6 +30,7 @@ class PollHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("Use NeoChatRoom::poll")
/**
* @brief The question for the poll.
@@ -91,7 +92,7 @@ Q_SIGNALS:
void hasEndedChanged();
private:
const Quotient::PollStartEvent *m_pollStartEvent;
const Quotient::PollStartEvent *m_pollStartEvent = nullptr;
void updatePoll(Quotient::RoomEventsRange events);

View File

@@ -26,6 +26,7 @@
"Name[nn]": "Tobias Fella",
"Name[pl]": "Tobias Fella",
"Name[ru]": "Tobias Fella",
"Name[sk]": "Tobias Fella",
"Name[sl]": "Tobias Fella",
"Name[sv]": "Tobias Fella",
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
@@ -93,6 +94,7 @@
"Name[nn]": "NeoChat",
"Name[pl]": "NeoChat",
"Name[ru]": "NeoChat",
"Name[sk]": "NeoChat",
"Name[sl]": "NeoChat",
"Name[sv]": "NeoChat",
"Name[ta]": "நியோச்சாட்",

View File

@@ -27,7 +27,8 @@ QQC2.Menu {
text: "https://matrix.to/#/" + root.connection.localUser.id,
title: root.connection.localUser.displayName,
subtitle: root.connection.localUser.id,
avatarSource: root.connection.makeMediaUrl(root.connection.localUser.avatarUrl)
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
});
if (typeof root.closeDialog === "function") {
root.closeDialog();

View File

@@ -125,7 +125,7 @@ Kirigami.Dialog {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
}
source: userDelegate.connection.localUser.avatarMediaId ? userDelegate.connection.makeMediaUrl("mxc://" + userDelegate.connection.localUser.avatarMediaId) : ""
source: userDelegate.connection.localUser.avatarUrl.toString().length > 0 ? userDelegate.connection.makeMediaUrl(userDelegate.connection.localUser.avatarUrl) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
}

View File

@@ -18,7 +18,7 @@ QQC2.ItemDelegate {
required property NeoChatRoom currentRoom
required property bool categoryVisible
required property string filterText
required property string avatar
required property url avatar
required property string displayName
topPadding: Kirigami.Units.largeSpacing
@@ -32,7 +32,7 @@ QQC2.ItemDelegate {
visible: root.categoryVisible || filterText.length > 0
contentItem: KirigamiComponents.Avatar {
source: root.avatar ? root.currentRoom.connection.makeMediaUrl("mxc://" + root.avatar) : ""
source: root.avatar
name: root.displayName
sourceSize {

View File

@@ -27,38 +27,17 @@ Loader {
Component {
id: regularMenu
QQC2.Menu {
QQC2.MenuItem {
text: room.isFavourite ? i18n("Remove from Favorites") : i18n("Add to Favorites")
icon.name: room.isFavourite ? "bookmark-remove" : "bookmark-new"
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
}
QQC2.MenuItem {
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
icon.name: room.isLowPriority ? "arrow-up-symbolic" : "arrow-down-symbolic"
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
}
QQC2.MenuItem {
text: i18n("Mark as Read")
icon.name: "checkmark"
enabled: room.notificationCount > 0
onTriggered: room.markAllMessagesAsRead()
}
QQC2.MenuItem {
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy"
onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(room.canonicalAlias);
}
}
QQC2.MenuSeparator {}
QQC2.Menu {
title: i18n("Notification State")
title: i18nc("@action:inmenu", "Notifications")
icon.name: "notifications"
QQC2.MenuItem {
@@ -107,6 +86,32 @@ Loader {
}
}
QQC2.MenuItem {
text: room.isFavourite ? i18n("Remove from Favorites") : i18n("Add to Favorites")
icon.name: room.isFavourite ? "rating" : "rating-unrated"
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
}
QQC2.MenuItem {
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
icon.name: room.isLowPriority ? "arrow-up-symbolic" : "arrow-down-symbolic"
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
}
QQC2.MenuSeparator {}
QQC2.MenuItem {
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy"
onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id);
} else {
Clipboard.saveText(room.canonicalAlias);
}
}
QQC2.MenuItem {
text: i18nc("@action:inmenu", "Room Settings")
icon.name: 'settings-configure-symbolic'
@@ -163,7 +168,7 @@ Loader {
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
source: room.avatarMediaId ? root.connection.makeMediaUrl("mxc://" + room.avatarMediaId) : ""
source: room.avatarMediaUrl
name: room.displayName
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3

View File

@@ -5,6 +5,7 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.formcard as FormCard
@@ -177,10 +178,25 @@ Loader {
Repeater {
model: root.actions
QQC2.MenuItem {
visible: modelData.visible
action: modelData
onClicked: root.item.close()
DelegateChooser {
role: "separator"
DelegateChoice {
roleValue: true
QQC2.MenuSeparator {
visible: modelData.visible
}
}
DelegateChoice {
roleValue: false
QQC2.MenuItem {
visible: modelData.visible
action: modelData
onClicked: root.item.close()
}
}
}
}
QQC2.Menu {
@@ -341,15 +357,30 @@ Loader {
id: listViewAction
model: root.actions
FormCard.FormButtonDelegate {
icon.name: modelData.icon.name
icon.color: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
modelData.triggered();
root.item.close();
DelegateChooser {
role: "separator"
DelegateChoice {
roleValue: true
FormCard.FormDelegateSeparator {
visible: modelData.visible
}
}
DelegateChoice {
roleValue: false
FormCard.FormButtonDelegate {
icon.name: modelData.icon.name
icon.color: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
modelData.triggered();
root.item.close();
}
}
}
}
}

View File

@@ -38,7 +38,7 @@ ColumnLayout {
contentItem: KirigamiComponents.Avatar {
name: root.room ? root.room.displayName : ""
source: root.room ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
source: root.room ? root.room.avatarMediaUrl : ""
Rectangle {
visible: root.room.usesEncryption

View File

@@ -42,30 +42,36 @@ DelegateContextMenu {
* Each action will be instantiated as a single line in the menu.
*/
property list<Kirigami.Action> actions: [
DelegateContextMenu.ReplyMessageAction {},
Kirigami.Action {
text: i18n("Open Externally")
separator: true
},
Kirigami.Action {
text: i18nc("@action:inmenu", "Open Image")
icon.name: "document-open"
onTriggered: {
currentRoom.openEventMediaExternally(root.eventId);
}
},
Kirigami.Action {
text: i18n("Save As")
text: i18nc("@action:inmenu", "Save Image…")
icon.name: "document-save"
onTriggered: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
dialog.selectedFile = currentRoom.fileNameToDownload(eventId);
dialog.open();
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId);
}
},
DelegateContextMenu.ReplyMessageAction {},
Kirigami.Action {
text: i18n("Copy")
text: i18nc("@action:inmenu", "Copy Image")
icon.name: "edit-copy"
onTriggered: {
currentRoom.copyEventMedia(root.eventId);
}
},
Kirigami.Action {
separator: true
},
Kirigami.Action {
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
text: i18n("Remove")
@@ -88,7 +94,13 @@ DelegateContextMenu {
},
DelegateContextMenu.ReportMessageAction {},
DelegateContextMenu.ShowUserAction {},
DelegateContextMenu.ViewSourceAction {}
Kirigami.Action {
separator: true
visible: viewSourceAction.visible
},
DelegateContextMenu.ViewSourceAction {
id: viewSourceAction
}
]
/**

View File

@@ -102,7 +102,7 @@ Labs.MenuBar {
}
Labs.MenuItem {
text: i18nc("menu", "About KDE")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDE"))
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
}
}
}

View File

@@ -34,7 +34,7 @@ ColumnLayout {
Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : ""
source: root.room ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
source: root.room ? root.room.avatarMediaUrl : ""
Rectangle {
visible: room.usesEncryption
@@ -75,6 +75,7 @@ ColumnLayout {
textFormat: TextEdit.PlainText
visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
}
}
QQC2.AbstractButton {
@@ -92,7 +93,7 @@ ColumnLayout {
text: barcode.content,
title: root.room ? root.room.displayName : "",
subtitle: root.room ? root.room.id : "",
avatarSource: root.room && root.room.avatarMediaId ? root.room.connection.makeMediaUrl("mxc://" + root.room.avatarMediaId) : ""
avatarSource: root.room ? root.room.avatarMediaUrl : ""
});
map.open();
}

View File

@@ -7,7 +7,6 @@ import QtQuick.Layouts
import QtCore as Core
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kquickimageeditor as KQuickImageEditor
Kirigami.Page {
@@ -168,10 +167,11 @@ Kirigami.Page {
}
}
footer: KirigamiComponents.Banner {
footer: Kirigami.InlineMessage {
id: msg
type: Kirigami.MessageType.Error
showCloseButton: true
visible: false
position: Kirigami.InlineMessage.Position.Header
}
}

View File

@@ -44,7 +44,7 @@ DelegateContextMenu {
},
DelegateContextMenu.ReplyMessageAction {},
Kirigami.Action {
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward")
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward")
icon.name: "mail-forward-symbolic"
onTriggered: {
let page = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
@@ -59,21 +59,33 @@ DelegateContextMenu {
});
}
},
Kirigami.Action {
separator: true
},
DelegateContextMenu.RemoveMessageAction {},
Kirigami.Action {
text: i18n("Copy")
text: i18nc("@action:inmenu", "Copy Text")
icon.name: "edit-copy"
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
},
DelegateContextMenu.ReportMessageAction {},
DelegateContextMenu.ShowUserAction {},
DelegateContextMenu.ViewSourceAction {},
Kirigami.Action {
text: i18n("Copy Link")
text: i18nc("@action:inmenu", "Copy Message Link")
icon.name: "edit-copy"
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
}
},
Kirigami.Action {
separator: true
},
DelegateContextMenu.ReportMessageAction {},
DelegateContextMenu.ShowUserAction {},
Kirigami.Action {
separator: true
visible: viewSourceAction.visible
},
DelegateContextMenu.ViewSourceAction {
id: viewSourceAction
}
]
}

View File

@@ -104,12 +104,14 @@ Components.AlbumMaximizeComponent {
}
}
onOpened: forceActiveFocus()
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
onSaveItem: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
dialog.selectedFile = currentRoom.fileNameToDownload(root.currentEventId);
dialog.open();
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId);
}
Connections {

View File

@@ -12,7 +12,9 @@ Components.AbstractMaximizeComponent {
required property string text
property color avatarColor
required property string avatarSource
required property url avatarSource
onOpened: forceActiveFocus()
Shortcut {
sequences: [StandardKey.Cancel]

View File

@@ -30,9 +30,9 @@ Kirigami.Dialog {
FormCard.AbstractFormDelegate {
background: null
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing * 4
spacing: Kirigami.Units.largeSpacing
Avatar {
source: root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar)
source: SpaceHierarchyCache.recommendedSpaceAvatar.toString().length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : 0
name: SpaceHierarchyCache.recommendedSpaceDisplayName
}
ColumnLayout {
@@ -51,6 +51,7 @@ Kirigami.Dialog {
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Join")
icon.name: "list-add-symbolic"
onClicked: {
SpaceHierarchyCache.recommendedSpaceHidden = true;
RoomManager.resolveResource(SpaceHierarchyCache.recommendedSpaceId, "join");
@@ -58,6 +59,7 @@ Kirigami.Dialog {
}
}
FormCard.FormButtonDelegate {
icon.name: "mail-thread-ignored-symbolic"
text: i18nc("@action:button", "Ignore")
onClicked: {
SpaceHierarchyCache.recommendedSpaceHidden = true;

View File

@@ -21,7 +21,7 @@ Delegates.RoundedItemDelegate {
required property bool hasHighlightNotifications
required property NeoChatRoom currentRoom
required property NeoChatConnection connection
required property string avatar
required property url avatar
required property string subtitleText
required property string displayName
@@ -55,7 +55,7 @@ Delegates.RoundedItemDelegate {
spacing: Kirigami.Units.largeSpacing
AvatarNotification {
source: root.avatar ? root.connection.makeMediaUrl("mxc://" + root.avatar) : ""
source: root.avatar
name: root.displayName
visible: NeoChatConfig.showAvatarInRoomDrawer
implicitHeight: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2)

View File

@@ -99,8 +99,11 @@ Kirigami.OverlayDrawer {
Layout.preferredHeight: pageStack.globalToolBar.preferredHeight
contentItem: RowLayout {
spacing: 0
Kirigami.Heading {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
text: drawerItemLoader.item ? drawerItemLoader.item.title : ""
}

View File

@@ -39,13 +39,16 @@ QQC2.ScrollView {
/**
* @brief The title that should be displayed for this component if available.
*/
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room information")
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room Information")
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView {
id: userList
topMargin: Kirigami.Units.largeSpacing
leftMargin: Kirigami.Units.largeSpacing
rightMargin: Kirigami.Units.largeSpacing
header: ColumnLayout {
id: columnLayout
@@ -57,7 +60,6 @@ QQC2.ScrollView {
Loader {
active: true
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
visible: !root.room.isSpace
sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
onItemChanged: if (item) {
@@ -133,7 +135,7 @@ QQC2.ScrollView {
Delegates.RoundedItemDelegate {
id: leaveButton
icon.name: "arrow-left-symbolic"
text: i18nc("@action:button", "Leave this room")
text: root.room.isSpace ? i18nc("@action:button", "Leave this space") : i18nc("@action:button", "Leave this room")
activeFocusOnTab: true
Layout.fillWidth: true
@@ -209,7 +211,7 @@ QQC2.ScrollView {
section.delegate: Kirigami.ListSectionHeader {
required property string section
width: ListView.view.width
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
text: section
}
@@ -219,11 +221,12 @@ QQC2.ScrollView {
required property int index
required property string name
required property string userId
required property string avatar
required property url avatar
required property int powerLevel
required property string powerLevelString
implicitHeight: Kirigami.Units.gridUnit * 2
width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
text: name

View File

@@ -7,7 +7,6 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
@@ -99,11 +98,12 @@ Kirigami.Page {
}
}
header: KirigamiComponents.Banner {
header: Kirigami.InlineMessage {
id: banner
showCloseButton: true
visible: false
position: Kirigami.InlineMessage.Position.Header
}
Loader {
@@ -253,7 +253,6 @@ Kirigami.Page {
messageComponentType: messageComponentType,
plainText: plainText,
htmlText: htmlText,
isThread: isThread
});
contextMenu.open();
}
@@ -265,7 +264,6 @@ Kirigami.Page {
plainText: plainText,
mimeType: mimeType,
progressInfo: progressInfo,
isThread: isThread
});
contextMenu.open();
}

View File

@@ -182,7 +182,7 @@ QQC2.Control {
id: spaceDelegate
required property string displayName
required property string avatar
required property url avatar
required property string roomId
required property var currentRoom
@@ -191,7 +191,7 @@ QQC2.Control {
Layout.maximumHeight: width - Kirigami.Units.smallSpacing
text: displayName
source: avatar ? root.connection.makeMediaUrl("mxc://" + avatar) : ""
source: avatar
notificationCount: spaceDelegate.currentRoom.childrenNotificationCount
notificationHighlight: spaceDelegate.currentRoom.childrenHaveHighlightNotifications
@@ -219,7 +219,7 @@ QQC2.Control {
visible: SpaceHierarchyCache.recommendedSpaceId.length > 0 && !root.connection.room(SpaceHierarchyCache.recommendedSpaceId) && !SpaceHierarchyCache.recommendedSpaceHidden
text: i18nc("Join <name of a space>", "Join %1", SpaceHierarchyCache.recommendedSpaceDisplayName)
source: SpaceHierarchyCache.recommendedSpaceAvatar.length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : ""
source: SpaceHierarchyCache.recommendedSpaceAvatar.toString().length > 0 ? root.connection.makeMediaUrl(SpaceHierarchyCache.recommendedSpaceAvatar) : ""
onSelected: {
recommendedSpaceDialogComponent.createObject(QQC2.Overlay.overlay, {
connection: root.connection

View File

@@ -62,7 +62,7 @@ ColumnLayout {
onClicked: _private.createRoom(root.currentRoom.id)
}
QQC2.Button {
text: i18nc("@button", "Leave the space")
text: i18nc("@action:button", "Leave this space")
icon.name: "go-previous"
onClicked: RoomManager.leaveRoom(root.currentRoom)
}

View File

@@ -96,7 +96,7 @@ Loader {
KirigamiComponents.Avatar {
id: avatar
source: room.avatarMediaId ? root.room.connection.makeMediaUrl("mxc://" + room.avatarMediaId) : ""
source: room.avatarMediaUrl
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop

View File

@@ -7,7 +7,6 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
@@ -20,11 +19,12 @@ FormCard.FormCardPage {
leftPadding: 0
rightPadding: 0
header: KirigamiComponents.Banner {
header: Kirigami.InlineMessage {
id: banner
showCloseButton: true
visible: false
type: Kirigami.MessageType.Error
position: Kirigami.InlineMessage.Position.Header
}
property SSSSHandler ssssHandler: SSSSHandler {

View File

@@ -75,6 +75,8 @@ Kirigami.Dialog {
QQC2.AbstractButton {
Layout.minimumHeight: avatar.height * 0.75
Layout.maximumHeight: avatar.height * 1.5
Layout.maximumWidth: avatar.height * 1.5
contentItem: Barcode {
id: barcode
barcodeType: Barcode.QRCode

View File

@@ -27,7 +27,7 @@ RowLayout {
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 2 : -1
Layout.minimumHeight: bottomEdge ? Kirigami.Units.gridUnit * 3 : -1
onVisibleChanged: {
if (!visible) {
@@ -37,14 +37,15 @@ RowLayout {
}
KirigamiComponents.AvatarButton {
id: accountButton
readonly property string mediaId: root.connection.localUser.avatarMediaId
readonly property url avatarUrl: root.connection.localUser.avatarUrl
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
Layout.leftMargin: Kirigami.Units.largeSpacing
text: i18n("Edit this account")
source: mediaId ? root.connection.makeMediaUrl("mxc://" + mediaId) : ""
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(avatarUrl) : ""
name: root.connection.localUser.displayName
activeFocusOnTab: true

View File

@@ -134,16 +134,15 @@ void RoomManager::activateUserModel()
m_userListModel->activate();
}
UriResolveResult RoomManager::resolveResource(const Uri &uri)
{
return UriResolverBase::visitResource(m_connection, uri);
}
void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
{
Uri uri{idOrUri};
resolveResource(Uri{idOrUri}, action);
}
void RoomManager::resolveResource(Uri uri, const QString &action)
{
if (!uri.isValid()) {
Q_EMIT showMessage(MessageType::Warning, i18n("Malformed or empty Matrix id<br />%1 is not a correct Matrix identifier", idOrUri));
Q_EMIT showMessage(MessageType::Warning, i18n("Malformed or empty Matrix id<br />%1 is not a correct Matrix identifier", uri.toDisplayString()));
return;
}
@@ -237,7 +236,6 @@ void RoomManager::loadInitialRoom()
}
if (m_isMobile) {
// We still need to remember the last space on mobile
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
// We don't want to open a room on startup on mobile
return;
@@ -260,6 +258,7 @@ void RoomManager::openRoomForActiveConnection()
setCurrentSpace({}, false);
return;
}
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
const auto &lastRoom = m_lastRoomConfig.readEntry(m_connection->userId(), QString());
if (lastRoom.isEmpty() || !m_connection->room(lastRoom)) {
setCurrentRoom({});
@@ -267,7 +266,6 @@ void RoomManager::openRoomForActiveConnection()
m_currentRoom = nullptr;
resolveResource(lastRoom);
}
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
}
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
@@ -315,7 +313,9 @@ void RoomManager::visitRoom(Room *r, const QString &eventId)
// It's important that we compare room *objects* here, not just room *ids*, since we need to deal with the object changing when going invite -> joined
if (m_currentRoom && m_currentRoom == room) {
Q_EMIT goToEvent(eventId);
if (!eventId.isEmpty()) {
Q_EMIT goToEvent(eventId);
}
} else {
setCurrentRoom(room->id());
}

View File

@@ -168,16 +168,6 @@ public:
UserListModel *userListModel() const;
Q_INVOKABLE void activateUserModel();
/**
* @brief Resolve the given URI resource.
*
* @note It's actually Quotient::UriResolverBase::visitResource() but with Q_INVOKABLE
* and the connection grabbed from RoomManager.
*
* @sa Quotient::UriResolverBase::visitResource()
*/
Q_INVOKABLE UriResolveResult resolveResource(const Uri &uri);
/**
* @brief Resolve the given resource.
*
@@ -188,6 +178,16 @@ public:
*/
Q_INVOKABLE void resolveResource(const QString &idOrUri, const QString &action = {});
/**
* @brief Resolve the given resource URI.
*
* @note It's actually Quotient::UriResolverBase::visitResource() but with Q_INVOKABLE
* and the connection grabbed from RoomManager.
*
* @sa Quotient::UriResolverBase::visitResource()
*/
Q_INVOKABLE void resolveResource(Uri uri, const QString &action = {});
bool hasOpenRoom() const;
/**

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