Compare commits

...

703 Commits

Author SHA1 Message Date
Torrie Fischer
4139421e8e roomlist: first pass at some new, different extensible sorting options 2022-10-11 15:48:52 +02:00
Tobias Fella
b2fa269515 Send correct width and height for images 2022-10-10 18:49:43 +00:00
James Graham
589f9f88a3 Handle images with size (0,0) in fullscreen mode 2022-10-10 18:41:28 +00:00
Tobias Fella
de0b138992 Handle images with incorrect (0, 0) size better
BUG: 460205
BUG: 460150
2022-10-10 20:06:27 +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
l10n daemon script
5b848961bc GIT_SILENT Sync po/docbooks with svn 2022-06-24 01:46:09 +00:00
Tobias Fella
51574f5125 Fix actions in ListItems
Apparently the combination of SwipeListItem and BasicListItem is evil, so port away from it to ensure that actions show up again.
2022-06-23 23:07:10 +00:00
Tobias Fella
a779907500 Add changelog for 22.06 2022-06-23 18:06:42 +00:00
Tobias Fella
f195db323d Fix matrix room link in appdata 2022-06-23 18:31:39 +02:00
Bhushan Shah
da47d76a7f GIT_SILENT: bump version to 22.06 2022-06-23 21:11:20 +05:30
l10n daemon script
6dc8c4976c GIT_SILENT Sync po/docbooks with svn 2022-06-23 01:48:38 +00:00
l10n daemon script
fec7680068 GIT_SILENT Sync po/docbooks with svn 2022-06-22 01:48:25 +00:00
l10n daemon script
b84264891d GIT_SILENT Sync po/docbooks with svn 2022-06-21 01:49:20 +00:00
l10n daemon script
465a981033 GIT_SILENT Sync po/docbooks with svn 2022-06-20 02:04:09 +00:00
l10n daemon script
06b4c40b33 GIT_SILENT Sync po/docbooks with svn 2022-06-19 01:48:06 +00:00
Tobias Fella
efae510fda Don't use PublicRoomsChunk::aliases
Doesn't seem to be part of the spec and currently isn't in libQuotient; if it comes back, we can revert this.
Until then, this fixes the build
2022-06-18 15:10:25 +02:00
l10n daemon script
f9fc8c5c0b GIT_SILENT Sync po/docbooks with svn 2022-06-18 01:48:49 +00:00
Weng Xuetian
49c9c63bf5 Fix the switch room direction
Down should be next and Up should be Previous
2022-06-17 18:11:24 +00:00
James Graham
90cee0f437 Clear the text from the user list filter in the room drawer when the room is changed 2022-06-17 18:59:33 +01:00
l10n daemon script
5bc9362fde GIT_SILENT Sync po/docbooks with svn 2022-06-17 01:47:27 +00:00
l10n daemon script
10922aeb52 GIT_SILENT Sync po/docbooks with svn 2022-06-16 02:02:06 +00:00
Tobias Fella
f9a96ccdab It's 2022 2022-06-15 16:16:51 +02:00
l10n daemon script
04056d9ed1 GIT_SILENT Sync po/docbooks with svn 2022-06-15 01:52:09 +00:00
l10n daemon script
ecf373e317 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-06-15 01:45:58 +00:00
James Graham
9c2e0669f6 Fixe Reply or Edit from Chatbar
Fixes it so that the cleaned text is shown when using the shortcuts to reply or edit from the chatbar. Also ensures that the correct eventids are passed when the message is an edit.

This also fixes the issue of having html pasted into the chatbar when editing and edit.

Fixes network/neochat#448

BUG: 455016
2022-06-14 13:47:13 +00:00
l10n daemon script
2b8aa9f975 GIT_SILENT Sync po/docbooks with svn 2022-06-14 01:58:53 +00:00
l10n daemon script
2f61090413 GIT_SILENT Sync po/docbooks with svn 2022-06-13 02:51:21 +00:00
l10n daemon script
aa9daad704 GIT_SILENT made messages (after extraction) 2022-06-13 01:02:32 +00:00
l10n daemon script
0e79d3506d GIT_SILENT Sync po/docbooks with svn 2022-06-12 01:51:48 +00:00
l10n daemon script
72994d0349 GIT_SILENT Sync po/docbooks with svn 2022-06-11 01:45:58 +00:00
l10n daemon script
0ee5ba76c9 GIT_SILENT Sync po/docbooks with svn 2022-06-09 02:19:15 +00:00
l10n daemon script
d1398f6726 GIT_SILENT made messages (after extraction) 2022-06-09 00:52:50 +00:00
l10n daemon script
9084817450 GIT_SILENT Sync po/docbooks with svn 2022-06-08 01:49:25 +00:00
Jan Bidler
083a2f9772 Compact Mode improvements
BUG: 454897
2022-06-07 12:33:14 +00:00
Jan Bidler
b44e81c849 Make Right Click on Room bring up Context Menu
BUG: 454892
2022-06-07 08:42:39 +00:00
l10n daemon script
014826bd09 GIT_SILENT Sync po/docbooks with svn 2022-06-07 02:33:47 +00:00
l10n daemon script
2858fcfad2 GIT_SILENT made messages (after extraction) 2022-06-07 00:53:49 +00:00
l10n daemon script
b6db36a9f2 GIT_SILENT Sync po/docbooks with svn 2022-06-06 01:48:51 +00:00
James Graham
ede860c99f For all html messages \n needs to be replaces with <br> or the linebreaks are lost 2022-06-05 12:36:33 +01:00
l10n daemon script
f8951fc760 GIT_SILENT Sync po/docbooks with svn 2022-06-05 01:49:04 +00:00
Tobias Fella
525015fe78 Fix fix 2022-06-05 00:41:59 +02:00
Akseli Lahtinen
b834510be0 Don't show notifications if application is active and same room is active 2022-06-04 23:24:24 +02:00
Tobias Fella
8700611235 Fix hoverActions 2022-06-04 20:32:49 +02:00
l10n daemon script
15ddcef115 GIT_SILENT Sync po/docbooks with svn 2022-06-04 01:49:22 +00:00
l10n daemon script
7216da8b6f GIT_SILENT Sync po/docbooks with svn 2022-06-03 01:47:11 +00:00
Tobias Fella
7bd4aac692 Fix custom emoji creation 2022-06-02 13:19:12 +02:00
l10n daemon script
10e17d9f0f GIT_SILENT Sync po/docbooks with svn 2022-06-02 01:48:24 +00:00
James Graham
db5e328869 Add automatic room sidebar hiding option
Add menu option to change whether the room information drawer is opened automatically or not. This also adds some code to switch off the dim effect during the first animation after modal is changed as this looked bad.

Implements network/neochat#243

Slight cleanup removing the edge option for context drawer from main.qml as this is duplicated from RoomDrawer.qml
2022-06-01 20:09:47 +00:00
l10n daemon script
921667565e GIT_SILENT Sync po/docbooks with svn 2022-06-01 02:05:25 +00:00
l10n daemon script
9cd8a380ed GIT_SILENT Sync po/docbooks with svn 2022-05-31 02:13:37 +00:00
James Graham
29816730e4 Add space after an autocomplete
Adds a space automatically after an autocomplete if the last char isn't one.

Implements network/neochat#132
2022-05-30 19:17:47 +00:00
Tobias Fella
7214936eaa Fix compilation against libQuotient 0.6 2022-05-30 14:41:00 +02:00
Tobias Fella
5a7c3295dc Revert "Linkify urls"
This seems to mess up user mentions. Probably a bug in the regex;
Reverting for now

This reverts commit 1763dc13c5.
2022-05-30 14:28:35 +02:00
Tobias Fella
dce4a409c7 Adapt to libQuotient API changes 2022-05-30 14:23:29 +02:00
l10n daemon script
bd27904f17 GIT_SILENT Sync po/docbooks with svn 2022-05-29 01:52:07 +00:00
l10n daemon script
731b234dda GIT_SILENT Sync po/docbooks with svn 2022-05-28 02:35:16 +00:00
l10n daemon script
ce0fc637c4 GIT_SILENT made messages (after extraction) 2022-05-28 00:56:00 +00:00
Yuri Chornoivan
070fe45a2d Fix XML 2022-05-27 09:27:48 +03:00
l10n daemon script
3a8d078e6c GIT_SILENT Sync po/docbooks with svn 2022-05-27 01:47:34 +00:00
Tobias Fella
fb9183e5c3 Update bug reporting urls 2022-05-26 17:18:29 +02:00
l10n daemon script
853113df3f GIT_SILENT Sync po/docbooks with svn 2022-05-22 01:48:20 +00:00
James Graham
e62288e6f1 Adds some basic mouse contorls to the quickswitcher. The icons can now be clicked to select the room and the highlight is moved to the current hovered room. 2022-05-21 10:55:43 +00:00
Tobias Fella
4f978a950b Appstream: define launchable 2022-05-20 13:13:36 +02:00
l10n daemon script
36b2868933 GIT_SILENT Sync po/docbooks with svn 2022-05-20 01:47:21 +00:00
Tobias Fella
1763dc13c5 Linkify urls 2022-05-19 23:14:33 +02:00
l10n daemon script
b7e4c2c6a2 GIT_SILENT Sync po/docbooks with svn 2022-05-19 01:47:37 +00:00
l10n daemon script
5969612ead 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-05-19 01:41:56 +00:00
Tobias Fella
0d00d4200c Don't crash while trying to load last message by own user
Fixes #535
2022-05-17 13:50:54 +02:00
l10n daemon script
35f30c293b GIT_SILENT Sync po/docbooks with svn 2022-05-17 01:50:13 +00:00
Tobias Fella
77e20ec446 Don't escape html while posting messages 2022-05-16 20:29:44 +02:00
Tobias Fella
101b57c581 Revert "Fix double quoting and missing new lines in message sent"
This reverts commit f2cf82ee8e.
2022-05-16 20:26:35 +02:00
l10n daemon script
b994907be4 GIT_SILENT Sync po/docbooks with svn 2022-05-16 01:56:18 +00:00
l10n daemon script
97ce81daca 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-05-16 01:45:56 +00:00
Nicolas Fella
4e61c5e53c Fix rasing window when activating notifications
This was done for invite notifications but not regular notifications
2022-05-15 23:28:13 +02:00
Tobias Fella
6871ed051c Always send messages as HTML
This works around limitations of Qt's rich text detection and prevents
some messages from being shown wrong, like in #532.

In theory it is not ideal to send every message as HTML, however it's
not a significant problem and we already do it for edits and replies
(which also explains why edited messages are sometimes magically
rendered correctly while the original is not).
2022-05-15 20:31:18 +00:00
James Graham
10da870ab3 Fix search item being behind the roomlist in collapsed mode by moving code into ListView. Now the search item is always at the top of the list. 2022-05-15 14:17:38 +01:00
l10n daemon script
6b5f76296a GIT_SILENT Sync po/docbooks with svn 2022-05-15 02:04:12 +00:00
l10n daemon script
93a4930301 GIT_SILENT made messages (after extraction) 2022-05-15 00:48:20 +00:00
l10n daemon script
7b393f2681 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-05-14 01:41:26 +00:00
l10n daemon script
fb6266fa15 GIT_SILENT made messages (after extraction) 2022-05-14 00:44:34 +00:00
l10n daemon script
334b245669 GIT_SILENT Sync po/docbooks with svn 2022-05-13 01:51:00 +00:00
l10n daemon script
3a969189b8 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-05-13 01:44:55 +00:00
James Graham
cef5d11130 Fix scrollbar behaviour in Room List
In the Room List there is always a gap left for the scrollbar in normal mode whether it is visible or not. This commit makes the gap disappear when the scrollbar is not visible by using the verticalscrollbarpolicy of the scrollpage.

Fixes network/neochat#518
2022-05-12 19:16:40 +00:00
l10n daemon script
216c751d81 GIT_SILENT Sync po/docbooks with svn 2022-05-12 01:46:13 +00:00
l10n daemon script
154109dde1 GIT_SILENT Sync po/docbooks with svn 2022-05-10 01:50:40 +00:00
l10n daemon script
312db10439 GIT_SILENT Sync po/docbooks with svn 2022-05-09 01:49:41 +00:00
l10n daemon script
f4f540e805 GIT_SILENT Sync po/docbooks with svn 2022-05-08 01:47:31 +00:00
l10n daemon script
b3ca71580f GIT_SILENT Sync po/docbooks with svn 2022-05-07 01:49:11 +00:00
l10n daemon script
2fc2ac113e GIT_SILENT Sync po/docbooks with svn 2022-05-06 01:49:57 +00:00
l10n daemon script
2ea95ea080 GIT_SILENT Sync po/docbooks with svn 2022-05-05 01:51:03 +00:00
l10n daemon script
22168dcef9 GIT_SILENT Sync po/docbooks with svn 2022-05-04 01:49:52 +00:00
l10n daemon script
e25ffd0c41 GIT_SILENT Sync po/docbooks with svn 2022-05-03 01:53:55 +00:00
Tobias Fella
5595d8f896 Allow disabling sending of typing notifications 2022-05-02 10:36:48 +00:00
l10n daemon script
abed37518d GIT_SILENT Sync po/docbooks with svn 2022-05-02 01:44:26 +00:00
l10n daemon script
57493e87ee GIT_SILENT Sync po/docbooks with svn 2022-05-01 02:01:04 +00:00
Tobias Fella
1bcff6503f Fix typo 2022-04-30 19:40:07 +02:00
Tobias Fella
98571cb37d Remove leftover spellchecking files 2022-04-30 14:48:28 +02:00
l10n daemon script
b64cd3c1b8 GIT_SILENT Sync po/docbooks with svn 2022-04-29 01:48:29 +00:00
l10n daemon script
69ced8406b GIT_SILENT Sync po/docbooks with svn 2022-04-27 01:51:49 +00:00
Nicolas Fella
1f551b5f59 Use proper reuse CI job 2022-04-26 00:27:23 +02:00
l10n daemon script
48a2a793c8 GIT_SILENT Sync po/docbooks with svn 2022-04-25 01:56:50 +00:00
Bhushan Shah
be116e1ba7 GIT_SILENT: add changelog entries for 22.04 2022-04-23 18:25:50 +05:30
Bhushan Shah
3396f831d4 GIT_SILENT: bump version to 22.04 2022-04-23 17:04:13 +05:30
l10n daemon script
a0f6170539 GIT_SILENT Sync po/docbooks with svn 2022-04-23 01:49:18 +00:00
l10n daemon script
731c6f924c GIT_SILENT Sync po/docbooks with svn 2022-04-22 01:53:07 +00:00
l10n daemon script
3011c3d885 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-04-22 01:44:58 +00:00
Nicolas Fella
709b2c8fd9 Use undeprecated install dirs
Using kde-dev-scripts/kf5/cmakelists_install_vars.pl
2022-04-21 20:58:00 +02:00
l10n daemon script
0cfa87e23d GIT_SILENT Sync po/docbooks with svn 2022-04-21 01:48:36 +00:00
l10n daemon script
538ed7dd02 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-04-21 01:42:02 +00:00
l10n daemon script
180a754e67 GIT_SILENT Sync po/docbooks with svn 2022-04-19 01:51:51 +00:00
l10n daemon script
81ba5f6ee5 GIT_SILENT Sync po/docbooks with svn 2022-04-17 01:57:04 +00:00
l10n daemon script
a15b406cff 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-04-16 01:44:16 +00:00
Marcus Harrison
d0bc8f3d05 Fix mis-aligned user messages
In compact mode with userMessagesOnRight, the user
avatar disappeared and their messages left space
on the right for an avatar that wasn't displayed
anymore.
2022-04-14 14:38:02 +02:00
l10n daemon script
c83f4b4f75 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-04-10 01:45:29 +00:00
Tobias Fella
0f5425e030 Require passing tests on CI 2022-04-09 21:33:52 +02:00
Tobias Fella
f381cc4623 Close WelcomePage after account is loaded 2022-04-09 19:47:36 +02:00
Tobias Fella
decd528079 Disable busyindicator 2022-04-09 19:47:17 +02:00
Tobias Fella
0c5bd57976 Fix REUSE check on CI
The CI installs files to _include and _build in the source directory, which breaks
the REUSE check
2022-04-09 15:19:35 +00:00
Tobias Fella
7362b90c42 Don't try to load more messages than there are in the timeline
The function call from qml is removed because it is redundant
2022-04-08 18:44:30 +00:00
Tobias Fella
aef6d6fc85 More typing notification improvements 2022-04-08 20:37:17 +02:00
Tobias Fella
432e209b16 Try fixing stuck read notifications 2022-04-08 20:33:41 +02:00
l10n daemon script
a72cac5ea3 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-04-08 01:44:45 +00:00
Tobias Fella
b9152dc93c Add ki18n_install 2022-04-07 17:25:16 +02:00
l10n daemon script
e5791970da 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-04-07 01:43:10 +00:00
Carl Schwan
c157625645 Fix link 2022-04-06 14:04:21 +00:00
Nicolas Fella
026c7660bc Add Windows CI 2022-04-06 12:01:47 +02:00
Nicolas Fella
be10e66974 Fix condition to build runner 2022-04-06 12:01:47 +02:00
l10n daemon script
024fb1a97a 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-04-04 01:46:19 +00:00
l10n daemon script
e4c8b6b676 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-04-03 01:54:27 +00:00
l10n daemon script
863508b629 GIT_SILENT made messages (after extraction) 2022-04-03 00:48:36 +00:00
l10n daemon script
ef5550bafd 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-04-02 01:41:06 +00:00
Nicolas Fella
1cc8d915bc Add rooms runner
This allows to search for and open rooms in KRunner
2022-04-01 10:56:19 +00:00
Snehit Sah
9a5f2e4938 Show subtitle text without markdown
Create new role in RoomListModel to send back cleaned subtitle text
2022-03-31 17:39:34 +00:00
l10n daemon script
a747d44cac 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-03-14 01:43:37 +00:00
Tobias Fella
334c13b36c Set preferredWidth and preferredHeight of images 2022-03-11 15:09:57 +01:00
Tobias Fella
aac96da2e2 Revert "Show RoomList when cached state is loaded"
This reverts commit db5f328539.
2022-03-08 21:10:38 +01:00
Tobias Fella
12f3f72a67 Lower typing notification timeouts 2022-03-08 15:00:00 +01:00
Tobias Fella
62f6cfbf9a Force RoomListDelegate to use plaintext
Text.AutoText isn't robust enough to handle this
2022-03-08 14:45:33 +01:00
l10n daemon script
c59e3db1dd 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-03-05 01:43:56 +00:00
Tobias Fella
9252e0e65e Disable the BusyIndicator
For some reason having the busyindicator running increases the time
required to load the state cache by several orders of magnitude
2022-03-01 12:18:07 +00:00
Carl Schwan
80ee5e9356 Apply 1 suggestion(s) to 1 file(s) 2022-03-01 00:29:07 +00:00
Tobias Fella
be802a28c2 Make invitation notifications persistent 2022-03-01 00:29:07 +00:00
Tobias Fella
b2a8430fa2 Don't apply autocompletion when autocomplete list is empty
Fixes sending messages like ':)'
2022-03-01 00:26:28 +00:00
Tobias Fella
db5f328539 Show RoomList when cached state is loaded
This should somewhat speed up the loading since we don't need to wait
until the first sync is done.

It's still slow though since loading the cache is slow
2022-03-01 00:29:48 +01:00
l10n daemon script
9ac1fbd99b GIT_SILENT made messages (after extraction) 2022-02-27 00:46:56 +00:00
Tobias Fella
022951a9df Add nicer delegate message for widget events 2022-02-25 20:49:57 +00:00
Tobias Fella
47a0d30e57 Fix quitting without tray icon
Setting KSNI status to Passive doesn't *disable* the tray icon, it just
moves it to the overflow menu. This causes the application to not quit
when closing the app even when disabling the tray icon
2022-02-25 20:19:12 +00:00
Tobias Fella
faeb1964bd Prepare Image & Video loading for E2EE
Changes the urls to make sure they are decrypted, while making sure that
it is backwards compatible to libQuotient 0.6
2022-02-25 21:15:46 +01:00
Tobias Fella
db8b2fd64b Aggregate similar state events 2022-02-25 20:10:07 +00:00
Tobias Fella
37c7fe380b Don't load backlog until read marker
This is bad if there are a lot of unread messages
2022-02-25 12:29:03 +01:00
l10n daemon script
537a1e44b1 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-02-25 01:50:55 +00:00
Tobias Fella
dc9d574b58 Fix login regex 2022-02-23 22:49:58 +01:00
Jose Flores
7c807e6a25 Modifies regex check for valid matrix server to accept ip addresses. 2022-02-22 22:54:43 +00:00
Jose Flores
f74c6a41ae Wraps the checkbox text for messages on the right to wrap on mobile devices (PinePhone). 2022-02-22 03:47:20 +00:00
Jose Flores
7d5a8c87a1 Wraps the quick edit checkbox using workaround 2022-02-22 03:47:20 +00:00
Sandro Knauß
ca719b835e The component of QtCoro5 is called Core and not Coro ;) 2022-02-21 16:31:24 +00:00
l10n daemon script
9b5ad3a3a0 GIT_SILENT made messages (after extraction) 2022-02-21 00:43:44 +00:00
Jose Flores
fdfbbb1b04 Uses the formatted message to enable clickable links for mobile. 2022-02-19 14:30:16 +00:00
Tobias Fella
dd91cb91d0 Load replied-to message when it isn't in the timeline already 2022-02-18 16:11:51 +01:00
Tobias Fella
290b2249c4 Port away from CMake deprecation 2022-02-14 22:41:42 +01:00
Jose Flores
8b8e521c56 Fix issue with clear image button. Will only be visible if the user has an avatar (local or saved) 2022-02-13 22:46:51 +00:00
Tobias Fella
cba88e1af7 Allow disabling notification inline reply
Is temporarily required for encrypted rooms
2022-02-12 22:33:10 +01:00
Tobias Fella
1661d34d7c Use Quotient's NetworkAccessManager in QML
Will be required for showing encrypted images
2022-02-12 22:23:59 +01:00
Tobias Fella
dc3b1a3c87 Remove unneeded parameter 2022-02-12 22:09:38 +01:00
Tobias Fella
f55dc19d95 Make user colors update when colortheme changes 2022-02-11 02:06:46 +01:00
Vitaly Zaitsev
6014c15b4f SingleMainWindow is a part of XDG SPEC version 1.5 and bogus on 1.0.
Signed-off-by: Vitaly Zaitsev <vitaly@easycoding.org>
2022-02-09 17:30:19 +01:00
l10n daemon script
a5f835b1eb GIT_SILENT made messages (after extraction) 2022-02-09 00:47:40 +00:00
Bhushan Shah
1fd6b615ff GIT_SILENT: Update version and appstream data for 22.02 2022-02-08 18:23:50 +05:30
Carl Schwan
dd4ef2e4ac GIT_SILENT: Add appstream description 2022-02-08 12:51:47 +00:00
Nicolas Fella
3b73409b7a Don't recreate config group when saving last room
Instead have the group as a member of the room manager
2022-02-07 21:54:46 +01:00
Tobias Fella
335ef240f5 Don't crash on empty creation event 2022-02-07 15:35:45 +01:00
Carl Schwan
ca8a21c0eb Implement sharing with Purpose (export)
This provide both a mobile and desktop view

Fix #181
2022-02-05 16:30:02 +00:00
ivan tkachenko
3e6f38c8ea Use ellipsis in «Loading…» strings 2022-02-04 20:59:17 +03:00
Tobias Fella
a6ab447955 Implement adding labels for account
This gives the user the ability to label different account (e.g. "work",
"private") and shows this label in the account switcher. Showing the
label in more places will be done in future MRs.

The label is stored in the user's account data and thus transfers
automatically to other instances of neochat
2022-01-31 22:45:17 +01:00
Tobias Fella
0b7dcd70ac Immediately apply leave/join event setting
Fixes #374
2022-01-31 21:52:28 +01:00
Tobias Fella
bce560b03b Fix left margin in EncryptedDelegate 2022-01-30 23:21:00 +01:00
Tobias Fella
5a1198d28c Set empty state key for room avatar change events 2022-01-29 01:13:55 +01:00
l10n daemon script
b236e61ea7 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-01-22 01:48:34 +00:00
l10n daemon script
79faeb21c9 GIT_SILENT made messages (after extraction) 2022-01-22 00:44:10 +00:00
Felipe Kinoshita
6ac6234886 Give settings window a title
Now the settings window title is "Configure ─ NeoChat" instead of
"Neochat <2>".
2022-01-21 21:42:46 +00:00
Devin Lin
fc9e4fc961 Reduce minimum height of the window
Reduce the minimum height of the window so that it doesn't go off the screen when on mobile landscape (Pinephone)
2022-01-18 23:56:57 +00:00
Friedrich W. H. Kossebau
24644887e0 Modernize code to activate window on user activation
* use KWindowSystem::updateStartupId() for abstract update of X11
  startup id or wayland activation token
* call QWindow::raise() also for wayland to prepare when it will
  support it
* call KWindowSystem::activateWindow() for all platforms, like done
  by other apps on user-triggered activation
2022-01-16 19:15:22 +00:00
Christopher Hock
a29ec0a18a Use the x-kde-origin-name notification hint to pass the account name to push notifications 2022-01-15 17:56:25 +01:00
Antonio Rojas
9300e65239 Fix build with qcoro 0.4
Cmake targets and config files have been renamed. Check for the 0.4 name first and fall back to the old one
2022-01-06 21:37:07 +00:00
Nate Graham
ee59006c08 Change X-GNOME-SingleWindow key to SingleMainWindow
X-GNOME-SingleWindow was upstreamed to be an XDG thing with the name
"SingleMainWindow" in
https://gitlab.freedesktop.org/xdg/xdg-specs/-/merge_requests/53
2022-01-05 17:02:54 -07:00
Tobias Fella
ca8702fd5e Don't set cmake policy when using libquotient 0.7 2022-01-02 22:47:42 +01:00
Tobias Fella
183c3227a9 Set bugs url 2022-01-02 22:34:06 +01:00
l10n daemon script
e86f70db85 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-01-02 01:57:27 +00:00
l10n daemon script
00d8fb75e3 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-01-01 01:42:37 +00:00
l10n daemon script
c3d149765c GIT_SILENT made messages (after extraction) 2021-12-29 00:48:31 +00:00
Carl Schwan
200281702a Raise windows also on other platforms
And unify code related to unique application handling
2021-12-28 23:50:25 +01:00
Carl Schwan
297684a139 Fix issues with saveFileAs
Fix #491
2021-12-28 23:42:16 +01:00
Jack Hill
aeee367e82 Changed "Settings" to "Configure NeoChat" in menu
Changed both the hamburger menu and the global menu
Fix #489
2021-12-28 16:51:30 +00:00
Carl Schwan
aa9dcc87cb Fix variable lookup in the timeline delegates
This fix issues with downloading and interacting with files
2021-12-28 16:18:05 +01:00
Carl Schwan
8a70e240e4 Improve toolbar on mobile
* Use Toolbar style with only back button when needed
* Don't show context drawer on room list

![image](/uploads/681f11e7d1a340a1b6a834df2b32960a/image.png)

![image](/uploads/564a91df531e7de363743efd4915b2e8/image.png)
2021-12-28 15:00:30 +00:00
Carl Schwan
50a7df8e03 ifdef version for compatibility with our minimal required version 2021-12-26 20:30:10 +00:00
Carl Schwan
dd977976db Improve emoji pane 2021-12-26 13:26:42 +00:00
Carl Schwan
de666b9377 Check if password can be changed 2021-12-26 13:08:05 +00:00
Carl Schwan
5f41378214 Fix image tooltip
display needs to be from the model and not from the Control
2021-12-26 00:14:09 +01:00
Carl Schwan
383b31c185 Expose hiddent GUI option
Fix #49
2021-12-25 20:34:47 +00:00
Carl Schwan
de2fbadba5 Adapt list setting pages to new style 2021-12-25 19:50:31 +00:00
Carl Schwan
67bc66ee0c Allow using ESC to go back to room list
Fix #392
2021-12-25 18:12:13 +00:00
Carl Schwan
924a1fed21 Port away from QNetworkConfigurationManager
QNetworkConfigurationManager was removed from Qt6 and it's better to
check the status of the sync job anyway. Only two issues:

* The timeout in quotient is quite high so it might take up to one
  minute before the message appear.
* Only sync job are listened but since they are continuously done, this
  is not a big issue and other job are affected by the same issue of an
  high timeout anyway.

