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/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/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.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/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 License: BSD-2-Clause
Files: src/neochatconfig.kcfg Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org> Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
License: BSD-2-Clause License: BSD-2-Clause
Files: src/neochat.notifyrc Files: src/neochat.notifyrc
@@ -42,3 +41,7 @@ License: BSD-2-Clause
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru> Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0 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) cmake_minimum_required(VERSION 3.16)
project(NeoChat) 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") set(QT_MIN_VERSION "5.15.2")
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
@@ -25,12 +25,14 @@ include(FeatureSummary)
include(ECMSetupVersion) include(ECMSetupVersion)
include(KDEInstallDirs) include(KDEInstallDirs)
include(ECMFindQmlModule) include(ECMFindQmlModule)
include(KDEClangFormat)
include(KDECMakeSettings) include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon) include(ECMAddAppIcon)
include(KDEGitCommitHooks) include(KDEGitCommitHooks)
include(ECMCheckOutboundLicense) include(ECMCheckOutboundLicense)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
if(NEOCHAT_FLATPAK) if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake) include(cmake/Flatpak.cmake)
@@ -41,8 +43,8 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
) )
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg) find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt5 PROPERTIES set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
) )
@@ -56,8 +58,8 @@ set_package_properties(KF5Kirigami2 PROPERTIES
PURPOSE "Kirigami application UI framework" PURPOSE "Kirigami application UI framework"
) )
find_package(Qt5Keychain) find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt5Keychain PROPERTIES set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Secure storage of account secrets" PURPOSE "Secure storage of account secrets"
) )
@@ -69,11 +71,12 @@ if(ANDROID)
PURPOSE "Encrypted communications" PURPOSE "Encrypted communications"
) )
else() else()
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets) find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES set_package_properties(KF5QQC2DesktopStyle PROPERTIES
TYPE RUNTIME TYPE RUNTIME
) )
ecm_find_qmlmodule(org.kde.sonnet 1.0)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0) ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
endif() endif()
@@ -108,8 +111,8 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments" PURPOSE "Add image editing capability to image attachments"
) )
find_package(QCoro5 COMPONENTS Core QUIET) find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
if(NOT QCoro5_FOUND) if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
find_package(QCoro REQUIRED) find_package(QCoro REQUIRED)
endif() endif()
@@ -136,11 +139,12 @@ add_subdirectory(src)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h) if (NOT ANDROID)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 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)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
endif()
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml) file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
# CI installs dependency headers to _install and _build, which break the reuse check # CI installs dependency headers to _install and _build, which break the reuse check
# Fixes the test by excluding this directory # 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). 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 ## Features
@@ -55,7 +55,7 @@ We welcome contributions in this direction.
## Contact ## 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). Development happens in http://invent.kde.org/network/neochat (not in GitHub).
## Acknowledgement ## Acknowledgement

View File

@@ -21,7 +21,7 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </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.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.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>

View File

