Compare commits

..

186 Commits

Author SHA1 Message Date
Torrie Fischer
e8f72a44fb page: welcomepage: add some icons for style points 2022-10-10 14:12:54 +02:00
Torrie Fischer
6e0aa7f683 page: welcomepage: animations++ 2022-10-10 14:12:54 +02:00
Torrie Fischer
afa1ec6a4d component: login: loading: Use a fancy LoadingPlaceholder instead of boring static text 2022-10-10 14:12:23 +02:00
Torrie Fischer
6e20a46525 page: welcomepage: display the users avatar when logging in 2022-10-10 14:12:18 +02:00
l10n daemon script
eba6c1faaa GIT_SILENT Sync po/docbooks with svn 2022-10-10 02:00:16 +00:00
James Graham
d00e122d88 Rework fullscreen image
As discussed in network/neochat#161, when clicking the image it now only covers the neochat window. A modal popup that covers the neochat window is now used. The app behind get dimmed.

Left clicking anywhere closes the preview as well as the using the close button. Right clicking on the image itself still gives the image's context menu.

Before
![fullscreenimage_before](/uploads/f7a64ab2f0b75405f3f0a16f32c029f3/fullscreenimage_before.png)

After
![fullscreenimage_updated2](/uploads/8feb6c79891019203a6a0a8439c71b70/fullscreenimage_updated2.png)

Latest
![fullscreenimage_updated_final](/uploads/61ca4c1251b914ae3a6bdd158f4dc396/fullscreenimage_updated_final.png)

Closes network/neochat#161
2022-10-09 16:27:51 +00:00
l10n daemon script
a761d36abd GIT_SILENT Sync po/docbooks with svn 2022-10-09 02:09:16 +00:00
Tobias Fella
22448ea9ae Clean up includes 2022-10-08 19:03:49 +02:00
Tobias Fella
6756e1fd45 Remove unused includes from main.cpp 2022-10-08 12:27:37 +02:00
l10n daemon script
005580dcea GIT_SILENT Sync po/docbooks with svn 2022-10-08 02:04:36 +00:00
James Graham
7cfc0e24e2 Fix Section After Read Marker Removal
After the readmarker is removed it leaves a gap in the indexs until the model is refreshed.
Add check to ignore the gap for the purpose of show author and show section roles.

Closes network/neochat#295

BUG: 454885
2022-10-07 13:32:05 +00:00
Tobias Fella
a6a152acdc Also fix video resolution in qt5
Replaces !395

Fixes #120
2022-10-07 09:56:08 +00:00
l10n daemon script
4ddf614108 GIT_SILENT Sync po/docbooks with svn 2022-10-07 02:14:21 +00:00
Tobias Fella
3ae6a0266d Fix compilation against libQuotient 0.6 2022-10-06 12:50:04 +02:00
Tobias Fella
c081b42fd4 Use non-deprecated name version of accessing the accountregistry 2022-10-06 12:47:42 +02:00
Tobias Fella
0dcd71af53 Compiler warnings-- 2022-10-06 12:44:03 +02:00
Tobias Fella
eee96bc462 Refactor and clean up spaces 2022-10-06 10:38:20 +00:00
l10n daemon script
932c3e10fe GIT_SILENT Sync po/docbooks with svn 2022-10-05 01:59:59 +00:00
James Graham
81bbfce7cd Fix bubble header in right to left mode
Convert to rowLayout so that the bubble header works in right to left mode.

Before
![bubble_header_righttoleft_before](/uploads/91d9b5e41a7f84f8ab2750d1193ed4b6/bubble_header_righttoleft_before.png)

After
![bubble_header_righttoleft_after](/uploads/abf8018cf9e103f3d32dfb686efa946f/bubble_header_righttoleft_after.png)
2022-10-04 18:15:15 +00:00
James Graham
1a3befef36 Add highlight on goToEvent
Add a temporary highlight when the goToEvent is triggered.

This implementation also alows a temporary highlight of an item at anytime if the timeline container's isTemporaryHighlighted property is set to true.

Closes network/neochat#34
2022-10-04 18:07:54 +00:00
Bharadwaj Raju
cd7232e7bf Replace links with HTML <a> tags when messages have a formatted_body and in all other cases
This makes (https) links show up as actual links when they're in a message with a `formatted_body`. For example, the pursuivant messages in any KDE channel (like #kirigami), or links which are in a reply to another message/image.

Also corrected a regex mistake in another place using the same link replacement regex.

Fix #331
2022-10-04 09:38:29 +00:00
l10n daemon script
f6609f55f8 GIT_SILENT Sync po/docbooks with svn 2022-10-04 02:06:09 +00:00
Tobias Fella
17a36f1959 Revert "Busy Indicator when Backfilling Room"
This reverts commit 0514a52ff1.
2022-10-03 15:27:46 +02:00
l10n daemon script
a4e16ad3f1 GIT_SILENT Sync po/docbooks with svn 2022-10-03 02:45:10 +00:00
James Graham
0514a52ff1 Busy Indicator when Backfilling Room
Add busy indicator when the user scrolls to the last available message if more are being loaded.

Closes network/neochat#48
2022-10-02 12:33:26 +00:00
l10n daemon script
8c3666ac74 GIT_SILENT Sync po/docbooks with svn 2022-10-01 02:03:07 +00:00
l10n daemon script
761ec0f1cb 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"
2022-10-01 01:54:38 +00:00
l10n daemon script
b5d7173e88 GIT_SILENT made messages (after extraction) 2022-10-01 00:48:07 +00:00
James Graham
7b81af43b1 Timeline Container Highlight Update
Moves the highlight to the timeline container object itself so the whole delegate is highlighted. This was suggested for compact mode but I also left it visible for bubble as I think it still works and adds some user feedback.

Fine tunes the hover actions as follows:
- In compact mode as long as the screen is wide enough they are inset further so they don't overlap the up and down buttons.
- In bubble mode for a user message on the right they are inset slightly further so that they don't overlap the scrollbar

Fine tuned the spacing around the avatar and highlight.

Based upon the suggestions here network/neochat#550

Bubble Before - no highlight can be seen
![bubble_highlight_before](/uploads/4417d80d62881ff7256bd18a1760002e/bubble_highlight_before.png)

Bubble After
![bubble_highlight_after](/uploads/817e8c749809bf07362af4c9dcdf7087/bubble_highlight_after.png)

Compact Before
![compact_highlight_before](/uploads/aada5d39cfd69563753e5048bd4c248d/compact_highlight_before.png)

Compact After
![compact_highlight_after](/uploads/d3b6c066e86910e7cd2162a970d77ca6/compact_highlight_after.png)
2022-09-30 15:02:19 +00:00
l10n daemon script
6bf69ae77e GIT_SILENT Sync po/docbooks with svn 2022-09-30 01:53:48 +00:00
Tobias Fella
055deb4115 Add back button to SSO login 2022-09-29 16:20:57 +00:00
James Graham
058e8034d9 Make sure that contentMaxWidth is used instead of bubbleMaxWidth for the message delegate. 2022-09-29 16:10:50 +00:00
Tobias Fella
17240ea8a6 Don't show url for matrix.to links
Since we open these in neochat directly

BUG: 457835
2022-09-29 16:05:11 +00:00
Tobias Fella
727842df4b Close RoomDrawer when opening userdetail while modal
BUG: 457890
2022-09-29 16:00:10 +00:00
l10n daemon script
04182f98f2 GIT_SILENT Sync po/docbooks with svn 2022-09-29 01:54:38 +00:00
l10n daemon script
4aff25347f GIT_SILENT Sync po/docbooks with svn 2022-09-28 02:04:13 +00:00
James Graham
f8e57d60f5 Fix margins for images and videos
This patch makes it so that the margins for images and videos is the same as other components.
2022-09-27 17:18:43 +00:00
Bhushan Shah
2839d44ea8 GIT_SILENT: bump version to 22.09 2022-09-27 20:53:11 +05:30
l10n daemon script
5f32ae79c1 GIT_SILENT Sync po/docbooks with svn 2022-09-27 01:50:19 +00:00
Tobias Fella
0fc94310c3 Implement reporting events
BUG: 458856
2022-09-26 14:38:45 +00:00
l10n daemon script
7f26651edf GIT_SILENT Sync po/docbooks with svn 2022-09-26 01:58:14 +00:00
l10n daemon script
36880d001c GIT_SILENT Sync po/docbooks with svn 2022-09-25 01:53:43 +00:00
Tobias Fella
e0e289d424 Fix dialogbuttonbox 2022-09-24 15:28:17 +02:00
Tobias Fella
b79956871f Fix build without E2EE 2022-09-24 15:27:19 +02:00
l10n daemon script
917f77152d GIT_SILENT Sync po/docbooks with svn 2022-09-24 01:53:35 +00:00
l10n daemon script
e28d1918f5 GIT_SILENT Sync po/docbooks with svn 2022-09-23 01:50:01 +00:00
Tobias Fella
149b11ba6f Set application domain before using any translated strings 2022-09-23 00:42:22 +02:00
Tobias Fella
c007961ef6 Fix some compilation warnings 2022-09-23 00:21:08 +02:00
James Graham
ef40f5a747 Fix Stickers
Fix so that stickers are rendered and that the context menu works for them.
2022-09-22 19:03:06 +00:00
Bharadwaj Raju
37780c2e3b Link Previews
Uses Matrix's preview API to generate embedded link previews.

Only title and description for now.

![image](/uploads/2c5d632480073fe54345cdbe22ea54dc/image.png)
2022-09-22 15:42:53 +00:00
James Graham
53b9f42399 Fix Slash Commands in the Middle of a Message
Make sure that a slash command only activates when the slash is at the beginning of the message.

BUG: 457474
2022-09-22 15:11:56 +00:00
l10n daemon script
d2ed1cfb2e GIT_SILENT Sync po/docbooks with svn 2022-09-22 01:51:02 +00:00
Carl Schwan
c7d4b1a529 Update Flatpak.cmake 2022-09-21 12:07:44 +00:00
Carl Schwan
a8045f2134 Install emoji fonts to correct location
See https://wiki.archlinux.org/title/font_configuration#Fontconfig_configuration
and https://invent.kde.org/network/neochat/-/issues/341#note_525953
2022-09-21 12:07:44 +00:00
Tobias Fella
b8262fef92 Implement device verification 2022-09-21 11:49:11 +00:00
l10n daemon script
3071901a47 GIT_SILENT Sync po/docbooks with svn 2022-09-21 01:54:16 +00:00
l10n daemon script
396260549a GIT_SILENT Sync po/docbooks with svn 2022-09-20 01:55:30 +00:00
l10n daemon script
50c2de8d52 GIT_SILENT Sync po/docbooks with svn 2022-09-19 01:52:03 +00:00
Ahmad Samir
d4e067d57c Use KDE_INSTALL_TARGETS_DEFAULT_ARGS
The KF_* variant is for KF repos

GIT_SILENT
2022-09-17 00:45:00 +02:00
l10n daemon script
3797b0129a GIT_SILENT Sync po/docbooks with svn 2022-09-15 01:50:41 +00:00
l10n daemon script
59b4146c63 GIT_SILENT Sync po/docbooks with svn 2022-09-14 01:50:01 +00:00
Bharadwaj Raju
c76524d540 Fix CI tripping on .reuse/dep5
The dep5 parser in the suse_tumbleweed_qt515 CI doesn't like duplicate Copyright entries.

cc @carlschwan
2022-09-13 13:40:54 +00:00
Bharadwaj Raju
10030efd08 Fix text selection with context menu
Make the context menu actually work with selected text in a message.
2022-09-13 10:04:45 +00:00
l10n daemon script
918bd5439c GIT_SILENT Sync po/docbooks with svn 2022-09-13 01:50:39 +00:00
Bharadwaj Raju
e3ff50bbe8 Use Kirigami LoadingPlaceholder everywhere
Replace the custom Placeholder + BusyIndicator with the new LoadingPlaceholder component.

Also remove the "Loading…" page title on first load, as that is redundant to the existing front-and-center "Loading…" message and hence doesn't look nice.
2022-09-12 20:41:23 +00:00
Bharadwaj Raju
76edc858aa Add proper tooltip delays
Split from !521
2022-09-12 20:35:40 +00:00
Bharadwaj Raju
2a2c117ac1 Use selected text colors from theme
When using Breeze light:

| Before | After |
| ------ | ------ |
| ![Screenshot_20220913_005141](/uploads/9b15c79acaaa92e23855def9ca6bc6ab/Screenshot_20220913_005141.png) | ![Screenshot_20220913_005049](/uploads/06cb614edae21f7ddb027bebaf732edc/Screenshot_20220913_005049.png) |
2022-09-12 20:34:47 +00:00
l10n daemon script
d3e4640af9 GIT_SILENT Sync po/docbooks with svn 2022-09-12 01:49:03 +00:00
James Graham
7e1f0f4ea7 PushRule RoomList Room Menu Option
Add an option to the roomlist room menu to set the room push rule override.

Rename default to "follow global setting" for clarity.
2022-09-11 10:29:05 +00:00
l10n daemon script
80bf279321 GIT_SILENT Sync po/docbooks with svn 2022-09-11 01:49:10 +00:00
l10n daemon script
6eb4b8c9d2 GIT_SILENT Sync po/docbooks with svn 2022-09-10 01:49:47 +00:00
James Graham
4bba505da6 Initial work to add push rule support
This commit adds the ability to set the master push rule and set push rules for individual rooms as per the matrix spec. See https://spec.matrix.org/v1.3/client-server-api/#push-rules.

The master push rule is just on/off and uses the existing notification setting in general setting to enable/disable the server default master push rule .m.rule.master.

For each room there is now a page in the room setting that allows the following to be set:
- Default
- All messages
- @mentions and keywords
- off

New room or override rules are added/removed to achieve this.

There is also functionality to check the master/room notification state whenever the setting menu is entered. This allows the status to be updated if changed in another client or get the initial state for a room as it isn't stored.

Note - There is currently no menu items in the room list for setting the room push rule settings. This will be added in a later commit, the aim is to focus on making sure the technical implementation is good for now.
2022-09-09 16:41:03 +00:00
l10n daemon script
c2fc4e44a7 GIT_SILENT Sync po/docbooks with svn 2022-09-09 01:54:13 +00:00
Tobias Fella
274bf824e3 Don't quote property names 2022-09-09 00:23:18 +02:00
Tobias Fella
d70a8a652a Always use system-style includes for libQuotient 2022-09-09 00:16:39 +02:00
Jan Bidler
8c436075d8 Changes room access into radio buttons 2022-09-08 17:31:37 +00:00
James Graham
828032ba06 RoomDrawer Cleanup
This started off as me just wanting to adjust the margin of the search bar but grew a touch ...

- Cleanup margins especially make the member search bar have the same padding as everything else
- Move room information title to header and align all buttons right.
- Make the room name actually bold
- Remove highlight from the user list item after the menu is closed
- Always use actual width so that the drawer isn't wider when modal
- Use control instead of pane for the search bar as the padding works more consistently
- Use BasicListItem instead of AbstractListItem for the member list as this has the layout predefined