Fix #414
2021-12-25 17:55:48 +00:00
Carl Schwan
b0a1de7572 Fix reuse issue 2021-12-25 18:55:32 +01:00
Carl Schwan
ca2b5fde8e Remove lag when starting user autocompletion
We realistically don't need to interate over every user when typing '@',
since this is not usefull for the user and create some lag.
2021-12-25 18:31:22 +01:00
Nicolas Fella
26f0cd4cf4 Set single window hint in desktop file
NeoChat is a single window application

This allows shells to hide the 'Open new window' action for NeoChat
2021-12-25 17:30:50 +01:00
Carl Schwan
0801b815c8 Make room address selectable
Help with #469
2021-12-25 15:33:26 +01:00
Carl Schwan
28137c8c86 Display monochrome icon in tray
Fix #471
2021-12-25 15:30:20 +01:00
Carl Schwan
e79e06235f Fix QuickSwitcher activation
By making sure the global menu bar is disabled when not needed. This
should also help with memory usage.

Fix #482
2021-12-25 14:41:43 +01:00
Nicolas Fella
dce7fde7a6 Fix Windows/mac build 2021-12-23 23:24:29 +01:00
Tobias Fella
8d9f3b8658 Revert "Add CI for FreeBSD"
This reverts commit d71ccc46d0
2021-12-22 16:52:43 +00:00
Nicolas Fella
5e1adf7ea7 Fix notifications on Android
Bundle the notifyrc file in qrc so that KNotifications finds it
2021-12-22 14:23:47 +00:00
Tobias Fella
d71ccc46d0 Add CI for FreeBSD 2021-12-22 14:14:30 +00:00
Aleix Pol
284a1734ae Support raising when we receive a notification 2021-12-15 15:08:32 +00:00
Tobias Fella
8722c99c93 Remove unused function 2021-12-15 01:04:51 +01:00
Tobias Fella
0c5932b3da Use a reasonable role for message source 2021-12-15 01:03:05 +01:00
Tobias Fella
332d6c9782 Minor improvements
- Rename TextDelegate to RichLabel since it's not actually a delegate
- Allow web search for whole messages
2021-12-15 00:53:43 +01:00
Tobias Fella
91f3f64bb5 Don't connect to something that isn't a signal 2021-12-14 22:34:32 +00:00
Tobias Fella
599ab11656 Refactor delegates 2021-12-14 22:27:29 +00:00
Tobias Fella
ff707b7a58 Remove dead code 2021-12-14 16:48:42 +01:00
Tobias Fella
e551319245 Don't render html in RoomDrawer heading 2021-12-14 15:54:14 +01:00
Fushan Wen
59430cce89 Add support for minimizing to system tray on startup
If the user wants to automatically launch NeoChat when the system
starts up, the user may also want to minimize the window to system tray
on startup. So a new option named "Minimize to system tray on startup"
is added.

The option is only visible on desktop platforms, and is only enabled
when "Close to system tray" is checked.

In order to restore window geometry for the first time the user opens
the window if the option is checked,

1. a new function named `restoreWindowGeometry` is added, and
   `restoreWindowGeometryConnections` will be enabled if the option is
   checked, and will be disabled after the window debuts.
2. `saveWindowGeometryConnections` will be enabled if the option is not
   checked, and will be disabled if checked and enabled after the window
   debuts.
2021-12-13 22:05:20 +08:00
Carl Schwan
d1bbb5e3f7 Use non blocking passord reading
This also remove the do while loop that might cause problem and expose
the error message to the user.

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-12-12 22:13:19 +00:00
Carl Schwan
6e1c07047e Add a mobile oriented context menu for the room list
It works similarly as in the timeline with a bottom based drawer on
mobile and a normal context menu on desktop

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-12-12 22:09:46 +00:00
Carl Schwan
738270f513 Fix loading room settings on mobile
Required properties don't work correctly with StackLayou.push

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-12-12 16:57:40 +00:00
l10n daemon script
9496127e88 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"
2021-12-12 01:44:42 +00:00
Nicolas Fella
16d43e9ee8 Use icon from qrc for system tray icon
Fixes the system tray on Windows
2021-12-11 20:10:59 +01:00
Tobias Fella
d0e04e0c97 Adapt to libQuotient API change 2021-12-10 18:06:12 +01:00
Carl Schwan
658eb187c9 Prevent instability with TextArea with null as background 2021-12-08 14:16:34 +01:00
Tobias Fella
041a5ff590 Add 21.12 release notes 2021-12-05 20:06:33 +00:00
Bhushan Shah
28d68444d9 GIT_SILENT Update version number for 21.12 2021-12-05 10:18:09 +05:30
Bhushan Shah
32cd42f03f cmake: use the PROJECT_VERSION variable
Makes it easier to bump version using scripts
2021-12-04 18:01:38 +05:30
l10n daemon script
98bc0b8c46 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"
2021-11-30 01:51:14 +00:00
Tobias Fella
5498cf1cd7 Add CI 2021-11-29 13:53:34 +01:00
l10n daemon script
babc87d023 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"
2021-11-29 01:35:58 +00:00
l10n daemon script
724e9d50a6 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"
2021-11-28 01:30:02 +00:00
l10n daemon script
8c0a6c1079 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"
2021-11-25 01:30:18 +00:00
l10n daemon script
6f33ad529e 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"
2021-11-24 01:36:52 +00:00
l10n daemon script
f9b5aa328a 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"
2021-11-23 01:29:26 +00:00
Tobias Fella
5b6e3d0902 Revert "Fix updating events when delegate choice changes"
This reverts commit 7b7c659a3a
2021-11-22 19:36:16 +00:00
l10n daemon script
5c5b805d3c 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"
2021-11-22 01:29:40 +00:00
Tobias Fella
d65962cbaa Use plaintext in completion menu 2021-11-22 00:20:49 +01:00
l10n daemon script
3658715ff6 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"
2021-11-21 01:25:36 +00:00
Carl Schwan
bf08303a8e Fix glitch in timeline scrolling
Turnout that reuseItems with loader and dynamically sized items is not
great.
2021-11-19 22:52:51 +01:00
Tobias Fella
935a51b477 More invite -> invitation 2021-11-19 15:47:31 +01:00
l10n daemon script
5b9a95878e 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"
2021-11-19 01:31:57 +00:00
Tobias Fella
560bd739e0 Invite -> Invitation 2021-11-18 15:27:50 +01:00
Tobias Fella
5b893d7736 Show a notification for invited rooms 2021-11-17 12:24:25 +00:00
Tobias Fella
c81ca6f8bb Set the height of statedelegates 2021-11-16 00:45:42 +01:00
Tobias Fella
740662e3f0 Remove visibility setting from FullScreenImage 2021-11-16 00:21:01 +01:00
Carl Schwan
46e1e64ee1 Improve source menu 2021-11-14 19:35:00 +00:00
Tobias Fella
7b7c659a3a Fix updating events when delegate choice changes 2021-11-13 22:19:15 +00:00
Tobias Fella
0a19d42799 Improve handling of closed keychain 2021-11-13 22:18:53 +00:00
Carl Schwan
8aa710d50f Full reuse compliance + ci check 2021-11-13 19:13:55 +01:00
Carl Schwan
7b81b545b9 Port to std::as_const 2021-11-13 19:11:47 +01:00
Tobias Fella
b0fde6d6c3 Add things to .gitignore 2021-11-13 14:59:21 +01:00
Tobias Fella
cb7b8bac99 Fix i18n message 2021-11-13 14:32:50 +01:00
Tobias Fella
9027db264a Don't capture 'this' implicitely 2021-11-13 14:17:20 +01:00
Carl Schwan
0f7461bd66 Bump dependencies 2021-11-13 13:21:01 +01:00
Carl Schwan
b44963d572 Copy SonnetConfigPage since we can't put it in Sonnet for now 2021-11-13 13:10:28 +01:00
Carl Schwan
25ac18e800 Revert "Revert "Spellchecking with new Sonnet declarative API""
This reverts commit dada3e300b.
2021-11-13 13:10:16 +01:00
Tobias Fella
8089e5bdfa Fix pagestack after login after logout 2021-11-12 16:21:48 +01:00
Christopher Hock
d1dce37ea7 Allow user to copy the room address to the clipboard
Contributes to #469
2021-11-07 16:12:29 +00:00
Carl Schwan
dd75eaec2c Remove dead code
It seems this was never used even by the commit introducing it
2021-11-05 20:54:29 +01:00
Tobias Fella
0568bed62d Use plaintext in TypingPane 2021-11-02 00:08:02 +01:00
Tobias Fella
d494eb1c63 Use Quotient's accountregistry 2021-11-01 19:36:39 +00:00
Carl Schwan
ee8be4b755 bump dependencies 2021-10-27 08:02:14 +00:00
l10n daemon script
97b0767b8f GIT_SILENT made messages (after extraction) 2021-10-25 00:18:07 +00:00
Nicolas Fella
1e0ff63ab8 Fix version variable 2021-10-24 23:03:02 +02:00
Nicolas Fella
b6341eebfe Pass version information to AndroidManifest
Fixes #463
2021-10-24 22:49:33 +02:00
Carl Schwan
f2cf82ee8e Fix double quoting and missing new lines in message sent
* Don't encode text inside code block
* Make sure to replace \n with <br> in the html rendering. It's not
  respecting the common mark spec but this is the same behavior as
  Element
2021-10-23 20:35:19 +00:00
Carl Schwan
a146fab5a0 Fix color of Pane in room info drawer
This is temporary hack and the real solution is to add a Pane
implementation in qqc2-desktop-style
2021-10-23 20:33:34 +00:00
l10n daemon script
fb6745b49a GIT_SILENT made messages (after extraction) 2021-10-23 00:17:59 +00:00
Carl Schwan
6c3ae87340 Support resizing right drawer
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-10-21 23:04:52 +02:00
Carl Schwan
6afeaf1619 Move copy pasted to TextDelegate component
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-10-21 22:18:47 +02:00
Carl Schwan
890860df92 Improve room setting
* Port away from OverlaySheet
* Use Kirigami.CategorizedSettings
* Add join rules (read only for now)
2021-10-21 20:00:50 +00:00
Carl Schwan
6b8358874a Simplify function call in RoomPage
Instead of passing every argument in the right order, pass the entire
model/event object to the context menu functions. This is less copy
pasta of code and the order of the args is now less likely to break in
the future.

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-10-21 19:54:06 +00:00
Carl Schwan
fc9f37d4a4 Port global settings to Kirigami.CategorizedSettings 2021-10-21 19:38:03 +00:00
Tobias Fella
48e410196c Don't allow opening a room in a new window on mobile 2021-10-18 21:07:35 +00:00
Tobias Fella
65cc392805 Fix flicking the timeline 2021-10-18 15:35:56 +02:00
Tobias Fella
a6dd5b9a57 Escape html before processing text to be sent 2021-10-16 20:45:14 +02:00
Tobias Fella
1d7c20e1c7 Make user list search case insensitive 2021-10-16 18:18:07 +00:00
Tobias Fella
22609b21df Add custom eventToString message for power level events 2021-10-16 18:17:49 +00:00
Tobias Fella
6c5ca0ac9d FIx querying power levels
Fixes #422
2021-10-16 20:16:36 +02:00
Tobias Fella
b22ebf3671 Show number of joined users instead of joined+invited users in room drawer 2021-10-16 19:28:51 +02:00
Carl Schwan
ec1cc34855 Fix missing import 2021-10-16 18:11:59 +02:00
Carl Schwan
a5aafde331 Unify look of loading pages 2021-10-16 17:59:31 +02:00
Carl Schwan
d42ad85b30 Port to OverlaySheet.title 2021-10-16 17:44:42 +02:00
Tobias Fella
8648b4a3bf Fix copying whole messages 2021-10-14 22:14:36 +02:00
Tobias Fella
bdca636fb8 Copy only selected text instead of whole message
Fixes #457
2021-10-14 21:44:32 +02:00
Tobias Fella
7bb7b7850b Remove some dead code 2021-10-12 13:29:38 +02:00
Tobias Fella
82d49b91e7 Add custom message for acl events in statedelegates 2021-10-12 11:15:15 +00:00
l10n daemon script
ecc6c6e2eb 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"
2021-10-12 01:18:05 +00:00
l10n daemon script
e6c594f3d2 GIT_SILENT made messages (after extraction) 2021-10-12 00:17:15 +00:00
Tobias Fella
97f8ad88fb Fix statedelegate text for displayname changes 2021-10-12 00:28:47 +02:00
Carl Schwan
14a358ed58 Fix cliping problems in room list when using multiple accounts
Before the rooms from the room list would go above the account selector,
making it not possible to switch accounts and creating weird visual
artifacts.
2021-10-11 18:35:13 +02:00
Tobias Fella
1e78c6caa4 Fix opening rooms 2021-10-10 12:40:39 +02:00
Tobias Fella
2fc4c6456f Escape more html 2021-10-10 12:26:35 +02:00
Tobias Fella
76675719cf Wrap the placeholdertext in the chatbar 2021-10-09 21:17:45 +02:00
Tobias Fella
6b331ca8b4 Adapt to libQuotient API changes 2021-10-04 19:34:38 +02:00
Tobias Fella
7d6faaafea More HTML safety in statedelegates 2021-10-04 15:56:24 +02:00
Tobias Fella
4e26dc5d57 Improve RoomSettingsDialog: use better text color and clearer label for aliases 2021-10-03 23:12:09 +02:00
Tobias Fella
304c74101e Raise required CMake version 2021-10-03 19:13:33 +02:00
Tobias Fella
c844f2a157 Fix SSO login 2021-10-03 18:34:16 +02:00
Tobias Fella
21c571e2a1 Remove leftover debug log 2021-10-03 18:23:27 +02:00
Tobias Fella
011cf3c652 Fix highlighted room when opening room from notification 2021-10-03 16:21:53 +00:00
Tobias Fella
6b86c113f4 Refactor login.cpp and fix crash 2021-10-03 18:17:37 +02:00
Tobias Fella
4511c1a07e Fix default color scheme selection 2021-10-03 14:31:16 +02:00
Tobias Fella
1345ddc9ee Remove ifdef for older versions of KNotifications
We require a newer version
2021-10-03 14:15:00 +02:00
Tobias Fella
452380e274 Don't parent connections to controller/Login.
This prevents a crash upon destruction for newer libquotient versions
2021-10-03 14:13:04 +02:00
Tobias Fella
cd745d1df7 Minor fixes 2021-10-03 13:43:20 +02:00
Tobias Fella
5bf0aa7a68 Make QML more declarative 2021-10-02 16:34:31 +00:00
Tobias Fella
fa631ece3a Add feature to delete all loaded messages by user 2021-10-02 15:08:31 +00:00
Arnav Rawat
334930808c Change wording for knock events 2021-10-02 14:37:06 +00:00
Carl Schwan
c105642e4c Don't load twice the room list when adding another account
Just check if the room list is not yet loaded before loading it

Fix #444
2021-10-02 14:34:39 +00:00
Carl Schwan
6a0b51a78e Insist on that loading might take a while for initial loading of account 2021-10-02 14:34:15 +00:00
Carl Schwan
00ecb4703e Make sure the right sidebar width is not considered when no room is open
Fix #460
2021-10-02 14:33:52 +00:00
Tobias Fella
3977aee4ba Remove usage of Kirigami.Units.devicePixelRatio
It's deprecated and as far as i can tell it doesn't really do anything
since it is always set to 1
2021-10-02 14:33:27 +00:00
Tobias Fella
e9f935609f Don't segfault on shutdown
Use the current QPalette for user color instead of getting the platform
theme from QML, which caused segfaults.
2021-09-30 18:42:19 +02:00
Tobias Fella
886ee1f5b6 Refactor startup 2021-09-29 22:38:45 +02:00
Tobias Fella
71d01593b1 Fix rejecting and accepting invitations 2021-09-24 23:32:19 +02:00
David Redondo
6dab24bc74 Search for syntax-highlighting qml module
It's an optional part of syntax-highlighting so just searching for
the it is not enough we need to make sure to have the qml module.
Also don't link to it, we only need it at runtime.
2021-09-24 17:03:51 +02:00
Tobias Fella
7c7d296981 Fix HTML escaping 2021-09-24 12:44:32 +02:00
Tobias Fella
d28279313d Rename type because of conflict 2021-09-24 12:42:05 +02:00
Tobias Fella
b3d9861d3d Fix label alignment in statedelegate 2021-09-23 20:01:59 +02:00
Tobias Fella
218eda63b3 Fix typo 2021-09-20 23:49:18 +02:00
Tobias Fella
cb2ea5f4e3 Allow TextDelegate to wrap anywhere if required
Prevents lines from going out of the message bubble
2021-09-20 23:31:36 +02:00
Tobias Fella
50040230f5 Port away from onFoo in Connections 2021-09-20 23:04:40 +02:00
Tobias Fella
da7b3a1ea9 Fix text formatting 2021-09-20 22:55:55 +02:00
Tobias Fella
0a39fccec8 Add syntaxhighlighting to the MessageSourceSheet 2021-09-19 18:56:57 +00:00
Nicolas Fella
132c9c50fa Check license compatibility
Adds a unit test that fails if our source files don't match our target license
2021-09-19 20:53:20 +02:00
Tobias Fella
821993c443 Fix DevicesPage and AccountsPage on mobile
Also make the padding smaller
2021-09-17 19:24:16 +00:00
Devin Lin
8118068fb5 Move mobile sidebar handles to top 2021-09-17 15:52:40 +00:00
Tobias Fella
68830bae97 Don't do things in an assert 2021-09-17 16:22:19 +02:00
Christopher Hock
c421d4047b Add arrow-down icon to imported icons list in CMakeLists.txt 2021-09-16 14:04:24 +00:00
Tobias Fella
9cdf6bfa04 Fix displayname in reply component 2021-09-14 23:24:59 +02:00
Christopher Hock
bda7326d3b Update links to Android packages in Readme 2021-09-14 15:56:06 +02:00
Tobias Fella
9d4151dd00 Fix build against quotient 0.6 2021-09-12 17:00:32 +02:00
Tobias Fella
af29925798 Open users in UserDetailSheet instead of browser in statedelegate 2021-09-11 20:04:36 +02:00
Tobias Fella
c1ee480823 One more 2021-09-11 16:37:42 +02:00
Tobias Fella
f5853dd1f7 Three's a crowd 2021-09-11 16:36:22 +02:00
Tobias Fella
bc76197487 Fix more HTML injections 2021-09-11 16:33:09 +02:00
Tobias Fella
83d03b0e06 Fix html injection 2021-09-11 16:29:49 +02:00
Noah Davis
2a9d739bc9 Use new crop tool from KQuickImageEditor 2021-09-11 01:08:29 +00:00
Smitty van Bodegom
44bc2388c6 Don't show option to private chat with oneself
While this option works, it doesn't make much sense since there isn't
much of a good reason to talk to oneself aside from for testing
purposes. The created conversation also has confusing UI, showing as an
"empty room", not a DM. If needed, creating an empty room with E2E
encryption has the same effect as creating a private chat with
yourself.

Closes #446.
2021-09-10 18:44:56 -04:00
Tobias Fella
35b88de526 Fix build against libQuotient API changes 2021-09-10 00:09:20 +02:00
Tobias Fella
7dc48cabcb Don't try to remove the access token from the settings during login 2021-09-08 20:35:05 +02:00
Tobias Fella
98a35fe09f Remove unused imports 2021-09-08 20:31:00 +02:00
Tobias Fella
caafd8c0bc Don't try removing the access token from the accountsettings 2021-09-08 20:30:16 +02:00
Tobias Fella
5795bf5429 Fix typo 2021-09-08 20:28:55 +02:00
Tom Z
c39e46a1f0 For the "Compact" view; show hovered background 2021-09-08 13:24:21 +00:00
Aleix Pol
8d59e2ba67 Remove unneeded headers 2021-09-07 11:27:13 +00:00
Aleix Pol
265fcbfead Fix wayland activation
Adds support for xdg_activation_v1 when calling the application from the
system tray by using KStatusNotifier which supports it.
Listens to XDG_ACTIVATION_TOKEN as it's passed when we are started from
dbus.
2021-09-07 11:27:13 +00:00
Tobias Fella
5c86692fb5 Port away from onFoo in Connection 2021-09-06 22:58:37 +02:00
Tobias Fella
e778cd72af Android: Package missing icons 2021-09-06 21:37:55 +02:00
Tobias Fella
9e709e4741 Android: Add another missing icon 2021-09-05 17:26:40 +02:00
Tobias Fella
868fe7543e Android: add missing icon 2021-09-05 17:22:36 +02:00
Tobias Fella
0c95556618 Android: don't set fallback icon theme 2021-09-05 17:13:05 +02:00
Tobias Fella
31d395009e It's 2021 2021-09-05 16:55:50 +02:00
Tobias Fella
710f4d86d6 Android: Use breeze style 2021-09-05 16:51:45 +02:00
l10n daemon script
1d5b7dee94 GIT_SILENT made messages (after extraction) 2021-08-31 00:20:59 +00:00
Tobias Fella
c9935804b0 Fix QML warning in ImageDelegate 2021-08-25 00:20:24 +02:00
Tobias Fella
6570cf396a Only emit initialSyncFinished once 2021-08-24 01:40:19 +02:00
Tobias Fella
bd08f7fd36 Fix QML error 2021-08-24 00:45:57 +02:00
Tom Z
2ccde21896 On showWindow, also raise 2021-08-23 21:42:42 +00:00
Tom Z
cd461da413 Fix hoverItem positioning.
In the "Compact" layout the hover item is on top of the time label
and the wrong mapping base caused a weird offset. Now we simply
just position the hover item immediately above the current item.
2021-08-23 21:42:11 +00:00
Tom Z
df4df171dd Fix valgrind warnings 2021-08-23 22:51:41 +02:00
Nicolas Fella
61202dcd5f Add clang-format commit hook 2021-08-23 19:38:56 +02:00
Nicolas Fella
589dfe0343 Re-run clang-format 2021-08-23 19:38:56 +02:00
Hannah von Reth
0f106b1bbc Use anonymous namespace to mark internal functions 2021-08-23 09:18:32 +02:00
Hannah von Reth
d7e9bc4c14 Dynamic arrays are illegal with msvc 2021-08-23 09:16:20 +02:00
Tobias Fella
d1451780c2 Port away from deprecated functions 2021-08-22 21:09:23 +02:00
Tobias Fella
b72cde9543 Remove unnecessary qml type registration 2021-08-22 18:22:50 +02:00
Tobias Fella
046e823d1b Show blurhashes for image events 2021-08-21 00:39:57 +02:00
Tobias Fella
dada3e300b Revert "Spellchecking with new Sonnet declarative API"
This reverts commit 17bbc60f6f
2021-08-20 20:31:25 +00:00
Jan Blackquill
4955b1f7a0 Improve the file delegate
- cleaner code
- supports stopping downloads
- utilises the mimetype in order to display an icon
- better size formatting
- tooltips
2021-08-19 14:01:14 -04:00
Tobias Fella
d7ce0b7468 Show a placeholder for encrypted messages
Tells the user that the message could not be decrypted because the key
was not shared with the device. At the moment, this is technically not
completely correct, but it will be when libQuotient supports reading
encrypted messages
2021-08-18 21:54:06 +02:00
Felipe Kinoshita
94c4bbc3db Center section label and change it's color
This commit horizontally centers the section label and gives it a
disabled text color, this helps visually separate this label from
the actual messages and makes it easier to scan.
2021-08-17 16:44:42 -03:00
Tobias Fella
ecc639fb0a Refactor room leaving
The old code has a small bug where the room would not be left in certain
conditions.
2021-08-14 23:01:16 +00:00
Carl Schwan
21bd5fa94e Handle immutable settings 2021-08-15 01:00:34 +02:00
Carl Schwan
17bbc60f6f Spellchecking with new Sonnet declarative API
This replaces all the custom code with a shared implementation
in QQC2-desktop style
2021-08-14 20:27:11 +00:00
Milo Kerr
e064243d66 Adds the ability to show avatars in the sidebar independently of whether they are shown in the chat area.
Fix #408
2021-08-11 20:33:56 +00:00
Tobias Fella
e91a4f79a5 Adapt to libQuotient API changes 2021-08-10 17:09:21 +02:00
Tobias Fella
9e4d0ddc9b Fix i18n argument 2021-08-04 23:01:08 +02:00
Tobias Fella
2554ce55ed QML errors-- 2021-08-04 22:58:03 +02:00
Tobias Fella
de98fd05f0 Fix wrong fix 2021-08-04 18:12:28 +02:00
Smitty van Bodegom
383d2a6e71 Match sed behavior for sed editing
Before s/a/b/ turns "aaa" into "bbb", while sed would turn it into
"baa". This updates the sed editing syntax to match sed behavior. The
/g flag is supported for doing global replacements when needed.
2021-08-04 13:00:30 +00:00
Smitty van Bodegom
db1a9a0c4c Categorize Spaces as such in sidebar
Currently they are shown at the bottom, since Neochat doesn't support
displaying Spaces yet.
2021-08-03 19:54:48 +00:00
Tobias Fella
4658574732 Fix warning 2021-08-03 20:49:48 +02:00
Tobias Fella
4efd1207ee Some more clazy fixes 2021-08-03 20:44:22 +02:00
Tobias Fella
d92b1895a9 More clazy fixes 2021-08-03 20:12:36 +02:00
Tobias Fella
3be9c0cfb3 Clazy fixes 2021-08-03 20:06:36 +02:00
Tobias Fella
1427c8bf3f Check if arguments are empty 2021-08-03 19:24:45 +02:00
Tobias Fella
adbbb7e42a Fix deletion of custom emojis 2021-08-03 18:34:12 +02:00
Tobias Fella
e187110680 qAsConst all the things 2021-08-03 17:29:08 +02:00
Tobias Fella
77318436ad Don't push too many room pages when switching accounts 2021-08-03 17:20:27 +02:00
Tobias Fella
db77dad0c2 SettingsPage: s/Account/Accounts 2021-08-03 17:12:36 +02:00
Tobias Fella
73245c0f00 AccountsPage: remove broken action 2021-08-03 17:11:47 +02:00
Tobias Fella
07d4d384f3 More AccountList fixes 2021-08-03 16:42:49 +02:00
Tobias Fella
4b75e7d588 Make login more robust 2021-08-03 14:34:19 +00:00
Tobias Fella
00494f8f88 Clean up the AccountListModel 2021-07-30 23:03:08 +00:00
Tobias Fella
de7354f056 Adapt to changes in libQuotient API 2021-07-31 01:00:58 +02:00
Tobias Fella
2e1ab639c6 Fix build failure against changes in libQuotient API
This changes the RoomListModel's JoinState role to expose the enum value
instead of a string. We don't actually use that role anywhere so it's
fine.
2021-07-30 22:57:57 +00:00
l10n daemon script
40f0893048 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"
2021-07-28 01:18:55 +00:00
l10n daemon script
5245d2399f GIT_SILENT made messages (after extraction) 2021-07-28 00:18:10 +00:00
Tobias Fella
bbf9eb2ddc Undefined behavior-- 2021-07-27 23:43:35 +02:00
Devin Lin
592d3160df Add translucency slider and set default to 30%
Apply 1 suggestion(s) to 1 file(s)
Fix
2021-07-27 13:34:15 -04:00
Tobias Fella
97737b753b Don't store custom emojis with ':' before and after the shortcode
As per recent changes to the MSC
2021-07-24 15:29:46 +00:00
Tobias Fella
f8db9c4ecb Add libQuotient as KAboutComponent
With frameworks 5.85, this will make it show up in the aboutpage
2021-07-24 15:29:22 +00:00
l10n daemon script
9567c7ecf7 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"
2021-07-20 01:15:33 +00:00
l10n daemon script
df6c2a85f6 GIT_SILENT made messages (after extraction) 2021-07-20 00:17:26 +00:00
Tobias Fella
ae60834c36 Refactor qml type registration 2021-07-14 20:19:17 +02:00
TomZ TomZ
44de5c1730 Avoid saturated colors in appearance setting page
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2021-07-14 10:31:56 +02:00
Tobias Fella
35aac4e362 Port away from implicitely defined onFoo properties 2021-07-07 22:09:36 +02:00
Tobias Fella
73de1af321 Fix pagestack after login 2021-07-06 15:45:19 +00:00
l10n daemon script
aba96ffb83 GIT_SILENT made messages (after extraction) 2021-07-05 00:17:58 +00:00
Jan Blackquill
537416354a feat: global menu
This adds a global menu to NeoChat, providing
a standard and consistent set of File/Edit/View/so on
menus.
2021-07-02 09:16:31 +00:00
Tobias Fella
6bbb4b4985 Fix connection 2021-07-01 13:37:30 +02:00
Tobias Fella
7411447f22 Remove unused parameter 2021-06-30 21:33:26 +02:00
Tobias Fella
84bc207174 Gitignore++ 2021-06-30 20:30:09 +02:00
Carl Schwan
54f5097ff4 Don't assume there is an active connection when loading devices list
Instead listen to Controller::activeConnectionChanged and reset model
when the active connection is changed.