@@ -9,6 +9,6 @@ install(
FILES FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
DESTINATION 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 { Loader {
id: root 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) readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
active: visible active: visible
sourceComponent: Component { sourceComponent: Component {
Pane { Pane {
id: attachmentPane 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 Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: Item { contentItem: Item {
@@ -46,7 +46,7 @@ Loader {
asynchronous: true asynchronous: true
cache: false // Cache is not needed. Images will rarely be shown repeatedly. 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 smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
source: hasImage ? ChatBoxHelper.attachmentPath : "" source: hasImage ? chatBoxHelper.attachmentPath : ""
visible: hasImage visible: hasImage
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@@ -162,14 +162,14 @@ Loader {
Component { Component {
id: imageEditorPage id: imageEditorPage
ImageEditorPage { ImageEditorPage {
imagePath: ChatBoxHelper.attachmentPath imagePath: chatBoxHelper.attachmentPath
} }
} }
onClicked: { onClicked: {
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage); let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function(newPath) { imageEditor.newPathChanged.connect(function(newPath) {
applicationWindow().pageStack.layers.pop(); applicationWindow().pageStack.layers.pop();
ChatBoxHelper.attachmentPath = newPath; chatBoxHelper.attachmentPath = newPath;
}); });
} }
ToolTip.text: text ToolTip.text: text
@@ -180,7 +180,7 @@ Loader {
icon.name: "dialog-cancel" icon.name: "dialog-cancel"
text: i18n("Cancel") text: i18n("Cancel")
display: AbstractButton.IconOnly display: AbstractButton.IconOnly
onClicked: ChatBoxHelper.clearAttachment(); onClicked: chatBoxHelper.clearAttachment();
ToolTip.text: text ToolTip.text: text
ToolTip.visible: hovered ToolTip.visible: hovered
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,12 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 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 import org.kde.kirigami 2.15 as Kirigami
ApplicationWindow { Popup {
id: root id: root
property alias source: image.source property alias source: image.source
@@ -14,57 +16,293 @@ ApplicationWindow {
property string blurhash: "" property string blurhash: ""
property int imageWidth: -1 property int imageWidth: -1
property int imageHeight: -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 { Control {
sequence: "Escape" Layout.fillWidth: true
onActivated: root.destroy()
}
color: Kirigami.Theme.backgroundColor contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
background: AbstractButton { Kirigami.Avatar {
onClicked: root.destroy() id: avatar
}
BusyIndicator { Layout.preferredWidth: Kirigami.Units.iconSizes.medium
visible: image.status !== Image.Ready && root.blurhash === "" Layout.preferredHeight: Kirigami.Units.iconSizes.medium
anchors.centerIn: parent
running: visible
}
AnimatedImage { name: modelData.author.name ?? modelData.author.displayName
id: image source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : ""
anchors.centerIn: parent color: modelData.author.color
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width) Label {
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height) id: nameLabel
fillMode: Image.PreserveAspectFit text: modelData.author.displayName
textFormat: Text.PlainText
font.weight: Font.Bold
color: author.color
}
Label {
id: timeLabel
Image { text: time.toLocaleString(Qt.locale(), Locale.ShortFormat)
anchors.centerIn: parent }
width: image.width }
height: image.height Label {
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : "" id: imageLabel
visible: root.blurhash !== "" && parent.status !== Image.Ready 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 { Component {
anchors.top: parent.top id: saveAsDialog
anchors.right: parent.right FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
if (!currentFile) {
return;
}
currentRoom.downloadFile(eventId, currentFile)
}
}
}
text: i18n("Close") onClosed: {
icon.name: "dialog-close" image.scaleFactor = 1
display: AbstractButton.IconOnly image.rotationAngle = 0
width: Kirigami.Units.gridUnit * 2
height: Kirigami.Units.gridUnit * 2
onClicked: root.destroy()
} }
} }

View File

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

View File

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

View File

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

View File

@@ -20,21 +20,29 @@ LoginStep {
Connections { Connections {
target: LoginHelper target: LoginHelper
function onSsoUrlChanged() { function onSsoUrlChanged() {
Qt.openUrlExternally(LoginHelper.ssoUrl) UrlHelper.openUrl(LoginHelper.ssoUrl)
} }
function onConnected() { function onConnected() {
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml") processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
} }
} }
RowLayout {
QQC2.Button {
text: i18nc("@action:button", "Back")
QQC2.Button { onClicked: {
text: i18n("Login") module.source = "qrc:/imports/NeoChat/Component/Login/Login.qml"
onClicked: { }
LoginHelper.loginWithSso() }
root.showMessage(i18n("Complete the authentication steps in your browser")) 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-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 as Platform
import QtMultimedia 5.15 import QtMultimedia 5.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
TimelineContainer { TimelineContainer {
id: audioDelegate id: audioDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID) onReplyClicked: ListView.view.goToEvent(eventID)
onOpenContextMenu: openFileContext(model, audioDelegate)
readonly property bool downloaded: model.progressInfo && model.progressInfo.completed
onDownloadedChanged: audio.play()
hoverComponent: hoverActions hoverComponent: hoverActions
innerObject: Control { innerObject: Control {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: audioDelegate.bubbleMaxWidth Layout.maximumWidth: audioDelegate.contentMaxWidth
Audio { Audio {
id: audio id: audio
source: currentRoom.urlToMxcUrl(content.url) source: model.progressInfo.localPath
autoLoad: false autoLoad: false
} }
TapHandler { states: [
acceptedButtons: Qt.RightButton State {
onTapped: openFileContext(model, parent) name: "notDownloaded"
} when: !model.progressInfo.completed && !model.progressInfo.active
TapHandler {
acceptedButtons: Qt.LeftButton PropertyChanges {
onLongPressed: openFileContext(model, parent) 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 { contentItem: ColumnLayout {
RowLayout { RowLayout {
ToolButton { ToolButton {
icon.name: audio.playbackState == Audio.PlayingState ? "media-playback-pause" : "media-playback-start" id: playButton
onClicked: {
if (audio.playbackState == Audio.PlayingState) {
audio.pause()
} else {
audio.play()
}
}
} }
Label { Label {
text: model.display 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 { RowLayout {
visible: audio.hasAudio visible: audio.hasAudio
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
ProgressBar { Slider {
from: 0 from: 0
to: audio.duration to: audio.duration
value: audio.position value: audio.position
onMoved: audio.seek(value)
} }
Label { Label {

View File

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

View File

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

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
@@ -16,11 +15,12 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer { TimelineContainer {
id: fileDelegate id: fileDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID) onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, fileDelegate)
readonly property bool downloaded: progressInfo && progressInfo.completed readonly property bool downloaded: progressInfo && progressInfo.completed
function saveFileAs() { function saveFileAs() {
@@ -30,14 +30,14 @@ TimelineContainer {
} }
function openSavedFile() { function openSavedFile() {
if (Qt.openUrlExternally(progressInfo.localPath)) return; if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return; if (UrlHelper.openUrl(progressInfo.localDir)) return;
} }
innerObject: RowLayout { innerObject: RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: fileDelegate.bubbleMaxWidth Layout.maximumWidth: fileDelegate.contentMaxWidth
Layout.margins: Kirigami.Units.largeSpacing Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
@@ -53,6 +53,7 @@ TimelineContainer {
icon.name: "document-open" 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.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() onClicked: openSavedFile()
} }
@@ -70,6 +71,7 @@ TimelineContainer {
icon.name: "media-playback-stop" 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.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) 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 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
import NeoChat.Component 1.0 import NeoChat.Component 1.0
import NeoChat.Dialog 1.0 import NeoChat.Dialog 1.0
@@ -16,11 +17,11 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer { TimelineContainer {
id: imageDelegate id: imageDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID) onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, imageDelegate)
property var content: model.content property var content: model.content
readonly property bool isAnimated: contentType === "image/gif" readonly property bool isAnimated: contentType === "image/gif"
@@ -35,8 +36,8 @@ TimelineContainer {
innerObject: Image { innerObject: Image {
id: img id: img
Layout.maximumWidth: imageDelegate.bubbleMaxWidth Layout.maximumWidth: imageDelegate.contentMaxWidth
Layout.maximumHeight: imageDelegate.bubbleMaxWidth / imageDelegate.info.w * imageDelegate.info.h Layout.maximumHeight: imageDelegate.contentMaxWidth / imageDelegate.info.w * imageDelegate.info.h
Layout.preferredWidth: imageDelegate.info.w Layout.preferredWidth: imageDelegate.info.w
Layout.preferredHeight: imageDelegate.info.h Layout.preferredHeight: imageDelegate.info.h
source: model.mediaUrl source: model.mediaUrl
@@ -51,6 +52,7 @@ TimelineContainer {
ToolTip.text: model.display ToolTip.text: model.display
ToolTip.visible: hoverHandler.hovered ToolTip.visible: hoverHandler.hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
@@ -86,25 +88,25 @@ TimelineContainer {
} }
} }
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler { TapHandler {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent) onLongPressed: openFileContext(model, parent)
onTapped: { onTapped: {
fullScreenImage.createObject(parent, { img.ToolTip.hide()
filename: eventId, fullScreenImage.open()
source: model.mediaUrl,
blurhash: model.content.info["xyz.amorgan.blurhash"],
imageWidth: content.info.w,
imageHeight: content.info.h
}).showFullScreen();
} }
} }
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() { function downloadAndOpen() {
if (downloaded) { if (downloaded) {
openSavedFile() openSavedFile()
@@ -115,8 +117,8 @@ TimelineContainer {
} }
function openSavedFile() { function openSavedFile() {
if (Qt.openUrlExternally(progressInfo.localPath)) return; if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) 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 { TimelineContainer {
id: messageDelegate id: messageDelegate
width: ListView.view.width
property bool isEmote: false property bool isEmote: false
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
onReplyClicked: ListView.view.goToEvent(eventID) onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions hoverComponent: hoverActions
innerObject: RichLabel { innerObject: ColumnLayout {
isEmote: messageDelegate.isEmote Layout.maximumWidth: messageDelegate.contentMaxWidth
Layout.maximumWidth: messageDelegate.bubbleMaxWidth RichLabel {
id: label
TapHandler { isEmote: messageDelegate.isEmote
acceptedButtons: Qt.RightButton
onTapped: openMessageContext(model, parent.selectedText)
} }
Loader {
TapHandler { id: linkPreviewLoader
acceptedButtons: Qt.LeftButton Layout.fillWidth: true
onLongPressed: openMessageContext(model, parent.selectedText) 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 import org.kde.neochat 1.0
QQC2.ItemDelegate { QQC2.ItemDelegate {
id: readMarkerDelegate
padding: Kirigami.Units.largeSpacing padding: Kirigami.Units.largeSpacing
topInset: Kirigami.Units.largeSpacing topInset: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing * 2 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 { contentItem: QQC2.Label {
text: i18nc("Relative time since the room was last read", "Last read: %1", time) text: i18nc("Relative time since the room was last read", "Last read: %1", time)
} }

View File

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

View File

@@ -15,14 +15,31 @@ TextEdit {
readonly property var hasSpoiler: /data-mx-spoiler/g readonly property var hasSpoiler: /data-mx-spoiler/g
property bool isEmote: false 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) property bool spoilerRevealed: !hasSpoiler.test(textMessage)
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage)) ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
Layout.fillWidth: Config.compactLayout Layout.fillWidth: true
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0 persistentSelection: true
text: "<style> text: "<style>
table { table {
@@ -44,6 +61,10 @@ a{
text-decoration: none; text-decoration: none;
} }
" + (!spoilerRevealed ? " " + (!spoilerRevealed ? "
[data-mx-spoiler] a {
color: transparent;
background: " + Kirigami.Theme.textColor + ";
}
[data-mx-spoiler] { [data-mx-spoiler] {
color: transparent; color: transparent;
background: " + Kirigami.Theme.textColor + "; 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>") : "") </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 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 font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile selectByMouse: !Kirigami.Settings.isMobile
readOnly: true readOnly: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
textFormat: Text.RichText textFormat: Text.RichText
onLinkActivated: RoomManager.openResource(link) onLinkActivated: {
onHoveredLinkChanged: if (hoveredLink.length > 0) { spoilerRevealed = true
RoomManager.openResource(link)
}
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
applicationWindow().hoverLinkIndicator.text = hoveredLink; applicationWindow().hoverLinkIndicator.text = hoveredLink;
} else { } else {
applicationWindow().hoverLinkIndicator.text = ""; 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.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0 import NeoChat.Component 1.0
import NeoChat.Dialog 1.0 import NeoChat.Dialog 1.0
Control { Control {
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing id: stateDelegate
width: ListView.view.width - Kirigami.Units.largeSpacing - x // 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 height: sectionDelegate.height + rowLayout.height
SectionDelegate { SectionDelegate {
id: sectionDelegate id: sectionDelegate
width: parent.width
anchors.top: parent.top anchors.top: parent.top
anchors.leftMargin: Kirigami.Units.smallSpacing anchors.left: parent.left
anchors.right: parent.right
visible: model.showSection visible: model.showSection
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
} }
@@ -27,8 +61,11 @@ Control {
RowLayout { RowLayout {
id: rowLayout id: rowLayout
height: label.contentHeight height: label.contentHeight
width: parent.width
anchors.bottom: parent.bottom 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 { Kirigami.Avatar {
id: icon id: icon

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
@@ -13,19 +12,35 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0 import NeoChat.Dialog 1.0
QQC2.ItemDelegate { QQC2.ItemDelegate {
id: messageDelegate id: timelineContainer
default property alias innerObject : column.children default property alias innerObject : column.children
// readonly property bool failed: marks == EventStatus.SendingFailed // readonly property bool failed: marks == EventStatus.SendingFailed
property bool isEmote: false property bool isEmote: false
property bool cardBackground: true 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 && property bool showUserMessageOnRight: Config.showLocalMessagesOnRight &&
model.author.isLocalUser && model.author.isLocalUser && !Config.compactLayout
!applicationWindow().wideScreen &&
!Config.compactLayout
signal openExternally() signal openExternally()
signal replyClicked(string eventID) signal replyClicked(string eventID)
@@ -38,7 +53,17 @@ QQC2.ItemDelegate {
topPadding: 0 topPadding: 0
bottomPadding: 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 property Item hoverComponent
// show hover actions // show hover actions
@@ -51,13 +76,39 @@ QQC2.ItemDelegate {
// updates the global hover component to point to this delegate, and update its position // updates the global hover component to point to this delegate, and update its position
function updateHoverComponent() { function updateHoverComponent() {
if (hoverComponent) { if (hoverComponent) {
hoverComponent.delegate = timelineContainer
hoverComponent.bubble = bubble hoverComponent.bubble = bubble
hoverComponent.updateFunction = updateHoverComponent; hoverComponent.updateFunction = updateHoverComponent;
hoverComponent.event = model 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 { SectionDelegate {
id: sectionDelegate id: sectionDelegate
@@ -70,15 +121,20 @@ QQC2.ItemDelegate {
Kirigami.Avatar { Kirigami.Avatar {
id: 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 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.width: width
sourceSize.height: width sourceSize.height: width
anchors { anchors {
top: sectionDelegate.bottom 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 left: parent.left
leftMargin: Kirigami.Units.largeSpacing leftMargin: Kirigami.Units.smallSpacing
} }
visible: model.showAuthor && visible: model.showAuthor &&
@@ -107,22 +163,19 @@ QQC2.ItemDelegate {
id: bubble id: bubble
topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.smallSpacing leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
hoverEnabled: true hoverEnabled: true
// state: Config.compactLayout ? "compactLayout" : "default"
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
anchors { anchors {
top: avatar.top top: avatar.top
leftMargin: Kirigami.Units.largeSpacing leftMargin: Kirigami.Units.smallSpacing
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
} }
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably // 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 // states for anchor animations on window resize
// as setting anchors to undefined did not work reliably // as setting anchors to undefined did not work reliably
states: [ states: [
@@ -152,31 +205,23 @@ QQC2.ItemDelegate {
contentItem: ColumnLayout { contentItem: ColumnLayout {
id: column id: column
spacing: 0 spacing: Kirigami.Units.smallSpacing
Item { RowLayout {
id: rowLayout id: rowLayout
spacing: Kirigami.Units.smallSpacing
visible: model.showAuthor && !isEmote 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 { QQC2.Label {
id: nameLabel id: nameLabel
topInset: 0
visible: model.showAuthor && !isEmote Layout.maximumWidth: contentMaxWidth - timeLabel.implicitWidth - rowLayout.spacing
anchors.left: rowLayout.left
anchors.right: timeLabel.left
anchors.rightMargin: Kirigami.Units.smallSpacing
text: visible ? author.displayName : "" text: visible ? author.displayName : ""
textFormat: Text.PlainText textFormat: Text.PlainText
font.weight: Font.Bold font.weight: Font.Bold
color: author.color color: author.color
wrapMode: Text.Wrap elide: Text.ElideRight
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -193,10 +238,16 @@ QQC2.ItemDelegate {
} }
QQC2.Label { QQC2.Label {
id: timeLabel id: timeLabel
anchors.right: rowLayout.right
visible: model.showAuthor && !isEmote text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
text: visible ? time.toLocaleTimeString(Locale.ShortFormat) : ""
color: Kirigami.Theme.disabledTextColor 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 { Loader {
@@ -204,8 +255,6 @@ QQC2.ItemDelegate {
active: model.reply !== undefined active: model.reply !== undefined
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml' source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
visible: active visible: active
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
Connections { Connections {
target: replyLoader.item target: replyLoader.item
@@ -217,19 +266,14 @@ QQC2.ItemDelegate {
} }
background: Item { 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 { Kirigami.ShadowedRectangle {
id: bubbleBackground
visible: cardBackground && !Config.compactLayout visible: cardBackground && !Config.compactLayout
anchors.fill: parent anchors.fill: parent
color: { color: {
if (model.author.isLocalUser) { if (model.author.isLocalUser) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15) return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
} else if (model.isHighlighted) { } else if (timelineContainer.isHighlighted) {
return Kirigami.Theme.positiveBackgroundColor return Kirigami.Theme.positiveBackgroundColor
} else { } else {
return Kirigami.Theme.backgroundColor return Kirigami.Theme.backgroundColor
@@ -237,9 +281,13 @@ QQC2.ItemDelegate {
} }
radius: Kirigami.Units.smallSpacing radius: Kirigami.Units.smallSpacing
shadow.size: 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.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1 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 visible: active
sourceComponent: ReactionDelegate { } 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 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import QtMultimedia 5.15 import QtMultimedia 5.15
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
@@ -18,8 +17,6 @@ import NeoChat.Menu.Timeline 1.0
TimelineContainer { TimelineContainer {
id: videoDelegate id: videoDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID) onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions hoverComponent: hoverActions
@@ -29,6 +26,8 @@ TimelineContainer {
property bool supportStreaming: true property bool supportStreaming: true
readonly property int maxWidth: 1000 // TODO messageListView.width readonly property int maxWidth: 1000 // TODO messageListView.width
onOpenContextMenu: openFileContext(model, vid)
onDownloadedChanged: { onDownloadedChanged: {
if (downloaded) { if (downloaded) {
vid.source = progressInfo.localPath vid.source = progressInfo.localPath
@@ -43,7 +42,7 @@ TimelineContainer {
innerObject: Video { innerObject: Video {
id: vid id: vid
Layout.maximumWidth: videoDelegate.bubbleMaxWidth Layout.maximumWidth: videoDelegate.contentMaxWidth
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 15 Layout.maximumHeight: Kirigami.Units.gridUnit * 15
Layout.minimumHeight: Kirigami.Units.gridUnit * 5 Layout.minimumHeight: Kirigami.Units.gridUnit * 5
@@ -124,16 +123,6 @@ TimelineContainer {
videoDelegate.downloadAndPlay() videoDelegate.downloadAndPlay()
} }
} }
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
} }
function downloadAndPlay() { function downloadAndPlay() {

View File

@@ -12,3 +12,4 @@ EncryptedDelegate 1.0 EncryptedDelegate.qml
EventDelegate 1.0 EventDelegate.qml EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.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 { Kirigami.OverlaySheet {
id: root id: root
signal closed()
property var room property var room
property var user property var user
@@ -52,7 +54,7 @@ Kirigami.OverlaySheet {
onClicked: { onClicked: {
if (avatarMediaId) { 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 {} FullScreenImage {}
} }
} }
onSheetOpenChanged: {
if (!sheetOpen) {
closed()
}
}
} }

View File

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

View File

@@ -45,7 +45,7 @@ Labs.MenuBar {
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "New Private Chat…") text: i18nc("menu", "New Private Chat…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0 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 { Labs.MenuItem {
text: i18nc("menu", "New Group…") text: i18nc("menu", "New Group…")
@@ -58,7 +58,7 @@ Labs.MenuBar {
} }
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "Browse Chats…") 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 { EditMenu {
@@ -92,7 +92,7 @@ Labs.MenuBar {
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "Matrix FAQ") 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 { Menu {
MenuItem { MenuItem {
id: newWindow id: newWindow
text: i18n("Open in new window") text: i18n("Open in New Window")
onTriggered: RoomManager.openWindow(room); onTriggered: RoomManager.openWindow(room);
visible: !Kirigami.Settings.isMobile visible: !Kirigami.Settings.isMobile
} }
@@ -49,7 +49,7 @@ Loader {
} }
MenuItem { MenuItem {
text: i18nc("@action:inmenu", "Copy address to clipboard") text: i18nc("@action:inmenu", "Copy Address to Clipboard")
onTriggered: if (room.canonicalAlias.length === 0) { onTriggered: if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id) Clipboard.saveText(room.id)
} else { } 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 {} MenuSeparator {}
MenuItem { MenuItem {

View File

@@ -14,6 +14,8 @@ import NeoChat.Menu 1.0
MessageDelegateContextMenu { MessageDelegateContextMenu {
id: root id: root
signal closeFullscreen
required property var file required property var file
required property var progressInfo required property var progressInfo
required property string mimeType required property string mimeType
@@ -24,13 +26,13 @@ MessageDelegateContextMenu {
icon.name: "document-open" icon.name: "document-open"
onTriggered: { onTriggered: {
if (file.downloaded) { if (file.downloaded) {
if (!Qt.openUrlExternally(progressInfo.localPath)) { if (!UrlHelper.openUrl(progressInfo.localPath)) {
Qt.openUrlExternally(progressInfo.localDir); UrlHelper.openUrl(progressInfo.localDir);
} }
} else { } else {
file.onDownloadedChanged.connect(function() { file.onDownloadedChanged.connect(function() {
if (!Qt.openUrlExternally(progressInfo.localPath)) { if (!UrlHelper.openUrl(progressInfo.localPath)) {
Qt.openUrlExternally(progressInfo.localDir); UrlHelper.openUrl(progressInfo.localDir);
} }
}); });
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId)) currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
@@ -50,7 +52,8 @@ MessageDelegateContextMenu {
text: i18n("Reply") text: i18n("Reply")
icon.name: "mail-replied-symbolic" icon.name: "mail-replied-symbolic"
onTriggered: { onTriggered: {
ChatBoxHelper.replyToMessage(eventId, message, author); chatBoxHelper.replyToMessage(eventId, message, author);
root.closeFullscreen()
} }
}, },
Kirigami.Action { Kirigami.Action {
@@ -60,8 +63,18 @@ MessageDelegateContextMenu {
icon.color: "red" icon.color: "red"
onTriggered: { onTriggered: {
currentRoom.redactEvent(eventId); 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 { Kirigami.Action {
text: i18n("View Source") text: i18n("View Source")
icon.name: "code-context" icon.name: "code-context"
@@ -72,6 +85,7 @@ MessageDelegateContextMenu {
title: i18n("Message Source"), title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25 width: Kirigami.Units.gridUnit * 25
}); });
root.closeFullscreen()
} }
} }
] ]

View File

@@ -20,6 +20,7 @@ Loader {
property string formattedBody: "" property string formattedBody: ""
required property string source required property string source
property string selectedText: "" property string selectedText: ""
required property string plainMessage
property list<Kirigami.Action> nestedActions property list<Kirigami.Action> nestedActions
@@ -27,13 +28,13 @@ Loader {
Kirigami.Action { Kirigami.Action {
text: i18n("Edit") text: i18n("Edit")
icon.name: "document-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") visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
}, },
Kirigami.Action { Kirigami.Action {
text: i18n("Reply") text: i18n("Reply")
icon.name: "mail-replied-symbolic" icon.name: "mail-replied-symbolic"
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author); onTriggered: chatBoxHelper.replyToMessage(eventId, message, author);
}, },
Kirigami.Action { Kirigami.Action {
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact") visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
@@ -45,7 +46,16 @@ Loader {
Kirigami.Action { Kirigami.Action {
text: i18n("Copy") text: i18n("Copy")
icon.name: "edit-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 { Kirigami.Action {
text: i18n("View Source") text: i18n("View Source")
@@ -111,7 +121,7 @@ Loader {
Instantiator { Instantiator {
model: WebShortcutModel { model: WebShortcutModel {
id: webshortcutmodel id: webshortcutmodel
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.message selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.plainMessage
onOpenUrl: RoomManager.visitNonMatrix(url) onOpenUrl: RoomManager.visitNonMatrix(url)
} }
delegate: QQC2.MenuItem { delegate: QQC2.MenuItem {

View File

@@ -22,6 +22,10 @@ Kirigami.Page {
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
contentWidth: availableWidth contentWidth: availableWidth
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea { TextArea {
id: sourceTextArea id: sourceTextArea
text: sourceText 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 MessageDelegateContextMenu 1.0 MessageDelegateContextMenu.qml
FileDelegateContextMenu 1.0 FileDelegateContextMenu.qml FileDelegateContextMenu 1.0 FileDelegateContextMenu.qml
MessageSourceSheet 1.0 MessageSourceSheet.qml MessageSourceSheet 1.0 MessageSourceSheet.qml
ReportSheet 1.0 ReportSheet.qml

View File

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

View File

@@ -3,18 +3,11 @@
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12 as QQC2 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 { Kirigami.Page {
title: i18n("Loading…") Kirigami.LoadingPlaceholder {
Kirigami.PlaceholderMessage {
id: loadingIndicator id: loadingIndicator
anchors.centerIn: parent 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 import NeoChat.Menu 1.0
Kirigami.ScrollablePage { 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 id: page
title: i18n("Rooms") title: i18n("Rooms")
@@ -68,7 +144,6 @@ Kirigami.ScrollablePage {
} }
} }
ListView { ListView {
id: listView id: listView
@@ -100,6 +175,8 @@ Kirigami.ScrollablePage {
} }
} }
Layout.fillWidth: true
Kirigami.PlaceholderMessage { Kirigami.PlaceholderMessage {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4) width: parent.width - (Kirigami.Units.largeSpacing * 4)
@@ -233,6 +310,7 @@ Kirigami.ScrollablePage {
} }
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse
onTapped: createRoomListContextMenu() onTapped: createRoomListContextMenu()
} }
@@ -247,15 +325,17 @@ Kirigami.ScrollablePage {
trailing: RowLayout { trailing: RowLayout {
QQC2.Label { QQC2.Label {
text: notificationCount text: notificationCount > 0 ? notificationCount : "●"
visible: notificationCount > 0 visible: unreadCount > 0
padding: Kirigami.Units.smallSpacing padding: Kirigami.Units.smallSpacing
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor color: Kirigami.Theme.textColor
Layout.minimumWidth: height Layout.minimumWidth: height
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
background: Rectangle { background: Rectangle {
visible: notificationCount > 0
Kirigami.Theme.colorSet: Kirigami.Theme.Button 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 radius: height / 2
} }
} }
@@ -275,7 +355,7 @@ Kirigami.ScrollablePage {
} }
function createRoomListContextMenu() { function createRoomListContextMenu() {
const menu = roomListContextMenu.createObject(page, {"room": currentRoom}) const menu = roomListContextMenu.createObject(page, {room: currentRoom})
configButton.visible = true configButton.visible = true
configButton.down = true configButton.down = true
menu.closed.connect(function() { 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.platform 1.1 as Platform
import Qt.labs.qmlmodels 1.0 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.kitemmodels 1.0
import org.kde.neochat 1.0 import org.kde.neochat 1.0
@@ -23,6 +23,7 @@ Kirigami.ScrollablePage {
/// It's not readonly because of the seperate window view. /// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom 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 /// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false; property bool hasScrolledUpBefore: false;
@@ -52,7 +53,26 @@ Kirigami.ScrollablePage {
onCurrentRoomChanged: { onCurrentRoomChanged: {
hasScrolledUpBefore = false; 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 { ActionsHandler {
@@ -61,6 +81,10 @@ Kirigami.ScrollablePage {
connection: Controller.activeConnection connection: Controller.activeConnection
} }
ChatBoxHelper {
id: chatBoxHelper
}
Shortcut { Shortcut {
sequence: StandardKey.Cancel sequence: StandardKey.Cancel
onActivated: applicationWindow().pageStack.get(0).forceActiveFocus() onActivated: applicationWindow().pageStack.get(0).forceActiveFocus()
@@ -81,17 +105,17 @@ Kirigami.ScrollablePage {
function onShowMessage(messageType, message) { function onShowMessage(messageType, message) {
page.header.contentItem.text = message; page.header.contentItem.text = message;
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information; 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 { header: QQC2.Control {
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
visible: contentItem.visible visible: false
padding: Kirigami.Units.smallSpacing padding: Kirigami.Units.smallSpacing
contentItem: Kirigami.InlineMessage { contentItem: Kirigami.InlineMessage {
showCloseButton: true showCloseButton: true
visible: false visible: true
} }
} }
@@ -120,15 +144,10 @@ Kirigami.ScrollablePage {
} }
} }
Kirigami.PlaceholderMessage { Kirigami.LoadingPlaceholder {
id: loadingIndicator id: loadingIndicator
anchors.centerIn: parent anchors.centerIn: parent
visible: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite) visible: loading
text: i18n("Loading…")
QQC2.BusyIndicator {
running: loadingIndicator.visible
Layout.alignment: Qt.AlignHCenter
}
} }
focus: true focus: true
@@ -147,9 +166,9 @@ Kirigami.ScrollablePage {
Keys.onPressed: { Keys.onPressed: {
if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) { if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomDown(); switchRoomDown();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) { } else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
event.accepted = true; event.accepted = true;
chatBox.addText(event.text); 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 // hover actions on a delegate, activated in TimelineContainer.qml
Connections { Connections {
target: page.flickable 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 int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property bool isLoaded: page.width * page.height > 10 readonly property bool isLoaded: page.width * page.height > 10
spacing: Config.compactLayout ? 1 : Kirigami.Units.smallSpacing spacing: 0
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500 highlightMoveDuration: 500
@@ -242,7 +270,7 @@ Kirigami.ScrollablePage {
fileDialog.chosen.connect(function(path) { fileDialog.chosen.connect(function(path) {
if (!path) return if (!path) return
ChatBoxHelper.attachmentPath = path; chatBoxHelper.attachmentPath = path;
}) })
fileDialog.open() fileDialog.open()
@@ -264,7 +292,7 @@ Kirigami.ScrollablePage {
if (!Clipboard.saveImage(localPath)) { if (!Clipboard.saveImage(localPath)) {
return; return;
} }
ChatBoxHelper.attachmentPath = localPath; chatBoxHelper.attachmentPath = localPath;
attachDialog.close(); attachDialog.close();
} }
} }
@@ -339,7 +367,7 @@ Kirigami.ScrollablePage {
DropArea { DropArea {
id: dropAreaFile id: dropAreaFile
anchors.fill: parent anchors.fill: parent
onDropped: ChatBoxHelper.attachmentPath = drop.urls[0] onDropped: chatBoxHelper.attachmentPath = drop.urls[0]
} }
QQC2.Pane { QQC2.Pane {
@@ -388,7 +416,7 @@ Kirigami.ScrollablePage {
currentRoom.usersTyping.length, currentRoom.usersTyping.length,
currentRoom.usersTyping.map(user => user.displayName).join(", ") currentRoom.usersTyping.map(user => user.displayName).join(", ")
) : "" ) : ""
anchors.right: parent.right anchors.left: parent.left
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
Behavior on height { Behavior on height {
NumberAnimation { NumberAnimation {
@@ -402,13 +430,17 @@ Kirigami.ScrollablePage {
headerPositioning: ListView.OverlayHeader headerPositioning: ListView.OverlayHeader
function goToEvent(eventID) { function goToEvent(eventID) {
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain) const index = eventToIndex(eventID)
messageListView.positionViewAtIndex(index, ListView.Center)
itemAtIndex(index).isTemporaryHighlighted = true
} }
Item { Item {
id: hoverActions id: hoverActions
property var event: null 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 bubble: null
property var hovered: bubble && bubble.hovered property var hovered: bubble && bubble.hovered
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
@@ -424,8 +456,11 @@ Kirigami.ScrollablePage {
interval: 200 interval: 200
onTriggered: hoverActions.visible = hoverActions.visibleDelayed; 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; y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
visible: false visible: false
property var updateFunction property var updateFunction
@@ -441,10 +476,23 @@ Kirigami.ScrollablePage {
id: hoverHandler id: hoverHandler
margin: Kirigami.Units.smallSpacing 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.Button {
QQC2.ToolTip.text: i18n("React") QQC2.ToolTip.text: i18n("React")
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "preferences-desktop-emoticons" icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open(); onClicked: emojiDialog.open();
EmojiDialog { EmojiDialog {
@@ -458,11 +506,12 @@ Kirigami.ScrollablePage {
QQC2.Button { QQC2.Button {
QQC2.ToolTip.text: i18n("Edit") QQC2.ToolTip.text: i18n("Edit")
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
visible: hoverActions.showEdit visible: hoverActions.showEdit
icon.name: "document-edit" icon.name: "document-edit"
onClicked: { onClicked: {
if (hoverActions.showEdit) { 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(); chatBox.focusInputField();
} }
@@ -470,9 +519,10 @@ Kirigami.ScrollablePage {
QQC2.Button { QQC2.Button {
QQC2.ToolTip.text: i18n("Reply") QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "mail-replied-symbolic" icon.name: "mail-replied-symbolic"
onClicked: { 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(); chatBox.focusInputField();
} }
} }
@@ -492,14 +542,14 @@ Kirigami.ScrollablePage {
onEditLastUserMessage: { onEditLastUserMessage: {
const targetMessage = messageEventModel.getLastLocalUserMessageEventId(); const targetMessage = messageEventModel.getLastLocalUserMessageEventId();
if (targetMessage) { if (targetMessage) {
ChatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]); chatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
chatBox.focusInputField(); chatBox.focusInputField();
} }
} }
onReplyPreviousUserMessage: { onReplyPreviousUserMessage: {
const replyResponse = messageEventModel.getLatestMessageFromIndex(0); const replyResponse = messageEventModel.getLatestMessageFromIndex(0);
if (replyResponse && replyResponse["event_id"]) { 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) { function warning(title, message) {
page.header.contentItem.text = `${title}<br />${message}`; page.header.contentItem.text = `${title}<br />${message}`;
page.header.contentItem.type = Kirigami.MessageType.Warning; page.header.contentItem.type = Kirigami.MessageType.Warning;
page.header.contentItem.visible = true; page.header.visible = true;
} }
function showUserDetail(user) { function showUserDetail(user) {
@@ -588,6 +638,14 @@ Kirigami.ScrollablePage {
return index; 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 /// Open message context dialog for file and videos
function openFileContext(event, file) { function openFileContext(event, file) {
const contextMenu = fileDelegateContextMenu.createObject(page, { const contextMenu = fileDelegateContextMenu.createObject(page, {
@@ -598,12 +656,13 @@ Kirigami.ScrollablePage {
file: file, file: file,
mimeType: event.mimeType, mimeType: event.mimeType,
progressInfo: event.progressInfo, progressInfo: event.progressInfo,
plainMessage: event.message,
}); });
contextMenu.open(); contextMenu.open();
} }
/// Open context menu for normal message /// Open context menu for normal message
function openMessageContext(event, selectedText) { function openMessageContext(event, selectedText, plainMessage) {
const contextMenu = messageDelegateContextMenu.createObject(page, { const contextMenu = messageDelegateContextMenu.createObject(page, {
selectedText: selectedText, selectedText: selectedText,
author: event.author, author: event.author,
@@ -611,7 +670,8 @@ Kirigami.ScrollablePage {
eventId: event.eventId, eventId: event.eventId,
formattedBody: event.formattedBody, formattedBody: event.formattedBody,
source: event.source, source: event.source,
eventType: event.eventType eventType: event.eventType,
plainMessage: plainMessage,
}); });
contextMenu.open(); contextMenu.open();
} }

View File

@@ -5,7 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15 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 org.kde.neochat 1.0
import NeoChat.Component.Login 1.0 import NeoChat.Component.Login 1.0
@@ -45,10 +45,81 @@ Kirigami.ScrollablePage {
} }
ColumnLayout { ColumnLayout {
Kirigami.Icon { Item {
source: "org.kde.neochat" Layout.preferredHeight: Kirigami.Units.gridUnit * 10
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true 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 { Controls.Label {
Layout.fillWidth: true Layout.fillWidth: true
@@ -66,6 +137,7 @@ Kirigami.ScrollablePage {
headerMessage.text = "" headerMessage.text = ""
} }
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@@ -78,16 +150,26 @@ Kirigami.ScrollablePage {
onClicked: { onClicked: {
module.source = welcomePage.currentStep.previousUrl module.source = welcomePage.currentStep.previousUrl
} }
icon.name: "go-back"
} }
Controls.Button { Controls.Button {
id: continueButton id: continueButton
enabled: welcomePage.currentStep.acceptable enabled: welcomePage.currentStep.acceptable
visible: welcomePage.currentStep.showContinueButton opacity: welcomePage.currentStep.showContinueButton ? 1 : 0
Behavior on opacity { NumberAnimation {} }
action: welcomePage.currentStep.action 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 { Connections {
target: currentStep target: currentStep

View File

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

View File

@@ -3,12 +3,12 @@
import QtQuick 2.15 import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings { Kirigami.CategorizedSettings {
id: root id: root
property var room property var room
objectName: "settingsPage" objectName: "settingsPage"
actions: [ actions: [
Kirigami.SettingAction { Kirigami.SettingAction {
@@ -30,6 +30,16 @@ Kirigami.CategorizedSettings {
room: root.room 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 canChangeTopic: room.canSendState("m.room.topic")
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias") readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
title: i18n('General') title: i18n("General")
ColumnLayout { ColumnLayout {
Kirigami.FormLayout { 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 property var room
title: i18n('Security') title: i18n("Security")
ColumnLayout { ColumnLayout {
Kirigami.FormLayout { Kirigami.FormLayout {
Layout.fillWidth: true Layout.fillWidth: true
CheckBox { RadioButton {
text: i18nc("@option:check", "Private (invite only)") text: i18nc("@option:check", "Private (invite only)")
Kirigami.FormData.label: i18nc("@option:check", "Access:") Kirigami.FormData.label: i18nc("@option:check", "Access:")
checked: room.joinRule === "invite" checked: room.joinRule === "invite"
@@ -32,7 +32,7 @@ Kirigami.ScrollablePage {
text: i18n("Only invited people can join.") text: i18n("Only invited people can join.")
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont
} }
CheckBox { RadioButton {
text: i18nc("@option:check", "Space members") text: i18nc("@option:check", "Space members")
checked: room.joinRule === "restricted" checked: room.joinRule === "restricted"
enabled: false enabled: false
@@ -41,7 +41,7 @@ Kirigami.ScrollablePage {
text: i18n("Anyone in a space can find and join.") text: i18n("Anyone in a space can find and join.")
font: Kirigami.Theme.smallFont font: Kirigami.Theme.smallFont
} }
CheckBox { RadioButton {
text: i18nc("@option:check", "Public") text: i18nc("@option:check", "Public")
checked: room.joinRule === "public" checked: room.joinRule === "public"
enabled: false enabled: false

View File

@@ -7,6 +7,6 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.AboutPage { Kirigami.AboutPage {
title: i18nc('@title:window', 'About NeoChat') title: i18nc("@title:window", "About NeoChat")
aboutData: Controller.aboutData 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 { ListView {
model: AccountRegistry model: AccountRegistry
anchors.fill: parent
delegate: Kirigami.BasicListItem { delegate: Kirigami.BasicListItem {
text: model.connection.localUser.displayName text: model.connection.localUser.displayName
labelItem.textFormat: Text.PlainText labelItem.textFormat: Text.PlainText
@@ -30,31 +31,39 @@ Kirigami.ScrollablePage {
icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user" icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user"
onClicked: { onClicked: {
Controller.activeConnection = model.connection Controller.activeConnection = model.connection;
pageStack.layers.pop() pageStack.layers.pop();
} }
trailing: RowLayout { trailing: RowLayout {
Controls.ToolButton { Controls.ToolButton {
display: Controls.AbstractButton.IconOnly display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action { action: Kirigami.Action {
text: i18n("Edit this account") text: i18n("Edit this account")
iconName: "document-edit" iconName: "document-edit"
onTriggered: { onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
userEditSheet.connection = model.connection connection: model.connection
userEditSheet.open() }, {
} title: i18n("Account editor")
});
} }
} }
Controls.ToolButton { Controls.ToolButton {
display: Controls.AbstractButton.IconOnly display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action { action: Kirigami.Action {
text: i18n("Logout") text: i18n("Logout")
iconName: "im-kick-user" iconName: "im-kick-user"
onTriggered: { onTriggered: {
Controller.logout(model.connection, true) Controller.logout(model.connection, true);
if(Controller.accountCount === 1) if (Controller.accountCount === 1) {
pageStack.layers.pop() pageStack.layers.pop();
}
} }
} }
} }
@@ -76,6 +85,7 @@ Kirigami.ScrollablePage {
} }
} }
} }
Connections { Connections {
target: Controller target: Controller
function onConnectionAdded() { function onConnectionAdded() {
@@ -83,134 +93,21 @@ Kirigami.ScrollablePage {
pageStack.layers.pop() pageStack.layers.pop()
} }
function onPasswordStatus(status) { function onPasswordStatus(status) {
if(status == Controller.Success) if (status === Controller.Success) {
showPassiveNotification(i18n("Password changed successfully")) showPassiveNotification(i18n("Password changed successfully"));
else if(status == Controller.Wrong) } else if (status === Controller.Wrong) {
showPassiveNotification(i18n("Wrong password entered")) showPassiveNotification(i18n("Wrong password entered"));
else } else {
showPassiveNotification(i18n("Unknown problem while trying to change password")) showPassiveNotification(i18n("Unknown problem while trying to change password"));
}
} }
} }
Component { property Component openFileDialog: Component {
id: openFileDialog id: openFileDialog
OpenFileDialog { OpenFileDialog {
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) 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 import NeoChat.Settings 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
title: i18nc('@title:window', 'Appearance') title: i18nc("@title:window", "Appearance")
ColumnLayout { ColumnLayout {
RowLayout { RowLayout {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
@@ -177,7 +177,7 @@ Kirigami.ScrollablePage {
Kirigami.FormLayout { Kirigami.FormLayout {
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
QQC2.CheckBox { QQC2.CheckBox {
Kirigami.FormData.label: "Show Avatar:" Kirigami.FormData.label: i18n("Show Avatar:")
text: i18n("In Chat") text: i18n("In Chat")
checked: Config.showAvatarInTimeline checked: Config.showAvatarInTimeline
onToggled: { onToggled: {
@@ -237,6 +237,7 @@ Kirigami.ScrollablePage {
HoverHandler { id: sliderHover } HoverHandler { id: sliderHover }
QQC2.ToolTip.visible: sliderHover.hovered && !enabled QQC2.ToolTip.visible: sliderHover.hovered && !enabled
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.") QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
} }
QQC2.Label { QQC2.Label {
text: Math.round(Config.transparency * 100) + "%" text: Math.round(Config.transparency * 100) + "%"
@@ -245,7 +246,7 @@ Kirigami.ScrollablePage {
QQC2.CheckBox { QQC2.CheckBox {
text: i18n("Show your messages on the right") text: i18n("Show your messages on the right")
checked: Config.showLocalMessagesOnRight checked: Config.showLocalMessagesOnRight
enabled: !Config.isShowLocalMessagesOnRightImmutable enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
onToggled: { onToggled: {
Config.showLocalMessagesOnRight = checked Config.showLocalMessagesOnRight = checked
Config.save() Config.save()

View File

@@ -5,7 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15 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 org.kde.neochat 1.0
@@ -17,13 +17,11 @@ Kirigami.ScrollablePage {
id: devices id: devices
} }
Kirigami.PlaceholderMessage { anchors.fill: parent
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
Kirigami.LoadingPlaceholder {
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
anchors.centerIn: parent anchors.centerIn: parent
text: i18n("Loading…")
Controls.BusyIndicator {
running: parent.visible
}
} }
delegate: Kirigami.BasicListItem { 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 { Controls.ToolButton {
display: Controls.AbstractButton.IconOnly display: Controls.AbstractButton.IconOnly
action: Kirigami.Action { action: Kirigami.Action {

View File

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

View File

@@ -11,9 +11,10 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
title: i18nc('@title:window', 'General') title: i18nc("@title:window", "General")
ColumnLayout { ColumnLayout {
Kirigami.FormLayout { Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.CheckBox { QQC2.CheckBox {
Kirigami.FormData.label: i18n("General settings:") Kirigami.FormData.label: i18n("General settings:")
text: i18n("Close to system tray") text: i18n("Close to system tray")
@@ -45,6 +46,7 @@ Kirigami.ScrollablePage {
onToggled: { onToggled: {
Config.showNotifications = checked Config.showNotifications = checked
Config.save() Config.save()
NotificationsManager.globalNotificationsEnabled = checked
} }
} }
QQC2.CheckBox { QQC2.CheckBox {
@@ -96,19 +98,16 @@ Kirigami.ScrollablePage {
QQC2.CheckBox { QQC2.CheckBox {
id: quickEditCheckbox id: quickEditCheckbox
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
contentItem: QQC2.Label { text: i18n("Use s/text/replacement syntax to edit your last message")
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
}
checked: Config.allowQuickEdit checked: Config.allowQuickEdit
enabled: !Config.isAllowQuickEditImmutable enabled: !Config.isAllowQuickEditImmutable
onToggled: { onToggled: {
Config.allowQuickEdit = checked Config.allowQuickEdit = checked
Config.save() Config.save()
} }
// TODO KF5.97 remove this line
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
} }
QQC2.CheckBox { QQC2.CheckBox {
text: i18n("Send Typing Notifications") text: i18n("Send Typing Notifications")
@@ -121,12 +120,16 @@ Kirigami.ScrollablePage {
} }
QQC2.CheckBox { QQC2.CheckBox {
text: i18n("Automatically hide/unhide the room information when resizing the window") text: i18n("Automatically hide/unhide the room information when resizing the window")
Layout.maximumWidth: parent.width
checked: Config.autoRoomInfoDrawer checked: Config.autoRoomInfoDrawer
enabled: !Config.isAutoRoomInfoDrawerImmutable enabled: !Config.isAutoRoomInfoDrawerImmutable
onToggled: { onToggled: {
Config.autoRoomInfoDrawer = checked Config.autoRoomInfoDrawer = checked
Config.save() 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 QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings { Kirigami.CategorizedSettings {
@@ -25,7 +24,7 @@ Kirigami.CategorizedSettings {
page: Qt.resolvedUrl("AccountsPage.qml") page: Qt.resolvedUrl("AccountsPage.qml")
}, },
Kirigami.SettingAction { Kirigami.SettingAction {
text: i18n("Custom Emoji") text: i18n("Custom Emojis")
icon.name: "preferences-desktop-emoticons" icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("Emoticons.qml") page: Qt.resolvedUrl("Emoticons.qml")
}, },

View File

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

View File

@@ -25,6 +25,7 @@
<name xml:lang="ia">Neochat</name> <name xml:lang="ia">Neochat</name>
<name xml:lang="id">NeoChat</name> <name xml:lang="id">NeoChat</name>
<name xml:lang="it">NeoChat</name> <name xml:lang="it">NeoChat</name>
<name xml:lang="ka">NeoChat</name>
<name xml:lang="ko">NeoChat</name> <name xml:lang="ko">NeoChat</name>
<name xml:lang="nl">NeoChat</name> <name xml:lang="nl">NeoChat</name>
<name xml:lang="nn">NeoChat</name> <name xml:lang="nn">NeoChat</name>
@@ -44,7 +45,7 @@
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary> <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="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">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="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="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</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="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="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="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="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</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> <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="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="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">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="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="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> <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="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="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">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="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="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> <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="ia">Le communitate de KDE</developer_name>
<developer_name xml:lang="id">Komunitas 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="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="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name> <developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name> <developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
@@ -206,6 +209,9 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <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"> <release version="22.06" date="2022-06-24">
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url> <url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
<description> <description>

View File

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