![image](/uploads/ab2c4066479b0510bcb2fe6ae91f7bc3/image.png)
2022-09-08 17:30:58 +00:00
l10n daemon script
c30abfefa9 GIT_SILENT Sync po/docbooks with svn 2022-09-08 01:56:22 +00:00
Jan Bidler
5d4efad2f8 Make "show avatar" in settings localizable 2022-09-07 13:55:26 +00:00
l10n daemon script
3a391aafe8 GIT_SILENT Sync po/docbooks with svn 2022-09-07 01:53:26 +00:00
l10n daemon script
7b5b8197bc 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"
2022-09-07 01:48:36 +00:00
Jan Bidler
55659e488f unify custom emojis text 2022-09-06 19:42:53 +00:00
Jan Bidler
c2c235eff1 Use " in all i18n functions instead of ' 2022-09-06 19:42:53 +00:00
l10n daemon script
f6f4edec86 GIT_SILENT Sync po/docbooks with svn 2022-09-06 01:51:42 +00:00
Nicolas Fella
55847cb9cc Refactor window handling code
Currently when we want to show/raise the window in reaction to the tray icon/notification being clicked etc we do this by emitting a signal on the controller.
This is connected to in main.qml, which does some things, then calls back to controller to do more things.

This is quite convoluted. Instead introduce a new class WindowController that is responsible for all things window, in particular showing/raising and config saving
2022-09-05 19:27:55 +00:00
Nicolas Fella
736c4b02ed Don't call show() on visible windows
It messes with the geometry
2022-09-05 19:27:55 +00:00
Nicolas Fella
4cf5b516d0 Extract code for obtaining a window from the QML engine into a function 2022-09-05 19:27:55 +00:00
Nicolas Fella
c379a7fa27 Fix Android packaging
In the AndroidManifest we specify 'neochat' as the executable

That causes the lib to be used instead of the executable, which breaks things

Shuffle around the names so that the executable is picked
2022-09-05 18:42:27 +00:00
Tobias Fella
2318fb95d9 Adapt to libQuotient API changes 2022-09-05 20:37:55 +02:00
James Graham
14e57e7833 Fix show author after state regression
The avatar should be shown after a state message

Fixes network/neochat#553
2022-09-03 14:33:58 +00:00
l10n daemon script
8c9ea72a9b GIT_SILENT Sync po/docbooks with svn 2022-09-02 01:50:06 +00:00
l10n daemon script
efd3f2e8d0 GIT_SILENT Sync po/docbooks with svn 2022-08-30 01:49:54 +00:00
l10n daemon script
ea70782771 GIT_SILENT Sync po/docbooks with svn 2022-08-28 01:50:03 +00:00
James Graham
6cca3dbe3a Add the right click menu for image delegates in fullscreen mode
Add the right click menu for image delegates in fullscreen mode

BUG: 455147
2022-08-27 11:52:45 +00:00
l10n daemon script
696b34e094 GIT_SILENT Sync po/docbooks with svn 2022-08-27 01:57:39 +00:00
l10n daemon script
6ce74bbd0c GIT_SILENT made messages (after extraction) 2022-08-27 00:47:18 +00:00
Carl Schwan
3c7e85fbbf Fix loading state detection in devices page
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-08-26 23:39:05 +02:00
Carl Schwan
6c6e408497 Cleanup account page
- Fix small padding on the right when they is not enough elements for a
  scrollbar to appear (possibly due to a kirigami regression)
- Move account editor to a seperate page
- Cleanup a bit the code style
- Add tooltip to toolbuttons

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-08-26 23:29:29 +02:00
Jan Bidler
1ac79273b6 Fix emoji add button being unavailable 2022-08-26 20:35:09 +00:00
Tobias Fella
d4d99284cc Correctly hide spoiler links
BUG: 458311
2022-08-26 22:08:22 +02:00
Tobias Fella
25226aa61f Don't save connection state when destructing the controller
This causes neochat to crash and is done automatically by the connections

BUG: 458353
2022-08-26 22:00:38 +02:00
James Graham
6748a2d21d Fix the reply an edit text being shown in all chat rooms when multiple windows are open
Fix the reply an edit text being shown in all chat rooms when multiple windows are open. This is done by changing chatBoxHelper from a singleton to being instantiated for each instance of roompage.

BUG: 454963
2022-08-26 19:33:20 +00:00
Carl Schwan
fdb424e65e Improve contrast of new notification background
This is required to be accessible

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2022-08-26 21:24:07 +02:00
Jan Bidler
8cd0b12c4a Make typing indocator anchor on the left 2022-08-26 19:09:45 +00:00
James Graham
d133c4fab7 Improve showAuthor behaviour
2 changes to showAuthor role; the first removes the same eventRole condition as currently a video or image followed by text will both show the avatar, second is to fix it so that the avatar is actually shown if messages are 10 mins apart as this is currently broken.
2022-08-26 19:00:02 +00:00
Jan Bidler
2f8303348b Make NeoChat room a link 2022-08-26 18:43:56 +00:00
Tobias Fella
7dfac8a9f7 Fix audio playback
Since the preparation for encrypted events landed, audio playback was broken since QtMultimedia (gstreamer)
doesn't use our custom QNAM. Instead of letting QtMultimedia download the media, we thus need to manually download it
and point QML Audio to the local file. On the positive side, this also allows encrypted Audio files to be played and enables us to seek in the audio delegate :)
It does however mean that we do need to do an annoying bit of manual state management.

BUG: 457687
2022-08-26 18:37:18 +00:00
Jan Bidler
3c98a8fac4 Make clearing reply not remove your text 2022-08-26 18:29:19 +00:00
Jan Bidler
57b1bb659c Remove normal PageUp and PageDown from switching rooms 2022-08-26 18:22:17 +00:00
Jan Bidler
da8c8c48bc Fix CTRL+Page moving into wrong direction 2022-08-26 18:22:17 +00:00
Tobias Fella
07c5cd8016 Consider nested space when populating space hierarchy 2022-08-26 20:21:35 +02:00
Jan Bidler
a23ef130ca Make unread messages use an ascii circle 2022-08-26 12:33:43 +00:00
Jan Bidler
2f4116796a Rework notification colors 2022-08-26 12:33:43 +00:00
Tobias Fella
f004f9e3c8 Fix build 2022-08-26 12:52:46 +02:00
l10n daemon script
9a3726f9ff GIT_SILENT Sync po/docbooks with svn 2022-08-26 01:50:05 +00:00
Snehit Sah
91d1f6ffeb Show spaces horizontal bar
### Summary

This merge request adds a horizontal bar at top of room list, which shows spaces. By clicking on a space, user can filter out rooms belonging only to that specific space.

### Pending/ Help needed

#### Segfault when loading active connection on startup

Refer `void SortFilterRoomListModel::cacheSpaceHierarchy()` ([link](8c372800d7 (b969e462c30df43ef3714ea441948d8d8027f6a0_117_126))) in `src/sortfilterroomlistmodel.cpp`.

On [line 129](8c372800d7 (b969e462c30df43ef3714ea441948d8d8027f6a0_117_129)), I have called `connection->allRooms()`, which segfaults if the active connection hasn't been loaded yet. Is there a way to ensure that `Controller::instance().activeConnection()` on line 128 waits till connection is loaded?

#### Avatars

Avatars on space horizontal bar aren't aligned to vertical middle. I'll need help with that. 

Using the code below doesn't help with padding

```qml
delegate: QQC2.Control {
    topPadding: 10
    contentItem: Kirigami.Avatar { ..... }
}
```

This complains about uninitialized properties in `Kirigami.Avatar`. (`id`, `currentRoom`, `avatar`, `index` are properties utilized during run time)

After we get around these two issue, this MR will be ready from my side.
2022-08-25 13:46:09 +00:00
Tobias Fella
cd895f1b06 QML warnings -= 2 2022-08-23 21:24:41 +02:00
Tobias Fella
fead1a69b3 QML warning-- 2022-08-23 21:22:02 +02:00
James Graham
63af4cae77 Fix timeline layout for non text messages
Change the target of the hover component and delegate layout to timeline container so it applies to all delegates not just text messages.

BUG: 457689
2022-08-22 17:38:50 +00:00
l10n daemon script
89090690b4 GIT_SILENT Sync po/docbooks with svn 2022-08-21 01:56:52 +00:00
l10n daemon script
6a16334eab GIT_SILENT Sync po/docbooks with svn 2022-08-20 01:49:36 +00:00
l10n daemon script
5b05622058 GIT_SILENT Sync po/docbooks with svn 2022-08-16 01:54:13 +00:00
l10n daemon script
1eb705c17f GIT_SILENT Sync po/docbooks with svn 2022-08-14 01:52:46 +00:00
l10n daemon script
f9ad0e8426 GIT_SILENT Sync po/docbooks with svn 2022-08-13 01:50:50 +00:00
l10n daemon script
ef1ff04ef2 GIT_SILENT Sync po/docbooks with svn 2022-08-10 01:57:08 +00:00
James Graham
9b54aff3d5 Mark unread messages in the room as read when all messages are visible
This is an alternative to network/neochat!467. When discussing in the matrix channel this option seemed more popular so I implemented it.

Mark the unread messages in the room as read when all messages are visible to the user after a short timer. This happens on entry and when new messages come in as long as Neochat is active. If neochat isn't active, the room hasn't loaded or the read marker hasn't loaded the timer is for this is reset until all 3 conditions are false.
2022-08-09 16:42:32 +00:00
l10n daemon script
52a093d449 GIT_SILENT Sync po/docbooks with svn 2022-08-09 01:51:02 +00:00
Nate Graham
619369e148 Add hackaround for Qt bug to all non-horizontally-scrollable scrollviews
https://bugreports.qt.io/browse/QTBUG-83890 has been open for years with
a patch that's been stalled for years. There's no indication that it's
going to be fixed anytime soon, and it generates bug reports for us.
Let's add the typical hackaround for all non-horizontally-scrollable
scrollviews.

BUG: 457584
2022-08-08 15:02:09 -06:00
l10n daemon script
e63a9a9be1 GIT_SILENT Sync po/docbooks with svn 2022-08-08 01:58:51 +00:00
l10n daemon script
5c97e67404 GIT_SILENT Sync po/docbooks with svn 2022-08-04 01:52:39 +00:00
l10n daemon script
84a265b7f9 GIT_SILENT Sync po/docbooks with svn 2022-07-31 01:58:40 +00:00
l10n daemon script
61201a7097 GIT_SILENT Sync po/docbooks with svn 2022-07-29 01:56:35 +00:00
Snehit Sah
b9630ad2f2 Add Flatpak CI Support
Signed-off-by: Snehit Sah <snehitsah@protonmail.com>
2022-07-27 10:01:35 +00:00
l10n daemon script
179a201113 GIT_SILENT Sync po/docbooks with svn 2022-07-24 01:50:54 +00:00
l10n daemon script
6eef58e57d GIT_SILENT Sync po/docbooks with svn 2022-07-23 01:55:00 +00:00
l10n daemon script
94f325609a GIT_SILENT Sync po/docbooks with svn 2022-07-20 01:49:27 +00:00
l10n daemon script
a40ba493b6 GIT_SILENT Sync po/docbooks with svn 2022-07-19 01:46:04 +00:00
Tobias Fella
916e7465f1 Don't show a link preview for empty links
When hovering over a link without a target, qt5 will report the link target to be "1", which is wrong.
To work around this, we manually check if the link is "1" and if it is, we discard it.
In theory, this means that we won't get a preview for any link that actually *is* "1", but why would any link be "1"?

It's not worth reporting this to Qt since it seems fixed in Qt6

BUG: 456877
2022-07-18 21:18:48 +02:00
Tobias Fella
8257a9d65e Refactor delegates and improve context menu opening
This unifies the context menu opening and makes sure that clicking *anywhere* on the delegate opens the context menu, not just on the content
2022-07-18 20:12:26 +02:00
l10n daemon script
a75048761b GIT_SILENT Sync po/docbooks with svn 2022-07-18 01:47:32 +00:00
Carl Schwan
f21822aba7 Fix checkbox label on small screen
See https://invent.kde.org/frameworks/qqc2-desktop-style/-/merge_requests/173
2022-07-17 14:46:18 +00:00
l10n daemon script
75eb5a51af GIT_SILENT Sync po/docbooks with svn 2022-07-17 01:54:19 +00:00
l10n daemon script
7e37c31011 GIT_SILENT made messages (after extraction) 2022-07-17 00:45:21 +00:00
l10n daemon script
f80039a5c4 GIT_SILENT Sync po/docbooks with svn 2022-07-16 01:56:39 +00:00
James Graham
400d86a1e9 Precise time on hover
Implement showing a long datetime as a tooltip when hovering over the timestamp for a message. The timestamp is also moved to just after the username of the poster this means it will not be under the hover buttons unless the message is very short.

Also some cleanup of the alignment of items in the bubble. The reply text is now indented the same amount as the username and message and the padding isn't removed from the username and message when avatars are disabled.

Implements: network/neochat#223
2022-07-15 15:39:33 +00:00
l10n daemon script
b20b1c10d0 GIT_SILENT Sync po/docbooks with svn 2022-07-15 01:55:09 +00:00
Tobias Fella
a37fd0713f Convert C++ parts into a static library
This allows us to start adding unit tests
2022-07-14 15:27:29 +00:00
Tobias Fella
b1581a54d1 Allow sending encrypted messages if build supports it 2022-07-14 16:59:37 +02:00
l10n daemon script
ded60b906b GIT_SILENT Sync po/docbooks with svn 2022-07-14 01:58:39 +00:00
James Graham
6957dd0fa2 Apply margin in SectionDelegate both for Compact and Bubble mode 2022-07-13 17:27:13 +00:00
Tobias Fella
7de4014b28 Update formatting 2022-07-13 17:27:13 +00:00
James Graham
63e7ec1bd7 Spell like an American 2022-07-13 17:27:13 +00:00
James Graham
f24428fab3 Apply margin in ReadMarkerDelegate both for Compact and Bubble mode 2022-07-13 17:27:13 +00:00
James Graham
c3a5a767c2 Fix alignment of hover buttons when user message on the right is thin. In this case the buttons sould align to the right of the message not the left so they don't go off screen. 2022-07-13 17:27:13 +00:00
James Graham
5fb311b509 Implement new delegate behaviour on ReadMarkerDelegate
Make sure that the messgae hover buttons account for the delegate x displacement
2022-07-13 17:27:13 +00:00
James Graham
f0d832f756 Make sure extra width is never less than 0 2022-07-13 17:27:13 +00:00
James Graham
a7c137ca39 Allow the delegate and bubble widths to grow when the ListView is very wide.
Disable user message on the right setting when in compact mode as it doesn't work anyway.
2022-07-13 17:27:13 +00:00
James Graham
a96e8958c9 Centre the timline when using bubbles but not in compact mode 2022-07-13 17:27:13 +00:00
James Graham
7dc951d2cd Support user messages on the right even when wide
Limit maximum delegate width to ensure that the gap between user and non-user messages isn't too large
2022-07-13 17:27:13 +00:00
Tobias Fella
c3ee277ede Fix opening room using touch
The previous fix wasn't enough for non-mobile touch devices. Now, we limit the TapHandler to mouse instead
2022-07-13 14:59:16 +02:00
Tobias Fella
78d62e9376 Revert "Disable opening context menu by right-clicking on mobile"
This reverts commit 51efecaa25.
2022-07-13 14:55:55 +02:00
l10n daemon script
11e9eaf3e9 GIT_SILENT Sync po/docbooks with svn 2022-07-13 01:52:14 +00:00
James Graham
4337d0d5d8 Removing all \n is incorrect as these are used to show linebreaks in the html formatted body. Instead use the CMARK_OPT_HARDBREAKS options so these softbreaks in the markdown string are converted to hard breaks <br/> in the html.
Also remove 2 step process to replace <!-- raw HTML omitted --> and straight replace with "" to ensure no real breaks are removed
2022-07-12 14:03:21 +00:00
l10n daemon script
a07537367f GIT_SILENT Sync po/docbooks with svn 2022-07-12 01:55:45 +00:00
Tobias Fella
51efecaa25 Disable opening context menu by right-clicking on mobile
Apparently TapHandlers interpret a tap as a right click, which causes rooms to not open reliably
2022-07-11 12:41:27 +02:00
l10n daemon script
830a47c5ff GIT_SILENT Sync po/docbooks with svn 2022-07-11 01:57:02 +00:00
Tobias Fella
24748d42d8 Port C++ to Qt6
QML is still broken
2022-07-10 21:43:57 +00:00
l10n daemon script
19fe439e95 GIT_SILENT Sync po/docbooks with svn 2022-07-10 01:57:44 +00:00
Tobias Fella
2bcd7118f4 Ensure that text isn't formatted in context menu 2022-07-09 23:01:13 +02:00
l10n daemon script
27e660178e GIT_SILENT Sync po/docbooks with svn 2022-07-09 01:57:03 +00:00
Tobias Fella
e0df553a72 Remove unused imports & includes 2022-07-08 13:16:07 +02:00
l10n daemon script
53f040cb28 GIT_SILENT Sync po/docbooks with svn 2022-07-08 01:54:23 +00:00
Nicolas Fella
28cc7cf616 Add FreeBSD CI 2022-07-07 12:51:33 +02:00
l10n daemon script
d224df8aa2 GIT_SILENT Sync po/docbooks with svn 2022-07-07 01:48:15 +00:00
l10n daemon script
1dff2b8273 GIT_SILENT Sync po/docbooks with svn 2022-07-06 01:49:30 +00:00
Nicolas Fella
722aa422e7 Fix activating browser windows on Wayland
QDesktopServices::openUrl does not have XDG activation support yet so it can't raise an existing browser window when opening URLs