Fix #413
2021-06-30 11:23:00 +02:00
l10n daemon script
dd841bb836 GIT_SILENT made messages (after extraction) 2021-06-28 00:19:26 +00:00
Carl Schwan
92eb06aaa6 Explicitely don't show horizontal ScrollBar on account page
Fix #407
2021-06-23 17:19:34 +02:00
Smitty van Bodegom
3e8c7caefd support table flipping and unflipping 2021-06-23 14:29:39 +00:00
Carl Schwan
b1d8956036 Fix links now working anymore and simplify code 2021-06-22 10:14:53 +02:00
Carl Schwan
f2215f10e2 Fix bug with room list header 2021-06-22 10:14:35 +02:00
Smitty van Bodegom
211f6004eb Support sending spoilers 2021-06-18 12:04:00 -04:00
Tobias Fella
9ec20dc02d Use new format for custom emojis
The data structure changed during the MSC process. This patch makes
NeoChat send the new format, while accepting both the old and the new
format.
2021-06-18 13:54:01 +02:00
Carl Schwan
680b0cc3bd Bind spoiler revealed state 2021-06-17 22:39:50 +00:00
Smitty van Bodegom
8474136f57 Support displaying spoilers
This adds support for displaying recieved spoilers, but not sending
them. Spoilers are displayed as a black rectangle, and can be clicked on
to be revealed. If the last message in a channel was a spoiler, it is
not shown on the left sidebar.

The spoiler blackening is done in CSS, but to check if a message
contains a spoiler for determining if it should cause a different cursor
to be displayed and if it should be shown in the sidebar, a simple check
of if the message contains "data-mx-spoiler" is used.
2021-06-17 22:39:50 +00:00
Nicolas Fella
81d0db7f1e Bump minimum KF5 version
We use KWindowSystem API from 5.82
2021-06-17 22:56:24 +02:00
Carl Schwan
b8a341eda8 Don't use C++20 syntax (designated initializers) 2021-06-17 13:08:30 +02:00
Carl Schwan
d7345ee4e6 Unify look of scrolling setting page
* Add frame on desktop
* Remove frame on mobile
* Use actions.main when needed on mobile
* Don't push a new setting page on mobile
2021-06-17 13:05:16 +02:00
Smitty van Bodegom
6487ea7414 Alias /j -> /join, /leave -> /part 2021-06-16 19:55:48 -04:00
Smitty van Bodegom
edd5fadbde Better formatting for slash commands 2021-06-16 23:26:56 +00:00
Carl Schwan
9ad5a7b02b Fix some minor warnings 2021-06-17 00:39:04 +02:00
Carl Schwan
768fd74361 Keep completion behavior similar to before
* Use tab to jump to the next completion item
* Space to accept change
2021-06-17 00:37:57 +02:00
Jan Blackquill
6898670499 feat: better autocompletion UX
The autocompletion bar is now more similar to mainstream
chat applications, which use up/down to change the selection,
and tab to confirm the selection. An extra space is also added
to keep the flow of typing going.
2021-06-16 16:29:14 -04:00
Jan Blackquill
30965cb503 feat: ponies.im emoji support (custom emojum) 2021-06-16 15:34:55 -04:00
l10n daemon script
9961483f5c 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"
2021-06-16 01:23:20 +00:00
Laurent Montel
d5cd175d71 It's not a signal 2021-06-15 13:12:07 +02:00
Carl Schwan
c79d835b5c Fix a warning about KWebShortcutModelPrivate struct vs class mismatch 2021-06-14 23:07:18 +00:00
Carl Schwan
75b70dc6a2 Build spellcheckhighlighter.cpp on Android too
It should be noop
2021-06-14 18:05:51 +00:00
Volker Krause
56ca6bf2a4 Fix build without KIO
Relevant on Android.
2021-06-14 18:34:32 +02:00
Tobias Fella
9f6d8aa550 Add missing license text 2021-06-14 16:23:37 +02:00
Carl Schwan
8c1129f88e Add missing license 2021-06-14 16:18:25 +02:00
Carl Schwan
a086964769 Make sidebar collapsible 2021-06-14 12:50:09 +00:00
l10n daemon script
5c6a540807 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"
2021-06-14 01:15:12 +00:00
Srevin Saju
b412719f2c feat: show local users's messages on right 2021-06-13 18:52:17 +00:00
Smitty van Bodegom
68194055aa /react to last message if none specified 2021-06-13 12:50:50 +00:00
l10n daemon script
d1d4d6ef38 GIT_SILENT made messages (after extraction) 2021-06-13 00:18:32 +00:00
Carl Schwan
0ff9425fee Add spellchecking suggestions 2021-06-10 11:29:59 +00:00
l10n daemon script
5cb8424a83 GIT_SILENT made messages (after extraction) 2021-06-09 00:18:20 +00:00
l10n daemon script
ff8a71663a GIT_SILENT made messages (after extraction) 2021-06-08 00:17:18 +00:00
Carl Schwan
0d0f180c3e Don't change text color for spelling mistakes
Fix #98
2021-06-07 14:43:04 +02:00
Carl Schwan
efb70287b9 Add setting page 2021-06-07 11:49:35 +02:00
Carl Schwan
8f309ca958 Add SpellChecking to NeoChat
Fix #98
2021-06-07 11:34:38 +02:00
Carl Schwan
23bd73c499 Draft: Big overhaul of the settings 2021-06-07 11:34:12 +02:00
l10n daemon script
d9eaa95b9d GIT_SILENT made messages (after extraction) 2021-06-07 00:17:46 +00:00
Carl Schwan
51ca4994ef Add webshortcut search 2021-06-06 14:27:12 +00:00
l10n daemon script
01f861fbbd GIT_SILENT made messages (after extraction) 2021-06-06 00:17:28 +00:00
l10n daemon script
3b86d0088d GIT_SILENT made messages (after extraction) 2021-06-04 00:19:09 +00:00
Jan Blackquill
ce15a8d697 Add quick switcher 2021-06-04 00:02:00 +00:00
l10n daemon script
5d927065bb GIT_SILENT made messages (after extraction) 2021-06-03 00:17:17 +00:00
l10n daemon script
60d78036ed GIT_SILENT made messages (after extraction) 2021-06-02 00:18:06 +00:00
l10n daemon script
d55e27b791 GIT_SILENT made messages (after extraction) 2021-06-01 00:17:57 +00:00
Carl Schwan
649f7716d6 Update apstream screenshots
(cherry picked from commit bc977c3fc6)
2021-05-31 20:26:30 +02:00
Carl Schwan
4539aa3442 Update versioning to 1.2.80 2021-05-31 17:26:09 +02:00
Carl Schwan
87d1fefae2 Don't mark message as read when the current window is not visible
Fix #378
2021-05-31 17:21:03 +02:00
Carl Schwan
6e5bca4928 Mark all message as read when clicking on down button
Fix #379
2021-05-31 16:59:31 +02:00
l10n daemon script
4d236a201b GIT_SILENT made messages (after extraction) 2021-05-31 00:17:13 +00:00
Carl Schwan
a015497efe Update Appstream 2021-05-29 22:18:08 +00:00
Srevin Saju
3e78bff8a1 feat: show the username and avatar again on date-change
when the clock hits 00:00 in the user's time zone, but in the
case of a continuous discussion, it is likely that "Today"
date-change marker would obstruct the conversation, and the
username and avatar would be missing.
2021-05-29 22:15:55 +00:00
Carl Schwan
807112fb19 Don't steal focus in panel search field 2021-05-30 00:06:16 +02:00
Carl Schwan
e15e10d319 Reinitialize completion list after switching room 2021-05-29 23:55:34 +02:00
Carl Schwan
c7fd5cc511 Make sure we only add non empty name or display name in autocompletion
list

Otherwise this will breaks when replacing names later
2021-05-29 23:13:16 +02:00
Carl Schwan
b37152ff89 Open room when pressing Enter or Return
Fix #381
2021-05-29 20:15:46 +02:00
Tobias Fella
562b59d834 Fix unban permission check 2021-05-28 21:44:07 +02:00
Arnav Rawat
df0ad391ba Restore I-Beam cursor on hover
Should not cause issues with themes
2021-05-28 13:34:22 +00:00
Carl Schwan
3329739d55 Fix reverse tabbing not working in autocompletion
Now call autocomplete() also for shift+tab

Fix #377
2021-05-28 14:56:36 +02:00
Carl Schwan
7bec8c73f8 Fix size of replies in mesage delegate
(cherry picked from commit 76bd529c3c)
2021-05-28 14:54:08 +02:00
Carl Schwan
d9125148fe Fix completion when tabing users
This was caused by weird bindings. Now just access the userId value
directly.

Fix #324
2021-05-28 14:25:55 +02:00
Carl Schwan
7cd9f788dd pushReplace more 2021-05-28 13:14:53 +02:00
Carl Schwan
db0f421811 Better read market handling: Mark room as read when scrolling to the
bottom

Fix #372
2021-05-27 19:12:47 +00:00
Noah Davis
8a55db4eb8 Move TypingIndicator to the right side
So that it's less likely to cover message text.
2021-05-27 14:11:01 -04:00
Carl Schwan
3d251b9b25 Fix date being show too often 2021-05-27 18:17:26 +02:00
Carl Schwan
13888401fa Don't use SystemTray integration on GNOME and ElementaryOS
These platforms don't support it so hiding NeoChat in the tray in these
platforms is not a good idea and other a rather poor user experience.
2021-05-27 17:39:30 +02:00
Carl Schwan
d6394fcd47 Set fallback icon theme to breeze 2021-05-27 17:24:11 +02:00
Noah Davis
bbcf4239a4 Use 3 dot typing indicator, clean up code a bit.
Move TypingIndicator.qml out of ChatBox folder.
It wasn't part of the ChatBox.

fixes #367 by eliding instead of wrapping text
2021-05-27 14:17:22 +00:00
Hannah von Reth
92fcff1dce Fix Windows builds 2021-05-26 10:22:36 +02:00
Nicolas Fella
41838d01df Remove minSdk version from AndroidManifest 2021-05-26 03:14:54 +02:00
Nicolas Fella
af75cebba1 Fix Android ifdef 2021-05-26 03:10:43 +02:00
Nicolas Fella
1cec672c0a remove spurious QFileDialog include 2021-05-26 03:09:08 +02:00
Nicolas Fella
bd5f6c9c9e remove spurious QMenu include 2021-05-26 03:07:13 +02:00
Nicolas Fella
6e04d343b7 Use QGuiApplication instead of QApplication where appropriate 2021-05-26 03:05:00 +02:00
Nicolas Fella
454e35433b Don't find Widgets on Android 2021-05-26 02:49:29 +02:00
Tobias Fella
4dea02197c Fix multiple headers for the same sections 2021-05-25 12:31:42 +00:00
Adriaan de Groot
294f0c7e1a CMake: various tidying-up 2021-05-24 15:44:05 +00:00
Carl Schwan
d14674c2cd Fix minor bugs 2021-05-24 16:50:54 +02:00
Carl Schwan
49c1736f7c Make sure message are loaded when scrolling to the top 2021-05-24 16:48:05 +02:00
Carl Schwan
db62f06de4 Make effects more visible 2021-05-24 16:47:40 +02:00
Carl Schwan
34da8734a2 Fix qml warnings 2021-05-23 22:55:20 +02:00
Carl Schwan
0dbb56ba1e Fix creating broken direct chat for user with a direct chat already open
Just enter the existing room instead of trying to create a new one but
broken.

Fix !237
2021-05-23 21:46:40 +02:00
Carl Schwan
93064ec5bf Fix username autocompletion 2021-05-23 18:38:52 +02:00
Carl Schwan
7bdfdc0eec Minor optimization 2021-05-23 18:28:39 +02:00
Carl Schwan
bae7813f68 Fix loading events when scrolling or opening a room for the first time
Fix #362
2021-05-23 18:27:01 +02:00
Carl Schwan
dded804f00 Fix mode without avatar
It seems that this mode didn't get much love when I added the bubbles so
it was quite broken. This patches removes the bubbles and fix the
alignment issues when using this mode.

We probably should rename it to compact mode in a follow up commit (but
not this one so we can backport it to the stable branch).
2021-05-23 16:30:16 +00:00
l10n daemon script
4026bf10b2 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"
2021-05-23 01:13:55 +00:00
l10n daemon script
e04234a0d4 GIT_SILENT made messages (after extraction) 2021-05-23 00:16:54 +00:00
Tobias Fella
c755084bb0 Finish reuse compatability 2021-05-22 19:12:29 +00:00
Carl Schwan
dbb43addc8 Fix i18n 2021-05-22 16:23:14 +00:00
Tobias Fella
7939055640 Add missing license text 2021-05-22 16:27:31 +02:00
Tobias Fella
135b2e49fa Don't hide redacted events 2021-05-22 14:16:32 +02:00
Tobias Fella
011f649cbf Revert "Fix showing multiple deleted messages from the same author"
This reverts commit b48c9bdadc.
2021-05-22 14:14:58 +02:00
Tobias Fella
36a2f5719f Revert "Show deleted messages"
This reverts commit 116f883699.
2021-05-22 14:14:53 +02:00
Tobias Fella
89d4c1ff95 Allow unbanning users 2021-05-22 13:54:54 +02:00
Tobias Fella
af6880b2ca Fix banning users 2021-05-22 13:37:31 +02:00
Tobias Fella
48d1fa27cf Don't offer banning users that are already banned 2021-05-22 13:09:52 +02:00
Tobias Fella
bd893adb34 Don't offer to kick users that already left 2021-05-22 00:32:49 +02:00
Tobias Fella
b48c9bdadc Fix showing multiple deleted messages from the same author 2021-05-22 00:14:18 +02:00
Tobias Fella
116f883699 Show deleted messages
We used to show those, then a bug was fixed in the code that was
supposed to hide them.
2021-05-21 22:30:51 +02:00
Tobias Fella
3ea783b370 Prioritize "low priority" over "direct chat" in roomList categories
There's no exact right or wrong here, but if a room was explicitly
marked as "low priority", we should honor this tag.

Fixes a slight inconsistency with the implementation in Element

Fixes #357
2021-05-21 21:09:21 +02:00
231 changed files with 98762 additions and 3295 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"
]
}
]
}

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ build
neochat.kdev4
compile_commands.json
.cache/
.vscode/
kate.project.ctags.*

12
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
# TODO enable once we can have qt6 libQuotient on the CI
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

28
.kde-ci.yml Normal file
View File

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
# SPDX-License-Identifier: BSD-2-Clause
Dependencies:
- 'on': ['@all']
'require':
'frameworks/extra-cmake-modules': '@stable'
'frameworks/kcoreaddons': '@stable'
'frameworks/kirigami': '@stable'
'frameworks/ki18n': '@stable'
'frameworks/kconfig': '@stable'
'frameworks/syntax-highlighting': '@stable'
'frameworks/kitemmodels': '@stable'
'frameworks/knotifications': '@stable'
'libraries/kquickimageeditor': '@stable'
- 'on': ['Windows', 'Linux', 'FreeBSD']
'require':
'frameworks/qqc2-desktop-style': '@stable'
'frameworks/kio': '@stable'
'frameworks/kwindowsystem': '@stable'
'frameworks/sonnet': '@stable'
'frameworks/kconfigwidgets': '@stable'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@stable'
Options:
require-passing-tests-on: [ 'Linux', 'Windows' ]

View File

@@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: NeoChat
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat-symbolic.svg android/res/drawable/neochat.png
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.svg android/res/drawable/neochat.png
Copyright: 2020 Carson Black <uhhadd@gmail.com>
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
@@ -13,3 +13,35 @@ License: CC0-1.0
Files: android/res/drawable/splash.xml
Copyright: 2020 Tobias Fella <fella@posteo.de>
License: BSD-2-Clause
Files: */qmldir .gitignore
Copyright: None
License: CC0-1.0
Files: .gitlab/issue_templates/bug.md
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: CC0-1.0
Files: res.qrc res_android.qrc res_desktop.qrc
Copyright: None
License: CC0-1.0
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
License: BSD-2-Clause
Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <fella@posteo.de>
License: BSD-2-Clause
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <fella@posteo.de>
License: BSD-2-Clause

View File

@@ -4,43 +4,47 @@
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.16)
project(NeoChat)
set(PROJECT_VERSION "22.09")
set(KF5_MIN_VERSION "5.77.0")
set(QT_MIN_VERSION "5.15.0")
set(KF5_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2")
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(KDE_COMPILERSETTINGS_LEVEL 5.84)
include(FeatureSummary)
include(ECMSetupVersion)
include(KDEInstallDirs)
include(ECMQMLModules)
include(KDEClangFormat)
include(ECMFindQmlModule)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(KDEGitCommitHooks)
include(ECMCheckOutboundLicense)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
cmake_policy(SET CMP0063 OLD)
ecm_setup_version(1.1.80
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX NEOCHAT
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
)
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt5 PROPERTIES
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
@@ -54,8 +58,8 @@ set_package_properties(KF5Kirigami2 PROPERTIES
PURPOSE "Kirigami application UI framework"
)
find_package(Qt5Keychain)
set_package_properties(Qt5Keychain PROPERTIES
find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
TYPE REQUIRED
PURPOSE "Secure storage of account secrets"
)
@@ -67,10 +71,13 @@ if(ANDROID)
PURPOSE "Encrypted communications"
)
else()
find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.sonnet 1.0)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
@@ -80,7 +87,7 @@ endif()
find_package(Quotient 0.6)
set_package_properties(Quotient PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper arround Matrix API"
DESCRIPTION "Qt wrapper around Matrix API"
URL "https://github.com/quotient-im/libQuotient/"
PURPOSE "Talk with matrix server"
)
@@ -104,14 +111,27 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments"
)
find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
find_package(QCoro REQUIRED)
endif()
qcoro_enable_coroutines()
if(NOT Quotient_VERSION_MINOR GREATER 6)
cmake_policy(SET CMP0063 OLD)
endif()
if(ANDROID)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
endif()
ki18n_install(po)
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16/apps RENAME org.kde.neochat.svg)
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16@2/apps RENAME org.kde.neochat.svg)
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16@3/apps RENAME org.kde.neochat.svg)
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/symbolic/apps)
install(FILES neochat.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
add_definitions(-DQT_NO_FOREACH)
@@ -119,5 +139,14 @@ add_subdirectory(src)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
if (NOT ANDROID)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
endif()
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
# CI installs dependency headers to _install and _build, which break the reuse check
# Fixes the test by excluding this directory
list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX [[_(install|build)/.*]])
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})

11
LICENSES/BSD-3-Clause.txt Normal file
View File