Instead use KIO::OpenUrlJob, which does support that
2022-07-05 22:55:12 +00:00
Akseli Lahtinen
70de0dc624 add settings button to room context menu 2022-07-05 21:26:12 +00:00
l10n daemon script
9d804e6ea7 GIT_SILENT Sync po/docbooks with svn 2022-07-05 01:46:44 +00:00
Volker Krause
52d552650d Use product screenshots from CDN rather than expensive direct Gitlab links 2022-07-03 12:16:43 +02:00
l10n daemon script
0c5007fd56 GIT_SILENT Sync po/docbooks with svn 2022-07-03 02:06:37 +00:00
l10n daemon script
af19829225 GIT_SILENT Sync po/docbooks with svn 2022-06-28 01:49:26 +00:00
l10n daemon script
0be8828dd4 GIT_SILENT Sync po/docbooks with svn 2022-06-27 01:47:51 +00:00
l10n daemon script
729b6bd354 GIT_SILENT Sync po/docbooks with svn 2022-06-26 01:48:37 +00:00
Heiko Becker
ef10042179 Sonnet is only a runtime dependency
Since 98571cb37d.
2022-06-25 15:16:54 +02:00
l10n daemon script
846d430947 GIT_SILENT Sync po/docbooks with svn 2022-06-25 02:08:13 +00:00
l10n daemon script
cce4a3ebdf GIT_SILENT made messages (after extraction) 2022-06-25 00:49:02 +00:00
172 changed files with 31828 additions and 13555 deletions

157
.flatpak-manifest.json Normal file
View File

@@ -0,0 +1,157 @@
{
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "5.15-21.08",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
"nightly"
],
"desktop-file-name-suffix": " (Nightly)",
"finish-args": [
"--share=network",
"--share=ipc",
"--socket=x11",
"--socket=wayland",
"--device=dri",
"--filesystem=xdg-download",
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.kde.kwalletd5",
"--talk-name=org.kde.StatusNotifierWatcher",
"--own-name=org.kde.StatusNotifierItem-2-2"
],
"modules": [
{
"name": "kquickimageeditor",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://invent.kde.org/libraries/kquickimageeditor"
}
]
},
{
"name": "olm",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git",
"tag": "3.2.10",
"x-checker-data": {
"type": "git",
"tag-pattern": "^([\\d.]+)$"
},
"commit": "9908862979147a71dc6abaecd521be526ae77be1"
}
]
},
{
"name": "libsecret",
"buildsystem": "meson",
"config-opts": [
"-Dmanpage=false",
"-Dvapi=false",
"-Dgtk_doc=false",
"-Dintrospection=false",
"-Dgcrypt=false"
],
"sources": [
{
"type": "archive",
"url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
"sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
"x-checker-data": {
"type": "gnome",
"name": "libsecret",
"stable-only": true
}
}
]
},
{
"name": "qtkeychain",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "archive",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/v0.13.2.tar.gz",
"sha256": "20beeb32de7c4eb0af9039b21e18370faf847ac8697ab3045906076afbc4caa5",
"x-checker-data": {
"type": "anitya",
"project-id": 4138,
"stable-only": true,
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
}
}
],
"config-opts": [
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO"
]
},
{
"name": "libQuotient",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://github.com/quotient-im/libQuotient.git",
"branch": "dev"
}
],
"config-opts": [
"-DQuotient_ENABLE_E2EE=ON"
]
},
{
"name": "cmark",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://github.com/commonmark/cmark.git"
}
],
"config-opts": [
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"builddir": true
},
{
"name": "qcoro",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.4.0.tar.gz",
"sha256": "0e68b3f0ce7bf521ffbdd731464d2d60d8d7a39a749b551ed26855a1707d86d1",
"x-checker-data": {
"type": "anitya",
"project-id": 236236,
"stable-only": true,
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
}
}
]
},
{
"name": "neochat",
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "dir",
"path": "."
}
],
"config-opts": [
"-DNEOCHAT_FLATPAK=ON"
]
}
]
}

View File

@@ -5,4 +5,8 @@ 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.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
# TODO enable once we can have qt6 libQuotient on the CI
# - 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.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

View File

@@ -31,8 +31,7 @@ Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
License: BSD-2-Clause
Files: src/neochat.notifyrc
@@ -42,3 +41,7 @@ License: BSD-2-Clause
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <fella@posteo.de>
License: BSD-2-Clause

View File

@@ -7,9 +7,9 @@
cmake_minimum_required(VERSION 3.16)
project(NeoChat)
set(PROJECT_VERSION "22.06")
set(PROJECT_VERSION "22.09")
set(KF5_MIN_VERSION "5.88.0")
set(KF5_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2")
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
@@ -25,12 +25,14 @@ include(FeatureSummary)
include(ECMSetupVersion)
include(KDEInstallDirs)
include(ECMFindQmlModule)
include(KDEClangFormat)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(KDEGitCommitHooks)
include(ECMCheckOutboundLicense)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
@@ -41,8 +43,8 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
)
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt5 PROPERTIES
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
@@ -56,8 +58,8 @@ set_package_properties(KF5Kirigami2 PROPERTIES
PURPOSE "Kirigami application UI framework"
)
find_package(Qt5Keychain)
set_package_properties(Qt5Keychain PROPERTIES
find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
TYPE REQUIRED
PURPOSE "Secure storage of account secrets"
)
@@ -69,11 +71,12 @@ if(ANDROID)
PURPOSE "Encrypted communications"
)
else()
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.sonnet 1.0)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
endif()
@@ -108,8 +111,8 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments"
)
find_package(QCoro5 COMPONENTS Core QUIET)
if(NOT QCoro5_FOUND)
find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
find_package(QCoro REQUIRED)
endif()
@@ -136,11 +139,12 @@ add_subdirectory(src)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
if (NOT ANDROID)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
endif()
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
# CI installs dependency headers to _install and _build, which break the reuse check
# Fixes the test by excluding this directory

View File

@@ -31,7 +31,7 @@ and can also directly be downloaded from the [binary factory](https://binary-fac
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
![Timeline](https://invent.kde.org/websites/product-screenshots/-/raw/master/neochat/application.png)
![Timeline](https://cdn.kde.org/screenshots/neochat/application.png)
## Features
@@ -55,7 +55,7 @@ We welcome contributions in this direction.
## Contact
You can reach the maintainers at #neochat:kde.org, if you are already on Matrix.
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
## Acknowledgement

View File

@@ -21,7 +21,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="neochat"/>
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>

View File

@@ -9,6 +9,6 @@ install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
DESTINATION
${CMAKE_INSTALL_SYSCONFDIR}/fonts/conf.d/
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
)

View File

@@ -14,14 +14,14 @@ import NeoChat.Page 1.0
Loader {
id: root
property var attachmentMimetype: FileType.mimeTypeForUrl(ChatBoxHelper.attachmentPath)
property var attachmentMimetype: FileType.mimeTypeForUrl(chatBoxHelper.attachmentPath)
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
active: visible
sourceComponent: Component {
Pane {
id: attachmentPane
property string baseFileName: ChatBoxHelper.attachmentPath.toString().substring(ChatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, ChatBoxHelper.attachmentPath.length)
property string baseFileName: chatBoxHelper.attachmentPath.toString().substring(chatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, chatBoxHelper.attachmentPath.length)
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: Item {
@@ -46,7 +46,7 @@ Loader {
asynchronous: true
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
source: hasImage ? ChatBoxHelper.attachmentPath : ""
source: hasImage ? chatBoxHelper.attachmentPath : ""
visible: hasImage
fillMode: Image.PreserveAspectFit
@@ -162,14 +162,14 @@ Loader {
Component {
id: imageEditorPage
ImageEditorPage {
imagePath: ChatBoxHelper.attachmentPath
imagePath: chatBoxHelper.attachmentPath
}
}
onClicked: {
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function(newPath) {
applicationWindow().pageStack.layers.pop();
ChatBoxHelper.attachmentPath = newPath;
chatBoxHelper.attachmentPath = newPath;
});
}
ToolTip.text: text
@@ -180,7 +180,7 @@ Loader {
icon.name: "dialog-cancel"
text: i18n("Cancel")
display: AbstractButton.IconOnly
onClicked: ChatBoxHelper.clearAttachment();
onClicked: chatBoxHelper.clearAttachment();
ToolTip.text: text
ToolTip.visible: hovered
}

View File

@@ -63,6 +63,9 @@ ToolBar {
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
+ inputField.topPadding + inputField.bottomPadding
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
FontMetrics {
id: fontMetrics
font: inputField.font
@@ -94,11 +97,11 @@ ToolBar {
//property int lineHeight: contentHeight / lineCount
text: inputFieldText
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message")
verticalAlignment: TextEdit.AlignVCenter
horizontalAlignment: TextEdit.AlignLeft
wrapMode: Text.Wrap
readOnly: currentRoom.usesEncryption
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
@@ -145,11 +148,7 @@ ToolBar {
}
Keys.onPressed: {
if (event.key === Qt.Key_PageDown) {
switchRoomDown();
} else if (event.key === Qt.Key_PageUp) {
switchRoomUp();
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
chatBar.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
replyPreviousUserMessage();
@@ -278,14 +277,14 @@ ToolBar {
}
Item {
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
visible: !chatBoxHelper.isReplying && (!chatBoxHelper.hasAttachment || uploadingBusySpinner.running)
implicitWidth: uploadButton.implicitWidth
implicitHeight: uploadButton.implicitHeight
ToolButton {
id: uploadButton
anchors.fill: parent
// Matrix does not allow sending attachments in replies
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
visible: !chatBoxHelper.isReplying && !chatBoxHelper.hasAttachment && !uploadingBusySpinner.running
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: AbstractButton.IconOnly
@@ -297,7 +296,7 @@ ToolBar {
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect((path) => {
if (!path) { return }
ChatBoxHelper.attachmentPath = path;
chatBoxHelper.attachmentPath = path;
})
fileDialog.open()
}
@@ -380,16 +379,16 @@ ToolBar {
if (!Clipboard.saveImage(localPath)) {
return;
}
ChatBoxHelper.attachmentPath = localPath;
chatBoxHelper.attachmentPath = localPath;
}
function postMessage() {
checkForFancyEffectsReason();
if (ChatBoxHelper.hasAttachment) {
if (chatBoxHelper.hasAttachment) {
// send attachment but don't reset the text
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
actionsHandler.postMessage("", chatBoxHelper.attachmentPath,
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, {}, this.customEmojiModel);
currentRoom.markAllMessagesAsRead();
messageSent();
return;
@@ -401,8 +400,8 @@ ToolBar {
actionsHandler.postEdit(inputField.text);
} else {
// send normal message
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
actionsHandler.postMessage(inputField.text.trim(), chatBoxHelper.attachmentPath,
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
}
currentRoom.markAllMessagesAsRead();
inputField.clear();

View File

@@ -127,8 +127,8 @@ Item {
ReplyPane {
id: replyPane
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
user: ChatBoxHelper.replyUser
visible: chatBoxHelper.isReplying || chatBoxHelper.isEditing
user: chatBoxHelper.replyUser
width: parent.width
height: visible ? implicitHeight : 0
anchors.bottom: attachmentSeparator.top
@@ -154,7 +154,7 @@ Item {
AttachmentPane {
id: attachmentPane
visible: ChatBoxHelper.hasAttachment
visible: chatBoxHelper.hasAttachment
width: parent.width
height: visible ? implicitHeight : 0
anchors.bottom: chatBarSeparator.top
@@ -248,7 +248,7 @@ Item {
}
Connections {
target: ChatBoxHelper
target: chatBoxHelper
function onShouldClearText() {
root.inputFieldText = "";
@@ -277,7 +277,7 @@ Item {
}
function closeAll() {
ChatBoxHelper.clear();
chatBoxHelper.clear();
chatBar.emojiPaneOpened = false;
}
}

View File

@@ -47,6 +47,7 @@ Popup {
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
contentItem: ScrollView {
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: completionListView

View File

@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
Loader {
id: root
readonly property bool isEdit: ChatBoxHelper.isEditing
readonly property bool isEdit: chatBoxHelper.isEditing
property var user: null
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
@@ -71,6 +71,10 @@ Loader {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: textArea
leftPadding: 0
@@ -79,7 +83,7 @@ Loader {
bottomPadding: 0
text: {
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
const content = chatBoxHelper.isReplying ? chatBoxHelper.replyEventContent : chatBoxHelper.editContent;
return stylesheet + content;
}
selectByMouse: true
@@ -102,7 +106,7 @@ Loader {
text: i18n("Cancel")
display: AbstractButton.IconOnly
onClicked: {
ChatBoxHelper.clearEditReply();
chatBoxHelper.clear();
root.replyCancelled();
}
ToolTip.text: text

View File

@@ -3,10 +3,12 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
ApplicationWindow {
Popup {
id: root
property alias source: image.source
@@ -14,57 +16,293 @@ ApplicationWindow {
property string blurhash: ""
property int imageWidth: -1
property int imageHeight: -1
property var modelData
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
parent: Overlay.overlay
closePolicy: Popup.CloseOnEscape
width: parent.width
height: parent.height
modal: true
padding: 0
background: null
title: i18n("Image View - %1", filename)
ColumnLayout {
anchors.fill: parent
spacing: Kirigami.Units.largeSpacing
Shortcut {
sequence: "Escape"
onActivated: root.destroy()
}
Control {
Layout.fillWidth: true
color: Kirigami.Theme.backgroundColor
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
background: AbstractButton {
onClicked: root.destroy()
}
Kirigami.Avatar {
id: avatar
BusyIndicator {
visible: image.status !== Image.Ready && root.blurhash === ""
anchors.centerIn: parent
running: visible
}
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
AnimatedImage {
id: image
anchors.centerIn: parent
name: modelData.author.name ?? modelData.author.displayName
source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : ""
color: modelData.author.color
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width)
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height)
Label {
id: nameLabel
fillMode: Image.PreserveAspectFit
text: modelData.author.displayName
textFormat: Text.PlainText
font.weight: Font.Bold
color: author.color
}
Label {
id: timeLabel
Image {
anchors.centerIn: parent
width: image.width
height: image.height
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
visible: root.blurhash !== "" && parent.status !== Image.Ready
text: time.toLocaleString(Qt.locale(), Locale.ShortFormat)
}
}
Label {
id: imageLabel
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
text: modelData.display
font.weight: Font.Bold
elide: Text.ElideRight
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Zoom in")
Accessible.name: text
icon.name: "zoom-in"
display: AbstractButton.IconOnly
onClicked: {
image.scaleFactor = image.scaleFactor + 0.25
if (image.scaleFactor > 3) {
image.scaleFactor = 3
}
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Zoom out")
Accessible.name: text
icon.name: "zoom-out"
display: AbstractButton.IconOnly
onClicked: {
image.scaleFactor = image.scaleFactor - 0.25
if (image.scaleFactor < 0.25) {
image.scaleFactor = 0.25
}
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Rotate left")
Accessible.name: text
icon.name: "image-rotate-left-symbolic"
display: AbstractButton.IconOnly
onClicked: image.rotationAngle = image.rotationAngle - 90
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Rotate right")
Accessible.name: text
icon.name: "image-rotate-right-symbolic"
display: AbstractButton.IconOnly
onClicked: image.rotationAngle = image.rotationAngle + 90
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Save as")
Accessible.name: text
icon.name: "document-save"
display: AbstractButton.IconOnly
onClicked: {
var dialog = saveAsDialog.createObject(ApplicationWindow.overlay)
dialog.open()
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Close")
Accessible.name: text
icon.name: "dialog-close"
display: AbstractButton.IconOnly
onClicked: {
root.close()
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
}
background: Rectangle {
color: Kirigami.Theme.alternateBackgroundColor
}
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 1
}
}
BusyIndicator {
Layout.fillWidth: true
visible: image.status !== Image.Ready && root.blurhash === ""
running: visible
}
// Provides container to fill the space that isn't taken up by the top controls and clips the image when zooming makes it larger than the available area.
Item {
id: imageContainer
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
clip: true
Image {
id: image
property var scaleFactor: 1
property int rotationAngle: 0
property var rotationInsensitiveWidth: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, imageContainer.width - Kirigami.Units.largeSpacing * 2)
property var rotationInsensitiveHeight: Math.min(root.imageHeight !== -1 ? root.imageHeight : sourceSize.height, imageContainer.height - Kirigami.Units.largeSpacing * 2)
anchors.centerIn: parent
width: rotationAngle % 180 === 0 ? rotationInsensitiveWidth : rotationInsensitiveHeight
height: rotationAngle % 180 === 0 ? rotationInsensitiveHeight : rotationInsensitiveWidth
fillMode: Image.PreserveAspectFit
clip: true
Behavior on width {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Behavior on height {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Image {
anchors.centerIn: parent
width: image.width
height: image.height
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
visible: root.blurhash !== "" && parent.status !== Image.Ready
}
transform: [
Rotation {
origin.x: image.width / 2
origin.y: image.height / 2
angle: image.rotationAngle
Behavior on angle {
RotationAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
},
Scale {
origin.x: image.width / 2
origin.y: image.height / 2
xScale: image.scaleFactor
yScale: image.scaleFactor
Behavior on xScale {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Behavior on yScale {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
}
]
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
const contextMenu = fileDelegateContextMenu.createObject(parent, {
author: modelData.author,
message: modelData.message,
eventId: modelData.eventId,
source: modelData.source,
file: root.parent,
mimeType: modelData.mimeType,
progressInfo: modelData.progressInfo,
plainMessage: modelData.message,
});
contextMenu.closeFullscreen.connect(root.close)
contextMenu.open();
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
root.close()
}
}
}
}
Button {
anchors.top: parent.top
anchors.right: parent.right
Component {
id: saveAsDialog
FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
if (!currentFile) {
return;
}
currentRoom.downloadFile(eventId, currentFile)
}
}
}
text: i18n("Close")
icon.name: "dialog-close"
display: AbstractButton.IconOnly
width: Kirigami.Units.gridUnit * 2
height: Kirigami.Units.gridUnit * 2
onClicked: root.destroy()
onClosed: {
image.scaleFactor = 1
image.rotationAngle = 0
}
}

View File

@@ -5,24 +5,18 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
property var showContinueButton: false
property var showBackButton: false
property string title: i18n("Loading…")
text: i18n("Synchronizing with your homeserver…")
icon.name: "cloud-download"
anchors.centerIn: parent
QQC2.Label {
text: i18n("Please wait. This might take a little while.")
}
QQC2.BusyIndicator {
Layout.alignment: Qt.AlignHCenter
running: false
}
explanation: i18n("Please wait. This might take a little while.")
}

View File

@@ -14,24 +14,29 @@ import NeoChat.Component 1.0
LoginStep {
id: login
showContinueButton: true
showContinueButton: LoginHelper.homeserverReachable
showBackButton: false
title: i18nc("@title", "Login")
message: i18n("Enter your Matrix ID")
message: i18n("Welcome to NeoChat!")
Component.onCompleted: {
LoginHelper.matrixId = ""
}
QQC2.Label {
text: i18n("To get started, enter your matrix ID:")
}
Kirigami.FormLayout {
QQC2.TextField {
id: matrixIdField
Kirigami.FormData.label: i18n("Matrix ID:")
placeholderText: "@user:matrix.org"
onTextChanged: {
if(acceptableInput) {
LoginHelper.matrixId = text
} else {
LoginHelper.matrixId = ""
}
}
@@ -61,5 +66,6 @@ LoginStep {
}
}
enabled: LoginHelper.homeserverReachable
iconName: "go-next"
}
}

View File

@@ -25,6 +25,7 @@ LoginStep {
onTriggered: {
LoginHelper.login();
}
iconName: "go-next"
}
Connections {

View File

@@ -20,21 +20,29 @@ LoginStep {
Connections {
target: LoginHelper
function onSsoUrlChanged() {
Qt.openUrlExternally(LoginHelper.ssoUrl)
UrlHelper.openUrl(LoginHelper.ssoUrl)
}
function onConnected() {
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
}
}
RowLayout {
QQC2.Button {
text: i18nc("@action:button", "Back")
QQC2.Button {
text: i18n("Login")
onClicked: {
LoginHelper.loginWithSso()
root.showMessage(i18n("Complete the authentication steps in your browser"))
onClicked: {
module.source = "qrc:/imports/NeoChat/Component/Login/Login.qml"
}
}
QQC2.Button {
text: i18n("Login")
onClicked: {
LoginHelper.loginWithSso()
root.showMessage(i18n("Complete the authentication steps in your browser"))
}
Component.onCompleted: forceActiveFocus()
Keys.onReturnPressed: clicked()
}
Component.onCompleted: forceActiveFocus()
Keys.onReturnPressed: clicked()
}
}
}

View File

@@ -1,71 +1,116 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 as Platform
import QtMultimedia 5.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
TimelineContainer {
id: audioDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
onOpenContextMenu: openFileContext(model, audioDelegate)
readonly property bool downloaded: model.progressInfo && model.progressInfo.completed
onDownloadedChanged: audio.play()
hoverComponent: hoverActions
innerObject: Control {
Layout.fillWidth: true
Layout.maximumWidth: audioDelegate.bubbleMaxWidth
Layout.maximumWidth: audioDelegate.contentMaxWidth
Audio {
id: audio
source: currentRoom.urlToMxcUrl(content.url)
source: model.progressInfo.localPath
autoLoad: false
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
states: [
State {
name: "notDownloaded"
when: !model.progressInfo.completed && !model.progressInfo.active
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: currentRoom.downloadFile(model.eventId)
}
},
State {
name: "downloading"
when: model.progressInfo.active && !model.progressInfo.completed
PropertyChanges {
target: downloadBar
visible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-stop"
onClicked: {
currentRoom.cancelFileTransfer(model.eventId)
}
}
},
State {
name: "paused"
when: model.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: {
audio.play()
}
}
},
State {
name: "playing"
when: model.progressInfo.completed && audio.playbackState === Audio.PlayingState
PropertyChanges {
target: playButton
icon.name: "media-playback-pause"
onClicked: audio.pause()
}
}
]
contentItem: ColumnLayout {
RowLayout {
ToolButton {
icon.name: audio.playbackState == Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
onClicked: {
if (audio.playbackState == Audio.PlayingState) {
audio.pause()
} else {
audio.play()
}
}
id: playButton
}
Label {
text: model.display
wrapMode: Text.Wrap
Layout.fillWidth: true
}
}
ProgressBar {
id: downloadBar
visible: false
Layout.fillWidth: true
from: 0
to: model.content.info.size
value: model.progressInfo.progress
}
RowLayout {
visible: audio.hasAudio
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
ProgressBar {
Slider {
from: 0
to: audio.duration
value: audio.position
onMoved: audio.seek(value)
}
Label {

View File

@@ -10,17 +10,18 @@ import org.kde.neochat 1.0
TimelineContainer {
id: encryptedDelegate
width: ListView.view.width
innerObject: TextEdit {
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
color: Kirigami.Theme.disabledTextColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.WordWrap
textFormat: Text.RichText
Layout.maximumWidth: encryptedDelegate.bubbleMaxWidth
Layout.maximumWidth: encryptedDelegate.contentMaxWidth
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
}
}

View File

@@ -42,9 +42,7 @@ DelegateChooser {
DelegateChoice {
roleValue: "sticker"
delegate: ImageDelegate {
cardBackground: false
}
delegate: ImageDelegate {}
}
DelegateChoice {

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
@@ -16,11 +15,12 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer {
id: fileDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, fileDelegate)
readonly property bool downloaded: progressInfo && progressInfo.completed
function saveFileAs() {
@@ -30,14 +30,14 @@ TimelineContainer {
}
function openSavedFile() {
if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return;
if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (UrlHelper.openUrl(progressInfo.localDir)) return;
}
innerObject: RowLayout {
Layout.fillWidth: true
Layout.maximumWidth: fileDelegate.bubbleMaxWidth
Layout.maximumWidth: fileDelegate.contentMaxWidth
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
@@ -53,6 +53,7 @@ TimelineContainer {
icon.name: "document-open"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: openSavedFile()
}
@@ -70,6 +71,7 @@ TimelineContainer {
icon.name: "media-playback-stop"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: currentRoom.cancelFileTransfer(eventId)
}
},
@@ -131,14 +133,5 @@ TimelineContainer {
}
}
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
}
}

View File

@@ -4,9 +4,10 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
@@ -16,11 +17,11 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer {
id: imageDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, imageDelegate)
property var content: model.content
readonly property bool isAnimated: contentType === "image/gif"
@@ -35,8 +36,8 @@ TimelineContainer {
innerObject: Image {
id: img
Layout.maximumWidth: imageDelegate.bubbleMaxWidth
Layout.maximumHeight: imageDelegate.bubbleMaxWidth / imageDelegate.info.w * imageDelegate.info.h
Layout.maximumWidth: imageDelegate.contentMaxWidth
Layout.maximumHeight: imageDelegate.contentMaxWidth / imageDelegate.info.w * imageDelegate.info.h
Layout.preferredWidth: imageDelegate.info.w
Layout.preferredHeight: imageDelegate.info.h
source: model.mediaUrl
@@ -51,6 +52,7 @@ TimelineContainer {
ToolTip.text: model.display
ToolTip.visible: hoverHandler.hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {
id: hoverHandler
@@ -86,25 +88,25 @@ TimelineContainer {
}
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
onTapped: {
fullScreenImage.createObject(parent, {
filename: eventId,
source: model.mediaUrl,
blurhash: model.content.info["xyz.amorgan.blurhash"],
imageWidth: content.info.w,
imageHeight: content.info.h
}).showFullScreen();
img.ToolTip.hide()
fullScreenImage.open()
}
}
FullScreenImage {
id: fullScreenImage
filename: eventId
source: mediaUrl
blurhash: model.content.info["xyz.amorgan.blurhash"]
imageWidth: content.info.w
imageHeight: content.info.h
modelData: model
}
function downloadAndOpen() {
if (downloaded) {
openSavedFile()
@@ -115,8 +117,8 @@ TimelineContainer {
}
function openSavedFile() {
if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return;
if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (UrlHelper.openUrl(progressInfo.localDir)) return;
}
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
RowLayout {
id: row
property var links: model.display.match(/(\bhttps?:\/\/[^\s\<\>\"\']*[^\s\<\>\"\'])/g)
// don't show previews for room links or user mentions
.filter(link => !link.includes("https://matrix.to"))
// remove ending fullstops and commas
.map(link => (link.length && [".", ","].includes(link[link.length-1])) ? link.substring(0, link.length-1) : link)
LinkPreviewer {
id: lp
url: links[0]
}
visible: lp.loaded && lp.title
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
visible: lp.loaded && lp.title
color: Kirigami.Theme.highlightColor
}
Image {
visible: lp.imageSource
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
Layout.maximumWidth: Kirigami.Units.gridUnit * 5
source: lp.imageSource.replace("mxc://", "image://mxc/")
fillMode: Image.PreserveAspectFit
}
ColumnLayout {
id: column
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
level: 4
wrapMode: Text.Wrap
textFormat: Text.RichText
text: "<style>
a {
text-decoration: none;
}
</style>
<a href=\"" + links[0] + "\">" + lp.title.replace("&ndash;", "—") + "</a>"
visible: lp.loaded
onLinkActivated: RoomManager.openResource(link)
}
Label {
text: lp.description
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
wrapMode: Text.Wrap
visible: lp.loaded && lp.description
}
}
}

View File

@@ -13,24 +13,27 @@ import org.kde.neochat 1.0
TimelineContainer {
id: messageDelegate
width: ListView.view.width
property bool isEmote: false
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: RichLabel {
isEmote: messageDelegate.isEmote
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openMessageContext(model, parent.selectedText)
innerObject: ColumnLayout {
Layout.maximumWidth: messageDelegate.contentMaxWidth
RichLabel {
id: label
isEmote: messageDelegate.isEmote
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openMessageContext(model, parent.selectedText)
Loader {
id: linkPreviewLoader
Layout.fillWidth: true
height: active ? item.implicitHeight : 0
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
visible: active
sourceComponent: LinkPreviewDelegate {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

View File

@@ -11,11 +11,49 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
QQC2.ItemDelegate {
id: readMarkerDelegate
padding: Kirigami.Units.largeSpacing
topInset: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing * 2
width: ListView.view.width - Kirigami.Units.gridUnit
x: Kirigami.Units.gridUnit / 2
// extraWidth defines how the delegate can grow after the listView gets very wide
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width - Kirigami.Units.largeSpacing * 2 : Math.min(messageListView.width - Kirigami.Units.largeSpacing * 2, Kirigami.Units.gridUnit * 40 + extraWidth)
width: delegateMaxWidth
anchors.leftMargin: Kirigami.Units.largeSpacing
anchors.rightMargin: Kirigami.Units.largeSpacing
state: Config.compactLayout ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles
states: [
State {
name: "alignLeft"
AnchorChanges {
target: readMarkerDelegate
anchors.horizontalCenter: undefined
anchors.left: parent ? parent.left : undefined
}
},
State {
name: "alignCenter"
AnchorChanges {
target: readMarkerDelegate
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
anchors.left: undefined
}
}
]
transitions: [
Transition {
AnchorAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
}
]
contentItem: QQC2.Label {
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
}

View File

@@ -15,11 +15,11 @@ MouseArea {
id: replyButton
Layout.fillWidth: true
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
Component.onCompleted: {
parent.Layout.fillWidth = true;
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
parent.Layout.maximumWidth = Qt.binding(function() { return bubbleMaxWidth + Kirigami.Units.largeSpacing * 2; })
parent.Layout.maximumWidth = Qt.binding(function() { return contentMaxWidth + Kirigami.Units.largeSpacing * 2; })
}
Rectangle {
id: replyLeftBorder
@@ -79,7 +79,7 @@ MouseArea {
id: replyText
textMessage: reply.display
textFormat: Text.RichText
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
}
}
@@ -94,7 +94,7 @@ MouseArea {
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
source: "image://mxc/" + mediaId
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
height: reply.content.info.h / reply.content.info.w * width
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
}

View File

@@ -15,14 +15,31 @@ TextEdit {
readonly property var hasSpoiler: /data-mx-spoiler/g
property bool isEmote: false
property string textMessage: model.display
/* Turn all links which aren't already in <a> tags into <a> hyperlinks */
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\]+)/g
property string textMessage: model.display.includes("http")
? model.display.replace(linkRegex, function() {
if (arguments[1]) {
return arguments[0];
} else {
var l = arguments[2];
if ([".", ","].includes(l[l.length-1])) {
var link = l.substring(0, l.length-1);
var leftover = l[l.length-1];
return "<a href=\"" + link + "\">" + link + "</a>" + leftover;
}
return "<a href=\"" + l + "\">" + l + "</a>";
}
})
: model.display
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
Layout.fillWidth: Config.compactLayout
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
Layout.fillWidth: true
persistentSelection: true
text: "<style>
table {
@@ -44,6 +61,10 @@ a{
text-decoration: none;
}
" + (!spoilerRevealed ? "
[data-mx-spoiler] a {
color: transparent;
background: " + Kirigami.Theme.textColor + ";
}
[data-mx-spoiler] {
color: transparent;
background: " + Kirigami.Theme.textColor + ";
@@ -52,14 +73,19 @@ a{
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.Wrap
textFormat: Text.RichText
onLinkActivated: RoomManager.openResource(link)
onHoveredLinkChanged: if (hoveredLink.length > 0) {
onLinkActivated: {
spoilerRevealed = true
RoomManager.openResource(link)
}
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
applicationWindow().hoverLinkIndicator.text = hoveredLink;
} else {
applicationWindow().hoverLinkIndicator.text = "";

View File

@@ -7,19 +7,53 @@ import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
Control {
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
width: ListView.view.width - Kirigami.Units.largeSpacing - x
id: stateDelegate
// extraWidth defines how the delegate can grow after the listView gets very wide
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width: Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
width: delegateMaxWidth
// anchors.leftMargin: Kirigami.Units.largeSpacing
// anchors.rightMargin: Kirigami.Units.largeSpacing
state: Config.compactLayout ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles
states: [
State {
name: "alignLeft"
AnchorChanges {
target: stateDelegate
anchors.horizontalCenter: undefined
anchors.left: parent ? parent.left : undefined
}
},
State {
name: "alignCenter"
AnchorChanges {
target: stateDelegate
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
anchors.left: undefined
}
}
]
transitions: [
Transition {
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
}
]
height: sectionDelegate.height + rowLayout.height
SectionDelegate {
id: sectionDelegate
width: parent.width
anchors.top: parent.top
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.left: parent.left
anchors.right: parent.right
visible: model.showSection
height: visible ? implicitHeight : 0
}
@@ -27,8 +61,11 @@ Control {
RowLayout {
id: rowLayout
height: label.contentHeight
width: parent.width
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
anchors.rightMargin: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: icon

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.15 as Kirigami
@@ -13,19 +12,35 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
QQC2.ItemDelegate {
id: messageDelegate
id: timelineContainer
default property alias innerObject : column.children
// readonly property bool failed: marks == EventStatus.SendingFailed
property bool isEmote: false
property bool cardBackground: true
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
property bool isTemporaryHighlighted: false
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width - Kirigami.Units.largeSpacing * 4 : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
onIsTemporaryHighlightedChanged: if (isTemporaryHighlighted) temporaryHighlightTimer.start()
Timer {
id: temporaryHighlightTimer
interval: 1500
onTriggered: isTemporaryHighlighted = false
}
signal openContextMenu
// The bubble and delegate widths are allowed to grow once the ListView gets beyond a certain size
// extraWidth defines this as the excess after a certain ListView width, capped to a max value
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int bubbleMaxWidth: Kirigami.Units.gridUnit * 20 + extraWidth * 0.5
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width : Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
readonly property int contentMaxWidth: Config.compactLayout ? width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, bubbleMaxWidth)
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight &&
model.author.isLocalUser &&
!applicationWindow().wideScreen &&
!Config.compactLayout
model.author.isLocalUser && !Config.compactLayout
signal openExternally()
signal replyClicked(string eventID)
@@ -38,7 +53,17 @@ QQC2.ItemDelegate {
topPadding: 0
bottomPadding: 0
background: null
topInset: showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
leftInset: Kirigami.Units.smallSpacing
rightInset: Kirigami.Units.smallSpacing
width: delegateMaxWidth
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing))
background: Rectangle {
visible: timelineContainer.hovered
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
radius: Kirigami.Units.smallSpacing
}
property Item hoverComponent
// show hover actions
@@ -51,13 +76,39 @@ QQC2.ItemDelegate {
// updates the global hover component to point to this delegate, and update its position
function updateHoverComponent() {
if (hoverComponent) {
hoverComponent.delegate = timelineContainer
hoverComponent.bubble = bubble
hoverComponent.updateFunction = updateHoverComponent;
hoverComponent.event = model
}
}
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
state: Config.compactLayout ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles
states: [
State {
name: "alignLeft"
AnchorChanges {
target: timelineContainer
anchors.horizontalCenter: undefined
anchors.left: parent ? parent.left : undefined
}
},
State {
name: "alignCenter"
AnchorChanges {
target: timelineContainer
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
anchors.left: undefined
}
}
]
transitions: [
Transition {
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
}
]
SectionDelegate {
id: sectionDelegate
@@ -70,15 +121,20 @@ QQC2.ItemDelegate {
Kirigami.Avatar {
id: avatar
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing * 2 : 0
height: width
padding: Kirigami.Units.smallSpacing
topInset: Kirigami.Units.smallSpacing
bottomInset: Kirigami.Units.smallSpacing
leftInset: Kirigami.Units.smallSpacing
rightInset: Kirigami.Units.smallSpacing
sourceSize.width: width
sourceSize.height: width
anchors {
top: sectionDelegate.bottom
topMargin: model.showAuthor ? Kirigami.Units.largeSpacing : 0
topMargin: model.showAuthor ? Kirigami.Units.largeSpacing : (Config.compactLayout ? 1 : Kirigami.Units.smallSpacing)
left: parent.left
leftMargin: Kirigami.Units.largeSpacing
leftMargin: Kirigami.Units.smallSpacing
}
visible: model.showAuthor &&
@@ -107,22 +163,19 @@ QQC2.ItemDelegate {
id: bubble
topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
hoverEnabled: true
// state: Config.compactLayout ? "compactLayout" : "default"
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
anchors {
top: avatar.top
leftMargin: Kirigami.Units.largeSpacing
leftMargin: Kirigami.Units.smallSpacing
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
}
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
width: Config.compactLayout ? messageDelegate.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
width: Config.compactLayout ? timelineContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
// states for anchor animations on window resize
// as setting anchors to undefined did not work reliably
states: [
@@ -152,31 +205,23 @@ QQC2.ItemDelegate {
contentItem: ColumnLayout {
id: column
spacing: 0
Item {
spacing: Kirigami.Units.smallSpacing
RowLayout {
id: rowLayout
spacing: Kirigami.Units.smallSpacing
visible: model.showAuthor && !isEmote
Layout.fillWidth: true
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing * 2
Layout.maximumWidth: bubbleMaxWidth
implicitHeight: visible ? nameLabel.implicitHeight : 0
QQC2.Label {
id: nameLabel
topInset: 0
visible: model.showAuthor && !isEmote
anchors.left: rowLayout.left
anchors.right: timeLabel.left
anchors.rightMargin: Kirigami.Units.smallSpacing
Layout.maximumWidth: contentMaxWidth - timeLabel.implicitWidth - rowLayout.spacing
text: visible ? author.displayName : ""
textFormat: Text.PlainText
font.weight: Font.Bold
color: author.color
wrapMode: Text.Wrap
elide: Text.ElideRight
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
@@ -193,10 +238,16 @@ QQC2.ItemDelegate {
}
QQC2.Label {
id: timeLabel
anchors.right: rowLayout.right
visible: model.showAuthor && !isEmote
text: visible ? time.toLocaleTimeString(Locale.ShortFormat) : ""
text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: hoverHandler.hovered
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {
id: hoverHandler
}
}
}
Loader {
@@ -204,8 +255,6 @@ QQC2.ItemDelegate {
active: model.reply !== undefined
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
visible: active
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
Connections {
target: replyLoader.item
@@ -217,19 +266,14 @@ QQC2.ItemDelegate {
}
background: Item {
Rectangle {
visible: messageDelegate.hovered
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
radius: Kirigami.Units.smallSpacing
anchors.fill: parent
}
Kirigami.ShadowedRectangle {
id: bubbleBackground
visible: cardBackground && !Config.compactLayout
anchors.fill: parent
color: {
if (model.author.isLocalUser) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
} else if (model.isHighlighted) {
} else if (timelineContainer.isHighlighted) {
return Kirigami.Theme.positiveBackgroundColor
} else {
return Kirigami.Theme.backgroundColor
@@ -237,9 +281,13 @@ QQC2.ItemDelegate {
}
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
shadow.color: timelineContainer.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
Behavior on color {
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
}
}
}
}
@@ -258,4 +306,14 @@ QQC2.ItemDelegate {
visible: active
sourceComponent: ReactionDelegate { }
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: timelineContainer.openContextMenu()
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: timelineContainer.openContextMenu()
}
}

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import QtMultimedia 5.15
import Qt.labs.platform 1.1 as Platform
@@ -18,8 +17,6 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer {
id: videoDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
@@ -29,6 +26,8 @@ TimelineContainer {
property bool supportStreaming: true
readonly property int maxWidth: 1000 // TODO messageListView.width
onOpenContextMenu: openFileContext(model, vid)
onDownloadedChanged: {
if (downloaded) {
vid.source = progressInfo.localPath
@@ -43,7 +42,7 @@ TimelineContainer {
innerObject: Video {
id: vid
Layout.maximumWidth: videoDelegate.bubbleMaxWidth
Layout.maximumWidth: videoDelegate.contentMaxWidth
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
@@ -124,16 +123,6 @@ TimelineContainer {
videoDelegate.downloadAndPlay()
}
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
}
function downloadAndPlay() {

View File

@@ -12,3 +12,4 @@ EncryptedDelegate 1.0 EncryptedDelegate.qml
EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: emojiItem
property string emoji
property string description
QQC2.Label {
id: emojiLabel
x: 0
y: 0
width: parent.width
height: parent.height * 0.75
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: emojiItem.emoji
font.family: "emoji"
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
}
QQC2.Label {
x: 0
y: parent.height * 0.75
width: parent.width
height: parent.height * 0.25
text: emojiItem.description
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Row {
id: emojiRow
property alias model: repeater.model
anchors.horizontalCenter: parent.horizontalCenter
Repeater {
id: repeater
delegate: EmojiItem {
emoji: modelData.emoji
description: modelData.description
width: emojiRow.height
height: width
}
}
}

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: emojiSas
required property var model
signal accept()
signal reject()
visible: dialog.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
anchors.centerIn: parent
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
text: i18n("Confirm the emoji below are displayed on both devices, in the same order.")
}
EmojiRow {
anchors.horizontalCenter: parent.horizontalCenter
height: Kirigami.Units.gridUnit * 4
model: emojiSas.model.slice(0, 4)
}
EmojiRow {
anchors.horizontalCenter: parent.horizontalCenter
height: Kirigami.Units.gridUnit * 4
model: emojiSas.model.slice(4, 7)
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They match")
icon.name: "dialog-ok"
onClicked: emojiSas.accept()
}
QQC2.Button {
anchors.bottom: parent.bottom
text: i18n("They don't match")
icon.name: "dialog-cancel"
onClicked: emojiSas.reject()
}
}
}

View File

@@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Kirigami.Page {
id: dialog
title: i18n("Session Verification")
required property var session
Item {
anchors.fill: parent
VerificationCanceled {
visible: dialog.session.state === KeyVerificationSession.CANCELED
anchors.centerIn: parent
reason: dialog.session.error
}
EmojiSas {
anchors.centerIn: parent
visible: dialog.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
model: dialog.session.sasEmojis
onReject: dialog.session.cancelVerification(KeyVerificationSession.MISMATCHED_SAS)
onAccept: dialog.session.sendMac()
}
Message {
visible: dialog.session.state === KeyVerificationSession.WAITINGFORREADY
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Waiting for device to accept verification.")
}
Message {
visible: dialog.session.state === KeyVerificationSession.INCOMING
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Incoming key verification request from device **%1**", dialog.session.remoteDeviceId)
}
Message {
visible: dialog.session.state === KeyVerificationSession.WAITINGFORMAC
anchors.centerIn: parent
icon: "security-medium-symbolic"
text: i18n("Waiting for other party to verify.")
}
Kirigami.BasicListItem {
id: emojiVerification
text: "Emoji Verification"
visible: dialog.session.state === KeyVerificationSession.READY
subtitle: i18n("Compare a set of emoji on both devices")
onClicked: {
dialog.session.sendStartSas()
}
}
Message {
visible: dialog.session.state === KeyVerificationSession.DONE
anchors.centerIn: parent
text: i18n("Successfully verified device **%1**", dialog.session.remoteDeviceId)
icon: "security-high"
}
}
footer: QQC2.ToolBar {
visible: dialog.session.state === KeyVerificationSession.INCOMING
QQC2.DialogButtonBox {
anchors.fill: parent
Item { Layout.fillWidth: true }
QQC2.Button {
text: i18n("Accept")
icon.name: "dialog-ok"
onClicked: dialog.session.sendReady()
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
}
QQC2.Button {
text: i18n("Decline")
icon.name: "dialog-cancel"
onClicked: dialog.session.cancelVerification("m.user", "Declined")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.CancelRole
}
}
}
}

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Column {
id: message
required property string icon
required property string text
anchors.centerIn: parent
Kirigami.Icon {
width: Kirigami.Units.iconSizes.enormous
height: width
anchors.horizontalCenter: parent.horizontalCenter
source: message.icon
}
QQC2.Label {
text: message.text
textFormat: Text.MarkdownText
}
}

View File

@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQml 2.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Message {
id: verificationCanceled
required property int reason
anchors.centerIn: parent
icon: "security-low"
text: {
switch(verificationCanceled.reason) {
case KeyVerificationSession.NONE:
return i18n("The session verification was canceled for unknown reason.");
case KeyVerificationSession.TIMEOUT:
return i18n("The session verification timed out.");
case KeyVerificationSession.REMOTE_TIMEOUT:
return i18n("The session verification timed out for remote party.");
case KeyVerificationSession.USER:
return i18n("You canceled the session verification.");
case KeyVerificationSession.REMOTE_USER:
return i18n("The remote party canceled the session verification.");
case KeyVerificationSession.UNEXPECTED_MESSAGE:
return i18n("The session verification was canceled because we received an unexpected message.");
case KeyVerificationSession.REMOTE_UNEXPECTED_MESSAGE:
return i18n("The remote party canceled the session verification because it received an unexpected message.");
case KeyVerificationSession.UNKNOWN_TRANSACTION:
return i18n("The session verification was canceled because it received a message for an unknown session.");
case KeyVerificationSession.REMOTE_UNKNOWN_TRANSACTION:
return i18n("The remote party canceled the session verification because it received a message for an unknown session.");
case KeyVerificationSession.UNKNOWN_METHOD:
return i18n("The session verification was canceled because NeoChat is unable to handle this verification method.");
case KeyVerificationSession.REMOTE_UNKNOWN_METHOD:
return i18n("The remote party canceled the session verification because it is unable to handle this verification method.");
case KeyVerificationSession.KEY_MISMATCH:
return i18n("The session verification was canceled because the keys are incorrect.");
case KeyVerificationSession.REMOTE_KEY_MISMATCH:
return i18n("The remote party canceled the session verification because the keys are incorrect.");
case KeyVerificationSession.USER_MISMATCH:
return i18n("The session verification was canceled because it verifies an unexpected user.");
case KeyVerificationSession.REMOTE_USER_MISMATCH:
return i18n("The remote party canceled the session verification because it verifies an unexpected user.");
case KeyVerificationSession.INVALID_MESSAGE:
return i18n("The session verification was canceled because we received an invalid message.");
case KeyVerificationSession.REMOTE_INVALID_MESSAGE:
return i18n("The remote party canceled the session verification because it received an invalid message.");
case KeyVerificationSession.SESSION_ACCEPTED:
return i18n("The session was accepted on a different device"); //TODO this should not be visible
case KeyVerificationSession.REMOTE_SESSION_ACCEPTED:
return i18n("The session was accepted on a different device"); //TODO neither should this
case KeyVerificationSession.MISMATCHED_COMMITMENT:
return i18n("The session verification was canceled because of a mismatched key.");
case KeyVerificationSession.REMOTE_MISMATCHED_COMMITMENT:
return i18n("The remote party canceled the session verification because of a mismatched key.");
case KeyVerificationSession.MISMATCHED_SAS:
return i18n("The session verification was canceled because the keys do not match.");
case KeyVerificationSession.REMOTE_MISMATCHED_SAS:
return i18n("The remote party canceled the session verification because the keys do not match.");
default:
return i18n("The session verification was canceled due to an unknown error.");
}
}
}

View File

@@ -0,0 +1,7 @@
module NeoChat.Dialog.KeyVerification
KeyVerificationDialog 1.0 KeyVerificationDialog.qml
Message 1.0 Message.qml
VerificationCanceled 1.0 VerificationCanceled.qml
EmojiItem 1.0 EmojiItem.qml
EmojiRow 1.0 EmojiRow.qml
EmojiSas 1.0 EmojiSas.qml

View File

@@ -14,6 +14,8 @@ import NeoChat.Component 1.0
Kirigami.OverlaySheet {
id: root
signal closed()
property var room
property var user
@@ -52,7 +54,7 @@ Kirigami.OverlaySheet {
onClicked: {
if (avatarMediaId) {
fullScreenImage.createObject(parent, {"filename": displayName, "source": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
}
}
}
@@ -164,5 +166,11 @@ Kirigami.OverlaySheet {
FullScreenImage {}
}
}
onSheetOpenChanged: {
if (!sheetOpen) {
closed()
}
}
}

View File

@@ -7,3 +7,4 @@ OpenFileDialog 1.0 OpenFileDialog.qml
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
StartChatDialog 1.0 StartChatDialog.qml
EmojiDialog 1.0 EmojiDialog.qml
KeyVerificationDialog 1.0 KeyVerificationDialog.qml

View File

@@ -45,7 +45,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "New Private Chat…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {connection: Controller.activeConnection})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
@@ -58,7 +58,7 @@ Labs.MenuBar {
}
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {connection: Controller.activeConnection})
}
}
EditMenu {
@@ -92,7 +92,7 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "Matrix FAQ")
onTriggered: Qt.openUrlExternally("https://matrix.org/faq/")
onTriggered: UrlHelper.openUrl("https://matrix.org/faq/")
}
}
}

View File

@@ -24,7 +24,7 @@ Loader {
Menu {
MenuItem {
id: newWindow
text: i18n("Open in new window")
text: i18n("Open in New Window")
onTriggered: RoomManager.openWindow(room);
visible: !Kirigami.Settings.isMobile
}
@@ -49,7 +49,7 @@ Loader {
}
MenuItem {
text: i18nc("@action:inmenu", "Copy address to clipboard")
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
onTriggered: if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id)
} else {
@@ -57,6 +57,56 @@ Loader {
}
}
Menu {
title: i18n("Notification State")
MenuItem {
text: i18n("Follow Global Setting")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Default
}
}
MenuItem {
text: i18nc("As in 'notify for all messages'","All")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.All
}
}
MenuItem {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.MentionKeyword
}
}
MenuItem {
text: i18nc("As in 'do not notify for any messages'","Off")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Mute
}
}
}
MenuItem {
text: i18n("Room Settings")
onTriggered: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
}
MenuSeparator {}
MenuItem {

View File

@@ -14,6 +14,8 @@ import NeoChat.Menu 1.0
MessageDelegateContextMenu {
id: root
signal closeFullscreen
required property var file
required property var progressInfo
required property string mimeType
@@ -24,13 +26,13 @@ MessageDelegateContextMenu {
icon.name: "document-open"
onTriggered: {
if (file.downloaded) {
if (!Qt.openUrlExternally(progressInfo.localPath)) {
Qt.openUrlExternally(progressInfo.localDir);
if (!UrlHelper.openUrl(progressInfo.localPath)) {
UrlHelper.openUrl(progressInfo.localDir);
}
} else {
file.onDownloadedChanged.connect(function() {
if (!Qt.openUrlExternally(progressInfo.localPath)) {
Qt.openUrlExternally(progressInfo.localDir);
if (!UrlHelper.openUrl(progressInfo.localPath)) {
UrlHelper.openUrl(progressInfo.localDir);
}
});
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
@@ -50,7 +52,8 @@ MessageDelegateContextMenu {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
ChatBoxHelper.replyToMessage(eventId, message, author);
chatBoxHelper.replyToMessage(eventId, message, author);
root.closeFullscreen()
}
},
Kirigami.Action {
@@ -60,8 +63,18 @@ MessageDelegateContextMenu {
icon.color: "red"
onTriggered: {
currentRoom.redactEvent(eventId);
root.closeFullscreen()
}
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: author.id !== currentRoom.localUser.id
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
},
Kirigami.Action {
text: i18n("View Source")
icon.name: "code-context"
@@ -72,6 +85,7 @@ MessageDelegateContextMenu {
title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25
});
root.closeFullscreen()
}
}
]

View File

@@ -20,6 +20,7 @@ Loader {
property string formattedBody: ""
required property string source
property string selectedText: ""
required property string plainMessage
property list<Kirigami.Action> nestedActions
@@ -27,13 +28,13 @@ Loader {
Kirigami.Action {
text: i18n("Edit")
icon.name: "document-edit"
onTriggered: ChatBoxHelper.edit(message, formattedBody, eventId);
onTriggered: chatBoxHelper.edit(message, formattedBody, eventId);
visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
},
Kirigami.Action {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author);
onTriggered: chatBoxHelper.replyToMessage(eventId, message, author);
},
Kirigami.Action {
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
@@ -45,7 +46,16 @@ Loader {
Kirigami.Action {
text: i18n("Copy")
icon.name: "edit-copy"
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.message : loadRoot.selectedText)
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.plainMessage : loadRoot.selectedText)
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: author.id !== currentRoom.localUser.id
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
},
Kirigami.Action {
text: i18n("View Source")
@@ -111,7 +121,7 @@ Loader {
Instantiator {
model: WebShortcutModel {
id: webshortcutmodel
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.message
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.plainMessage
onOpenUrl: RoomManager.visitNonMatrix(url)
}
delegate: QQC2.MenuItem {

View File

@@ -22,6 +22,10 @@ Kirigami.Page {
ScrollView {
anchors.fill: parent
contentWidth: availableWidth
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: sourceTextArea
text: sourceText

View File

@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami
Kirigami.Page {
id: reportSheet
property var room
property string eventId
title: i18n("Report Message")
QQC2.TextArea {
id: reason
placeholderText: i18n("Reason for reporting this message")
anchors.fill: parent
wrapMode: TextEdit.Wrap
}
footer: QQC2.ToolBar {
QQC2.DialogButtonBox {
anchors.fill: parent
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
reportSheet.room.reportEvent(eventId, reason.text)
reportSheet.closeDialog()
}
}
QQC2.Button {
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: reportSheet.closeDialog()
}
}
}
}

View File

@@ -2,3 +2,4 @@ module NeoChat.Menu.Timeline
MessageDelegateContextMenu 1.0 MessageDelegateContextMenu.qml
FileDelegateContextMenu 1.0 FileDelegateContextMenu.qml
MessageSourceSheet 1.0 MessageSourceSheet.qml
ReportSheet 1.0 ReportSheet.qml

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 as Platform
import org.kde.kirigami 2.15 as Kirigami

View File

@@ -3,18 +3,11 @@
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12 as QQC2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
Kirigami.Page {
title: i18n("Loading…")
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
text: i18n("Loading…")
QQC2.BusyIndicator {
running: false
Layout.alignment: Qt.AlignHCenter
}
}
}

View File

@@ -15,6 +15,82 @@ import NeoChat.Component 1.0
import NeoChat.Menu 1.0
Kirigami.ScrollablePage {
header: ColumnLayout {
visible: !page.collapsedMode
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
ListView {
id: spaceList
property string activeSpaceId: ''
orientation: Qt.Horizontal
spacing: Kirigami.Units.largeSpacing
clip:true
visible: spaceList.count > 0
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.fillWidth: true
model: SortFilterSpaceListModel {
id: sortFilterSpaceListModel
sourceModel: RoomListModel {
id: spaceListModel
connection: Controller.activeConnection
}
}
header: QQC2.Control {
contentItem: QQC2.RoundButton {
id: homeButton
flat: true
padding: Kirigami.Units.gridUnit / 2
icon.name: "home"
text: i18nc("@action:button", "Show All Rooms")
display: QQC2.AbstractButton.IconOnly
onClicked: {
sortFilterRoomListModel.activeSpaceId = "";
spaceList.activeSpaceId = '';
listView.positionViewAtIndex(0, ListView.Beginning);
}
QQC2.ToolTip {
text: homeButton.text
}
}
}
delegate: QQC2.Control {
required property string avatar
required property var currentRoom
required property int index
required property string id
implicitWidth: ListView.view.headerItem.implicitWidth
implicitHeight: ListView.view.headerItem.implicitHeight
contentItem: Kirigami.Avatar {
actions.main: Kirigami.Action {
onTriggered: {
spaceList.activeSpaceId = id;
sortFilterRoomListModel.activeSpaceId = id;
}
}
QQC2.ToolTip {
text: currentRoom.displayName
}
source: avatar !== "" ? "image://mxc/" + avatar : ""
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
}
id: page
title: i18n("Rooms")
@@ -68,7 +144,6 @@ Kirigami.ScrollablePage {
}
}
ListView {
id: listView
@@ -100,6 +175,8 @@ Kirigami.ScrollablePage {
}
}
Layout.fillWidth: true
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4)
@@ -233,6 +310,7 @@ Kirigami.ScrollablePage {
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse
onTapped: createRoomListContextMenu()
}
@@ -247,15 +325,17 @@ Kirigami.ScrollablePage {
trailing: RowLayout {
QQC2.Label {
text: notificationCount
visible: notificationCount > 0
text: notificationCount > 0 ? notificationCount : "●"
visible: unreadCount > 0
padding: Kirigami.Units.smallSpacing
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
color: Kirigami.Theme.textColor
Layout.minimumWidth: height
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
visible: notificationCount > 0
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightCount > 0 ? 1 : 0.3
radius: height / 2
}
}
@@ -275,7 +355,7 @@ Kirigami.ScrollablePage {
}
function createRoomListContextMenu() {
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
configButton.visible = true
configButton.down = true
menu.closed.connect(function() {

View File

@@ -8,7 +8,7 @@ import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1 as Platform
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
@@ -23,6 +23,7 @@ Kirigami.ScrollablePage {
/// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom
property bool loading: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
/// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false;
@@ -52,7 +53,26 @@ Kirigami.ScrollablePage {
onCurrentRoomChanged: {
hasScrolledUpBefore = false;
ChatBoxHelper.clearEditReply()
chatBoxHelper.clearEditReply()
}
Connections {
target: messageEventModel
function onRowsInserted() {
markReadIfVisibleTimer.restart()
}
}
Timer {
id: markReadIfVisibleTimer
interval: 1000
onTriggered: {
if (loading || !currentRoom.readMarkerLoaded || !applicationWindow().active) {
restart()
} else {
markReadIfVisible()
}
}
}
ActionsHandler {
@@ -61,6 +81,10 @@ Kirigami.ScrollablePage {
connection: Controller.activeConnection
}
ChatBoxHelper {
id: chatBoxHelper
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: applicationWindow().pageStack.get(0).forceActiveFocus()
@@ -81,17 +105,17 @@ Kirigami.ScrollablePage {
function onShowMessage(messageType, message) {
page.header.contentItem.text = message;
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
page.header.contentItem.visible = true;
page.header.visible = true;
}
}
header: QQC2.Control {
height: visible ? implicitHeight : 0
visible: contentItem.visible
visible: false
padding: Kirigami.Units.smallSpacing
contentItem: Kirigami.InlineMessage {
showCloseButton: true
visible: false
visible: true
}
}
@@ -120,15 +144,10 @@ Kirigami.ScrollablePage {
}
}
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
visible: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
text: i18n("Loading…")
QQC2.BusyIndicator {
running: loadingIndicator.visible
Layout.alignment: Qt.AlignHCenter
}
visible: loading
}
focus: true
@@ -147,9 +166,9 @@ Kirigami.ScrollablePage {
Keys.onPressed: {
if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomDown();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
event.accepted = true;
chatBox.addText(event.text);
@@ -158,6 +177,15 @@ Kirigami.ScrollablePage {
}
}
Connections {
target: currentRoom
function onPositiveMessage(message) {
page.header.contentItem.text = message;
page.header.contentItem.type = Kirigami.MessageType.Positive;
page.header.visible = true;
}
}
// hover actions on a delegate, activated in TimelineContainer.qml
Connections {
target: page.flickable
@@ -179,7 +207,7 @@ Kirigami.ScrollablePage {
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property bool isLoaded: page.width * page.height > 10
spacing: Config.compactLayout ? 1 : Kirigami.Units.smallSpacing
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500
@@ -242,7 +270,7 @@ Kirigami.ScrollablePage {
fileDialog.chosen.connect(function(path) {
if (!path) return
ChatBoxHelper.attachmentPath = path;
chatBoxHelper.attachmentPath = path;
})
fileDialog.open()
@@ -264,7 +292,7 @@ Kirigami.ScrollablePage {
if (!Clipboard.saveImage(localPath)) {
return;
}
ChatBoxHelper.attachmentPath = localPath;
chatBoxHelper.attachmentPath = localPath;
attachDialog.close();
}
}
@@ -339,7 +367,7 @@ Kirigami.ScrollablePage {
DropArea {
id: dropAreaFile
anchors.fill: parent
onDropped: ChatBoxHelper.attachmentPath = drop.urls[0]
onDropped: chatBoxHelper.attachmentPath = drop.urls[0]
}
QQC2.Pane {
@@ -388,7 +416,7 @@ Kirigami.ScrollablePage {
currentRoom.usersTyping.length,
currentRoom.usersTyping.map(user => user.displayName).join(", ")
) : ""
anchors.right: parent.right
anchors.left: parent.left
height: visible ? implicitHeight : 0
Behavior on height {
NumberAnimation {
@@ -402,13 +430,17 @@ Kirigami.ScrollablePage {
headerPositioning: ListView.OverlayHeader
function goToEvent(eventID) {
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
const index = eventToIndex(eventID)
messageListView.positionViewAtIndex(index, ListView.Center)
itemAtIndex(index).isTemporaryHighlighted = true
}
Item {
id: hoverActions
property var event: null
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
property bool showEdit: event && (userMsg && (event.eventType === "emote" || event.eventType === "message"))
property var delegate: null
property var bubble: null
property var hovered: bubble && bubble.hovered
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
@@ -424,8 +456,11 @@ Kirigami.ScrollablePage {
interval: 200
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
}
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0) - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 0)) : 0
property int childOffset: userMsg && Config.showLocalMessagesOnRight && !Config.compactLayout ? (bubble ? bubble.width : 0) - childWidth : Math.max((bubble ? bubble.width : 0) - childWidth, 0)
x: delegate && bubble ? (delegate.x + bubble.x + Kirigami.Units.largeSpacing + childOffset - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 + (delegate.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 : 0 ): 0) - (userMsg && !Config.compactLayout ? Kirigami.Units.gridUnit : 0)) : 0
y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
visible: false
property var updateFunction
@@ -441,10 +476,23 @@ Kirigami.ScrollablePage {
id: hoverHandler
margin: Kirigami.Units.smallSpacing
}
Kirigami.Icon {
source: "security-high"
width: height
height: parent.height
visible: hoverActions.event.verified
HoverHandler {
id: hover
}
QQC2.ToolTip.text: i18n("This message was sent from a verified device")
QQC2.ToolTip.visible: hover.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Button {
QQC2.ToolTip.text: i18n("React")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open();
EmojiDialog {
@@ -458,11 +506,12 @@ Kirigami.ScrollablePage {
QQC2.Button {
QQC2.ToolTip.text: i18n("Edit")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
visible: hoverActions.showEdit
icon.name: "document-edit"
onClicked: {
if (hoverActions.showEdit) {
ChatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
chatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
}
chatBox.focusInputField();
}
@@ -470,9 +519,10 @@ Kirigami.ScrollablePage {
QQC2.Button {
QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "mail-replied-symbolic"
onClicked: {
ChatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
chatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
chatBox.focusInputField();
}
}
@@ -492,14 +542,14 @@ Kirigami.ScrollablePage {
onEditLastUserMessage: {
const targetMessage = messageEventModel.getLastLocalUserMessageEventId();
if (targetMessage) {
ChatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
chatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
chatBox.focusInputField();
}
}
onReplyPreviousUserMessage: {
const replyResponse = messageEventModel.getLatestMessageFromIndex(0);
if (replyResponse && replyResponse["event_id"]) {
ChatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
chatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
}
}
}
@@ -543,7 +593,7 @@ Kirigami.ScrollablePage {
function warning(title, message) {
page.header.contentItem.text = `${title}<br />${message}`;
page.header.contentItem.type = Kirigami.MessageType.Warning;
page.header.contentItem.visible = true;
page.header.visible = true;
}
function showUserDetail(user) {
@@ -588,6 +638,14 @@ Kirigami.ScrollablePage {
return index;
}
// Mark all messages as read if all unread messages are visible to the user
function markReadIfVisible() {
let readMarkerRow = eventToIndex(currentRoom.readMarkerEventId)
if (readMarkerRow > 0 && readMarkerRow < firstVisibleIndex()) {
currentRoom.markAllMessagesAsRead()
}
}
/// Open message context dialog for file and videos
function openFileContext(event, file) {
const contextMenu = fileDelegateContextMenu.createObject(page, {
@@ -598,12 +656,13 @@ Kirigami.ScrollablePage {
file: file,
mimeType: event.mimeType,
progressInfo: event.progressInfo,
plainMessage: event.message,
});
contextMenu.open();
}
/// Open context menu for normal message
function openMessageContext(event, selectedText) {
function openMessageContext(event, selectedText, plainMessage) {
const contextMenu = messageDelegateContextMenu.createObject(page, {
selectedText: selectedText,
author: event.author,
@@ -611,7 +670,8 @@ Kirigami.ScrollablePage {
eventId: event.eventId,
formattedBody: event.formattedBody,
source: event.source,
eventType: event.eventType
eventType: event.eventType,
plainMessage: plainMessage,
});
contextMenu.open();
}

View File

@@ -5,7 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component.Login 1.0
@@ -45,10 +45,81 @@ Kirigami.ScrollablePage {
}
ColumnLayout {
Kirigami.Icon {
source: "org.kde.neochat"
Item {
Layout.preferredHeight: Kirigami.Units.gridUnit * 10
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
id: swapper
states: [
State {
when: !LoginHelper.homeserverReachable
name: "idle"
PropertyChanges {
target: icon
opacity: 1
}
PropertyChanges {
target: avi
opacity: 0
}
},
State {
when: LoginHelper.homeserverReachable
name: "showAvi"
PropertyChanges {
target: icon
opacity: 0
}
PropertyChanges {
target: avi
opacity: 1
}
}
]
transitions: [
Transition {
to: "showAvi"
SequentialAnimation {
NumberAnimation { target: icon; properties: "opacity";}
NumberAnimation { target: avi; properties: "opacity";}
}
},
Transition {
from: "showAvi"
SequentialAnimation {
NumberAnimation { target: avi; properties: "opacity";}
NumberAnimation { target: icon; properties: "opacity";}
}
}
]
Kirigami.Icon {
id: icon
source: "org.kde.neochat"
anchors.fill: parent
implicitWidth: height
}
ColumnLayout {
id: avi
opacity: 0
anchors.fill: parent
Kirigami.Avatar {
source: LoginHelper.loginAvatar
name: LoginHelper.loginName
Layout.fillHeight: true
implicitWidth: height
Layout.alignment: Qt.AlignHCenter
}
Controls.Label {
text: LoginHelper.loginName
font.pointSize: 24
Layout.alignment: Qt.AlignHCenter
}
}
}
Controls.Label {
Layout.fillWidth: true
@@ -66,6 +137,7 @@ Kirigami.ScrollablePage {
headerMessage.text = ""
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
@@ -78,16 +150,26 @@ Kirigami.ScrollablePage {
onClicked: {
module.source = welcomePage.currentStep.previousUrl
}
icon.name: "go-back"
}
Controls.Button {
id: continueButton
enabled: welcomePage.currentStep.acceptable
visible: welcomePage.currentStep.showContinueButton
opacity: welcomePage.currentStep.showContinueButton ? 1 : 0
Behavior on opacity { NumberAnimation {} }
action: welcomePage.currentStep.action
}
}
Kirigami.LoadingPlaceholder {
icon.name: "online"
opacity: LoginHelper.testing ? 1 : 0
text: i18n("Connecting to your homeserver...")
Behavior on opacity { NumberAnimation {} }
Layout.alignment: Qt.AlignHCenter
}
Connections {
target: currentStep

View File

@@ -18,7 +18,7 @@ Kirigami.OverlayDrawer {
id: roomDrawer
readonly property var room: RoomManager.currentRoom
width: modal ? undefined : actualWidth
width: actualWidth
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
@@ -77,48 +77,70 @@ Kirigami.OverlayDrawer {
sourceComponent: ColumnLayout {
id: columnLayout
property alias userSearchText: userListSearchField.text
property alias highlightedUser: userListView.currentIndex
spacing: 0
Kirigami.AbstractApplicationHeader {
Layout.fillWidth: true
topPadding: Kirigami.Units.smallSpacing / 2;
bottomPadding: Kirigami.Units.smallSpacing / 2;
rightPadding: Kirigami.Units.smallSpacing
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
RowLayout {
anchors.fill: parent
spacing: 0
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.fillWidth: true
text: i18n("Room information")
level: 1
}
ToolButton {
id: inviteButton
Layout.alignment: Qt.AlignRight
icon.name: "list-add-user"
text: i18n("Invite")
text: i18n("Invite user to room")
display: AbstractButton.IconOnly
onClicked: {
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
roomDrawer.close();
}
}
Item {
// HACK otherwise rating item is not right aligned
Layout.fillWidth: true
}
ToolTip {
text: inviteButton.text
}
}
ToolButton {
id: favouriteButton
Layout.alignment: Qt.AlignRight
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
checkable: true
checked: room && room.isFavourite
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
display: AbstractButton.IconOnly
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
ToolTip {
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
text: favouriteButton.text
}
}
ToolButton {
id: settingsButton
Layout.alignment: Qt.AlignRight
icon.name: 'settings-configure'
text: i18n("Room settings")
display: AbstractButton.IconOnly
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
ToolTip {
text: i18n("Room settings")
text: settingsButton.text
}
}
}
@@ -127,14 +149,11 @@ Kirigami.OverlayDrawer {
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Kirigami.Heading {
text: i18n("Room information")
level: 3
}
spacing: Kirigami.Units.largeSpacing
RowLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
@@ -151,10 +170,9 @@ Kirigami.OverlayDrawer {
spacing: 0
Kirigami.Heading {
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
Layout.fillWidth: true
level: 1
font.bold: true
type: Kirigami.Heading.Type.Primary
wrapMode: Label.Wrap
text: room ? room.displayName : i18n("No name")
textFormat: Text.PlainText
@@ -165,6 +183,8 @@ Kirigami.OverlayDrawer {
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
readOnly: true
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
}
@@ -174,12 +194,14 @@ Kirigami.OverlayDrawer {
TextEdit {
Layout.fillWidth: true
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
readonly property var replaceLinks: /(https:\/\/[^ ]*)/
textFormat: TextEdit.MarkdownText
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
onLinkActivated: Qt.openUrlExternally(link)
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
onLinkActivated: UrlHelper.openUrl(link)
readOnly: true
MouseArea {
anchors.fill: parent
@@ -192,16 +214,23 @@ Kirigami.OverlayDrawer {
Kirigami.ListSectionHeader {
label: i18n("Members")
activeFocusOnTab: false
Label {
Layout.alignment: Qt.AlignRight
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
}
}
Pane {
padding: Kirigami.Units.smallSpacing
implicitWidth: parent.width
z: 2
Control {
Layout.fillWidth: true
// Note need to set padding individually to guarantee it will always work
// see note - https://doc.qt.io/qt-6/qml-qtquick-controls2-control.html#padding-prop
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
@@ -209,6 +238,7 @@ Kirigami.OverlayDrawer {
}
contentItem: Kirigami.SearchField {
id: userListSearchField
onAccepted: sortedMessageEventModel.filterString = text;
}
}
@@ -217,11 +247,12 @@ Kirigami.OverlayDrawer {
Layout.fillWidth: true
Layout.fillHeight: true
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: userListView
clip: true
headerPositioning: ListView.OverlayHeader
boundsBehavior: Flickable.DragOverBounds
activeFocusOnTab: true
model: KSortFilterProxyModel {
@@ -236,58 +267,53 @@ Kirigami.OverlayDrawer {
filterCaseSensitivity: Qt.CaseInsensitive
}
delegate: Kirigami.AbstractListItem {
width: userListView.width
delegate: Kirigami.BasicListItem {
id: userListItem
implicitHeight: Kirigami.Units.gridUnit * 2
z: 1
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
contentItem: RowLayout {
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
visible: Config.showAvatarInRoomDrawer
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
Label {
Layout.fillWidth: true
text: name
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
Label {
visible: perm != UserType.Member
text: {
if (perm == UserType.Owner) {
return i18n("Owner")
}
if (perm == UserType.Admin) {
return i18n("Admin")
}
if (perm == UserType.Moderator) {
return i18n("Mod")
}
if (perm == UserType.Muted) {
return i18n("Muted")
}
return ""
}
color: Kirigami.Theme.disabledTextColor
font.pixelSize: 12
textFormat: Text.PlainText
wrapMode: Text.NoWrap
label: name
onClicked: {
const popup = userDetailDialog.createObject(ApplicationWindow.overlay, {room: room, user: user, displayName: name, avatarMediaId: avatar})
popup.closed.connect(function() {
userListItem.highlighted = false
})
if (roomDrawer.modal) {
roomDrawer.close()
}
popup.open()
}
action: Kirigami.Action {
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
leading: Kirigami.Avatar {
implicitWidth: height
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
trailing: Label {
visible: perm != UserType.Member
text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
default:
return "";
}
}
color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
}
}
@@ -296,7 +322,10 @@ Kirigami.OverlayDrawer {
}
onRoomChanged: {
loader.item.userSearchText = ""
if (loader.active) {
loader.item.userSearchText = ""
loader.item.highlightedUser = -1
}
if (room == null) {
close()
}

View File

@@ -3,12 +3,12 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
id: root
property var room
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
@@ -30,6 +30,16 @@ Kirigami.CategorizedSettings {
room: root.room
}
}
},
Kirigami.SettingAction {
text: i18n("Notifications")
icon.name: "notifications"
page: Qt.resolvedUrl("PushNotification.qml")
initialProperties: {
return {
room: root.room
}
}
}
]
}

View File

@@ -21,7 +21,7 @@ Kirigami.ScrollablePage {
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
title: i18n('General')
title: i18n("General")
ColumnLayout {
Kirigami.FormLayout {

View File

@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
property var room
title: i18nc('@title:window', 'Notifications')
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.RadioButton {
text: i18n("Follow global setting")
Kirigami.FormData.label: i18n("Room notifications setting:")
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Default
}
}
QQC2.RadioButton {
text: i18nc("As in 'notify for all messages'","All")
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.All
}
}
QQC2.RadioButton {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.MentionKeyword
}
}
QQC2.RadioButton {
text: i18nc("As in 'do not notify for any messages'","Off")
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState != PushNotificationState.Unknown
onToggled: {
room.pushNotificationState = PushNotificationState.Mute
}
}
}
}
}

View File

@@ -16,13 +16,13 @@ Kirigami.ScrollablePage {
property var room
title: i18n('Security')
title: i18n("Security")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
CheckBox {
RadioButton {
text: i18nc("@option:check", "Private (invite only)")
Kirigami.FormData.label: i18nc("@option:check", "Access:")
checked: room.joinRule === "invite"
@@ -32,7 +32,7 @@ Kirigami.ScrollablePage {
text: i18n("Only invited people can join.")
font: Kirigami.Theme.smallFont
}
CheckBox {
RadioButton {
text: i18nc("@option:check", "Space members")
checked: room.joinRule === "restricted"
enabled: false
@@ -41,7 +41,7 @@ Kirigami.ScrollablePage {
text: i18n("Anyone in a space can find and join.")
font: Kirigami.Theme.smallFont
}
CheckBox {
RadioButton {
text: i18nc("@option:check", "Public")
checked: room.joinRule === "public"
enabled: false

View File

@@ -7,6 +7,6 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.AboutPage {
title: i18nc('@title:window', 'About NeoChat')
title: i18nc("@title:window", "About NeoChat")
aboutData: Controller.aboutData
}

View File

@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
id: root
title: i18n("Edit Account")
property var connection
ColumnLayout {
Kirigami.FormLayout {
RowLayout {
Kirigami.Avatar {
id: avatar
source: root.connection && root.connection.localUser.avatarMediaId ? ("image://mxc/" + root.connection.localUser.avatarMediaId) : ""
MouseArea {
id: mouseArea
anchors.fill: parent
property var fileDialog: null;
onClicked: {
if (fileDialog != null) {
return;
}
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
fileDialog.chosen.connect(function(receivedSource) {
mouseArea.fileDialog = null;
if (!receivedSource) {
return;
}
parent.source = receivedSource;
});
fileDialog.onRejected.connect(function() {
mouseArea.fileDialog = null;
});
fileDialog.open();
}
}
}
Controls.Button {
visible: avatar.source.toString().length !== 0
icon.name: "edit-clear"
onClicked: avatar.source = ""
}
Kirigami.FormData.label: i18n("Avatar:")
}
Controls.TextField {
id: name
text: root.connection ? root.connection.localUser.displayName : ""
Kirigami.FormData.label: i18n("Name:")
}
Controls.TextField {
id: accountLabel
text: root.connection ? root.connection.localUser.accountLabel : ""
Kirigami.FormData.label: i18n("Label:")
}
Controls.TextField {
id: currentPassword
Kirigami.FormData.label: i18n("Current Password:")
enabled: roto.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
}
Controls.TextField {
id: newPassword
Kirigami.FormData.label: i18n("New Password:")
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
}
Controls.TextField {
id: confirmPassword
Kirigami.FormData.label: i18n("Confirm new Password:")
enabled: root.connection !== undefined && root.connection.canChangePassword !== false
echoMode: TextInput.Password
}
}
}
footer: RowLayout {
Item {
Layout.fillWidth: true
}
Controls.Button {
text: i18n("Save")
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
onClicked: {
if (!Controller.setAvatar(root.connection, avatar.source)) {
showPassiveNotification("The Avatar could not be set");
}
if (root.connection.localUser.displayName !== name.text) {
root.connection.localUser.rename(name.text);
}
if (root.connection.localUser.accountLabel !== accountLabel.text) {
root.connection.localUser.setAccountLabel(accountLabel.text);
}
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
if(newPassword.text === confirmPassword.text) {
Controller.changePassword(root.connection, currentPassword.text, newPassword.text);
} else {
showPassiveNotification(i18n("Passwords do not match"));
return;
}
}
root.closeDialog();
}
}
Controls.Button {
text: i18n("Cancel")
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing
onClicked: root.closeDialog();
}
}
}

View File

@@ -23,6 +23,7 @@ Kirigami.ScrollablePage {
ListView {
model: AccountRegistry
anchors.fill: parent
delegate: Kirigami.BasicListItem {
text: model.connection.localUser.displayName
labelItem.textFormat: Text.PlainText
@@ -30,31 +31,39 @@ Kirigami.ScrollablePage {
icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user"
onClicked: {
Controller.activeConnection = model.connection
pageStack.layers.pop()
Controller.activeConnection = model.connection;
pageStack.layers.pop();
}
trailing: RowLayout {
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action {
text: i18n("Edit this account")
iconName: "document-edit"
onTriggered: {
userEditSheet.connection = model.connection
userEditSheet.open()
}
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
connection: model.connection
}, {
title: i18n("Account editor")
});
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action {
text: i18n("Logout")
iconName: "im-kick-user"
onTriggered: {
Controller.logout(model.connection, true)
if(Controller.accountCount === 1)
pageStack.layers.pop()
Controller.logout(model.connection, true);
if (Controller.accountCount === 1) {
pageStack.layers.pop();
}
}
}
}
@@ -76,6 +85,7 @@ Kirigami.ScrollablePage {
}
}
}
Connections {
target: Controller
function onConnectionAdded() {
@@ -83,134 +93,21 @@ Kirigami.ScrollablePage {
pageStack.layers.pop()
}
function onPasswordStatus(status) {
if(status == Controller.Success)
showPassiveNotification(i18n("Password changed successfully"))
else if(status == Controller.Wrong)
showPassiveNotification(i18n("Wrong password entered"))
else
showPassiveNotification(i18n("Unknown problem while trying to change password"))
if (status === Controller.Success) {
showPassiveNotification(i18n("Password changed successfully"));
} else if (status === Controller.Wrong) {
showPassiveNotification(i18n("Wrong password entered"));
} else {
showPassiveNotification(i18n("Unknown problem while trying to change password"));
}
}
}
Component {
property Component openFileDialog: Component {
id: openFileDialog
OpenFileDialog {
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
}
}
Kirigami.OverlaySheet {
id: userEditSheet
property var connection
title: i18n("Edit Account")
Kirigami.FormLayout {
RowLayout {
Kirigami.Avatar {
id: avatar
source: userEditSheet.connection && userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
MouseArea {
id: mouseArea
anchors.fill: parent
property var fileDialog: null;
onClicked: {
if (fileDialog != null) {
return;
}
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
fileDialog.chosen.connect(function(receivedSource) {
mouseArea.fileDialog = null;
if (!receivedSource) {
return;
}
parent.source = receivedSource;
});
fileDialog.onRejected.connect(function() {
mouseArea.fileDialog = null;
});
fileDialog.open();
}
}
}
Controls.Button {
visible: avatar.source.toString().length !== 0
icon.name: "edit-clear"
onClicked: avatar.source = ""
}
Kirigami.FormData.label: i18n("Avatar:")
}
Controls.TextField {
id: name
text: userEditSheet.connection ? userEditSheet.connection.localUser.displayName : ""
Kirigami.FormData.label: i18n("Name:")
}
Controls.TextField {
id: accountLabel
text: userEditSheet.connection ? userEditSheet.connection.localUser.accountLabel : ""
Kirigami.FormData.label: i18n("Label:")
}
Controls.TextField {
id: currentPassword
Kirigami.FormData.label: i18n("Current Password:")
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
echoMode: TextInput.Password
}
Controls.TextField {
id: newPassword
Kirigami.FormData.label: i18n("New Password:")
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
echoMode: TextInput.Password
}
Controls.TextField {
id: confirmPassword
Kirigami.FormData.label: i18n("Confirm new Password:")
enabled: userEditSheet.connection !== undefined && userEditSheet.connection.canChangePassword !== false
echoMode: TextInput.Password
}
RowLayout {
Controls.Button {
text: i18n("Save")
onClicked: {
if(!Controller.setAvatar(userEditSheet.connection, avatar.source))
showPassiveNotification("The Avatar could not be set")
if(userEditSheet.connection.localUser.displayName !== name.text)
userEditSheet.connection.localUser.rename(name.text)
if(userEditSheet.connection.localUser.accountLabel !== accountLabel.text)
userEditSheet.connection.localUser.setAccountLabel(accountLabel.text)
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
if(newPassword.text === confirmPassword.text) {
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
} else {
showPassiveNotification(i18n("Passwords do not match"))
return
}
}
userEditSheet.close()
currentPassword.text = ""
newPassword.text = ""
confirmPassword.text = ""
}
}
Controls.Button {
text: i18n("Cancel")
onClicked: {
userEditSheet.close()
avatar.source = userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
currentPassword.text = ""
newPassword.text = ""
confirmPassword.text = ""
}
}
}
}
}
}

View File

@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
import NeoChat.Settings 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'Appearance')
title: i18nc("@title:window", "Appearance")
ColumnLayout {
RowLayout {
Layout.alignment: Qt.AlignCenter
@@ -177,7 +177,7 @@ Kirigami.ScrollablePage {
Kirigami.FormLayout {
Layout.maximumWidth: parent.width
QQC2.CheckBox {
Kirigami.FormData.label: "Show Avatar:"
Kirigami.FormData.label: i18n("Show Avatar:")
text: i18n("In Chat")
checked: Config.showAvatarInTimeline
onToggled: {
@@ -237,6 +237,7 @@ Kirigami.ScrollablePage {
HoverHandler { id: sliderHover }
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
text: Math.round(Config.transparency * 100) + "%"
@@ -245,7 +246,7 @@ Kirigami.ScrollablePage {
QQC2.CheckBox {
text: i18n("Show your messages on the right")
checked: Config.showLocalMessagesOnRight
enabled: !Config.isShowLocalMessagesOnRightImmutable
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
onToggled: {
Config.showLocalMessagesOnRight = checked
Config.save()

View File

@@ -5,7 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
@@ -17,13 +17,11 @@ Kirigami.ScrollablePage {
id: devices
}
Kirigami.PlaceholderMessage {
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
anchors.fill: parent
Kirigami.LoadingPlaceholder {
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
anchors.centerIn: parent
text: i18n("Loading…")
Controls.BusyIndicator {
running: parent.visible
}
}
delegate: Kirigami.BasicListItem {
@@ -43,6 +41,17 @@ Kirigami.ScrollablePage {
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
visible: Controller.encryptionSupported
action: Kirigami.Action {
text: i18n("Verify device")
iconName: "security-low-symbolic"
onTriggered: {
devices.connection.startKeyVerificationSession(model.id)
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
action: Kirigami.Action {

View File

@@ -16,12 +16,13 @@ import NeoChat.Component 1.0 as Components
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'Custom Emojis')
title: i18nc("@title:window", "Custom Emojis")
ListView {
anchors.fill: parent
model: CustomEmojiModel {
id: emojiModel
connection: Controller.activeConnection
}
@@ -102,7 +103,6 @@ Kirigami.ScrollablePage {
this.fileDialog = null
})
this.fileDialog.onRejected.connect(() => {
rej()
this.fileDialog = null
})
this.fileDialog.open()

View File

@@ -11,9 +11,10 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18nc('@title:window', 'General')
title: i18nc("@title:window", "General")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.CheckBox {
Kirigami.FormData.label: i18n("General settings:")
text: i18n("Close to system tray")
@@ -45,6 +46,7 @@ Kirigami.ScrollablePage {
onToggled: {
Config.showNotifications = checked
Config.save()
NotificationsManager.globalNotificationsEnabled = checked
}
}
QQC2.CheckBox {
@@ -96,19 +98,16 @@ Kirigami.ScrollablePage {
QQC2.CheckBox {
id: quickEditCheckbox
Layout.maximumWidth: parent.width
contentItem: QQC2.Label {
text: i18n("Use s/text/replacement syntax to edit your last message")
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
leftPadding: quickEditCheckbox.indicator.width + quickEditCheckbox.spacing
wrapMode: QQC2.Label.Wrap
}
text: i18n("Use s/text/replacement syntax to edit your last message")
checked: Config.allowQuickEdit
enabled: !Config.isAllowQuickEditImmutable
onToggled: {
Config.allowQuickEdit = checked
Config.save()
}
// TODO KF5.97 remove this line
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
}
QQC2.CheckBox {
text: i18n("Send Typing Notifications")
@@ -121,12 +120,16 @@ Kirigami.ScrollablePage {
}
QQC2.CheckBox {
text: i18n("Automatically hide/unhide the room information when resizing the window")
Layout.maximumWidth: parent.width
checked: Config.autoRoomInfoDrawer
enabled: !Config.isAutoRoomInfoDrawerImmutable
onToggled: {
Config.autoRoomInfoDrawer = checked
Config.save()
}
// TODO KF5.97 remove this line
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
}
}
}

View File

@@ -3,7 +3,6 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
@@ -25,7 +24,7 @@ Kirigami.CategorizedSettings {
page: Qt.resolvedUrl("AccountsPage.qml")
},
Kirigami.SettingAction {
text: i18n("Custom Emoji")
text: i18n("Custom Emojis")
icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("Emoticons.qml")
},

View File

@@ -47,7 +47,7 @@ Kirigami.Page {
dialog.close();
}
}
title: i18nc('@window:title', 'Spellchecking')
title: i18nc("@window:title", "Spellchecking")
QQC2.Dialog {
id: applyDialog
@@ -190,6 +190,10 @@ Kirigami.Page {
Layout.fillHeight: true
enabled: autodetectLanguageCheckbox.checked
Component.onCompleted: background.visible = wideMode
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView {
clip: true
model: settings.dictionaryModel
@@ -254,6 +258,10 @@ Kirigami.Page {
}
QQC2.ScrollView {
anchors.fill: parent
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
ListView {
model: settings.currentIgnoreList
delegate: Kirigami.BasicListItem {

View File

@@ -25,6 +25,7 @@
<name xml:lang="ia">Neochat</name>
<name xml:lang="id">NeoChat</name>
<name xml:lang="it">NeoChat</name>
<name xml:lang="ka">NeoChat</name>
<name xml:lang="ko">NeoChat</name>
<name xml:lang="nl">NeoChat</name>
<name xml:lang="nn">NeoChat</name>
@@ -44,7 +45,7 @@
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
@@ -56,6 +57,7 @@
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
<summary xml:lang="nn">Ein klient for Matrix, den desentraliserte lynmeldings­protokollen</summary>
@@ -102,7 +104,7 @@
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels chats encriptats i els chats de vídeo.</p>
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
@@ -129,7 +131,7 @@
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
@@ -174,6 +176,7 @@
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
<developer_name xml:lang="id">Komunitas KDE</developer_name>
<developer_name xml:lang="it">La comunità KDE</developer_name>
<developer_name xml:lang="ka">KDE-ის საზოგადოება</developer_name>
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
@@ -206,6 +209,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="22.09" date="2022-09-27">
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
</release>
<release version="22.06" date="2022-06-24">
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
<description>

View File

@@ -18,6 +18,7 @@ Name[hu]=NeoChat
Name[ia]=Neochat
Name[id]=NeoChat
Name[it]=NeoChat
Name[ka]=NeoChat
Name[ko]=NeoChat
Name[lt]=NeoChat
Name[nl]=NeoChat
@@ -51,6 +52,7 @@ GenericName[hu]=Matrix kliens
GenericName[ia]=Cliente de Matrice
GenericName[id]=Klien Matrix
GenericName[it]=Client Matrix
GenericName[ka]=Matrix -ის კლიენტი
GenericName[ko]=Matrix 클라이언트
GenericName[lt]=Matrix kliento programą
GenericName[nl]=Matrix-client
@@ -83,6 +85,7 @@ Comment[hu]=Kliens a Matrix protokollhoz
Comment[ia]=Cliente per le protocollo de Matrix
Comment[id]=Klien untuk protokol Matrix
Comment[it]=Client per il protocollo Matrix
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
Comment[ko]=Matrix 프로토콜용 클라이언트
Comment[lt]=Matrix protokolo kliento programa
Comment[nl]=Client voor het Matrix-protocol

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

2382
po/ka/neochat.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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