@@ -0,0 +1,11 @@
Copyright (c) <year> <owner>. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,446 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
[This is the first released version of the library GPL. It is numbered 2 because
it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public Licenses are intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users.
This license, the Library General Public License, applies to some specially
designated Free Software Foundation software, and to any other libraries whose
authors decide to use it. You can use it for your libraries, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of
the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis or for
a fee, you must give the recipients all the rights that we gave you. You must
make sure that they, too, receive or can get the source code. If you link
a program with the library, you must provide complete object files to the
recipients so that they can relink them with the library, after making changes
to the library and recompiling it. And you must show them these terms so they
know their rights.
Our method of protecting your rights has two steps: (1) copyright the library,
and (2) offer you this license which gives you legal permission to copy, distribute
and/or modify the library.
Also, for each distributor's protection, we want to make certain that everyone
understands that there is no warranty for this free library. If the library
is modified by someone else and passed on, we want its recipients to know
that what they have is not the original version, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that companies distributing free software will individually
obtain patent licenses, thus in effect transforming the program into proprietary
software. To prevent this, we have made it clear that any patent must be licensed
for everyone's free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary GNU
General Public License, which was designed for utility programs. This license,
the GNU Library General Public License, applies to certain designated libraries.
This license is quite different from the ordinary one; be sure to read it
in full, and don't assume that anything in it is the same as in the ordinary
license.
The reason we have a separate public license for some libraries is that they
blur the distinction we usually make between modifying or adding to a program
and simply using it. Linking a program with a library, without changing the
library, is in some sense simply using the library, and is analogous to running
a utility program or application program. However, in a textual and legal
sense, the linked executable is a combined work, a derivative of the original
library, and the ordinary General Public License treats it as such.
Because of this blurred distinction, using the ordinary General Public License
for libraries did not effectively promote software sharing, because most developers
did not use the libraries. We concluded that weaker conditions might promote
sharing better.
However, unrestricted linking of non-free programs would deprive the users
of those programs of all benefit from the free status of the libraries themselves.
This Library General Public License is intended to permit developers of non-free
programs to use free libraries, while preserving your freedom as a user of
such programs to change the free libraries that are incorporated in them.
(We have not seen how to achieve this as regards changes in header files,
but we have achieved it as regards changes in the actual functions of the
Library.) The hope is that this will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and modification
follow. Pay close attention to the difference between a "work based on the
library" and a "work that uses the library". The former contains code derived
from the library, while the latter only works together with the library.
Note that it is possible for a library to be covered by the ordinary General
Public License rather than by this special one.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which contains a
notice placed by the copyright holder or other authorized party saying it
may be distributed under the terms of this Library General Public License
(also called "this License"). Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data prepared
so as to be conveniently linked with application programs (which use some
of those functions and data) to form executables.
The "Library", below, refers to any such software library or work which has
been distributed under these terms. A "work based on the Library" means either
the Library or any derivative work under copyright law: that is to say, a
work containing the Library or a portion of it, either verbatim or with modifications
and/or translated straightforwardly into another language. (Hereinafter, translation
is included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for making modifications
to it. For a library, complete source code means all the source code for all
modules it contains, plus any associated interface definition files, plus
the scripts used to control compilation and installation of the library.
Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running a program
using the Library is not restricted, and output from such a program is covered
only if its contents constitute a work based on the Library (independent of
the use of the Library in a tool for writing it). Whether that is true depends
on what the Library does and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's complete source
code as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and disclaimer
of warranty; keep intact all the notices that refer to this License and to
the absence of any warranty; and distribute a copy of this License along with
the Library.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Library or any portion of it,
thus forming a work based on the Library, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all
of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices stating that
you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no charge to all
third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a table of
data to be supplied by an application program that uses the facility, other
than as an argument passed when the facility is invoked, then you must make
a good faith effort to ensure that, in the event an application does not supply
such function or table, the facility still operates, and performs whatever
part of its purpose remains meaningful.
(For example, a function in a library to compute square roots has a purpose
that is entirely well-defined independent of the application. Therefore, Subsection
2d requires that any application-supplied function or table used by this function
must be optional: if the application does not supply it, the square root function
must still compute square roots.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Library, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as
separate works. But when you distribute the same sections as part of a whole
which is a work based on the Library, the distribution of the whole must be
on the terms of this License, whose permissions for other licensees extend
to the entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works based
on the Library.
In addition, mere aggregation of another work not based on the Library with
the Library (or with a work based on the Library) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.
3. You may opt to apply the terms of the ordinary GNU General Public License
instead of this License to a given copy of the Library. To do this, you must
alter all the notices that refer to this License, so that they refer to the
ordinary GNU General Public License, version 2, instead of to this License.
(If a newer version than version 2 of the ordinary GNU General Public License
has appeared, then you can specify that version instead if you wish.) Do not
make any other change in these notices.
Once this change is made in a given copy, it is irreversible for that copy,
so the ordinary GNU General Public License applies to all subsequent copies
and derivative works made from that copy.
This option is useful when you wish to copy part of the code of the Library
into a program that is not a library.
4. You may copy and distribute the Library (or a portion or derivative of
it, under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you accompany it with the complete corresponding
machine-readable source code, which must be distributed under the terms of
Sections 1 and 2 above on a medium customarily used for software interchange.
If distribution of object code is made by offering access to copy from a designated
place, then offering equivalent access to copy the source code from the same
place satisfies the requirement to distribute the source code, even though
third parties are not compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the Library, but
is designed to work with the Library by being compiled or linked with it,
is called a "work that uses the Library". Such a work, in isolation, is not
a derivative work of the Library, and therefore falls outside the scope of
this License.
However, linking a "work that uses the Library" with the Library creates an
executable that is a derivative of the Library (because it contains portions
of the Library), rather than a "work that uses the library". The executable
is therefore covered by this License. Section 6 states terms for distribution
of such executables.
When a "work that uses the Library" uses material from a header file that
is part of the Library, the object code for the work may be a derivative work
of the Library even though the source code is not. Whether this is true is
especially significant if the work can be linked without the Library, or if
the work is itself a library. The threshold for this to be true is not precisely
defined by law.
If such an object file uses only numerical parameters, data structure layouts
and accessors, and small macros and small inline functions (ten lines or less
in length), then the use of the object file is unrestricted, regardless of
whether it is legally a derivative work. (Executables containing this object
code plus portions of the Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may distribute
the object code for the work under the terms of Section 6. Any executables
containing that work also fall under Section 6, whether or not they are linked
directly with the Library itself.
6. As an exception to the Sections above, you may also compile or link a "work
that uses the Library" with the Library to produce a work containing portions
of the Library, and distribute that work under terms of your choice, provided
that the terms permit modification of the work for the customer's own use
and reverse engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the Library
is used in it and that the Library and its use are covered by this License.
You must supply a copy of this License. If the work during execution displays
copyright notices, you must include the copyright notice for the Library among
them, as well as a reference directing the user to the copy of this License.
Also, you must do one of these things:
a) Accompany the work with the complete corresponding machine-readable source
code for the Library including whatever changes were used in the work (which
must be distributed under Sections 1 and 2 above); and, if the work is an
executable linked with the Library, with the complete machine-readable "work
that uses the Library", as object code and/or source code, so that the user
can modify the Library and then relink to produce a modified executable containing
the modified Library. (It is understood that the user who changes the contents
of definitions files in the Library will not necessarily be able to recompile
the application to use the modified definitions.)
b) Accompany the work with a written offer, valid for at least three years,
to give the same user the materials specified in Subsection 6a, above, for
a charge no more than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy from a designated
place, offer equivalent access to copy the above specified materials from
the same place.
d) Verify that the user has already received a copy of these materials or
that you have already sent this user a copy.
For an executable, the required form of the "work that uses the Library" must
include any data and utility programs needed for reproducing the executable
from it. However, as a special exception, the source code distributed need
not include anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the operating
system on which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license restrictions of
other proprietary libraries that do not normally accompany the operating system.
Such a contradiction means you cannot use both them and the Library together
in an executable that you distribute.
7. You may place library facilities that are a work based on the Library side-by-side
in a single library together with other library facilities not covered by
this License, and distribute such a combined library, provided that the separate
distribution of the work based on the Library and of the other library facilities
is otherwise permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work based on the
Library, uncombined with any other library facilities. This must be distributed
under the terms of the Sections above.
b) Give prominent notice with the combined library of the fact that part of
it is a work based on the Library, and explaining where to find the accompanying
uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute the Library
except as expressly provided under this License. Any attempt otherwise to
copy, modify, sublicense, link with, or distribute the Library is void, and
will automatically terminate your rights under this License. However, parties
who have received copies, or rights, from you under this License will not
have their licenses terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Library or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the Library
(or any work based on the Library), you indicate your acceptance of this License
to do so, and all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the Library),
the recipient automatically receives a license from the original licensor
to copy, distribute, link with or modify the Library subject to these terms
and conditions. You may not impose any further restrictions on the recipients'
exercise of the rights granted herein. You are not responsible for enforcing
compliance by third parties to this License.
11. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposed
on you (whether by court order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations, then as
a consequence you may not distribute the Library at all. For example, if a
patent license would not permit royalty-free redistribution of the Library
by all those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain entirely
from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system which is implemented by public license practices.
Many people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose
that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Library under this License may add an explicit geographical
distribution limitation excluding those countries, so that distribution is
permitted only in or among countries not thus excluded. In such case, this
License incorporates the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new versions of
the Library General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to address
new problems or concerns.
Each version is given a distinguishing version number. If the Library specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Library does not specify a license version number, you may choose any version
ever published by the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free programs
whose distribution conditions are incompatible with these, write to the author
to ask for permission. For software which is copyrighted by the Free Software
Foundation, write to the Free Software Foundation; we sometimes make exceptions
for this. Our decision will be guided by the two goals of preserving the free
status of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest possible
use to the public, we recommend making it free software that everyone can
redistribute and change. You can do so by permitting redistribution under
these terms (or, alternatively, under the terms of the ordinary General Public
License).
To apply these terms, attach the following notices to the library. It is safest
to attach them to the start of each source file to most effectively convey
the exclusion of warranty; and each file should have at least the "copyright"
line and a pointer to where the full notice is found.
one line to give the library's name and an idea of what it does.
Copyright (C) year name of author
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Library General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in
the library `Frob' (a library for tweaking knobs) written
by James Random Hacker.
signature of Ty Coon, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

175
LICENSES/LGPL-2.1-only.txt Normal file
View File

@@ -0,0 +1,175 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]
Preamble
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.
To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.
In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.
If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.
11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).
To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
one line to give the library's name and an idea of what it does.
Copyright (C) year name of author
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in
the library `Frob' (a library for tweaking knobs) written
by James Random Hacker.
signature of Ty Coon, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -0,0 +1,12 @@
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of
the license or (at your option) at any later version that is
accepted by the membership of KDE e.V. (or its successor
approved by the membership of KDE e.V.), which shall act as a
proxy as defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

19
LICENSES/MIT.txt Executable file
View File

@@ -0,0 +1,19 @@
MIT License Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,2 +1,4 @@
#! /usr/bin/env bash
# SPDX-FileCopyrightText: None
# SPDX-License-Identifier: CC0-1.0
$XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot

View File

@@ -1,3 +1,8 @@
<!--
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
SPDX-License-Identifier: CC0-1.0
-->
# NeoChat
NeoChat is a client for Matrix, the decentralized communication protocol for instant
@@ -9,7 +14,7 @@ KConfig and KI18n.
## Get it
A stable release [is available](https://apps.kde.org/en/neochat) for download for Linux distributions.
A stable release [is available](https://apps.kde.org/neochat) for download for Linux distributions.
Along with the stable release, a Flatpak version is available for the nightly
@@ -22,11 +27,11 @@ flatpak install kdeapps org.kde.neochat
```
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/Neochat_android/).
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
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
@@ -50,7 +55,7 @@ We welcome contributions in this direction.
## Contact
You can reach the maintainers at #neochat:kde.org, if you are already on Matrix.
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
## Acknowledgement

View File

@@ -6,8 +6,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.kde.neochat"
android:versionName="0.0.1"
android:versionCode="1604412458"
android:versionName="${versionName}"
android:versionCode="${versionCode}"
android:installLocation="auto">
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
@@ -21,7 +21,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="neochat"/>
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
@@ -53,7 +53,6 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

96
android/build.gradle Normal file
View File

@@ -0,0 +1,96 @@
/*
SPDX-FileCopyrightText: 2018-2020 Volker Krause <vkrause@kde.org>
SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2020 Gabriel Souza Franco <gabrielfrancosouza@gmail.com>
SPDX-License-Identifier: BSD-3-Clause
*/
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.4'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
apply from: '../version.gradle'
def timestamp = (int)(new Date().getTime()/1000)
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
defaultConfig {
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
}
packagingOptions {
exclude 'lib/*/*RemoteObjects*'
exclude 'lib/*/*StateMachine*'
exclude 'lib/*/*_imageformats_qico_*'
exclude 'lib/*/*_imageformats_qicns_*'
exclude 'lib/*/*_imageformats_qtga_*'
exclude 'lib/*/*_imageformats_qtiff_*'
exclude 'lib/*/*_qmltooling_*'
}
aaptOptions {
// different syntax than above
// see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/pie-release/tools/aapt2/util/Files.h#90
ignoreAssetsPattern '!<dir>ECM:!<dir>aclocal:!<dir>doc:!<dir>gtk-doc:!<dir>iso-codes:!<dir>man:!<dir>mime:!<dir>pkgconfig:!<dir>qlogging-categories5:!<file>iso_15924.mo:!<file>iso_3166-2.mo:!<file>iso_3166-3.mo:!<file>iso_4217.mo:!<file>iso_639-2.mo:!<file>iso_639-3.mo:!<file>iso_639-5.mo:!<file>kcodecs5_qt.qm:!<file>kde5_xml_mimetypes.qm'
}
}

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: BSD-3-Clause
ext {
projectVersionFull = "@NEOCHAT_VERSION@"
}

View File

@@ -7,14 +7,16 @@
# first try to find cmark-config.cmake
# path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/'
find_package(cmark CONFIG)
find_package(cmark CONFIG QUIET)
if(cmark_FOUND AND TARGET cmark::cmark)
# found it!
return()
endif()
include(FindPkgConfig)
pkg_check_modules(PC_CMARK QUIET cmark)
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(PC_CMARK QUIET cmark)
endif()
if(NOT CMARK_INCLUDE_DIR)
find_path(CMARK_INCLUDE_DIR

View File

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

View File

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

View File

@@ -5,10 +5,11 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import QtQuick.Templates 2.15 as T
import Qt.labs.platform 1.1 as Platform
import QtQuick.Window 2.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.18 as Kirigami
import org.kde.neochat 1.0
ToolBar {
@@ -62,10 +63,14 @@ ToolBar {
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
+ inputField.topPadding + inputField.bottomPadding
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
FontMetrics {
id: fontMetrics
font: inputField.font
}
TextArea {
id: inputField
focus: true
@@ -76,7 +81,12 @@ ToolBar {
* background colors being very different from the QPalette::Base color.
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
*/
background: null
background: MouseArea {
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
z: 1
}
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
topPadding: 0
@@ -87,12 +97,22 @@ ToolBar {
//property int lineHeight: contentHeight / lineCount
text: inputFieldText
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message")
verticalAlignment: TextEdit.AlignVCenter
horizontalAlignment: TextEdit.AlignLeft
wrapMode: Text.Wrap
readOnly: currentRoom.usesEncryption
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Kirigami.SpellChecking.enabled: true
color: Kirigami.Theme.textColor
selectionColor: Kirigami.Theme.highlightColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
hoverEnabled: !Kirigami.Settings.tabletMode
selectByMouse: !Kirigami.Settings.tabletMode
ChatDocumentHandler {
id: documentHandler
@@ -103,36 +123,21 @@ ToolBar {
room: currentRoom ?? null
}
Timer {
id: timeoutTimer
repeat: false
interval: 2000
onTriggered: {
repeatTimer.stop()
currentRoom.sendTypingNotification(false)
}
}
Timer {
id: repeatTimer
repeat: true
interval: 5000
triggeredOnStart: true
onTriggered: currentRoom.sendTypingNotification(true)
}
function sendMessage(event) {
if (isCompleting) {
if (isCompleting && completionMenu.count > 0) {
chatBar.complete();
isCompleting = false;
return;
}
if (event.modifiers & Qt.ShiftModifier) {
} else if (event.modifiers & Qt.ShiftModifier) {
inputField.insert(cursorPosition, "\n")
} else {
currentRoom.sendTypingNotification(false)
chatBar.postMessage()
}
isCompleting = false;
}
Keys.onReturnPressed: { sendMessage(event) }
@@ -143,11 +148,7 @@ ToolBar {
}
Keys.onPressed: {
if (event.key === Qt.Key_PageDown) {
switchRoomDown();
} else if (event.key === Qt.Key_PageUp) {
switchRoomUp();
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
chatBar.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
replyPreviousUserMessage();
@@ -165,12 +166,37 @@ ToolBar {
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
return
}
let decrementedIndex = completionMenu.currentIndex - 1
// Wrap around to the last item
if (decrementedIndex < 0) {
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
if (!autoAppeared) {
let decrementedIndex = completionMenu.currentIndex - 1
// Wrap around to the last item
if (decrementedIndex < 0) {
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
}
completionMenu.currentIndex = decrementedIndex
} else {
autoAppeared = false;
}
completionMenu.currentIndex = decrementedIndex
chatBar.complete();
}
// yes, decrement goes up and increment goes down visually.
Keys.onUpPressed: (event) => {
if (chatBar.isCompleting) {
event.accepted = true
completionMenu.listView.decrementCurrentIndex()
autoAppeared = true;
}
event.accepted = false
}
Keys.onDownPressed: (event) => {
if (chatBar.isCompleting) {
event.accepted = true
completionMenu.listView.incrementCurrentIndex()
autoAppeared = true;
}
event.accepted = false
}
Keys.onTabPressed: {
@@ -187,7 +213,7 @@ ToolBar {
// ignore first time tab was clicked so that user can select
// first emoji/user
if (autoAppeared === false) {
if (!autoAppeared) {
let incrementedIndex = completionMenu.currentIndex + 1;
// Wrap around to the first item
if (incrementedIndex > completionMenu.count - 1) {
@@ -202,16 +228,26 @@ ToolBar {
}
onTextChanged: {
timeoutTimer.restart()
if (!repeatTimer.running && Config.typingNotifications) {
currentRoom.sendTypingNotification(true)
}
repeatTimer.start()
currentRoom.cachedInput = text
autoAppeared = false;
const completionInfo = documentHandler.getAutocompletionInfo();
const completionInfo = documentHandler.getAutocompletionInfo(isCompleting);
if (completionInfo.type === ChatDocumentHandler.Ignore) {
if (completionInfo.keyword) {
// custom emojis
const idx = completionMenu.currentIndex;
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
completionMenu.currentIndex = idx;
}
return;
}
if (completionInfo.type === ChatDocumentHandler.None) {
isCompleting = false;
return;
@@ -219,33 +255,36 @@ ToolBar {
completionMenu.completionType = completionInfo.type
if (completionInfo.type === ChatDocumentHandler.User) {
completionMenu.model = currentRoom.getUsers(completionInfo.keyword);
completionMenu.model = currentRoom.getUsers(completionInfo.keyword, 10);
} else if (completionInfo.type === ChatDocumentHandler.Command) {
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
} else {
completionMenu.model = EmojiModel.filterModel(completionInfo.keyword);
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
}
if (completionMenu.model.length === 0) {
isCompleting = false;
return;
}
isCompleting = true
autoAppeared = true;
completionMenu.endPosition = cursorPosition
if (!isCompleting) {
isCompleting = true
autoAppeared = true;
completionMenu.endPosition = cursorPosition
}
}
}
}
Item {
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
visible: !chatBoxHelper.isReplying && (!chatBoxHelper.hasAttachment || uploadingBusySpinner.running)
implicitWidth: uploadButton.implicitWidth
implicitHeight: uploadButton.implicitHeight
ToolButton {
id: uploadButton
anchors.fill: parent
// Matrix does not allow sending attachments in replies
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
visible: !chatBoxHelper.isReplying && !chatBoxHelper.hasAttachment && !uploadingBusySpinner.running
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
display: AbstractButton.IconOnly
@@ -257,7 +296,7 @@ ToolBar {
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect((path) => {
if (!path) { return }
ChatBoxHelper.attachmentPath = path;
chatBoxHelper.attachmentPath = path;
})
fileDialog.open()
}
@@ -331,21 +370,25 @@ ToolBar {
}
}
property CustomEmojiModel customEmojiModel: CustomEmojiModel {
connection: Controller.activeConnection
}
function pasteImage() {
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
if (!Clipboard.saveImage(localPath)) {
return;
}
ChatBoxHelper.attachmentPath = localPath;
chatBoxHelper.attachmentPath = localPath;
}
function postMessage() {
checkForFancyEffectsReason();
if (ChatBoxHelper.hasAttachment) {
if (chatBoxHelper.hasAttachment) {
// send attachment but don't reset the text
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {});
actionsHandler.postMessage("", chatBoxHelper.attachmentPath,
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, {}, this.customEmojiModel);
currentRoom.markAllMessagesAsRead();
messageSent();
return;
@@ -357,8 +400,8 @@ ToolBar {
actionsHandler.postEdit(inputField.text);
} else {
// send normal message
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted);
actionsHandler.postMessage(inputField.text.trim(), chatBoxHelper.attachmentPath,
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
}
currentRoom.markAllMessagesAsRead();
inputField.clear();
@@ -370,8 +413,10 @@ ToolBar {
function complete() {
documentHandler.replaceAutoComplete(completionMenu.currentDisplayText);
if (completionMenu.completionType === "username") {
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentUserId;
if (completionMenu.completionType === ChatDocumentHandler.User
&& completionMenu.currentDisplayText.length > 0
&& completionMenu.currentItem.userId.length > 0) {
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentItem.userId;
}
}
}

View File

@@ -61,6 +61,10 @@ Item {
QQC2.Pane {
id: connectionPane
padding: fontMetrics.lineSpacing * 0.25
FontMetrics {
id: fontMetrics
font: networkLabel.font
}
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
@@ -69,6 +73,7 @@ Item {
visible: !Controller.isOnline
width: parent.width
QQC2.Label {
id: networkLabel
text: i18n("NeoChat is offline. Please check your network connection.")
}
anchors.bottom: emojiPickerLoaderSeparator.top
@@ -90,9 +95,16 @@ Item {
width: parent.width
height: visible ? implicitHeight : 0
anchors.bottom: replySeparator.top
sourceComponent: EmojiPicker{
textArea: chatBar.textField
onChosen: addText(emoji)
sourceComponent: QQC2.Pane {
topPadding: 0
bottomPadding: 0
rightPadding: 0
leftPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: EmojiPicker {
textArea: chatBar.textField
onChosen: addText(emoji)
}
}
Behavior on height {
NumberAnimation {
@@ -115,8 +127,8 @@ Item {
ReplyPane {
id: replyPane
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
user: ChatBoxHelper.replyUser
visible: chatBoxHelper.isReplying || chatBoxHelper.isEditing
user: chatBoxHelper.replyUser
width: parent.width
height: visible ? implicitHeight : 0
anchors.bottom: attachmentSeparator.top
@@ -142,7 +154,7 @@ Item {
AttachmentPane {
id: attachmentPane
visible: ChatBoxHelper.hasAttachment
visible: chatBoxHelper.hasAttachment
width: parent.width
height: visible ? implicitHeight : 0
anchors.bottom: chatBarSeparator.top
@@ -228,7 +240,15 @@ Item {
}
Connections {
target: ChatBoxHelper
target: RoomManager
function onCurrentRoomChanged() {
chatBar.userAutocompleted = {};
}
}
Connections {
target: chatBoxHelper
function onShouldClearText() {
root.inputFieldText = "";
@@ -257,7 +277,7 @@ Item {
}
function closeAll() {
ChatBoxHelper.clear();
chatBoxHelper.clear();
chatBar.emojiPaneOpened = false;
}
}

View File

@@ -10,20 +10,21 @@ import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
Popup {
id: control
// Expose internal ListView properties.
property alias model: completionListView.model
property alias listView: completionListView
property alias currentIndex: completionListView.currentIndex
property alias currentItem: completionListView.currentItem
property alias count: completionListView.count
property alias delegate: completionListView.delegate
// Autocomplee text
property string currentDisplayText: currentItem && currentItem.displayName ? currentItem.displayName : ""
property string currentUserId: currentItem && currentItem.id ? currentItem.id : ""
property string currentDisplayText: currentItem && (currentItem.displayName ?? "")
property int completionType: ChatDocumentHandler.Emoji
property int beginPosition: 0
@@ -43,9 +44,10 @@ Popup {
completionListView.currentIndex = 0;
}
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 5)
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
contentItem: ScrollView {
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: completionListView
@@ -78,19 +80,16 @@ Popup {
id: usernameItem
width: ListView.view.width ?? implicitWidth
property string displayName: modelData.displayName
property string userId: modelData.id
leading: Kirigami.Avatar {
implicitHeight: Kirigami.Units.gridUnit
implicitWidth: implicitHeight
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
}
labelItem.textFormat: Text.PlainText
text: modelData.displayName
onClicked: completeTriggered();
Component.onCompleted: {
completionMenu.currentUserId = Qt.binding(() => {
return modelData.id ?? "";
});
}
}
}
@@ -99,23 +98,40 @@ Popup {
Kirigami.BasicListItem {
id: emojiItem
width: ListView.view.width ?? implicitWidth
property string displayName: modelData.unicode
text: modelData.unicode + " " + modelData.shortname
property string displayName: modelData.isCustom ? modelData.shortname : modelData.unicode
text: modelData.shortname
height: Kirigami.Units.gridUnit * 2
leading: Label {
id: unicodeLabel
Layout.preferredHeight: Kirigami.Units.gridUnit
Layout.preferredWidth: textMetrics.tightBoundingRect.width
font.pointSize: Kirigami.Units.gridUnit * 0.75
text: modelData.unicode
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
TextMetrics {
id: textMetrics
text: modelData.unicode
font: unicodeLabel.font
leading: Image {
source: modelData.isCustom ? modelData.unicode : ""
width: height
sourceSize.width: width
sourceSize.height: height
Rectangle {
anchors.fill: parent
visible: parent.status === Image.Loading
radius: height/2
gradient: ShimmerGradient { }
}
Label {
id: unicodeLabel
visible: !modelData.isCustom
font.family: 'emoji'
font.pixelSize: height - 2
text: modelData.unicode
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.fill: parent
}
}
onClicked: completeTriggered();
}
}

View File

@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
Loader {
id: root
readonly property bool isEdit: ChatBoxHelper.isEditing
readonly property bool isEdit: chatBoxHelper.isEditing
property var user: null
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
@@ -57,7 +57,7 @@ Loader {
elide: Text.ElideRight
text: {
let heading = "<b>%1</b>"
let userName = user ? "<font color=\""+ user.color +"\">" + user.displayName + "</font>" : ""
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
if (isEdit) {
heading = heading.arg(i18n("Editing message:")) + "<br/>"
} else {
@@ -71,6 +71,10 @@ Loader {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: textArea
leftPadding: 0
@@ -79,7 +83,7 @@ Loader {
bottomPadding: 0
text: {
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
const content = chatBoxHelper.isReplying ? chatBoxHelper.replyEventContent : chatBoxHelper.editContent;
return stylesheet + content;
}
selectByMouse: true
@@ -87,7 +91,7 @@ Loader {
readOnly: true
wrapMode: Label.Wrap
textFormat: TextEdit.RichText
background: null
background: Item {}
HoverHandler {
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
}
@@ -102,7 +106,7 @@ Loader {
text: i18n("Cancel")
display: AbstractButton.IconOnly
onClicked: {
ChatBoxHelper.clearEditReply();
chatBoxHelper.clear();
root.replyCancelled();
}
ToolTip.text: text

View File

@@ -1,66 +0,0 @@
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0
Loader {
id: root
property var typingNotification: null
active: visible
sourceComponent: Pane {
id: typingPane
padding: fontMetrics.lineSpacing * 0.25
spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
spacing: 0
FontMetrics {
id: fontMetrics
font: typingLabel.font
}
Label {
id: typingLabel
textFormat: TextEdit.RichText
wrapMode: Label.Wrap
text: typingNotification
}
BusyIndicator {
running: root.active
Layout.alignment: Qt.AlignRight
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
}
}
background: Item {
Rectangle {
height: 1
property color borderColor: Kirigami.Theme.textColor
color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.1)
anchors {
left: typingIndicatorBackground.left
right: typingIndicatorBackground.right
bottom: typingIndicatorBackground.top
}
}
Rectangle {
anchors.fill: parent
id: typingIndicatorBackground
color: Kirigami.Theme.backgroundColor
}
}
}
}

View File

@@ -5,4 +5,3 @@ ReplyPane 1.0 ReplyPane.qml
AttachmentPane 1.0 AttachmentPane.qml
CompletionMenu 1.0 CompletionMenu.qml
EmojiPickerPane 1.0 EmojiPickerPane.qml
TypingPane 1.0 TypingPane.qml

View File

@@ -10,10 +10,16 @@ import org.kde.neochat 1.0 as NeoChat
import NeoChat.Component 1.0
ColumnLayout {
id: _picker
property string emojiCategory: "history"
property var textArea
readonly property var emojiModel: NeoChat.EmojiModel
property NeoChat.CustomEmojiModel customModel: NeoChat.CustomEmojiModel {
connection: NeoChat.Controller.activeConnection
}
signal chosen(string emoji)
spacing: 0
@@ -29,6 +35,7 @@ ColumnLayout {
orientation: ListView.Horizontal
model: ListModel {
ListElement { label: "custom"; category: "custom" }
ListElement { label: "⌛️"; category: "history" }
ListElement { label: "😏"; category: "people" }
ListElement { label: "🌲"; category: "nature" }
@@ -41,16 +48,23 @@ ColumnLayout {
}
delegate: ItemDelegate {
width: Kirigami.Units.gridUnit * 2
id: del
required property string label
required property string category
width: contentItem.Layout.preferredWidth
height: Kirigami.Units.gridUnit * 2
contentItem: Kirigami.Heading {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
level: 1
level: del.label === "custom" ? 4 : 1
font.family: 'emoji'
text: label
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
font.family: del.label === "custom" ? Kirigami.Theme.defaultFont.family : 'emoji'
text: del.label === "custom" ? i18n("Custom") : del.label
}
Rectangle {
@@ -87,6 +101,8 @@ ColumnLayout {
model: {
switch (emojiCategory) {
case "custom":
return _picker.customModel
case "history":
return emojiModel.history
case "people":
@@ -118,11 +134,32 @@ ColumnLayout {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: 'emoji'
text: modelData.unicode
text: modelData.isCustom ? "" : modelData.unicode
}
Image {
visible: modelData.isCustom
source: visible ? modelData.unicode : ""
anchors.fill: parent
anchors.margins: 2
sourceSize.width: width
sourceSize.height: height
Rectangle {
anchors.fill: parent
visible: parent.status === Image.Loading
radius: height/2
gradient: ShimmerGradient { }
}
}
onClicked: {
chosen(modelData.unicode)
if (modelData.isCustom) {
chosen(modelData.shortname)
} else {
chosen(modelData.unicode)
}
emojiModel.emojiUsed(modelData)
}
}

View File

@@ -8,7 +8,7 @@ import QtQuick.Particles 2.15
import org.kde.kirigami 2.15 as Kirigami
Rectangle {
Item {
id: item
property bool enabled: false
property int effectInterval: Kirigami.Units.veryLongDuration*10;
@@ -27,11 +27,6 @@ Rectangle {
fireworksTimer.start()
}
// backgroundColor
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
// Confetti
Timer {

View File

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

View File

@@ -15,7 +15,7 @@ LoginStep {
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
property bool loading: false
title: i18n("@title", "Select a Homeserver")
title: i18nc("@title", "Select a Homeserver")
action: Kirigami.Action {
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput

View File

@@ -10,11 +10,19 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
QQC2.BusyIndicator {
Kirigami.PlaceholderMessage {
property var showContinueButton: false
property var showBackButton: false
property string title: i18n("Loading")
property string title: i18n("Loading")
anchors.centerIn: parent
QQC2.Label {
text: i18n("Please wait. This might take a little while.")
}
QQC2.BusyIndicator {
Layout.alignment: Qt.AlignHCenter
running: false
}
}

View File

@@ -44,13 +44,13 @@ LoginStep {
}
validator: RegularExpressionValidator {
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*\.[a-zA-Z]+(:[0-9]+)?$/
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(\:[0-9]+)?$/
}
}
}
action: Kirigami.Action {
text: LoginHelper.testing && matrixIdField.acceptableInput ? i18n("Loading") : i18nc("@action:button", "Continue")
text: LoginHelper.testing && matrixIdField.acceptableInput ? i18n("Loading") : i18nc("@action:button", "Continue")
onTriggered: {
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
processed("qrc:/imports/NeoChat/Component/Login/LoginMethod.qml");

View File

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

View File

@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Layouts 1.10
import QtQuick.Controls 2.12 as QQC2
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
QQC2.Popup {
id: _popup
Shortcut {
sequence: "Ctrl+K"
enabled: !Kirigami.Settings.hasPlatformMenuBar
onActivated: _popup.open()
}
onVisibleChanged: {
if (!visible) {
return
}
quickSearch.forceActiveFocus()
quickSearch.text = ""
}
anchors.centerIn: QQC2.Overlay.overlay
background: Kirigami.Card {}
height: 2 * Math.round(implicitHeight / 2)
padding: Kirigami.Units.largeSpacing * 2
contentItem: ColumnLayout {
spacing: Kirigami.Units.largeSpacing * 2
Kirigami.SearchField {
id: quickSearch
// TODO: get this broken property removed/disabled by default in Kirigami,
// we used to be able to expect that the text field wouldn't attempt to
// perform a mini-DDOS attack using signals.
autoAccept: false
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
Keys.onLeftPressed: cView.decrementCurrentIndex()
Keys.onRightPressed: cView.incrementCurrentIndex()
onAccepted: {
const item = cView.itemAtIndex(cView.currentIndex)
RoomManager.enterRoom(item.currentRoom)
_popup.close()
}
}
ListView {
id: cView
orientation: Qt.Horizontal
spacing: Kirigami.Units.largeSpacing
model: SortFilterRoomListModel {
id: sortFilterRoomListModel
sourceModel: RoomListModel {
id: roomListModel
connection: Controller.activeConnection
}
filterText: quickSearch.text
roomSortOrder: SortFilterRoomListModel.LastActivity
}
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.fillWidth: true
delegate: Kirigami.Avatar {
id: del
implicitHeight: Kirigami.Units.gridUnit * 3
implicitWidth: Kirigami.Units.gridUnit * 3
required property string avatar
required property var currentRoom
required property int index
// When an item is hovered set the currentIndex of listview to it so that it is highlighted
onHoveredChanged: {
if (!hovered) {
return
}
cView.currentIndex = index
}
actions.main: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
_popup.close()
}
}
source: avatar != "" ? "image://mxc/" + avatar : ""
}
}
}
modal: true
focus: true
}

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
// Not to be confused with the Shimmer project.
// I like their gradiented GTK themes though.
import QtQuick 2.15
import org.kde.kirigami 2.15 as Kirigami
Gradient {
id: gradient
orientation: Gradient.Horizontal
property color color: Kirigami.Theme.textColor
property color translucent: Qt.rgba(color.r, color.g, color.b, 0.2)
property color bright: Qt.rgba(color.r, color.g, color.b, 0.3)
property real pos: 0.5
property real offset: 0.6
property SequentialAnimation ani: SequentialAnimation {
running: true
loops: Animation.Infinite
NumberAnimation {
from: -2.0
to: 2.0
duration: 700
target: gradient
properties: "pos"
}
PauseAnimation {
duration: 300
}
}
GradientStop { position: gradient.pos-gradient.offset; color: gradient.translucent }
GradientStop { position: gradient.pos; color: gradient.bright }
GradientStop { position: gradient.pos+gradient.offset; color: gradient.translucent }
}

View File

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

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
TimelineContainer {
id: encryptedDelegate
innerObject: TextEdit {
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
color: Kirigami.Theme.disabledTextColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.WordWrap
textFormat: Text.RichText
Layout.maximumWidth: encryptedDelegate.contentMaxWidth
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
}
}

View File

@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
DelegateChooser {
role: "eventType"
DelegateChoice {
roleValue: "state"
delegate: StateDelegate {}
}
DelegateChoice {
roleValue: "emote"
delegate: MessageDelegate {
isEmote: true
}
}
DelegateChoice {
roleValue: "message"
delegate: MessageDelegate {}
}
DelegateChoice {
roleValue: "notice"
delegate: MessageDelegate {}
}
DelegateChoice {
roleValue: "image"
delegate: ImageDelegate {}
}
DelegateChoice {
roleValue: "sticker"
delegate: ImageDelegate {}
}
DelegateChoice {
roleValue: "audio"
delegate: AudioDelegate {}
}
DelegateChoice {
roleValue: "video"
delegate: VideoDelegate {}
}
DelegateChoice {
roleValue: "file"
delegate: FileDelegate {}
}
DelegateChoice {
roleValue: "encrypted"
delegate: EncryptedDelegate {}
}
DelegateChoice {
roleValue: "readMarker"
delegate: ReadMarkerDelegate {}
}
DelegateChoice {
roleValue: "other"
delegate: Item {}
}
}

View File

@@ -2,9 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
@@ -14,70 +13,125 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
RowLayout {
id: root
property bool openOnFinished: false
TimelineContainer {
id: fileDelegate
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, fileDelegate)
readonly property bool downloaded: progressInfo && progressInfo.completed
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
onDownloadedChanged: if (downloaded && openOnFinished) {
openSavedFile();
}
ToolButton {
icon.name: progressInfo.completed ? "document-open" : "document-save"
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
level: 4
text: model.display
wrapMode: Label.Wrap
}
Label {
Layout.fillWidth: true
text: !progressInfo.completed && progressInfo.active ? (Controller.formatByteSize(progressInfo.progress) + "/" + Controller.formatByteSize(progressInfo.total)) : Controller.formatByteSize(content.info ? content.info.size : 0)
color: Kirigami.Theme.disabledTextColor
wrapMode: Label.Wrap
}
}
Component {
id: fileDialog
FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
currentRoom.downloadFile(eventId, file)
}
}
}
function saveFileAs() {
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
const dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
dialog.open()
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
}
function downloadAndOpen() {
if (downloaded) {
openSavedFile();
} else {
openOnFinished = true;
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/"
+ eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId));
}
function openSavedFile() {
if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (UrlHelper.openUrl(progressInfo.localDir)) return;
}
function openSavedFile() {
if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return;
innerObject: RowLayout {
Layout.fillWidth: true
Layout.maximumWidth: fileDelegate.contentMaxWidth
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
states: [
State {
name: "downloaded"
when: progressInfo.completed
PropertyChanges {
target: downloadButton
icon.name: "document-open"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: openSavedFile()
}
},
State {
name: "downloading"
when: progressInfo.active
PropertyChanges {
target: sizeLabel
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
}
PropertyChanges {
target: downloadButton
icon.name: "media-playback-stop"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: currentRoom.cancelFileTransfer(eventId)
}
},
State {
name: "raw"
when: true
PropertyChanges {
target: downloadButton
onClicked: fileDelegate.saveFileAs()
}
}
]
Kirigami.Icon {
id: ikon
source: model.fileMimetypeIcon
fallback: "unknown"
}
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
spacing: 0
QQC2.Label {
text: model.display
wrapMode: Text.Wrap
Layout.fillWidth: true
}
QQC2.Label {
id: sizeLabel
text: Controller.formatByteSize(content.info ? content.info.size : 0)
opacity: 0.7
Layout.fillWidth: true
}
}
QQC2.Button {
id: downloadButton
icon.name: "download"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
QQC2.ToolTip.visible: hovered
}
Component {
id: fileDialog
FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
currentRoom.downloadFile(eventId, file)
}
}
}
}
}

View File

@@ -4,16 +4,23 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
Image {
id: img
TimelineContainer {
id: imageDelegate
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
onOpenContextMenu: openFileContext(model, imageDelegate)
property var content: model.content
readonly property bool isAnimated: contentType === "image/gif"
@@ -25,69 +32,93 @@ Image {
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
readonly property var info: content.info
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
property bool readonly: false
source: "image://mxc/" + mediaId
innerObject: Image {
id: img
fillMode: Image.PreserveAspectFit
Layout.maximumWidth: imageDelegate.contentMaxWidth
Layout.maximumHeight: imageDelegate.contentMaxWidth / sourceSize.width * sourceSize.height
Layout.preferredWidth: imageDelegate.info.w > 0 ? imageDelegate.info.w : sourceSize.width
Layout.preferredHeight: imageDelegate.info.h > 0 ? imageDelegate.info.h : sourceSize.height
source: model.mediaUrl
ToolTip.text: display
ToolTip.visible: hoverHandler.hovered
HoverHandler {
id: hoverHandler
enabled: img.readonly
}
Rectangle {
anchors.fill: parent
visible: progressInfo.active && !downloaded
color: "#BB000000"
ProgressBar {
anchors.centerIn: parent
width: parent.width * 0.8
from: 0
to: progressInfo.total
value: progressInfo.progress
Image {
anchors.fill: parent
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
visible: parent.status !== Image.Ready
}
}
function saveFileAs() {
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
dialog.open()
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
}
fillMode: Image.PreserveAspectFit
Component {
id: fileDialog
ToolTip.text: model.display
ToolTip.visible: hoverHandler.hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
currentRoom.downloadFile(eventId, file)
HoverHandler {
id: hoverHandler
}
Rectangle {
anchors.fill: parent
visible: progressInfo.active && !downloaded
color: "#BB000000"
ProgressBar {
anchors.centerIn: parent
width: parent.width * 0.8
from: 0
to: progressInfo.total
value: progressInfo.progress
}
}
}
function downloadAndOpen()
{
if (downloaded) openSavedFile()
else
{
openOnFinished = true
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
Component {
id: fileDialog
FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
currentRoom.downloadFile(eventId, file)
}
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
onTapped: {
img.ToolTip.hide()
fullScreenImage.open()
}
}
FullScreenImage {
id: fullScreenImage
filename: eventId
source: mediaUrl
blurhash: model.content.info["xyz.amorgan.blurhash"]
imageWidth: content.info.w
imageHeight: content.info.h
modelData: model
}
function downloadAndOpen() {
if (downloaded) {
openSavedFile()
} else {
openOnFinished = true
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
}
}
function openSavedFile() {
if (UrlHelper.openUrl(progressInfo.localPath)) return;
if (UrlHelper.openUrl(progressInfo.localDir)) return;
}
}
function openSavedFile()
{
if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(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

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
TimelineContainer {
id: messageDelegate
property bool isEmote: false
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: ColumnLayout {
Layout.maximumWidth: messageDelegate.contentMaxWidth
RichLabel {
id: label
isEmote: messageDelegate.isEmote
}
Loader {
id: linkPreviewLoader
Layout.fillWidth: true
height: active ? item.implicitHeight : 0
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
visible: active
sourceComponent: LinkPreviewDelegate {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

View File

@@ -29,7 +29,7 @@ Flow {
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)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: Kirigami.Units.devicePixelRatio
border.width: 1
}

View File

@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
QQC2.ItemDelegate {
id: readMarkerDelegate
padding: Kirigami.Units.largeSpacing
topInset: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing * 2
// extraWidth defines how the delegate can grow after the listView gets very wide
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width - Kirigami.Units.largeSpacing * 2 : Math.min(messageListView.width - Kirigami.Units.largeSpacing * 2, Kirigami.Units.gridUnit * 40 + extraWidth)
width: delegateMaxWidth
anchors.leftMargin: Kirigami.Units.largeSpacing
anchors.rightMargin: Kirigami.Units.largeSpacing
state: Config.compactLayout ? "alignLeft" : "alignCenter"
// Align left when in compact mode and center when using bubbles
states: [
State {
name: "alignLeft"
AnchorChanges {
target: readMarkerDelegate
anchors.horizontalCenter: undefined
anchors.left: parent ? parent.left : undefined
}
},
State {
name: "alignCenter"
AnchorChanges {
target: readMarkerDelegate
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
anchors.left: undefined
}
}
]
transitions: [
Transition {
AnchorAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
}
]
contentItem: QQC2.Label {
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
opacity: 0.6
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
Timer {
id: makeMeDisapearTimer
interval: Kirigami.Units.humanMoment * 2
onTriggered: if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
currentRoom.markAllMessagesAsRead();
}
}
ListView.onPooled: makeMeDisapearTimer.stop()
ListView.onAdd: {
const view = ListView.view;
if (view.atYEnd) {
makeMeDisapearTimer.start()
}
}
// When the read marker is visible and we are at the end of the list,
// start the makeMeDisapearTimer
Connections {
target: ListView.view
function onAtYEndChanged() {
makeMeDisapearTimer.start();
}
}
ListView.onRemove: {
const view = ListView.view;
if (view.atYEnd) {
// easy case just mark everything as read
if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
currentRoom.markAllMessagesAsRead();
}
return;
}
// mark the last visible index
const lastVisibleIdx = lastVisibleIndex();
if (lastVisibleIdx < index) {
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIdx, 0), MessageEventModel.EventIdRole)
}
}
}

View File

@@ -15,24 +15,25 @@ MouseArea {
id: replyButton
Layout.fillWidth: true
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
Component.onCompleted: {
parent.Layout.fillWidth = true;
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
parent.Layout.maximumWidth = Qt.binding(function() { return bubbleMaxWidth + Kirigami.Units.largeSpacing * 2; })
parent.Layout.maximumWidth = Qt.binding(function() { return contentMaxWidth + Kirigami.Units.largeSpacing * 2; })
}
Rectangle {
id: replyLeftBorder
width: Kirigami.Units.smallSpacing
height: parent.height
x: Config.compactLayout ? Kirigami.Units.largeSpacing : 0
color: Kirigami.Theme.highlightColor
}
Kirigami.Avatar {
id: avatatReply
id: replyAvatar
anchors.left: replyLeftBorder.right
anchors.leftMargin: Kirigami.Units.smallSpacing
width: Kirigami.Units.gridUnit
width: visible ? Kirigami.Units.gridUnit : 0
height: Kirigami.Units.gridUnit
sourceSize.width: width
sourceSize.height: height
@@ -46,12 +47,12 @@ MouseArea {
QQC2.Label {
id: replyName
anchors {
left: avatatReply.right
left: replyAvatar.right
leftMargin: Kirigami.Units.smallSpacing
right: parent.right
rightMargin: Kirigami.Units.smallSpacing
}
text: reply.author.displayName
text: currentRoom.htmlSafeMemberName(reply.author.id)
color: reply.author.color
elide: Text.ElideRight
}
@@ -74,13 +75,12 @@ MouseArea {
Component {
id: textComponent
TextDelegate {
RichLabel {
id: replyText
textMessage: reply.display
textFormat: Text.RichText
wrapMode: Text.WordWrap
width: Math.min(implicitWidth, bubbleMaxWidth) - Kirigami.Units.smallSpacing * 5 - avatatReply.width
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
}
}
@@ -94,9 +94,9 @@ MouseArea {
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
source: "image://mxc/" + mediaId
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - avatatReply.width
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
height: reply.content.info.h / reply.content.info.w * width
x: Kirigami.Units.smallSpacing * 3 + avatatReply.width
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
}
}
}

View File

@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.neochat 1.0
import org.kde.kirigami 2.15 as Kirigami
TextEdit {
id: contentLabel
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
readonly property var hasSpoiler: /data-mx-spoiler/g
property bool isEmote: false
/* Turn all links which aren't already in <a> tags into <a> hyperlinks */
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\]+)/g
property string textMessage: model.display.includes("http")
? model.display.replace(linkRegex, function() {
if (arguments[1]) {
return arguments[0];
} else {
var l = arguments[2];
if ([".", ","].includes(l[l.length-1])) {
var link = l.substring(0, l.length-1);
var leftover = l[l.length-1];
return "<a href=\"" + link + "\">" + link + "</a>" + leftover;
}
return "<a href=\"" + l + "\">" + l + "</a>";
}
})
: model.display
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
Layout.fillWidth: true
persistentSelection: true
text: "<style>
table {
width:100%;
border-width: 1px;
border-collapse: collapse;
border-style: solid;
}
table th,
table td {
border: 1px solid black;
padding: 3px;
}
pre {
white-space: pre-wrap
}
a{
color: " + Kirigami.Theme.linkColor + ";
text-decoration: none;
}
" + (!spoilerRevealed ? "
[data-mx-spoiler] a {
color: transparent;
background: " + Kirigami.Theme.textColor + ";
}
[data-mx-spoiler] {
color: transparent;
background: " + Kirigami.Theme.textColor + ";
}
" : "") + "
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.Wrap
textFormat: Text.RichText
onLinkActivated: {
spoilerRevealed = true
RoomManager.openResource(link)
}
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
applicationWindow().hoverLinkIndicator.text = hoveredLink;
} else {
applicationWindow().hoverLinkIndicator.text = "";
}
HoverHandler {
cursorShape: (parent.hoveredLink || !spoilerRevealed) ? Qt.PointingHandCursor : Qt.IBeamCursor
}
TapHandler {
enabled: !parent.hoveredLink && !spoilerRevealed
onTapped: spoilerRevealed = true
}
}

View File

@@ -9,6 +9,8 @@ import org.kde.kirigami 2.15 as Kirigami
Kirigami.Heading {
level: 4
text: model.showSection ? section : ""
color: Kirigami.Theme.disabledTextColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
topPadding: Kirigami.Units.largeSpacing * 2
bottomPadding: Kirigami.Units.smallSpacing

View File

@@ -7,39 +7,97 @@ import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
RowLayout {
id: row
Control {
id: stateDelegate
// extraWidth defines how the delegate can grow after the listView gets very wide
readonly property int extraWidth: messageListView.width >= Kirigami.Units.gridUnit * 46 ? Math.min((messageListView.width - Kirigami.Units.gridUnit * 46), Kirigami.Units.gridUnit * 20) : 0
readonly property int delegateMaxWidth: Config.compactLayout ? messageListView.width: Math.min(messageListView.width, Kirigami.Units.gridUnit * 40 + extraWidth)
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.alignment: Qt.AlignTop
width: delegateMaxWidth
// anchors.leftMargin: Kirigami.Units.largeSpacing
// anchors.rightMargin: Kirigami.Units.largeSpacing
name: author.displayName
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
color: author.color
Component {
id: userDetailDialog
UserDetailDialog {}
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
}
}
]
MouseArea {
anchors.fill: parent
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
transitions: [
Transition {
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
}
]
height: sectionDelegate.height + rowLayout.height
SectionDelegate {
id: sectionDelegate
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
visible: model.showSection
height: visible ? implicitHeight : 0
}
Label {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
wrapMode: Text.WordWrap
textFormat: Text.RichText
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + author.displayName + "</a> " + display
onLinkActivated: Qt.openUrlExternally(link)
RowLayout {
id: rowLayout
height: label.contentHeight
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing + (Config.compactLayout ? Kirigami.Units.largeSpacing * 1.25 : 0)
anchors.rightMargin: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: icon
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.alignment: Qt.AlignTop
name: author.displayName
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
color: author.color
Component {
id: userDetailDialog
UserDetailDialog {}
}
MouseArea {
anchors.fill: parent
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
}
}
Label {
id: label
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.preferredHeight: icon.height
wrapMode: Text.WordWrap
textFormat: Text.RichText
text: `<style>a {text-decoration: none;}</style><a href="https://matrix.to/#/${author.id}" style="color: ${author.color}">${currentRoom.htmlSafeMemberName(author.id)}</a> ${aggregateDisplay}`
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
}
}
}

View File

@@ -1,64 +0,0 @@
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.neochat 1.0
import org.kde.kirigami 2.15 as Kirigami
TextEdit {
id: contentLabel
Layout.margins: Kirigami.Units.largeSpacing
Layout.topMargin: 0
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
property bool isEmote: false
property string textMessage: model.display
text: "<style>
table {
width:100%;
border-width: 1px;
border-collapse: collapse;
border-style: solid;
}
table th,
table td {
border: 1px solid black;
padding: 3px;
}
pre {
white-space: pre-wrap
}
a{
color: " + Kirigami.Theme.linkColor + ";
text-decoration: none;
}
</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
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.WordWrap
textFormat: Text.RichText
Layout.fillWidth: true
onLinkActivated: RoomManager.openResource(link)
onHoveredLinkChanged: if (hoveredLink.length > 0) {
applicationWindow().hoverLinkIndicator.text = hoveredLink;
} else {
applicationWindow().hoverLinkIndicator.text = "";
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
}
}

View File

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

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import QtMultimedia 5.15
import Qt.labs.platform 1.1 as Platform
@@ -15,13 +14,19 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
Video {
id: vid
TimelineContainer {
id: videoDelegate
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
property bool playOnFinished: false
readonly property bool downloaded: progressInfo && progressInfo.completed
property bool supportStreaming: true
readonly property int maxWidth: 1000 // TODO messageListView.width
onOpenContextMenu: openFileContext(model, vid)
onDownloadedChanged: {
if (downloaded) {
@@ -34,102 +39,94 @@ Video {
}
}
innerObject: Video {
id: vid
readonly property int maxWidth: 1000 // TODO messageListView.width
Layout.maximumWidth: videoDelegate.contentMaxWidth
Layout.fillWidth: true
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
Layout.preferredHeight: content.info.w > maxWidth ? (content.info.h / content.info.w * maxWidth) : content.info.h
Layout.preferredWidth: (model.content.info.w === undefined || model.content.info.w > videoDelegate.maxWidth) ? videoDelegate.maxWidth : content.info.w
Layout.preferredHeight: model.content.info.w === undefined ? (videoDelegate.maxWidth * 3 / 4) : (model.content.info.w > videoDelegate.maxWidth ? (model.content.info.h / model.content.info.w * videoDelegate.maxWidth) : model.content.info.h)
loops: MediaPlayer.Infinite
loops: MediaPlayer.Infinite
fillMode: VideoOutput.PreserveAspectFit
fillMode: VideoOutput.PreserveAspectFit
Component.onCompleted: {
if (downloaded) {
source = progressInfo.localPath
} else {
source = currentRoom.urlToMxcUrl(content.url)
onDurationChanged: {
if (!duration) {
vid.supportStreaming = false;
}
}
}
onDurationChanged: {
if (!duration) {
supportStreaming = false;
onErrorChanged: {
if (error != MediaPlayer.NoError) {
vid.supportStreaming = false;
}
}
}
onErrorChanged: {
if (error != MediaPlayer.NoError) {
supportStreaming = false;
Image {
anchors.fill: parent
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
source: model.content.thumbnailMediaId ? "image://mxc/" + model.content.thumbnailMediaId : ""
fillMode: Image.PreserveAspectFit
}
}
Image {
readonly property bool isThumbnail: content.info.thumbnail_info && content.thumbnailMediaId
readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
anchors.fill: parent
visible: isThumbnail && (vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError)
source: "image://mxc/" + (isThumbnail ? content.thumbnailMediaId : "")
sourceSize.width: info.w
sourceSize.height: info.h
fillMode: Image.PreserveAspectFit
}
Label {
anchors.centerIn: parent
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
color: "white"
text: i18n("Video")
font.pixelSize: 16
padding: 8
background: Rectangle {
radius: Kirigami.Units.smallSpacing
color: "black"
opacity: 0.3
}
}
Rectangle {
anchors.fill: parent
visible: progressInfo.active && !downloaded
color: "#BB000000"
ProgressBar {
Label {
anchors.centerIn: parent
width: parent.width * 0.8
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
color: "white"
text: i18n("Video")
font.pixelSize: 16
from: 0
to: progressInfo.total
value: progressInfo.progress
}
}
padding: 8
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: if (supportStreaming || progressInfo.completed) {
if (vid.playbackState == MediaPlayer.PlayingState) {
vid.pause()
} else {
vid.play()
background: Rectangle {
radius: Kirigami.Units.smallSpacing
color: "black"
opacity: 0.3
}
}
Rectangle {
anchors.fill: parent
visible: progressInfo.active && !videoDelegate.downloaded
color: "#BB000000"
ProgressBar {
anchors.centerIn: parent
width: parent.width * 0.8
from: 0
to: progressInfo.total
value: progressInfo.progress
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: if (vid.supportStreaming || progressInfo.completed) {
if (vid.playbackState == MediaPlayer.PlayingState) {
vid.pause()
} else {
vid.play()
}
} else {
videoDelegate.downloadAndPlay()
}
} else {
downloadAndPlay()
}
}
function downloadAndPlay() {
if (downloaded) {
if (vid.downloaded) {
playSavedFile()
} else {
playOnFinished = true

View File

@@ -1,6 +1,6 @@
module NeoChat.Component.Timeline
RichLabel 1.0 RichLabel.qml
TimelineContainer 1.0 TimelineContainer.qml
TextDelegate 1.0 TextDelegate.qml
StateDelegate 1.0 StateDelegate.qml
SectionDelegate 1.0 SectionDelegate.qml
ImageDelegate 1.0 ImageDelegate.qml
@@ -8,3 +8,8 @@ FileDelegate 1.0 FileDelegate.qml
VideoDelegate 1.0 VideoDelegate.qml
ReactionDelegate 1.0 ReactionDelegate.qml
AudioDelegate 1.0 AudioDelegate.qml
EncryptedDelegate 1.0 EncryptedDelegate.qml
EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.14 as Kirigami
import org.kde.neochat 1.0
Loader {
id: root
property string labelText: ""
active: visible
sourceComponent: Pane {
id: typingPane
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
FontMetrics {
id: fontMetrics
}
contentItem: RowLayout {
spacing: typingPane.spacing
Row {
id: dotRow
property int duration: 400
spacing: Kirigami.Units.smallSpacing
Repeater {
model: 3
delegate: Rectangle {
id: dot
color: Kirigami.Theme.textColor
radius: height/2
implicitWidth: fontMetrics.xHeight
implicitHeight: fontMetrics.xHeight
// rotating 45 degrees makes the dots look a bit smoother when scaled up
rotation: 45
opacity: 0.5
scale: 1
// FIXME: Sometimes the animation timings for each
// dot drift slightly reletative to each other.
// Not everyone can see this, but I'm pretty sure it's there.
SequentialAnimation {
running: true
PauseAnimation { duration: dotRow.duration * index / 2 }
SequentialAnimation {
loops: Animation.Infinite
ParallelAnimation {
// Animators unfortunately sync up instead of being
// staggered, so I'm using NumberAnimations instead.
NumberAnimation {
target: dot; property: "scale";
from: 1; to: 1.33
duration: dotRow.duration
}
NumberAnimation {
target: dot; property: "opacity"
from: 0.5; to: 1
duration: dotRow.duration
}
}
ParallelAnimation {
NumberAnimation {
target: dot; property: "scale"
from: 1.33; to: 1
duration: dotRow.duration
}
NumberAnimation {
target: dot; property: "opacity"
from: 1; to: 0.5
duration: dotRow.duration
}
}
PauseAnimation { duration: dotRow.duration }
}
}
}
}
}
Label {
id: typingLabel
elide: Text.ElideRight
text: root.labelText
textFormat: Text.PlainText
}
}
leftInset: !mirrored ? 0 : -background.radius
rightInset: mirrored ? 0 : -background.radius
bottomInset: -background.radius
background: Rectangle {
radius: 3
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
border.width: 1
}
}
}

View File

@@ -2,3 +2,6 @@ module NeoChat.Component
FullScreenImage 1.0 FullScreenImage.qml
ChatTextInput 1.0 ChatTextInput.qml
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
TypingPane 1.0 TypingPane.qml
QuickSwitcher 1.0 QuickSwitcher.qml
ShimmerGradient 1.0 ShimmerGradient.qml

View File

@@ -14,9 +14,7 @@ Kirigami.OverlaySheet {
parent: applicationWindow().overlay
header: Kirigami.Heading {
text: i18n("Create a Room")
}
title: i18n("Create a Room")
contentItem: Kirigami.FormLayout {
TextField {

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

@@ -1,207 +0,0 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
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
import NeoChat.Component 1.0
Kirigami.OverlaySheet {
id: root
property var room
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
readonly property bool canChangeName: room.canSendState("m.room.name")
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
parent: applicationWindow().overlay
header: Kirigami.Heading {
text: i18nc("%1 is the room name", "Room Settings - %1", room.displayName)
elide: Text.ElideRight
}
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: 16
Kirigami.Avatar {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
Layout.alignment: Qt.AlignTop
name: room.name
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
MouseArea {
anchors.fill: parent
enabled: canChangeAvatar
onClicked: {
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect(function(path) {
if (!path) return
room.changeAvatar(path)
})
fileDialog.open()
}
}
}
Kirigami.FormLayout {
Layout.fillWidth: true
TextField {
id: roomNameField
text: room.name
Kirigami.FormData.label: i18n("Room Name:")
enabled: canChangeName
}
TextArea {
id: roomTopicField
Layout.fillWidth: true
text: room.topic
Kirigami.FormData.label: i18n("Room topic:")
enabled: canChangeTopic
}
Button {
Layout.alignment: Qt.AlignRight
visible: canChangeName || canChangeTopic
text: i18n("Save")
highlighted: true
onClicked: {
if (room.name != roomNameField.text) {
room.setName(roomNameField.text)
}
if (room.topic != roomTopicField.text) {
room.setTopic(roomTopicField.text)
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
visible: canonicalAliasComboBox.visible || altAlias.visible
}
ComboBox {
id: canonicalAliasComboBox
visible: room.aliases && room.aliases.length
Kirigami.FormData.label: i18n("Canonical Alias:")
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
enabled: canChangeCanonicalAlias
model: room.aliases
currentIndex: room.aliases.indexOf(room.canonicalAlias)
onCurrentIndexChanged: {
if (room.canonicalAlias != room.aliases[currentIndex]) {
room.setCanonicalAlias(room.aliases[currentIndex])
}
}
}
RowLayout {
id: altAlias
Kirigami.FormData.label: i18n("Alt Aliases")
Layout.fillWidth: true
visible: room.altAliases && room.altAliases.length
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Repeater {
model: room.altAliases
delegate: RowLayout {
Layout.maximumWidth: parent.width
Label {
text: modelData
color: Kirigami.Theme.disabledColor
}
ToolButton {
icon.name: ""
onClicked: room.removeLocalAlias(modelData)
}
}
}
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
visible: next.visible || prev.visible
}
Control {
id: next
Layout.fillWidth: true
visible: room.predecessorId && room.connection.room(room.predecessorId)
padding: Kirigami.Units.largeSpacing
contentItem: Kirigami.InlineMessage {
text: i18n("This room continues another conversation.")
actions: Kirigami.Action {
text: i18n("See older messages...")
onTriggered: {
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
root.close()
}
}
}
}
Control {
id: prev
Layout.fillWidth: true
visible: room.successorId && room.connection.room(room.successorId)
padding: Kirigami.Units.largeSpacing
contentItem: Kirigami.InlineMessage {
text: i18n("This room has been replaced.")
actions: Kirigami.Action {
text: i18n("See new room...")
onTriggered: {
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
root.close()
}
}
}
}
Component {
id: openFileDialog
OpenFileDialog {}
}
}
}

View File

@@ -14,6 +14,8 @@ import NeoChat.Component 1.0
Kirigami.OverlaySheet {
id: root
signal closed()
property var room
property var user
@@ -27,13 +29,7 @@ Kirigami.OverlaySheet {
rightPadding: 0
topPadding: 0
header: Kirigami.Heading {
id: heading
text: i18nc("@title:menu Account detail dialog", "Account detail")
elide: Text.ElideRight
QQC2.ToolTip.visible: truncated && hovered
QQC2.ToolTip.text: text
}
title: i18nc("@title:menu Account detail dialog", "Account detail")
contentItem: ColumnLayout {
spacing: 0
@@ -58,7 +54,7 @@ Kirigami.OverlaySheet {
onClicked: {
if (avatarMediaId) {
fullScreenImage.createObject(parent, {"filename": displayName, "localPath": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
}
}
}
@@ -74,7 +70,7 @@ Kirigami.OverlaySheet {
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: displayName
text: room.htmlSafeMemberName(user.id)
}
QQC2.Label {
@@ -104,7 +100,7 @@ Kirigami.OverlaySheet {
}
}
Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("kick")
visible: user !== room.localUser && room.canSendState("kick") && room.containsUser(user.id)
action: Kirigami.Action {
text: i18n("Kick this user")
@@ -116,24 +112,50 @@ Kirigami.OverlaySheet {
}
}
Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("ban")
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
action: Kirigami.Action {
text: i18n("Ban this user")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
room.banMember(user.id)
room.ban(user.id)
root.close()
}
}
}
Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("ban") && room.isUserBanned(user.id)
action: Kirigami.Action {
text: i18n("Unban this user")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
room.unban(user.id)
root.close()
}
}
}
Kirigami.BasicListItem {
visible: user === room.localUser || room.canSendState("redact")
action: Kirigami.Action {
text: i18n("Delete recent messages by this user")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
room.deleteMessagesByUser(user.id)
}
}
}
Kirigami.BasicListItem {
visible: user !== room.localUser
action: Kirigami.Action {
text: i18n("Open a private chat")
icon.name: "document-send"
onTriggered: {
Controller.activeConnection.requestDirectChat(user)
Controller.openOrCreateDirectChat(user);
root.close()
}
}
@@ -144,5 +166,11 @@ Kirigami.OverlaySheet {
FullScreenImage {}
}
}
onSheetOpenChanged: {
if (!sheetOpen) {
closed()
}
}
}

View File

@@ -1,5 +1,4 @@
module NeoChat.Dialog
RoomSettingsDialog 1.0 RoomSettingsDialog.qml
UserDetailDialog 1.0 UserDetailDialog.qml
LoginDialog 1.0 LoginDialog.qml
CreateRoomDialog 1.0 CreateRoomDialog.qml
@@ -8,3 +7,4 @@ OpenFileDialog 1.0 OpenFileDialog.qml
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
StartChatDialog 1.0 StartChatDialog.qml
EmojiDialog 1.0 EmojiDialog.qml
KeyVerificationDialog 1.0 KeyVerificationDialog.qml

View File

@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import Qt.labs.platform 1.1 as Labs
import QtQuick 2.15
import QtQuick.Controls 2.12 as QQC2
import QtQuick.Layouts 1.10
import org.kde.kirigami 2.15 as Kirigami
Labs.Menu {
id: editMenu
required property Item field
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.canUndo
text: i18nc("text editing menu action", "Undo")
shortcut: StandardKey.Undo
onTriggered: {
editMenu.field.undo()
editMenu.close()
}
}
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.canRedo
text: i18nc("text editing menu action", "Redo")
shortcut: StandardKey.Redo
onTriggered: {
editMenu.field.undo()
editMenu.close()
}
}
Labs.MenuSeparator {
}
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.selectedText
text: i18nc("text editing menu action", "Cut")
shortcut: StandardKey.Cut
onTriggered: {
editMenu.field.cut()
editMenu.close()
}
}
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.selectedText
text: i18nc("text editing menu action", "Copy")
shortcut: StandardKey.Copy
onTriggered: {
editMenu.field.copy()
editMenu.close()
}
}
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.canPaste
text: i18nc("text editing menu action", "Paste")
shortcut: StandardKey.Paste
onTriggered: {
editMenu.field.paste()
editMenu.close()
}
}
Labs.MenuItem {
enabled: editMenu.field !== null && editMenu.field.selectedText !== ""
text: i18nc("text editing menu action", "Delete")
shortcut: ""
onTriggered: {
editMenu.field.remove(editMenu.field.selectionStart, editMenu.field.selectionEnd)
editMenu.close()
}
}
Labs.MenuSeparator {
}
Labs.MenuItem {
enabled: editMenu.field !== null
text: i18nc("text editing menu action", "Select All")
shortcut: StandardKey.SelectAll
onTriggered: {
editMenu.field.selectAll()
editMenu.close()
}
}
}

View File

@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Labs
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.12 as QQC2
import QtQuick.Layouts 1.10
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Page 1.0
import NeoChat.Panel 1.0
Labs.MenuBar {
Labs.Menu {
title: i18nc("menu", "NeoChat")
// TODO: make about page its own thing so we can go to it instead of settings where it's currently at
// Labs.MenuItem {
// text: i18nc("menu", "About NeoChat")
// }
Labs.MenuItem {
enabled: pageStack.layers.currentItem.title !== i18n("Configure NeoChat...")
text: i18nc("menu", "Configure NeoChat...")
shortcut: StandardKey.Preferences
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml", {}, {
title: i18n("Configure")
})
}
Labs.MenuItem {
text: i18nc("menu", "Quit NeoChat")
shortcut: StandardKey.Quit
onTriggered: Qt.quit()
}
}
Labs.Menu {
title: i18nc("menu", "File")
Labs.MenuItem {
text: i18nc("menu", "New Private Chat…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {connection: Controller.activeConnection})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
shortcut: StandardKey.New
onTriggered: {
const dialog = createRoomDialog.createObject(root.overlay)
dialog.open()
}
}
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {connection: Controller.activeConnection})
}
}
EditMenu {
title: i18nc("menu", "Edit")
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
}
Labs.Menu {
title: i18nc("menu", "View")
Labs.MenuItem {
text: i18nc("menu item that opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Open Quick Switcher")
shortcut: "Ctrl+K"
onTriggered: quickView.item.open()
}
}
Labs.Menu {
title: i18nc("menu", "Window")
// Labs.MenuItem {
// text: settings.userWantsSidebars ? i18nc("menu", "Hide Sidebar") : i18nc("menu", "Show Sidebar")
// onTriggered: settings.userWantsSidebars = !settings.userWantsSidebars
// }
Labs.MenuItem {
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
}
}
// TODO: offline help system (https://invent.kde.org/network/neochat/-/issues/411)
Labs.Menu {
title: i18nc("menu", "Help")
Labs.MenuItem {
text: i18nc("menu", "Matrix FAQ")
onTriggered: UrlHelper.openUrl("https://matrix.org/faq/")
}
}
}

View File

@@ -4,6 +4,9 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
import NeoChat.Page 1.0
@@ -11,41 +14,197 @@ import NeoChat.Page 1.0
/**
* Context menu when clicking on a room in the room list
*/
Menu {
Loader {
id: root
property var room
signal closed()
MenuItem {
text: i18n("Open in new window")
onTriggered: RoomManager.openWindow(room);
Component {
id: regularMenu
Menu {
MenuItem {
id: newWindow
text: i18n("Open in New Window")
onTriggered: RoomManager.openWindow(room);
visible: !Kirigami.Settings.isMobile
}
MenuSeparator {
visible: newWindow.visible
}
MenuItem {
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
}
MenuItem {
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
}
MenuItem {
text: i18n("Mark as Read")
onTriggered: room.markAllMessagesAsRead()
}
MenuItem {
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
onTriggered: if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id)
} else {
Clipboard.saveText(room.canonicalAlias)
}
}
Menu {
title: i18n("Notification State")
MenuItem {
text: i18n("Follow Global Setting")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Default
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Default
}
}
MenuItem {
text: i18nc("As in 'notify for all messages'","All")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.All
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.All
}
}
MenuItem {
text: i18nc("As in 'notify when the user is mentioned or the message contains a set keyword'","@Mentions and Keywords")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.MentionKeyword
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.MentionKeyword
}
}
MenuItem {
text: i18nc("As in 'do not notify for any messages'","Off")
checkable: true
autoExclusive: true
checked: room.pushNotificationState === PushNotificationState.Mute
enabled: room.pushNotificationState != PushNotificationState.Unknown
onTriggered: {
room.pushNotificationState = PushNotificationState.Mute
}
}
}
MenuItem {
text: i18n("Room Settings")
onTriggered: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
}
MenuSeparator {}
MenuItem {
text: i18n("Leave Room")
onTriggered: RoomManager.leaveRoom(room)
}
onClosed: {
root.closed()
destroy()
}
}
}
MenuSeparator {}
Component {
id: mobileMenu
MenuItem {
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
Kirigami.OverlayDrawer {
id: drawer
height: popupContent.implicitHeight
edge: Qt.BottomEdge
padding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
topPadding: 0
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
parent: applicationWindow().overlay
ColumnLayout {
id: popupContent
width: parent.width
spacing: 0
RowLayout {
id: headerLayout
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: avatar
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop
}
Kirigami.Heading {
level: 5
Layout.fillWidth: true
text: room.displayName
wrapMode: Text.WordWrap
}
ToolButton {
checked: room.isFavourite
checkable: true
icon.name: 'favorite'
Accessible.name: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
}
ToolButton {
icon.name: 'settings-configure'
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
}
}
Kirigami.BasicListItem {
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
onClicked: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
Kirigami.BasicListItem {
text: i18n("Mark as Read")
onClicked: room.markAllMessagesAsRead()
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
Kirigami.BasicListItem {
text: i18n("Leave Room")
onClicked: RoomManager.leaveRoom(room)
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
}
onClosed: root.closed()
}
}
MenuItem {
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
asynchronous: true
sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
function open() {
active = true;
}
MenuItem {
text: i18n("Mark as Read")
onTriggered: room.markAllMessagesAsRead()
onStatusChanged: if (status == Loader.Ready) {
if (Kirigami.Settings.isMobile) {
item.open();
} else {
item.popup();
}
}
MenuSeparator {}
MenuItem {
text: i18n("Leave Room")
onTriggered: RoomManager.leaveRoom(room)
}
onClosed: destroy()
}

View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15 as Controls
import org.kde.kirigami 2.14 as Kirigami
/**
* Action that allows an user to share data with other apps and service
* installed on their computer. The goal of this high level API is to
* adapte itself for each platform and adopt the native component.
*
* TODO add Android support
*/
Kirigami.Action {
id: shareAction
iconName: "emblem-shared-symbolic"
text: i18n("Share")
tooltip: i18n("Share the selected media")
property var doBeforeSharing: () => {}
visible: false
/**
* This property holds the input data for purpose.
*
* @code{.qml}
* Purpose.ShareAction {
* inputData: {
* 'urls': ['file://home/notroot/Pictures/mypicture.png'],
* 'mimeType': ['image/png']
* }
* }
* @endcode
*/
property var inputData: ({})
property Instantiator _instantiator: Instantiator {
Component.onCompleted: {
const purposeModel = Qt.createQmlObject('import org.kde.purpose 1.0 as Purpose;
Purpose.PurposeAlternativesModel {
pluginType: "Export"
}', shareAction._instantiator);
purposeModel.inputData = Qt.binding(function() {
return shareAction.inputData;
});
_instantiator.model = purposeModel;
shareAction.visible = true;
}
delegate: Kirigami.Action {
property int index
text: model.display
icon.name: model.iconName
onTriggered: {
doBeforeSharing();
applicationWindow().pageStack.pushDialogLayer('qrc:/imports/NeoChat/Menu/ShareDialog.qml', {
title: shareAction.tooltip,
index: index,
model: shareAction._instantiator.model
})
}
}
onObjectAdded: {
object.index = index;
shareAction.children.push(object)
}
onObjectRemoved: shareAction.children = Array.from(shareAction.children).filter(obj => obj.pluginId !== object.pluginId)
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
import org.kde.kirigami 2.14 as Kirigami
Kirigami.Action {
property var inputData: ({})
property var doBeforeSharing: () => {}
visible: false
}

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2017 Atul Sharma <atulsharma406@gmail.com>
* SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15 as Controls
import org.kde.purpose 1.0 as Purpose
import org.kde.notification 1.0
import org.kde.kirigami 2.14 as Kirigami
Kirigami.Page {
id: window
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
property alias index: jobView.index
property alias model: jobView.model
Controls.Action {
shortcut: 'Escape'
onTriggered: window.closeDialog()
}
Notification {
id: sharingFailed
eventId: "sharingFailed"
text: i18n("Sharing failed")
urgency: Notification.NormalUrgency
}
Notification {
id: sharingSuccess
eventId: "sharingSuccess"
flags: Notification.Persistent
}
Component.onCompleted: jobView.start()
contentItem: Purpose.JobView {
id: jobView
onStateChanged: {
if (state === Purpose.PurposeJobController.Finished) {
if (jobView.job.output.url !== "") {
// Show share url
// TODO no needed anymore in purpose > 5.90
sharingSuccess.text = i18n("Shared url for image is <a href='%1'>%1</a>", jobView.output.url);
sharingSuccess.sendEvent();
Clipboard.saveText(jobView.output.url);
}
window.closeDialog()
} else if (state === Purpose.PurposeJobController.Error) {
// Show failure notification
sharingFailed.sendEvent();
window.closeDialog()
} else if (state === Purpose.PurposeJobController.Cancelled) {
// Do nothing
window.closeDialog()
}
}
}
}

View File

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

View File

@@ -19,18 +19,22 @@ Loader {
property string eventType: ""
property string formattedBody: ""
required property string source
property string selectedText: ""
required property string plainMessage
property list<Kirigami.Action> nestedActions
property list<Kirigami.Action> actions: [
Kirigami.Action {
text: i18n("Edit")
icon.name: "document-edit"
onTriggered: ChatBoxHelper.edit(message, formattedBody, eventId);
onTriggered: chatBoxHelper.edit(message, formattedBody, eventId);
visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
},
Kirigami.Action {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author);
onTriggered: chatBoxHelper.replyToMessage(eventId, message, author);
},
Kirigami.Action {
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
@@ -42,13 +46,27 @@ Loader {
Kirigami.Action {
text: i18n("Copy")
icon.name: "edit-copy"
onTriggered: Clipboard.saveText(message)
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.plainMessage : loadRoot.selectedText)
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: author.id !== currentRoom.localUser.id
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
title: i18nc("@title", "Report Message"),
width: Kirigami.Units.gridUnit * 25
})
},
Kirigami.Action {
text: i18n("View Source")
icon.name: "code-context"
onTriggered: {
messageSourceSheet.createObject(page, {'sourceText': loadRoot.source}).open();
applicationWindow().pageStack.pushDialogLayer('qrc:/imports/NeoChat/Menu/Timeline/MessageSourceSheet.qml', {
sourceText: loadRoot.source
}, {
title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25
});
}
}
]
@@ -57,104 +75,77 @@ Loader {
id: regularMenu
QQC2.Menu {
id: menu
Instantiator {
model: loadRoot.nestedActions
delegate: QQC2.Menu {
id: menuItem
visible: modelData.visible
title: modelData.text
Instantiator {
model: modelData.children
delegate: QQC2.MenuItem {
text: modelData.text
icon.name: modelData.icon.name
onTriggered: modelData.trigger()
}
onObjectAdded: {
menuItem.insertItem(0, object)
}
}
}
onObjectAdded: {
object.visible = false;
menu.addMenu(object)
}
}
Repeater {
model: loadRoot.actions
QQC2.MenuItem {
id: menuItem
visible: modelData.visible
action: modelData
onClicked: loadRoot.item.close();
}
}
QQC2.Menu {
id: webshortcutmenu
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
property bool isVisible: webshortcutmodel.enabled
Component.onCompleted: {
webshortcutmenu.parent.visible = isVisible
}
onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
Instantiator {
model: WebShortcutModel {
id: webshortcutmodel
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.plainMessage
onOpenUrl: RoomManager.visitNonMatrix(url)
}
delegate: QQC2.MenuItem {
text: model.display
icon.name: model.decoration
onTriggered: webshortcutmodel.trigger(model.edit)
}
onObjectAdded: webshortcutmenu.insertItem(0, object)
}
QQC2.MenuSeparator {}
QQC2.MenuItem {
text: i18n("Configure Web Shortcuts...")
icon.name: "configure"
onTriggered: webshortcutmodel.configureWebShortcuts()
}
}
}
/*
Kirigami.OverlaySheet {
id: root
parent: applicationWindow().overlay
leftPadding: 0
rightPadding: 0
header: Kirigami.Heading {
text: i18nc("@title:menu Message detail dialog", "Message detail")
}
contentItem: ColumnLayout {
spacing: 0
RowLayout {
id: headerLayout
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: avatar
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop
name: author.displayName
color: author.color
}
ColumnLayout {
Layout.fillWidth: true
Kirigami.Heading {
level: 3
Layout.fillWidth: true
text: author.displayName
wrapMode: Text.WordWrap
}
QQC2.Label {
text: message
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 24
wrapMode: Text.WordWrap
onLinkActivated: RoomManager.openResource(link);
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
RowLayout {
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
Repeater {
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
delegate: QQC2.ItemDelegate {
Layout.fillWidth: true
Layout.fillHeight: true
contentItem: QQC2.Label {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 16
font.family: "emoji"
text: modelData
}
onClicked: {
currentRoom.toggleReaction(eventId, modelData)
loadRoot.item.close();
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
}
}*/
}
Component {
id: mobileMenu
Kirigami.OverlayDrawer {
id: drawer
height: popupContent.implicitHeight
height: stackView.implicitHeight
edge: Qt.BottomEdge
padding: 0
leftPadding: 0
@@ -164,85 +155,146 @@ Loader {
parent: applicationWindow().overlay
ColumnLayout {
id: popupContent
QQC2.StackView {
id: stackView
width: parent.width
spacing: 0
RowLayout {
id: headerLayout
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: avatar
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop
}
implicitHeight: currentItem.implicitHeight
Component {
id: nestedActionsComponent
ColumnLayout {
Layout.fillWidth: true
Kirigami.Heading {
level: 3
Layout.fillWidth: true
text: author.displayName
wrapMode: Text.WordWrap
id: actionLayout
property string title: ""
property list<Kirigami.Action> actions
width: parent.width
spacing: 0
RowLayout {
QQC2.ToolButton {
icon.name: 'draw-arrow-back'
onClicked: stackView.pop()
}
Kirigami.Heading {
level: 3
Layout.fillWidth: true
text: actionLayout.title
wrapMode: Text.WordWrap
}
}
QQC2.Label {
text: message
Layout.fillWidth: true
wrapMode: Text.WordWrap
Repeater {
id: listViewAction
model: actionLayout.actions
onLinkActivated: RoomManager.openResource(link);
Kirigami.BasicListItem {
icon: modelData.icon.name
iconColor: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
modelData.triggered()
loadRoot.item.close();
}
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
RowLayout {
initialItem: ColumnLayout {
id: popupContent
width: parent.width
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
Repeater {
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
delegate: QQC2.ItemDelegate {
RowLayout {
id: headerLayout
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
id: avatar
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
contentItem: Kirigami.Heading {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: "emoji"
text: modelData
Kirigami.Heading {
level: 3
Layout.fillWidth: true
text: currentRoom.htmlSafeMemberName(author.id)
wrapMode: Text.WordWrap
}
QQC2.Label {
text: message
Layout.fillWidth: true
wrapMode: Text.WordWrap
onLinkActivated: RoomManager.openResource(link);
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
RowLayout {
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
Repeater {
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
delegate: QQC2.ItemDelegate {
Layout.fillWidth: true
Layout.fillHeight: true
contentItem: Kirigami.Heading {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: "emoji"
text: modelData
}
onClicked: {
currentRoom.toggleReaction(eventId, modelData);
loadRoot.item.close();
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
Repeater {
id: listViewAction
model: loadRoot.actions
Kirigami.BasicListItem {
icon: modelData.icon.name
iconColor: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
currentRoom.toggleReaction(eventId, modelData);
modelData.triggered()
loadRoot.item.close();
}
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
Repeater {
id: listViewAction
model: loadRoot.actions
Kirigami.BasicListItem {
icon: modelData.icon.name
iconColor: modelData.icon.color ?? undefined
enabled: modelData.enabled
visible: modelData.visible
text: modelData.text
onClicked: {
modelData.triggered()
loadRoot.item.close();
Repeater {
model: loadRoot.nestedActions
Kirigami.BasicListItem {
action: modelData
visible: modelData.visible
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
onClicked: {
stackView.push(nestedActionsComponent, {
title: modelData.text,
actions: modelData.children
});
}
}
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
}
}
}

View File

@@ -5,20 +5,45 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import org.kde.syntaxhighlighting 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.OverlaySheet {
Kirigami.Page {
property string sourceText
header: Kirigami.Heading {
text: i18n("Message Source")
}
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
TextArea {
text: sourceText
readOnly: true
wrapMode: Text.WordWrap
title: i18n("Message Source")
ScrollView {
anchors.fill: parent
contentWidth: availableWidth
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: sourceTextArea
text: sourceText
readOnly: true
textFormat: TextEdit.PlainText
wrapMode: Text.WordWrap
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
}
SyntaxHighlighter {
textEdit: sourceTextArea
definition: "JSON"
repository: Repository
}
}
}
}

View File

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

View File

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

View File

@@ -1,2 +1,6 @@
module NeoChat.Menu
RoomListContextMenu 1.0 RoomListContextMenu.qml
GlobalMenu 1.0 GlobalMenu.qml
EditMenu 1.0 EditMenu.qml
ShareAction 1.0 ShareAction.qml
ShareDialog 1.0 ShareDialog.qml

View File

@@ -1,196 +0,0 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// 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 {
title: i18n("Accounts")
ListView {
model: AccountListModel { }
delegate: Kirigami.SwipeListItem {
leftPadding: 0
rightPadding: 0
action: Kirigami.Action {
onTriggered: Controller.activeConnection = model.connection
}
Kirigami.BasicListItem {
anchors.top: parent.top
anchors.bottom: parent.bottom
text: model.user.displayName
subtitle: model.user.id
icon: model.user.avatarMediaId ? ("image://mxc/" + model.user.avatarMediaId) : "im-user"
onClicked: {
Controller.activeConnection = model.connection
pageStack.layers.pop()
}
}
actions: [
Kirigami.Action {
text: i18n("Edit this account")
iconName: "document-edit"
onTriggered: {
userEditSheet.connection = model.connection
userEditSheet.open()
}
},
Kirigami.Action {
text: i18n("Logout")
iconName: "im-kick-user"
onTriggered: {
Controller.logout(model.connection, true)
if(Controller.accountCount === 1)
pageStack.layers.pop()
}
}
]
}
}
Connections {
target: Controller
function onConnectionAdded() {
if (pageStack.layers.depth > 2)
pageStack.layers.pop()
}
function onPasswordStatus(status) {
if(status == Controller.Success)
showPassiveNotification(i18n("Password changed successfully"))
else if(status == Controller.Wrong)
showPassiveNotification(i18n("Wrong password entered"))
else
showPassiveNotification(i18n("Unknown problem while trying to change password"))
}
}
actions.main: Kirigami.Action {
text: i18n("Add an account")
iconName: "list-add-user"
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
}
Component {
id: openFileDialog
OpenFileDialog {
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
}
}
Kirigami.OverlaySheet {
id: userEditSheet
property var connection
header: Kirigami.Heading {
text: i18n("Edit Account")
}
Kirigami.FormLayout {
anchors.top: passwordsMessage.bottom
RowLayout {
Kirigami.Avatar {
id: avatar
source: 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.length !== 0
icon.name: "edit-clear"
onClicked: avatar.source = ""
}
Kirigami.FormData.label: i18n("Avatar:")
}
Controls.TextField {
id: name
text: userEditSheet.connection.localUser.displayName
Kirigami.FormData.label: i18n("Name:")
}
Controls.TextField {
id: currentPassword
Kirigami.FormData.label: i18n("Current Password:")
echoMode: TextInput.Password
}
Controls.TextField {
id: newPassword
Kirigami.FormData.label: i18n("New Password:")
echoMode: TextInput.Password
}
Controls.TextField {
id: confirmPassword
Kirigami.FormData.label: i18n("Confirm new Password:")
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(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

@@ -1,100 +0,0 @@
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
// 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 org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18n("Devices")
ListView {
model: DevicesModel {
id: devices
}
delegate: Kirigami.SwipeListItem {
leftPadding: 0
rightPadding: 0
Kirigami.BasicListItem {
anchors.top: parent.top
anchors.bottom: parent.bottom
text: model.displayName
subtitle: model.id
icon: "network-connect"
}
actions: [
Kirigami.Action {
text: i18n("Edit device name")
iconName: "document-edit"
onTriggered: {
renameSheet.index = model.index
renameSheet.name = model.displayName
renameSheet.open()
}
},
Kirigami.Action {
text: i18n("Logout device")
iconName: "edit-delete-remove"
onTriggered: {
passwordSheet.index = index
passwordSheet.open()
}
}
]
}
}
Kirigami.OverlaySheet {
id: passwordSheet
property var index
header: Kirigami.Heading {
text: i18n("Remove device")
}
Kirigami.FormLayout {
Controls.TextField {
id: passwordField
Kirigami.FormData.label: i18n("Password:")
echoMode: TextInput.Password
}
Controls.Button {
text: i18n("Confirm")
onClicked: {
devices.logout(passwordSheet.index, passwordField.text)
passwordField.text = ""
passwordSheet.close()
}
}
}
}
Kirigami.OverlaySheet {
id: renameSheet
property int index
property string name
header: Kirigami.Heading {
text: i18n("Edit device")
}
Kirigami.FormLayout {
Controls.TextField {
id: nameField
Kirigami.FormData.label: i18n("Name:")
text: renameSheet.name
}
Controls.Button {
text: i18n("Save")
onClicked: {
devices.setName(renameSheet.index, nameField.text)
renameSheet.close()
}
}
}
}
}

View File

@@ -4,7 +4,6 @@
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import Qt.labs.platform 1.1 as Platform
import org.kde.kirigami 2.15 as Kirigami
@@ -21,14 +20,14 @@ Kirigami.Page {
title: i18n("Edit")
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
function crop() {
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
rootEditorView.resizing = false
imageDoc.crop(resizeRectangle.insideX / ratioX, resizeRectangle.insideY / ratioY, resizeRectangle.insideWidth / ratioX, resizeRectangle.insideHeight / ratioY);
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
}
actions {
@@ -58,8 +57,11 @@ Kirigami.Page {
contentItem: KQuickImageEditor.ImageItem {
KQuickImageEditor.ImageItem {
id: editImage
// Assigning this to the contentItem and setting the padding causes weird positioning issues
anchors.fill: parent
anchors.margins: Kirigami.Units.gridUnit
fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
image: imageDoc.image
@@ -76,12 +78,41 @@ Kirigami.Page {
Shortcut {
sequence: StandardKey.SaveAs
onActivated: saveAsAction.trigger();
} anchors.fill: parent
}
KQuickImageEditor.ImageDocument {
id: imageDoc
path: rootEditorView.imagePath
}
KQuickImageEditor.SelectionTool {
id: selectionTool
visible: rootEditorView.resizing
width: editImage.paintedWidth
height: editImage.paintedHeight
x: editImage.horizontalPadding
y: editImage.verticalPadding
KQuickImageEditor.CropBackground {
anchors.fill: parent
z: -1
insideX: selectionTool.selectionX
insideY: selectionTool.selectionY
insideWidth: selectionTool.selectionWidth
insideHeight: selectionTool.selectionHeight
}
Connections {
target: selectionTool.selectionArea
function onDoubleClicked() {
rootEditorView.crop()
}
}
}
onImageChanged: {
selectionTool.selectionX = 0
selectionTool.selectionY = 0
selectionTool.selectionWidth = Qt.binding(() => selectionTool.width)
selectionTool.selectionHeight = Qt.binding(() => selectionTool.height)
}
}
header: QQC2.ToolBar {
@@ -145,22 +176,4 @@ Kirigami.Page {
showCloseButton: true
visible: false
}
KQuickImageEditor.ResizeRectangle {
id: resizeRectangle
visible: rootEditorView.resizing
width: editImage.paintedWidth
height: editImage.paintedHeight
x: editImage.horizontalPadding
y: editImage.verticalPadding
insideX: 100
insideY: 100
insideWidth: 100
insideHeight: 100
onAcceptSize: rootEditorView.crop();
}
}

View File

@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12 as QQC2
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
Kirigami.Page {
title: i18n("Loading")
QQC2.BusyIndicator {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
}
}

View File

@@ -15,9 +15,101 @@ import NeoChat.Component 1.0
import NeoChat.Menu 1.0
Kirigami.ScrollablePage {
header: ColumnLayout {
visible: !page.collapsedMode
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
ListView {
id: spaceList
property string activeSpaceId: ''
orientation: Qt.Horizontal
spacing: Kirigami.Units.largeSpacing
clip:true
visible: spaceList.count > 0
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.fillWidth: true
model: SortFilterSpaceListModel {
id: sortFilterSpaceListModel
sourceModel: RoomListModel {
id: spaceListModel
connection: Controller.activeConnection
}
}
header: QQC2.Control {
contentItem: QQC2.RoundButton {
id: homeButton
flat: true
padding: Kirigami.Units.gridUnit / 2
icon.name: "home"
text: i18nc("@action:button", "Show All Rooms")
display: QQC2.AbstractButton.IconOnly
onClicked: {
sortFilterRoomListModel.activeSpaceId = "";
spaceList.activeSpaceId = '';
listView.positionViewAtIndex(0, ListView.Beginning);
}
QQC2.ToolTip {
text: homeButton.text
}
}
}
delegate: QQC2.Control {
required property string avatar
required property var currentRoom
required property int index
required property string id
implicitWidth: ListView.view.headerItem.implicitWidth
implicitHeight: ListView.view.headerItem.implicitHeight
contentItem: Kirigami.Avatar {
actions.main: Kirigami.Action {
onTriggered: {
spaceList.activeSpaceId = id;
sortFilterRoomListModel.activeSpaceId = id;
}
}
QQC2.ToolTip {
text: currentRoom.displayName
}
source: avatar !== "" ? "image://mxc/" + avatar : ""
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
}
id: page
title: i18n("Rooms")
property var enteredRoom
property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars
verticalScrollBarPolicy: collapsedMode ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
onCollapsedModeChanged: if (collapsedMode) {
sortFilterRoomListModel.filterText = "";
}
Connections {
target: RoomManager
function onCurrentRoomChanged() {
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
}
}
function goToNextRoom() {
do {
@@ -33,21 +125,57 @@ Kirigami.ScrollablePage {
listView.currentItem.action.trigger();
}
title: i18n("Rooms")
titleDelegate: collapsedMode ? empty : searchField
titleDelegate: Kirigami.SearchField {
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.fillHeight: true
Layout.fillWidth: true
onTextChanged: sortFilterRoomListModel.filterText = text
KeyNavigation.tab: listView
Component {
id: empty
Item {}
}
Component {
id: searchField
Kirigami.SearchField {
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.fillHeight: true
Layout.fillWidth: true
onTextChanged: sortFilterRoomListModel.filterText = text
KeyNavigation.tab: listView
}
}
ListView {
id: listView
activeFocusOnTab: true
clip: accountList.count > 1
header: QQC2.ItemDelegate {
visible: page.collapsedMode
action: Kirigami.Action {
id: enterRoomAction
onTriggered: quickView.item.open();
}
topPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
width: visible ? page.width : 0
height: visible ? Kirigami.Units.gridUnit * 2 : 0
Kirigami.Icon {
anchors.centerIn: parent
width: 22
height: 22
source: "search"
}
Kirigami.Separator {
width: parent.width
anchors.bottom: parent.bottom
}
}
Layout.fillWidth: true
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
@@ -64,6 +192,7 @@ Kirigami.ScrollablePage {
}
}
ItemSelectionModel {
id: itemSelection
model: roomListModel
@@ -87,6 +216,7 @@ Kirigami.ScrollablePage {
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
section.delegate: Kirigami.ListSectionHeader {
id: sectionHeader
height: implicitHeight
action: Kirigami.Action {
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
}
@@ -97,11 +227,14 @@ Kirigami.ScrollablePage {
level: 3
text: roomListModel.categoryName(section)
Layout.fillWidth: true
elide: Text.ElideRight
visible: !page.collapsedMode
}
Kirigami.Icon {
source: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
source: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
implicitHeight: Kirigami.Units.iconSizes.small
implicitWidth: Kirigami.Units.iconSizes.small
Layout.alignment: Qt.AlignHCenter
}
}
}
@@ -109,85 +242,134 @@ Kirigami.ScrollablePage {
reuseItems: true
currentIndex: -1 // we don't want any room highlighted by default
delegate: Kirigami.BasicListItem {
id: roomListItem
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
highlighted: listView.currentIndex === index
focus: true
icon: undefined
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(
sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
}
}
bold: unreadCount > 0
label: name ?? ""
subtitle: {
let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
if (txt.length) {
return txt
}
return " "
}
delegate: page.collapsedMode ? collapsedModeListComponent : normalModeListComponent
leading: Kirigami.Avatar {
source: avatar ? "image://mxc/" + avatar : ""
name: model.name || i18n("No Name")
implicitWidth: visible ? height : 0
visible: Config.showAvatarInTimeline
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
}
Component {
id: collapsedModeListComponent
trailing: RowLayout {
QQC2.Label {
text: notificationCount
visible: notificationCount > 0
padding: Kirigami.Units.smallSpacing
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
Layout.minimumWidth: height
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
radius: height / 2
QQC2.ItemDelegate {
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
}
}
QQC2.Button {
id: configButton
visible: roomListItem.hovered || Kirigami.Settings.isMobile
Accessible.name: i18n("Configure room")
Keys.onEnterPressed: enterRoomAction.trigger()
Keys.onReturnPressed: enterRoomAction.trigger()
topPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
width: ListView.view.width
height: ListView.view.width
action: Kirigami.Action {
id: optionAction
icon.name: "configure"
onTriggered: {
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
configButton.visible = true
configButton.down = true
menu.closed.connect(function() {
configButton.down = undefined
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
})
menu.popup()
}
}
contentItem: Kirigami.Avatar {
source: avatar ? "image://mxc/" + avatar : ""
name: model.name || i18n("No Name")
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
}
QQC2.ToolTip {
enabled: text.length !== 0
text: name ?? ""
}
}
}
Component {
id: roomListContextMenu
RoomListContextMenu {}
}
Component {
id: normalModeListComponent
Kirigami.BasicListItem {
id: roomListItem
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
highlighted: listView.currentIndex === index
focus: true
icon: undefined
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
}
}
Keys.onEnterPressed: enterRoomAction.trigger()
Keys.onReturnPressed: enterRoomAction.trigger()
bold: unreadCount > 0
label: name ?? ""
labelItem.textFormat: Text.PlainText
subtitle: subtitleText
subtitleItem.textFormat: Text.PlainText
onPressAndHold: {
createRoomListContextMenu()
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse
onTapped: createRoomListContextMenu()
}
leading: Kirigami.Avatar {
source: avatar ? "image://mxc/" + avatar : ""
name: model.name || i18n("No Name")
implicitWidth: visible ? height : 0
visible: Config.showAvatarInTimeline
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
}
trailing: RowLayout {
QQC2.Label {
text: notificationCount > 0 ? notificationCount : "●"
visible: unreadCount > 0
padding: Kirigami.Units.smallSpacing
color: Kirigami.Theme.textColor
Layout.minimumWidth: height
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
visible: notificationCount > 0
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightCount > 0 ? 1 : 0.3
radius: height / 2
}
}
QQC2.Button {
id: configButton
visible: roomListItem.hovered
Accessible.name: i18n("Configure room")
action: Kirigami.Action {
id: optionAction
icon.name: "configure"
onTriggered: {
createRoomListContextMenu()
}
}
}
}
function createRoomListContextMenu() {
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
configButton.visible = true
configButton.down = true
menu.closed.connect(function() {
configButton.down = undefined
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
})
menu.open()
}
}
}
}
footer: QQC2.ToolBar {
visible: accountList.count > 1
visible: accountList.count > 1 && !collapsedMode
height: visible ? implicitHeight : 0
leftPadding: 0
rightPadding: 0
@@ -197,16 +379,15 @@ Kirigami.ScrollablePage {
spacing: 0
Repeater {
id: accountList
model: AccountListModel {
id: accountListModel
}
model: AccountRegistry
delegate: Kirigami.BasicListItem {
checkable: true
checked: Controller.activeConnection.localUser.id === model.user.id
checked: Controller.activeConnection && Controller.activeConnection.localUserId === model.connection.localUserId
onClicked: Controller.activeConnection = model.connection
Layout.fillWidth: true
Layout.fillHeight: true
text: model.user.id
text: model.connection.localUserId
subtitle: model.connection.localUser.accountLabel
}
}
}

View File

@@ -8,7 +8,7 @@ import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1 as Platform
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
@@ -23,13 +23,57 @@ Kirigami.ScrollablePage {
/// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom
property bool loading: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
/// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false;
title: currentRoom.displayName
/// Disable cancel shortcut. Used by the seperate window since it provide its own
/// cancel implementation.
property bool disableCancelShortcut: false
title: currentRoom.htmlSafeDisplayName
KeyNavigation.left: pageStack.get(0)
Connections {
target: RoomManager
function onCurrentRoomChanged() {
if(!RoomManager.currentRoom) {
if(pageStack.lastItem == page) {
pageStack.pop()
}
} else if (page.currentRoom.isInvite) {
page.currentRoom.clearInvitationNotification();
}
}
}
signal switchRoomUp()
signal switchRoomDown()
onCurrentRoomChanged: ChatBoxHelper.clearEditReply()
onCurrentRoomChanged: {
hasScrolledUpBefore = false;
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 {
id: actionsHandler
@@ -37,10 +81,20 @@ Kirigami.ScrollablePage {
connection: Controller.activeConnection
}
ChatBoxHelper {
id: chatBoxHelper
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: applicationWindow().pageStack.get(0).forceActiveFocus()
enabled: !page.disableCancelShortcut
}
Connections {
target: Controller.activeConnection
function onJoinedRoom(room) {
if(room.id === invitation.id) {
function onJoinedRoom(room, invited) {
if(page.currentRoom.id === invited.id) {
RoomManager.enterRoom(room);
}
}
@@ -48,28 +102,26 @@ Kirigami.ScrollablePage {
Connections {
target: actionsHandler
onShowMessage: {
function onShowMessage(messageType, message) {
page.header.contentItem.text = message;
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
page.header.contentItem.visible = true;
page.header.visible = true;
}
}
header: QQC2.Control {
height: visible ? implicitHeight : 0
visible: contentItem.visible
visible: false
padding: Kirigami.Units.smallSpacing
contentItem: Kirigami.InlineMessage {
showCloseButton: true
visible: false
visible: true
}
}
Kirigami.PlaceholderMessage {
id: invitation
property var id
visible: currentRoom && currentRoom.isInvite
anchors.centerIn: parent
text: i18n("Accept this invitation?")
@@ -78,7 +130,7 @@ Kirigami.ScrollablePage {
Layout.alignment : Qt.AlignHCenter
text: i18n("Reject")
onClicked: RoomManager.leave(page.currentRoom);
onClicked: RoomManager.leaveRoom(page.currentRoom);
}
QQC2.Button {
@@ -87,22 +139,15 @@ Kirigami.ScrollablePage {
onClicked: {
currentRoom.acceptInvitation();
invitation.id = currentRoom.id
currentRoom = null
}
}
}
}
Kirigami.PlaceholderMessage {
Kirigami.LoadingPlaceholder {
id: loadingIndicator
anchors.centerIn: parent
visible: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
text: i18n("Loading")
QQC2.BusyIndicator {
running: loadingIndicator.visible
Layout.alignment: Qt.AlignHCenter
}
visible: loading
}
focus: true
@@ -121,9 +166,9 @@ Kirigami.ScrollablePage {
Keys.onPressed: {
if (event.key === Qt.Key_PageDown && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomDown();
} else if (event.key === Qt.Key_PageUp && (event.modifiers & Qt.ControlModifier)) {
switchRoomUp();
} else if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
event.accepted = true;
chatBox.addText(event.text);
@@ -132,6 +177,15 @@ Kirigami.ScrollablePage {
}
}
Connections {
target: currentRoom
function onPositiveMessage(message) {
page.header.contentItem.text = message;
page.header.contentItem.type = Kirigami.MessageType.Positive;
page.header.visible = true;
}
}
// hover actions on a delegate, activated in TimelineContainer.qml
Connections {
target: page.flickable
@@ -141,104 +195,24 @@ Kirigami.ScrollablePage {
}
}
Item {
id: hoverActions
property var event
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
property var bubble
property var hovered: bubble && bubble.hovered
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
onVisibleDelayedChanged: if (visibleDelayed) {
visible = true;
} else {
// HACK: delay disapearing by 200ms, otherwise this can create some glitches
// See https://invent.kde.org/network/neochat/-/issues/333
hoverActionsTimer.restart();
}
Timer {
id: hoverActionsTimer
interval: 200
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
}
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0)) : 0
y: bubble ? bubble.mapToItem(page, 0, -Kirigami.Units.largeSpacing - hoverActions.childHeight * 1.5).y : 0
visible: false
property var updateFunction
property alias childWidth: hoverActionsRow.width
property alias childHeight: hoverActionsRow.height
RowLayout {
id: hoverActionsRow
z: 4
spacing: 0
HoverHandler {
id: hoverHandler
margin: Kirigami.Units.smallSpacing
}
QQC2.Button {
QQC2.ToolTip.text: i18n("React")
QQC2.ToolTip.visible: hovered
icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open();
EmojiDialog {
id: emojiDialog
onReact: {
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
chatBox.focusInputField();
}
}
}
QQC2.Button {
QQC2.ToolTip.text: i18n("Edit")
QQC2.ToolTip.visible: hovered
visible: hoverActions.showEdit
icon.name: "document-edit"
onClicked: {
if (hoverActions.showEdit) {
ChatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
}
chatBox.focusInputField();
}
}
QQC2.Button {
QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered
icon.name: "mail-replied-symbolic"
onClicked: {
ChatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
chatBox.focusInputField();
}
}
}
CollapseStateProxyModel {
id: collapseStateProxyModel
sourceModel: sortedMessageEventModel
}
ListView {
id: messageListView
pixelAligned: true
visible: !invitation.visible
readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1
readonly property bool noNeedMoreContent: !currentRoom || currentRoom.eventsHistoryJob || currentRoom.allHistoryLoaded
readonly property bool isLoaded: page.width * page.height > 10
spacing: Kirigami.Units.smallSpacing
reuseItems: true
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
highlightMoveDuration: 500
model: !isLoaded ? undefined : sortedMessageEventModel
onContentYChanged: fetchMoreContent()
function fetchMoreContent() {
if(!noNeedMoreContent && contentY - 5000 < originY) {
currentRoom.getPreviousContent(20);
}
}
model: !isLoaded ? undefined : collapseStateProxyModel
MessageEventModel {
id: messageEventModel
@@ -246,6 +220,32 @@ Kirigami.ScrollablePage {
room: currentRoom
}
Timer {
interval: 1000
running: messageListView.atYBeginning
triggeredOnStart: true
onTriggered: {
if (messageListView.atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
}
}
repeat: true
}
// HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && messageEventModel.canFetchMore(messageEventModel.index(0, 0))) {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
}
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
currentRoom.markAllMessagesAsRead();
}
hasScrolledUpBefore = false;
} else if (!atYEnd) {
hasScrolledUpBefore = true;
}
QQC2.Popup {
anchors.centerIn: parent
@@ -270,7 +270,7 @@ Kirigami.ScrollablePage {
fileDialog.chosen.connect(function(path) {
if (!path) return
ChatBoxHelper.attachmentPath = path;
chatBoxHelper.attachmentPath = path;
})
fileDialog.open()
@@ -292,7 +292,7 @@ Kirigami.ScrollablePage {
if (!Clipboard.saveImage(localPath)) {
return;
}
ChatBoxHelper.attachmentPath = localPath;
chatBoxHelper.attachmentPath = localPath;
attachDialog.close();
}
}
@@ -312,290 +312,7 @@ Kirigami.ScrollablePage {
sourceModel: messageEventModel
}
delegate: DelegateChooser {
id: timelineDelegateChooser
role: "eventType"
property bool delegateLoaded: true
ListView.onPooled: delegateLoaded = false
ListView.onReused: delegateLoaded = true
DelegateChoice {
roleValue: "state"
delegate: QQC2.Control {
leftPadding: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
topPadding: 0
bottomPadding: 0
contentItem: StateDelegate { }
implicitWidth: messageListView.width - Kirigami.Units.largeSpacing
}
}
DelegateChoice {
roleValue: "emote"
delegate: TimelineContainer {
id: emoteContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
isEmote: true
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
innerObject: TextDelegate {
isEmote: true
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
}
}
}
}
DelegateChoice {
roleValue: "message"
delegate: TimelineContainer {
id: messageContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
innerObject: TextDelegate {
Layout.maximumWidth: messageContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openMessageContext(author, model.message, eventId, toolTip, eventType, model.formattedBody)
}
}
}
}
DelegateChoice {
roleValue: "notice"
delegate: TimelineContainer {
id: noticeContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
onReplyClicked: goToEvent(eventID)
innerObject: TextDelegate {
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
}
}
}
DelegateChoice {
roleValue: "image"
delegate: TimelineContainer {
id: imageContainer
isLoaded: timelineDelegateChooser.delegateLoaded
width: messageListView.width
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
innerObject: ImageDelegate {
Layout.preferredWidth: Kirigami.Units.gridUnit * 15
Layout.maximumWidth: imageContainer.bubbleMaxWidth
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.preferredHeight: info.h / info.w * width
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
onTapped: {
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
}
}
}
}
}
DelegateChoice {
roleValue: "sticker"
delegate: TimelineContainer {
isLoaded: timelineDelegateChooser.delegateLoaded
width: messageListView.width
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
cardBackground: false
innerObject: ImageDelegate {
readonly: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 10
Layout.minimumWidth: Kirigami.Units.gridUnit * 10
Layout.preferredHeight: info.h / info.w * width
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: TimelineContainer {
id: audioContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
innerObject: AudioDelegate {
Layout.fillWidth: true
Layout.maximumWidth: audioContainer.bubbleMaxWidth
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
}
}
}
DelegateChoice {
roleValue: "video"
delegate: TimelineContainer {
id: videoContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
onReplyClicked: goToEvent(eventID)
hoverComponent: hoverActions
innerObject: VideoDelegate {
Layout.fillWidth: true
Layout.maximumWidth: videoContainer.bubbleMaxWidth
Layout.preferredHeight: content.info.h / content.info.w * width
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
}
}
}
DelegateChoice {
roleValue: "file"
delegate: TimelineContainer {
id: fileContainer
width: messageListView.width
isLoaded: timelineDelegateChooser.delegateLoaded
onReplyClicked: goToEvent(eventID)
innerObject: FileDelegate {
Layout.fillWidth: true
Layout.maximumWidth: fileContainer.bubbleMaxWidth
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
}
}
}
}
DelegateChoice {
roleValue: "readMarker"
delegate: QQC2.ItemDelegate {
padding: Kirigami.Units.largeSpacing
topInset: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing * 2
width: ListView.view.width - Kirigami.Units.gridUnit
x: Kirigami.Units.gridUnit / 2
contentItem: QQC2.Label {
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
opacity: 0.6
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: Kirigami.Units.devicePixelRatio
}
Timer {
id: makeMeDisapearTimer
interval: Kirigami.Units.humanMoment * 2
onTriggered: currentRoom.markAllMessagesAsRead();
}
ListView.onPooled: makeMeDisapearTimer.stop()
ListView.onAdd: {
const view = ListView.view;
if (view.atYEnd) {
makeMeDisapearTimer.start()
}
}
// When the read marker is visible and we are at the end of the list,
// start the makeMeDisapearTimer
Connections {
target: ListView.view
function onAtYEndChanged() {
makeMeDisapearTimer.start();
}
}
ListView.onRemove: {
const view = ListView.view;
if (view.atYEnd) {
// easy case just mark everything as read
currentRoom.markAllMessagesAsRead();
return;
}
// mark the last visible index
const lastVisibleIdx = lastVisibleIndex();
if (lastVisibleIdx < index) {
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIdx, 0), MessageEventModel.EventIdRole)
}
}
}
}
DelegateChoice {
roleValue: "other"
delegate: Item {}
}
}
delegate: EventDelegate {}
QQC2.RoundButton {
anchors.right: parent.right
@@ -610,7 +327,7 @@ Kirigami.ScrollablePage {
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
action: Kirigami.Action {
onTriggered: {
goToEvent(currentRoom.readMarkerEventId)
messageListView.goToEvent(currentRoom.readMarkerEventId)
}
icon.name: "go-up"
}
@@ -622,7 +339,7 @@ Kirigami.ScrollablePage {
QQC2.RoundButton {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: Kirigami.Units.largeSpacing
anchors.bottomMargin: Kirigami.Units.largeSpacing + messageListView.headerItem.height
anchors.rightMargin: Kirigami.Units.largeSpacing
implicitWidth: Kirigami.Units.gridUnit * 2
implicitHeight: Kirigami.Units.gridUnit * 2
@@ -633,6 +350,7 @@ Kirigami.ScrollablePage {
action: Kirigami.Action {
onTriggered: {
goToLastMessage();
currentRoom.markAllMessagesAsRead();
}
icon.name: "go-down"
}
@@ -643,19 +361,13 @@ Kirigami.ScrollablePage {
}
Component.onCompleted: {
updateReadMarker()
if (currentRoom) {
if (currentRoom.timelineSize < 20)
currentRoom.getPreviousContent(50)
}
positionViewAtBeginning();
}
DropArea {
id: dropAreaFile
anchors.fill: parent
onDropped: ChatBoxHelper.attachmentPath = drop.urls[0]
onDropped: chatBoxHelper.attachmentPath = drop.urls[0]
}
QQC2.Pane {
@@ -678,12 +390,6 @@ Kirigami.ScrollablePage {
MessageDelegateContextMenu {}
}
Component {
id: messageSourceSheet
MessageSourceSheet {}
}
Component {
id: fileDelegateContextMenu
@@ -705,8 +411,12 @@ Kirigami.ScrollablePage {
header: TypingPane {
id: typingPane
visible: !loadingIndicator.visible && currentRoom && currentRoom.usersTyping.length > 0
typingNotification: visible ? i18ncp("Message displayed when some users are typing", "%2 is typing", "%2 are typing", currentRoom.usersTyping.length, currentRoom.usersTyping.map(user => user.displayName).join(", ")) : ""
width: parent.width
labelText: visible ? i18ncp(
"Message displayed when some users are typing", "%2 is typing", "%2 are typing",
currentRoom.usersTyping.length,
currentRoom.usersTyping.map(user => user.displayName).join(", ")
) : ""
anchors.left: parent.left
height: visible ? implicitHeight : 0
Behavior on height {
NumberAnimation {
@@ -719,6 +429,105 @@ Kirigami.ScrollablePage {
}
headerPositioning: ListView.OverlayHeader
function goToEvent(eventID) {
const index = eventToIndex(eventID)
messageListView.positionViewAtIndex(index, ListView.Center)
itemAtIndex(index).isTemporaryHighlighted = true
}
Item {
id: hoverActions
property var event: null
property bool userMsg: event && event.author.id === Controller.activeConnection.localUserId
property bool showEdit: event && (userMsg && (event.eventType === "emote" || event.eventType === "message"))
property var delegate: null
property var bubble: null
property var hovered: bubble && bubble.hovered
property var visibleDelayed: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
onVisibleDelayedChanged: if (visibleDelayed) {
visible = true;
} else {
// HACK: delay disapearing by 200ms, otherwise this can create some glitches
// See https://invent.kde.org/network/neochat/-/issues/333
hoverActionsTimer.restart();
}
Timer {
id: hoverActionsTimer
interval: 200
onTriggered: hoverActions.visible = hoverActions.visibleDelayed;
}
property int childOffset: userMsg && Config.showLocalMessagesOnRight && !Config.compactLayout ? (bubble ? bubble.width : 0) - childWidth : Math.max((bubble ? bubble.width : 0) - childWidth, 0)
x: delegate && bubble ? (delegate.x + bubble.x + Kirigami.Units.largeSpacing + childOffset - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 + (delegate.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 : 0 ): 0) - (userMsg && !Config.compactLayout ? Kirigami.Units.gridUnit : 0)) : 0
y: bubble ? bubble.mapToItem(parent, 0, 0).y - hoverActions.childHeight + Kirigami.Units.smallSpacing: 0;
visible: false
property var updateFunction
property alias childWidth: hoverActionsRow.width
property alias childHeight: hoverActionsRow.height
RowLayout {
id: hoverActionsRow
z: 4
spacing: 0
HoverHandler {
id: hoverHandler
margin: Kirigami.Units.smallSpacing
}
Kirigami.Icon {
source: "security-high"
width: height
height: parent.height
visible: hoverActions.event.verified
HoverHandler {
id: hover
}
QQC2.ToolTip.text: i18n("This message was sent from a verified device")
QQC2.ToolTip.visible: hover.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Button {
QQC2.ToolTip.text: i18n("React")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "preferences-desktop-emoticons"
onClicked: emojiDialog.open();
EmojiDialog {
id: emojiDialog
onReact: {
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
chatBox.focusInputField();
}
}
}
QQC2.Button {
QQC2.ToolTip.text: i18n("Edit")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
visible: hoverActions.showEdit
icon.name: "document-edit"
onClicked: {
if (hoverActions.showEdit) {
chatBoxHelper.edit(hoverActions.event.message, hoverActions.event.formattedBody, hoverActions.event.eventId)
}
chatBox.focusInputField();
}
}
QQC2.Button {
QQC2.ToolTip.text: i18n("Reply")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
icon.name: "mail-replied-symbolic"
onClicked: {
chatBoxHelper.replyToMessage(hoverActions.event.eventId, hoverActions.event.message, hoverActions.event.author);
chatBox.focusInputField();
}
}
}
}
}
@@ -733,20 +542,21 @@ Kirigami.ScrollablePage {
onEditLastUserMessage: {
const targetMessage = messageEventModel.getLastLocalUserMessageEventId();
if (targetMessage) {
ChatBoxHelper.edit(targetMessage["body"], targetMessage["body"], targetMessage["event_id"]);
chatBoxHelper.edit(targetMessage["message"], targetMessage["formattedBody"], targetMessage["event_id"]);
chatBox.focusInputField();
}
}
onReplyPreviousUserMessage: {
const replyResponse = messageEventModel.getLatestMessageFromIndex(0);
if (replyResponse && replyResponse["event_id"]) {
ChatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["event"], replyResponse["sender_id"]);
chatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["message"], replyResponse["sender_id"]);
}
}
}
background: FancyEffectsContainer {
id: fancyEffectsContainer
z: 100
enabled: Config.showFancyEffects
@@ -783,7 +593,7 @@ Kirigami.ScrollablePage {
function warning(title, message) {
page.header.contentItem.text = `${title}<br />${message}`;
page.header.contentItem.type = Kirigami.MessageType.Warning;
page.header.contentItem.visible = true;
page.header.visible = true;
}
function showUserDetail(user) {
@@ -799,10 +609,6 @@ Kirigami.ScrollablePage {
messageListView.positionViewAtIndex(0, ListView.End)
}
function goToEvent(eventID) {
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
}
function eventToIndex(eventID) {
const index = messageEventModel.eventIDToIndex(eventID)
if (index === -1)
@@ -832,29 +638,40 @@ Kirigami.ScrollablePage {
return index;
}
// Mark all messages as read if all unread messages are visible to the user
function markReadIfVisible() {
let readMarkerRow = eventToIndex(currentRoom.readMarkerEventId)
if (readMarkerRow > 0 && readMarkerRow < firstVisibleIndex()) {
currentRoom.markAllMessagesAsRead()
}
}
/// Open message context dialog for file and videos
function openFileContext(author, message, eventId, source, progressInfo, file) {
function openFileContext(event, file) {
const contextMenu = fileDelegateContextMenu.createObject(page, {
author: author,
message: message,
eventId: eventId,
source: source,
author: event.author,
message: event.message,
eventId: event.eventId,
source: event.source,
file: file,
progressInfo: progressInfo,
mimeType: event.mimeType,
progressInfo: event.progressInfo,
plainMessage: event.message,
});
contextMenu.open();
}
/// Open context menu for normal message
function openMessageContext(author, message, eventId, source, eventType, formattedBody) {
console.log("message", message)
function openMessageContext(event, selectedText, plainMessage) {
const contextMenu = messageDelegateContextMenu.createObject(page, {
author: author,
message: message,
eventId: eventId,
formattedBody: formattedBody,
source: source,
eventType: eventType
selectedText: selectedText,
author: event.author,
message: event.display,
eventId: event.eventId,
formattedBody: event.formattedBody,
source: event.source,
eventType: event.eventType,
plainMessage: plainMessage,
});
contextMenu.open();
}

View File

@@ -12,8 +12,14 @@ Kirigami.ApplicationWindow {
required property var currentRoom
minimumWidth: Kirigami.Units.gridUnit * 10
minimumHeight: Kirigami.Units.gridUnit * 15
Shortcut {
sequence: StandardKey.Cancel
onActivated: window.close()
}
pageStack.initialPage: RoomPage {
visible: true
currentRoom: window.currentRoom
disableCancelShortcut: true
}
}

View File

@@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: 2020 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.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18n("Settings")
Kirigami.FormLayout {
QQC2.CheckBox {
Kirigami.FormData.label: i18n("General settings:")
text: i18n("Close to system tray")
checked: Config.systemTray
visible: Controller.supportSystemTray
onToggled: {
Config.systemTray = checked
Config.save()
}
}
QQC2.CheckBox {
// TODO: When there are enough notification and timeline event
// settings, make 2 separate groups with FormData labels.
Kirigami.FormData.label: i18n("Notifications and events:")
text: i18n("Show notifications")
checked: Config.showNotifications
onToggled: {
Config.showNotifications = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Show leave and join events")
checked: Config.showLeaveJoinEvent
onToggled: {
Config.showLeaveJoinEvent = checked
Config.save()
}
}
QQC2.RadioButton {
Kirigami.FormData.label: i18n("Rooms and private chats:")
text: i18n("Separated")
checked: !Config.mergeRoomList
onToggled: {
Config.mergeRoomList = false
Config.save()
}
}
QQC2.RadioButton {
text: i18n("Intermixed")
checked: Config.mergeRoomList
onToggled: {
Config.mergeRoomList = true
Config.save()
}
}
QQC2.CheckBox {
Kirigami.FormData.label: i18n("Timeline:")
text: i18n("Show User Avatar")
checked: Config.showAvatarInTimeline
onToggled: {
Config.showAvatarInTimeline = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Show Fancy Effects")
checked: Config.showFancyEffects
onToggled: {
Config.showFancyEffects = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Use s/text/replacement syntax to edit your last message")
checked: Config.allowQuickEdit
onToggled: {
Config.allowQuickEdit = checked
Config.save()
}
}
}
}

View File

@@ -30,13 +30,20 @@ Kirigami.ScrollablePage {
Connections {
target: LoginHelper
onErrorOccured: {
function onErrorOccured(message) {
headerMessage.text = message;
headerMessage.visible = true;
headerMessage.type = Kirigami.MessageType.Error;
}
}
Connections {
target: Controller
function onInitiated() {
pageStack.layers.pop();
}
}
ColumnLayout {
Kirigami.Icon {
source: "org.kde.neochat"

View File

@@ -5,6 +5,5 @@ RoomPage 1.0 RoomPage.qml
RoomWindow 1.0 RoomWindow.qml
JoinRoomPage 1.0 JoinRoomPage.qml
InviteUserPage 1.0 InviteUserPage.qml
SettingsPage 1.0 SettingsPage.qml
ImageEditorPage 1.0 ImageEditorPage.qml

View File

@@ -18,132 +18,195 @@ Kirigami.OverlayDrawer {
id: roomDrawer
readonly property var room: RoomManager.currentRoom
width: actualWidth
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
property int actualWidth: {
if (Config.roomDrawerWidth === -1) {
return Kirigami.Units.gridUnit * 20;
} else {
return Config.roomDrawerWidth
}
}
MouseArea {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: undefined
width: 2
z: 500
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
enabled: true
visible: true
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
onReleased: {
Config.roomDrawerWidth = roomDrawer.actualWidth;
Config.save();
}
property real _lastX: -1
onPositionChanged: {
if (_lastX === -1) {
return;
}
if (Qt.application.layoutDirection === Qt.RightToLeft) {
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
} else {
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
}
}
}
enabled: true
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
// If modal has been changed and the drawer is closed automatically then dim on popup open will have been switched off in main.qml so switch it back on after the animation completes.
// This is to avoid dim being active for a split second when the drawer is switched to modal which looks terrible.
onAnimatingChanged: if (dim === false) dim = undefined
topPadding: 0
leftPadding: 0
rightPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: Loader {
id: loader
active: roomDrawer.drawerOpen
sourceComponent: ColumnLayout {
id: columnLayout
anchors.fill: parent
property alias userSearchText: userListSearchField.text
property alias highlightedUser: userListView.currentIndex
spacing: 0
Kirigami.AbstractApplicationHeader {
Layout.fillWidth: true
topPadding: Kirigami.Units.smallSpacing / 2;
bottomPadding: Kirigami.Units.smallSpacing / 2;
rightPadding: Kirigami.Units.smallSpacing
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
RowLayout {
anchors.fill: parent
spacing: 0
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.fillWidth: true
text: i18n("Room information")
level: 1
}
ToolButton {
id: inviteButton
Layout.alignment: Qt.AlignRight
icon.name: "list-add-user"
text: i18n("Invite")
text: i18n("Invite user to room")
display: AbstractButton.IconOnly
onClicked: {
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {"room": room})
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
roomDrawer.close();
}
}
Item {
// HACK otherwise rating item is not right aligned
Layout.fillWidth: true
}
ToolTip {
text: inviteButton.text
}
}
ToolButton {
id: favouriteButton
Layout.alignment: Qt.AlignRight
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
checkable: true
checked: room && room.isFavourite
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
display: AbstractButton.IconOnly
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
ToolTip {
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
text: favouriteButton.text
}
}
ToolButton {
id: settingsButton
Layout.alignment: Qt.AlignRight
icon.name: 'settings-configure'
onClicked: {
roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
if (!wideScreen) {
roomDrawer.close();
}
}
text: i18n("Room settings")
display: AbstractButton.IconOnly
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
ToolTip {
text: i18n("Room settings")
text: settingsButton.text
}
}
}
}
Control {
ColumnLayout {
Layout.fillWidth: true
padding: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
id: infoLayout
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
RowLayout {
Layout.fillWidth: true
Kirigami.Heading {
text: i18n("Room information")
level: 3
Layout.leftMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
name: room ? room.name : i18n("No name")
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
}
RowLayout {
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.alignment: Qt.AlignVCenter
spacing: 0
spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
name: room ? room.name : i18n("No name")
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
}
ColumnLayout {
Kirigami.Heading {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 0
Kirigami.Heading {
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
Layout.fillWidth: true
level: 1
font.bold: true
wrapMode: Label.Wrap
text: room ? room.displayName : i18n("No name")
}
Label {
Layout.fillWidth: true
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
}
level: 1
type: Kirigami.Heading.Type.Primary
wrapMode: Label.Wrap
text: room ? room.displayName : i18n("No name")
textFormat: Text.PlainText
}
TextEdit {
Layout.fillWidth: true
textFormat: TextEdit.PlainText
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
readOnly: true
text: room && room.canonicalAlias ? room.canonicalAlias : i18n("No Canonical Alias")
}
}
}
TextEdit {
Layout.maximumWidth: Kirigami.Units.gridUnit * 13
Layout.preferredWidth: Kirigami.Units.gridUnit * 13
Layout.fillWidth: true
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
textFormat: TextEdit.MarkdownText
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
onLinkActivated: Qt.openUrlExternally(link)
readOnly: true
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
}
TextEdit {
Layout.fillWidth: true
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
readonly property var replaceLinks: /(https:\/\/[^ ]*)/
textFormat: TextEdit.MarkdownText
wrapMode: Text.WordWrap
selectByMouse: true
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
onLinkActivated: UrlHelper.openUrl(link)
readOnly: true
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
}
}
}
@@ -151,9 +214,32 @@ Kirigami.OverlayDrawer {
Kirigami.ListSectionHeader {
label: i18n("Members")
activeFocusOnTab: false
Label {
Layout.alignment: Qt.AlignRight
text: room ? i18np("%1 Member", "%1 Members", room.totalMemberCount) : i18n("No Member Count")
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
}
}
Control {
Layout.fillWidth: true
// Note need to set padding individually to guarantee it will always work
// see note - https://doc.qt.io/qt-6/qml-qtquick-controls2-control.html#padding-prop
topPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
}
contentItem: Kirigami.SearchField {
id: userListSearchField
onAccepted: sortedMessageEventModel.filterString = text;
}
}
@@ -161,23 +247,14 @@ Kirigami.OverlayDrawer {
Layout.fillWidth: true
Layout.fillHeight: true
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: userListView
clip: true
headerPositioning: ListView.OverlayHeader
boundsBehavior: Flickable.DragOverBounds
activeFocusOnTab: true
header: Pane {
padding: Kirigami.Units.smallSpacing
implicitWidth: parent.width
z: 2
contentItem: Kirigami.SearchField {
id: userListSearchField
onTextChanged: sortedMessageEventModel.filterString = text;
}
}
model: KSortFilterProxyModel {
id: sortedMessageEventModel
@@ -187,60 +264,56 @@ Kirigami.OverlayDrawer {
sortRole: "perm"
filterRole: "name"
filterCaseSensitivity: Qt.CaseInsensitive
}
delegate: Kirigami.AbstractListItem {
width: userListView.width
delegate: Kirigami.BasicListItem {
id: userListItem
implicitHeight: Kirigami.Units.gridUnit * 2
z: 1
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
contentItem: RowLayout {
Kirigami.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
visible: Config.showAvatarInTimeline
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
Label {
Layout.fillWidth: true
text: name
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
Label {
visible: perm != UserType.Member
text: {
if (perm == UserType.Owner) {
return i18n("Owner")
}
if (perm == UserType.Admin) {
return i18n("Admin")
}
if (perm == UserType.Moderator) {
return i18n("Mod")
}
if (perm == UserType.Muted) {
return i18n("Muted")
}
return ""
}
color: Kirigami.Theme.disabledTextColor
font.pixelSize: 12
textFormat: Text.PlainText
wrapMode: Text.NoWrap
label: name
onClicked: {
const popup = userDetailDialog.createObject(ApplicationWindow.overlay, {room: room, user: user, displayName: name, avatarMediaId: avatar})
popup.closed.connect(function() {
userListItem.highlighted = false
})
if (roomDrawer.modal) {
roomDrawer.close()
}
popup.open()
}
action: Kirigami.Action {
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
leading: Kirigami.Avatar {
implicitWidth: height
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : ""
name: name
}
trailing: Label {
visible: perm != UserType.Member
text: {
switch (perm) {
case UserType.Owner:
return i18n("Owner");
case UserType.Admin:
return i18n("Admin");
case UserType.Moderator:
return i18n("Mod");
case UserType.Muted:
return i18n("Muted");
default:
return "";
}
}
color: Kirigami.Theme.disabledTextColor
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
}
}
@@ -249,17 +322,15 @@ Kirigami.OverlayDrawer {
}
onRoomChanged: {
if (loader.active) {
loader.item.userSearchText = ""
loader.item.highlightedUser = -1
}
if (room == null) {
close()
}
}
Component {
id: roomSettingDialog
RoomSettingsDialog {}
}
Component {
id: userDetailDialog

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
id: root
property var room
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
text: i18n("General")
icon.name: "settings-configure"
page: Qt.resolvedUrl("General.qml")
initialProperties: {
return {
room: root.room
}
}
},
Kirigami.SettingAction {
text: i18n("Security")
icon.name: "security-low"
page: Qt.resolvedUrl("Security.qml")
initialProperties: {
return {
room: root.room
}
}
},
Kirigami.SettingAction {
text: i18n("Notifications")
icon.name: "notifications"
page: Qt.resolvedUrl("PushNotification.qml")
initialProperties: {
return {
room: root.room
}
}
}
]
}

View File

@@ -0,0 +1,202 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
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
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
id: root
property var room
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
readonly property bool canChangeName: room.canSendState("m.room.name")
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
title: i18n("General")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
Kirigami.Avatar {
Layout.bottomMargin: Kirigami.Units.largeSpacing
name: room.name
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
RoundButton {
anchors.right: parent.right
anchors.bottom: parent.bottom
height: Kirigami.Units.gridUnits
width: Kirigami.Units.gridUnits
icon.name: 'cloud-upload'
Accessible.name: i18n("Update avatar")
enabled: canChangeAvatar
onClicked: {
const fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect(function(path) {
if (!path) return
room.changeAvatar(path)
})
fileDialog.open()
}
}
}
TextField {
id: roomNameField
text: room.name
Kirigami.FormData.label: i18n("Room Name:")
enabled: canChangeName
}
TextArea {
id: roomTopicField
Layout.fillWidth: true
text: room.topic
Kirigami.FormData.label: i18n("Room topic:")
enabled: canChangeTopic
}
Kirigami.Separator {
Layout.fillWidth: true
visible: canonicalAliasComboBox.visible || altAlias.visible
}
ComboBox {
id: canonicalAliasComboBox
visible: room.aliases && room.aliases.length
Kirigami.FormData.label: i18n("Canonical Alias:")
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
enabled: canChangeCanonicalAlias
model: room.aliases
currentIndex: room.aliases.indexOf(room.canonicalAlias)
onCurrentIndexChanged: {
if (room.canonicalAlias != room.aliases[currentIndex]) {
room.setCanonicalAlias(room.aliases[currentIndex])
}
}
}
RowLayout {
id: altAlias
Kirigami.FormData.label: i18n("Other Aliases:")
Layout.fillWidth: true
visible: room.altAliases && room.altAliases.length
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Repeater {
model: room.altAliases
delegate: RowLayout {
Layout.maximumWidth: parent.width
Label {
text: modelData
}
ToolButton {
icon.name: ""
onClicked: room.removeLocalAlias(modelData)
}
}
}
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
visible: next.visible || prev.visible
}
Control {
id: next
Layout.fillWidth: true
visible: room.predecessorId && room.connection.room(room.predecessorId)
padding: Kirigami.Units.largeSpacing
contentItem: Kirigami.InlineMessage {
text: i18n("This room continues another conversation.")
actions: Kirigami.Action {
text: i18n("See older messages...")
onTriggered: {
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
root.close()
}
}
}
}
Control {
id: prev
Layout.fillWidth: true
visible: room.successorId && room.connection.room(room.successorId)
padding: Kirigami.Units.largeSpacing
contentItem: Kirigami.InlineMessage {
text: i18n("This room has been replaced.")
actions: Kirigami.Action {
text: i18n("See new room...")
onTriggered: {
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
root.close()
}
}
}
}
Component {
id: openFileDialog
OpenFileDialog {}
}
}
footer: ToolBar {
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
enabled: room.name !== roomNameField.text || room.topic !== roomTopicField.text
text: i18n("Apply")
onClicked: {
if (room.name != roomNameField.text) {
room.setName(roomNameField.text)
}
if (room.topic != roomTopicField.text) {
room.setTopic(roomTopicField.text)
}
}
}
}
}
}

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

@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
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
import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
id: root
property var room
title: i18n("Security")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
RadioButton {
text: i18nc("@option:check", "Private (invite only)")
Kirigami.FormData.label: i18nc("@option:check", "Access:")
checked: room.joinRule === "invite"
enabled: false
}
Label {
text: i18n("Only invited people can join.")
font: Kirigami.Theme.smallFont
}
RadioButton {
text: i18nc("@option:check", "Space members")
checked: room.joinRule === "restricted"
enabled: false
}
Label {
text: i18n("Anyone in a space can find and join.")
font: Kirigami.Theme.smallFont
}
RadioButton {
text: i18nc("@option:check", "Public")
checked: room.joinRule === "public"
enabled: false
}
Label {
text: i18nc("@option:check", "Anyone can find and join.") + room.joinRule
font: Kirigami.Theme.smallFont
}
}
}
footer: ToolBar {
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
Button {
Layout.alignment: Qt.AlignRight
enabled: false
text: i18n("Apply")
}
}
}
}

View File

@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.0-or-later
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.AboutPage {
title: i18nc("@title:window", "About NeoChat")
aboutData: Controller.aboutData
}

View File

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

View File

@@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// 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 {
title: i18n("Accounts")
actions.main: Kirigami.Action {
text: i18n("Add an account")
icon.name: "list-add-user"
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
visible: !pageSettingStack.wideMode
}
ListView {
model: AccountRegistry
anchors.fill: parent
delegate: Kirigami.BasicListItem {
text: model.connection.localUser.displayName
labelItem.textFormat: Text.PlainText
subtitle: model.connection.localUserId
icon: model.connection.localUser.avatarMediaId ? ("image://mxc/" + model.connection.localUser.avatarMediaId) : "im-user"
onClicked: {
Controller.activeConnection = model.connection;
pageStack.layers.pop();
}
trailing: RowLayout {
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action {
text: i18n("Edit this account")
iconName: "document-edit"
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
connection: model.connection
}, {
title: i18n("Account editor")
});
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
Controls.ToolTip {
text: parent.action.text
}
action: Kirigami.Action {
text: i18n("Logout")
iconName: "im-kick-user"
onTriggered: {
Controller.logout(model.connection, true);
if (Controller.accountCount === 1) {
pageStack.layers.pop();
}
}
}
}
}
}
}
footer: Controls.ToolBar {
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.ActionToolBar {
alignment: Qt.AlignRight
rightPadding: Kirigami.Units.smallSpacing
width: parent.width
flat: false
actions: Kirigami.Action {
text: i18n("Add an account")
icon.name: "list-add-user"
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
}
}
}
Connections {
target: Controller
function onConnectionAdded() {
if (pageStack.layers.depth > 2)
pageStack.layers.pop()
}
function onPasswordStatus(status) {
if (status === Controller.Success) {
showPassiveNotification(i18n("Password changed successfully"));
} else if (status === Controller.Wrong) {
showPassiveNotification(i18n("Wrong password entered"));
} else {
showPassiveNotification(i18n("Unknown problem while trying to change password"));
}
}
}
property Component openFileDialog: Component {
id: openFileDialog
OpenFileDialog {
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
}
}
}

View File

@@ -0,0 +1,257 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// 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
import NeoChat.Settings 1.0
Kirigami.ScrollablePage {
title: i18nc("@title:window", "Appearance")
ColumnLayout {
RowLayout {
Layout.alignment: Qt.AlignCenter
spacing: Kirigami.Units.gridUnit * 2
QQC2.ButtonGroup { id: themeGroup }
ThemeRadioButton {
innerObject: [
RowLayout {
Layout.fillWidth: true
Kirigami.Avatar {
color: "#4a5bcc"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
QQC2.Control {
Layout.fillWidth: true
contentItem: ColumnLayout {
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Paul Müller"
color: "#4a5bcc"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
}
},
RowLayout {
Layout.fillWidth: true
Kirigami.Avatar {
color: "#9f244b"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
QQC2.Control {
Layout.fillWidth: true
contentItem: ColumnLayout {
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Jean Paul"
color: "#9f244b"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta , quis sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
}
}
]
text: i18n("Bubbles")
checked: !Config.compactLayout
QQC2.ButtonGroup.group: themeGroup
enabled: !Config.isCompactLayoutImmutable
onToggled: {
Config.compactLayout = !checked;
Config.save();
}
}
ThemeRadioButton {
innerObject: [
RowLayout {
Layout.fillWidth: true
Kirigami.Avatar {
color: "#4a5bcc"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
ColumnLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Paul Müller"
color: "#4a5bcc"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur elit. Vivamus facilisis porta mauris, finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
},
RowLayout {
Layout.fillWidth: true
Kirigami.Avatar {
color: "#9f244b"
Layout.alignment: Qt.AlignTop
visible: Config.showAvatarInTimeline
Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
ColumnLayout {
Layout.fillWidth: true
QQC2.Label {
Layout.fillWidth: true
font.weight: Font.Bold
font.pixelSize: 7
text: "Jean Paul"
color: "#9f244b"
wrapMode: Text.Wrap
}
QQC2.Label {
Layout.fillWidth: true
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt."
wrapMode: Text.Wrap
font.pixelSize: 7
}
}
}
]
text: i18n("Compact")
checked: Config.compactLayout
QQC2.ButtonGroup.group: themeGroup
enabled: !Config.isCompactLayoutImmutable
onToggled: {
Config.compactLayout = checked;
Config.save();
}
}
}
Kirigami.FormLayout {
Layout.maximumWidth: parent.width
QQC2.CheckBox {
Kirigami.FormData.label: i18n("Show Avatar:")
text: i18n("In Chat")
checked: Config.showAvatarInTimeline
onToggled: {
Config.showAvatarInTimeline = checked
Config.save()
}
enabled: !Config.isShowAvatarInTimelineImmutable
}
QQC2.CheckBox {
text: i18n("In Sidebar")
checked: Config.showAvatarInRoomDrawer
enabled: !Config.isShowAvatarInRoomDrawerImmutable
onToggled: {
Config.showAvatarInRoomDrawer = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Show Fancy Effects")
checked: Config.showFancyEffects
enabled: !Config.isShowFancyEffectsImmutable
onToggled: {
Config.showFancyEffects = checked;
Config.save();
}
}
Loader {
visible: item !== null
Kirigami.FormData.label: item ? i18n("Theme:") : ""
source: "qrc:/imports/NeoChat/Settings/ColorScheme.qml"
}
QQC2.CheckBox {
visible: Controller.hasWindowSystem
text: i18n("Use transparent chat page")
enabled: !Config.compactLayout && !Config.isBlurImmutable
checked: Config.blur
onToggled: {
Config.blur = checked;
Config.save();
}
}
RowLayout {
visible: Controller.hasWindowSystem && Config.blur
enabled: !Config.isTransparancyImmutable
Kirigami.FormData.label: i18n("Transparency:")
QQC2.Slider {
enabled: !Config.compactLayout && Config.blur
from: 0
to: 1
stepSize: 0.05
value: Config.transparency
onMoved: {
Config.transparency = value;
Config.save();
}
HoverHandler { id: sliderHover }
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
text: Math.round(Config.transparency * 100) + "%"
}
}
QQC2.CheckBox {
text: i18n("Show your messages on the right")
checked: Config.showLocalMessagesOnRight
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
onToggled: {
Config.showLocalMessagesOnRight = checked
Config.save()
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-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
import NeoChat.Settings 1.0
QQC2.ComboBox {
textRole: "display"
model: ColorSchemer.model
Component.onCompleted: currentIndex = ColorSchemer.indexForScheme(Config.colorScheme);
onActivated: {
ColorSchemer.apply(currentIndex);
Config.colorScheme = ColorSchemer.nameForIndex(currentIndex);
Config.save();
}
}

View File

@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
// 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 org.kde.kirigami 2.19 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18n("Devices")
ListView {
model: DevicesModel {
id: devices
}
anchors.fill: parent
Kirigami.LoadingPlaceholder {
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
anchors.centerIn: parent
}
delegate: Kirigami.BasicListItem {
text: model.displayName
subtitle: model.id
icon: "network-connect"
trailing: RowLayout {
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18n("Edit device name")
iconName: "document-edit"
onTriggered: {
renameSheet.index = model.index
renameSheet.name = model.displayName
renameSheet.open()
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
visible: Controller.encryptionSupported
action: Kirigami.Action {
text: i18n("Verify device")
iconName: "security-low-symbolic"
onTriggered: {
devices.connection.startKeyVerificationSession(model.id)
}
}
}
Controls.ToolButton {
display: Controls.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18n("Logout device")
iconName: "edit-delete-remove"
onTriggered: {
passwordSheet.index = index
passwordSheet.open()
}
}
}
}
}
}
Kirigami.OverlaySheet {
id: passwordSheet
property var index
title: i18n("Remove device")
Kirigami.FormLayout {
Controls.TextField {
id: passwordField
Kirigami.FormData.label: i18n("Password:")
echoMode: TextInput.Password
}
Controls.Button {
text: i18n("Confirm")
onClicked: {
devices.logout(passwordSheet.index, passwordField.text)
passwordField.text = ""
passwordSheet.close()
}
}
}
}
Kirigami.OverlaySheet {
id: renameSheet
property int index
property string name
title: i18n("Edit device")
Kirigami.FormLayout {
Controls.TextField {
id: nameField
Kirigami.FormData.label: i18n("Name:")
text: renameSheet.name
}
Controls.Button {
text: i18n("Save")
onClicked: {
devices.setName(renameSheet.index, nameField.text)
renameSheet.close()
}
}
}
}
}

View File

@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
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.Settings 1.0
import NeoChat.Component 1.0 as Components
import NeoChat.Dialog 1.0
Kirigami.ScrollablePage {
title: i18nc("@title:window", "Custom Emojis")
ListView {
anchors.fill: parent
model: CustomEmojiModel {
id: emojiModel
connection: Controller.activeConnection
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
text: i18n("No custom inline stickers found")
visible: parent.model.count === 0
}
delegate: Kirigami.BasicListItem {
id: del
required property string name
required property url imageURL
text: name
reserveSpaceForSubtitle: true
leading: Image {
width: height
sourceSize.width: width
sourceSize.height: height
source: imageURL
Rectangle {
anchors.fill: parent
visible: parent.status === Image.Loading
radius: height/2
gradient: Components.ShimmerGradient { }
}
}
trailing: QQC2.ToolButton {
width: height
icon.name: "delete"
onClicked: emojiModel.removeEmoji(del.name)
}
}
}
footer: QQC2.ToolBar {
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.ActionToolBar {
id: emojiCreator
alignment: Qt.AlignRight
rightPadding: Kirigami.Units.smallSpacing
width: parent.width
flat: false
property string name
actions: [
Kirigami.Action {
displayComponent: QQC2.TextField {
id: emojiField
placeholderText: i18n("new_emoji_name_here")
validator: RegularExpressionValidator {
regularExpression: /[a-zA-Z_0-9]*/
}
onTextChanged: emojiCreator.name = text
}
},
Kirigami.Action {
text: i18n("Add Emoji...")
enabled: emojiCreator.name.length > 0
property var fileDialog: null
icon.name: 'list-add'
onTriggered: {
if (this.fileDialog !== null) {
return;
}
this.fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay)
this.fileDialog.chosen.connect((url) => {
emojiModel.addEmoji(emojiCreator.name, url)
this.fileDialog = null
})
this.fileDialog.onRejected.connect(() => {
this.fileDialog = null
})
this.fileDialog.open()
}
}
]
}
}
Component {
id: openFileDialog
OpenFileDialog {
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
}
}
}

View File

@@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// 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 {
title: i18nc("@title:window", "General")
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.CheckBox {
Kirigami.FormData.label: i18n("General settings:")
text: i18n("Close to system tray")
checked: Config.systemTray
visible: Controller.supportSystemTray
enabled: !Config.isSystemTrayImmutable
onToggled: {
Config.systemTray = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Minimize to system tray on startup")
checked: Config.minimizeToSystemTrayOnStartup
visible: Controller.supportSystemTray && !Kirigami.Settings.isMobile
enabled: Config.systemTray && !Config.isMinimizeToSystemTrayOnStartupImmutable
onToggled: {
Config.minimizeToSystemTrayOnStartup = checked
Config.save()
}
}
QQC2.CheckBox {
// TODO: When there are enough notification and timeline event
// settings, make 2 separate groups with FormData labels.
Kirigami.FormData.label: i18n("Notifications and events:")
text: i18n("Show notifications")
checked: Config.showNotifications
enabled: !Config.isShowNotificationsImmutable
onToggled: {
Config.showNotifications = checked
Config.save()
NotificationsManager.globalNotificationsEnabled = checked
}
}
QQC2.CheckBox {
text: i18n("Show leave and join events")
checked: Config.showLeaveJoinEvent
enabled: !Config.isShowLeaveJoinEventImmutable
onToggled: {
Config.showLeaveJoinEvent = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Show name change events")
checked: Config.showRename
enabled: !Config.isShowRenameImmutable
onToggled: {
Config.showRename = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Show avatar update events")
checked: Config.showAvatarUpdate
enabled: !Config.isShowAvatarUpdateImmutable
onToggled: {
Config.showAvatarUpdate = checked
Config.save()
}
}
QQC2.RadioButton {
Kirigami.FormData.label: i18n("Rooms and private chats:")
text: i18n("Separated")
checked: !Config.mergeRoomList
enabled: !Config.isMergeRoomListImmutable
onToggled: {
Config.mergeRoomList = false
Config.save()
}
}
QQC2.RadioButton {
text: i18n("Intermixed")
checked: Config.mergeRoomList
enabled: !Config.isMergeRoomListImmutable
onToggled: {
Config.mergeRoomList = true
Config.save()
}
}
QQC2.CheckBox {
id: quickEditCheckbox
Layout.maximumWidth: parent.width
text: i18n("Use s/text/replacement syntax to edit your last message")
checked: Config.allowQuickEdit
enabled: !Config.isAllowQuickEditImmutable
onToggled: {
Config.allowQuickEdit = checked
Config.save()
}
// TODO KF5.97 remove this line
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
}
QQC2.CheckBox {
text: i18n("Send Typing Notifications")
checked: Config.typingNotifications
enabled: !Config.isTypingNotificationsImmutable
onToggled: {
Config.typingNotifications = checked
Config.save()
}
}
QQC2.CheckBox {
text: i18n("Automatically hide/unhide the room information when resizing the window")
Layout.maximumWidth: parent.width
checked: Config.autoRoomInfoDrawer
enabled: !Config.isAutoRoomInfoDrawerImmutable
onToggled: {
Config.autoRoomInfoDrawer = checked
Config.save()
}
// TODO KF5.97 remove this line
Component.onCompleted: this.contentItem.wrap = QQC2.Label.Wrap
}
}
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
text: i18n("General")
icon.name: "org.kde.neochat"
page: Qt.resolvedUrl("GeneralSettingsPage.qml")
},
Kirigami.SettingAction {
text: i18n("Appearance")
icon.name: "preferences-desktop-theme-global"
page: Qt.resolvedUrl("AppearanceSettingsPage.qml")
},
Kirigami.SettingAction {
text: i18n("Accounts")
icon.name: "preferences-system-users"
page: Qt.resolvedUrl("AccountsPage.qml")
},
Kirigami.SettingAction {
text: i18n("Custom Emojis")
icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("Emoticons.qml")
},
Kirigami.SettingAction {
text: i18n("Spell Checking")
iconName: "tools-check-spelling"
page: Qt.resolvedUrl("SonnetConfigPage.qml")
},
Kirigami.SettingAction {
text: i18n("Devices")
iconName: "network-connect"
page: Qt.resolvedUrl("DevicesPage.qml")
},
Kirigami.SettingAction {
text: i18n("About NeoChat")
icon.name: "help-about"
page: Qt.resolvedUrl("About.qml")
}
]
}

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