Compare commits
373 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2839d44ea8 | ||
|
|
5f32ae79c1 | ||
|
|
0fc94310c3 | ||
|
|
7f26651edf | ||
|
|
36880d001c | ||
|
|
e0e289d424 | ||
|
|
b79956871f | ||
|
|
917f77152d | ||
|
|
e28d1918f5 | ||
|
|
149b11ba6f | ||
|
|
c007961ef6 | ||
|
|
ef40f5a747 | ||
|
|
37780c2e3b | ||
|
|
53b9f42399 | ||
|
|
d2ed1cfb2e | ||
|
|
c7d4b1a529 | ||
|
|
a8045f2134 | ||
|
|
b8262fef92 | ||
|
|
3071901a47 | ||
|
|
396260549a | ||
|
|
50c2de8d52 | ||
|
|
d4e067d57c | ||
|
|
3797b0129a | ||
|
|
59b4146c63 | ||
|
|
c76524d540 | ||
|
|
10030efd08 | ||
|
|
918bd5439c | ||
|
|
e3ff50bbe8 | ||
|
|
76edc858aa | ||
|
|
2a2c117ac1 | ||
|
|
d3e4640af9 | ||
|
|
7e1f0f4ea7 | ||
|
|
80bf279321 | ||
|
|
6eb4b8c9d2 | ||
|
|
4bba505da6 | ||
|
|
c2fc4e44a7 | ||
|
|
274bf824e3 | ||
|
|
d70a8a652a | ||
|
|
8c436075d8 | ||
|
|
828032ba06 | ||
|
|
c30abfefa9 | ||
|
|
5d4efad2f8 | ||
|
|
3a391aafe8 | ||
|
|
7b5b8197bc | ||
|
|
55659e488f | ||
|
|
c2c235eff1 | ||
|
|
f6f4edec86 | ||
|
|
55847cb9cc | ||
|
|
736c4b02ed | ||
|
|
4cf5b516d0 | ||
|
|
c379a7fa27 | ||
|
|
2318fb95d9 | ||
|
|
14e57e7833 | ||
|
|
8c9ea72a9b | ||
|
|
efd3f2e8d0 | ||
|
|
ea70782771 | ||
|
|
6cca3dbe3a | ||
|
|
696b34e094 | ||
|
|
6ce74bbd0c | ||
|
|
3c7e85fbbf | ||
|
|
6c6e408497 | ||
|
|
1ac79273b6 | ||
|
|
d4d99284cc | ||
|
|
25226aa61f | ||
|
|
6748a2d21d | ||
|
|
fdb424e65e | ||
|
|
8cd0b12c4a | ||
|
|
d133c4fab7 | ||
|
|
2f8303348b | ||
|
|
7dfac8a9f7 | ||
|
|
3c98a8fac4 | ||
|
|
57b1bb659c | ||
|
|
da8c8c48bc | ||
|
|
07c5cd8016 | ||
|
|
a23ef130ca | ||
|
|
2f4116796a | ||
|
|
f004f9e3c8 | ||
|
|
9a3726f9ff | ||
|
|
91d1f6ffeb | ||
|
|
cd895f1b06 | ||
|
|
fead1a69b3 | ||
|
|
63af4cae77 | ||
|
|
89090690b4 | ||
|
|
6a16334eab | ||
|
|
5b05622058 | ||
|
|
1eb705c17f | ||
|
|
f9ad0e8426 | ||
|
|
ef1ff04ef2 | ||
|
|
9b54aff3d5 | ||
|
|
52a093d449 | ||
|
|
619369e148 | ||
|
|
e63a9a9be1 | ||
|
|
5c97e67404 | ||
|
|
84a265b7f9 | ||
|
|
61201a7097 | ||
|
|
b9630ad2f2 | ||
|
|
179a201113 | ||
|
|
6eef58e57d | ||
|
|
94f325609a | ||
|
|
a40ba493b6 | ||
|
|
916e7465f1 | ||
|
|
8257a9d65e | ||
|
|
a75048761b | ||
|
|
f21822aba7 | ||
|
|
75eb5a51af | ||
|
|
7e37c31011 | ||
|
|
f80039a5c4 | ||
|
|
400d86a1e9 | ||
|
|
b20b1c10d0 | ||
|
|
a37fd0713f | ||
|
|
b1581a54d1 | ||
|
|
ded60b906b | ||
|
|
6957dd0fa2 | ||
|
|
7de4014b28 | ||
|
|
63e7ec1bd7 | ||
|
|
f24428fab3 | ||
|
|
c3a5a767c2 | ||
|
|
5fb311b509 | ||
|
|
f0d832f756 | ||
|
|
a7c137ca39 | ||
|
|
a96e8958c9 | ||
|
|
7dc951d2cd | ||
|
|
c3ee277ede | ||
|
|
78d62e9376 | ||
|
|
11e9eaf3e9 | ||
|
|
4337d0d5d8 | ||
|
|
a07537367f | ||
|
|
51efecaa25 | ||
|
|
830a47c5ff | ||
|
|
24748d42d8 | ||
|
|
19fe439e95 | ||
|
|
2bcd7118f4 | ||
|
|
27e660178e | ||
|
|
e0df553a72 | ||
|
|
53f040cb28 | ||
|
|
28cc7cf616 | ||
|
|
d224df8aa2 | ||
|
|
1dff2b8273 | ||
|
|
722aa422e7 | ||
|
|
70de0dc624 | ||
|
|
9d804e6ea7 | ||
|
|
52d552650d | ||
|
|
0c5007fd56 | ||
|
|
af19829225 | ||
|
|
0be8828dd4 | ||
|
|
729b6bd354 | ||
|
|
ef10042179 | ||
|
|
846d430947 | ||
|
|
cce4a3ebdf | ||
|
|
5b848961bc | ||
|
|
51574f5125 | ||
|
|
a779907500 | ||
|
|
f195db323d | ||
|
|
da47d76a7f | ||
|
|
6dc8c4976c | ||
|
|
fec7680068 | ||
|
|
b84264891d | ||
|
|
465a981033 | ||
|
|
06b4c40b33 | ||
|
|
efae510fda | ||
|
|
f9fc8c5c0b | ||
|
|
49c9c63bf5 | ||
|
|
90cee0f437 | ||
|
|
5bc9362fde | ||
|
|
10922aeb52 | ||
|
|
f9a96ccdab | ||
|
|
04056d9ed1 | ||
|
|
ecf373e317 | ||
|
|
9c2e0669f6 | ||
|
|
2b8aa9f975 | ||
|
|
2f61090413 | ||
|
|
aa9daad704 | ||
|
|
0e79d3506d | ||
|
|
72994d0349 | ||
|
|
0ee5ba76c9 | ||
|
|
d1398f6726 | ||
|
|
9084817450 | ||
|
|
083a2f9772 | ||
|
|
b44e81c849 | ||
|
|
014826bd09 | ||
|
|
2858fcfad2 | ||
|
|
b6db36a9f2 | ||
|
|
ede860c99f | ||
|
|
f8951fc760 | ||
|
|
525015fe78 | ||
|
|
b834510be0 | ||
|
|
8700611235 | ||
|
|
15ddcef115 | ||
|
|
7216da8b6f | ||
|
|
7bd4aac692 | ||
|
|
10e17d9f0f | ||
|
|
db5e328869 | ||
|
|
921667565e | ||
|
|
9cd8a380ed | ||
|
|
29816730e4 | ||
|
|
7214936eaa | ||
|
|
5a7c3295dc | ||
|
|
dce4a409c7 | ||
|
|
bd27904f17 | ||
|
|
731b234dda | ||
|
|
ce0fc637c4 | ||
|
|
070fe45a2d | ||
|
|
3a8d078e6c | ||
|
|
fb9183e5c3 | ||
|
|
853113df3f | ||
|
|
e62288e6f1 | ||
|
|
4f978a950b | ||
|
|
36b2868933 | ||
|
|
1763dc13c5 | ||
|
|
b7e4c2c6a2 | ||
|
|
5969612ead | ||
|
|
0d00d4200c | ||
|
|
35f30c293b | ||
|
|
77e20ec446 | ||
|
|
101b57c581 | ||
|
|
b994907be4 | ||
|
|
97ce81daca | ||
|
|
4e61c5e53c | ||
|
|
6871ed051c | ||
|
|
10da870ab3 | ||
|
|
6b5f76296a | ||
|
|
93a4930301 | ||
|
|
7b393f2681 | ||
|
|
fb6266fa15 | ||
|
|
334b245669 | ||
|
|
3a969189b8 | ||
|
|
cef5d11130 | ||
|
|
216c751d81 | ||
|
|
154109dde1 | ||
|
|
312db10439 | ||
|
|
f4f540e805 | ||
|
|
b3ca71580f | ||
|
|
2fc2ac113e | ||
|
|
2ea95ea080 | ||
|
|
22168dcef9 | ||
|
|
e25ffd0c41 | ||
|
|
5595d8f896 | ||
|
|
abed37518d | ||
|
|
57493e87ee | ||
|
|
1bcff6503f | ||
|
|
98571cb37d | ||
|
|
b64cd3c1b8 | ||
|
|
69ced8406b | ||
|
|
1f551b5f59 | ||
|
|
48a2a793c8 | ||
|
|
be116e1ba7 | ||
|
|
3396f831d4 | ||
|
|
a0f6170539 | ||
|
|
731c6f924c | ||
|
|
3011c3d885 | ||
|
|
709b2c8fd9 | ||
|
|
0cfa87e23d | ||
|
|
538ed7dd02 | ||
|
|
180a754e67 | ||
|
|
81ba5f6ee5 | ||
|
|
a15b406cff | ||
|
|
d0bc8f3d05 | ||
|
|
c83f4b4f75 | ||
|
|
0f5425e030 | ||
|
|
f381cc4623 | ||
|
|
decd528079 | ||
|
|
0c5bd57976 | ||
|
|
7362b90c42 | ||
|
|
aef6d6fc85 | ||
|
|
432e209b16 | ||
|
|
a72cac5ea3 | ||
|
|
b9152dc93c | ||
|
|
e5791970da | ||
|
|
c157625645 | ||
|
|
026c7660bc | ||
|
|
be10e66974 | ||
|
|
024fb1a97a | ||
|
|
e4c8b6b676 | ||
|
|
863508b629 | ||
|
|
ef5550bafd | ||
|
|
1cc8d915bc | ||
|
|
9a5f2e4938 | ||
|
|
a747d44cac | ||
|
|
334c13b36c | ||
|
|
aac96da2e2 | ||
|
|
12f3f72a67 | ||
|
|
62f6cfbf9a | ||
|
|
c59e3db1dd | ||
|
|
9252e0e65e | ||
|
|
80ee5e9356 | ||
|
|
be802a28c2 | ||
|
|
b2a8430fa2 | ||
|
|
db5f328539 | ||
|
|
9ac1fbd99b | ||
|
|
022951a9df | ||
|
|
47a0d30e57 | ||
|
|
faeb1964bd | ||
|
|
db8b2fd64b | ||
|
|
37c7fe380b | ||
|
|
537a1e44b1 | ||
|
|
dc9d574b58 | ||
|
|
7c807e6a25 | ||
|
|
f74c6a41ae | ||
|
|
7d5a8c87a1 | ||
|
|
ca719b835e | ||
|
|
9b5ad3a3a0 | ||
|
|
fdfbbb1b04 | ||
|
|
dd91cb91d0 | ||
|
|
290b2249c4 | ||
|
|
8b8e521c56 | ||
|
|
cba88e1af7 | ||
|
|
1661d34d7c | ||
|
|
dc3b1a3c87 | ||
|
|
f55dc19d95 | ||
|
|
6014c15b4f | ||
|
|
a5f835b1eb | ||
|
|
1fd6b615ff | ||
|
|
dd4ef2e4ac | ||
|
|
3b73409b7a | ||
|
|
335ef240f5 | ||
|
|
ca8a21c0eb | ||
|
|
3e6f38c8ea | ||
|
|
a6ab447955 | ||
|
|
0b7dcd70ac | ||
|
|
bce560b03b | ||
|
|
5a1198d28c | ||
|
|
b236e61ea7 | ||
|
|
79faeb21c9 | ||
|
|
6ac6234886 | ||
|
|
fc9e4fc961 | ||
|
|
24644887e0 | ||
|
|
a29ec0a18a | ||
|
|
9300e65239 | ||
|
|
ee59006c08 | ||
|
|
ca8702fd5e | ||
|
|
183c3227a9 | ||
|
|
e86f70db85 | ||
|
|
00d8fb75e3 | ||
|
|
c3d149765c | ||
|
|
200281702a | ||
|
|
297684a139 | ||
|
|
aeee367e82 | ||
|
|
aa9dcc87cb | ||
|
|
8a70e240e4 | ||
|
|
50a7df8e03 | ||
|
|
dd977976db | ||
|
|
de666b9377 | ||
|
|
5f41378214 | ||
|
|
383b31c185 | ||
|
|
de2fbadba5 | ||
|
|
67bc66ee0c | ||
|
|
924a1fed21 | ||
|
|
b0a1de7572 | ||
|
|
ca2b5fde8e | ||
|
|
26f0cd4cf4 | ||
|
|
0801b815c8 | ||
|
|
28137c8c86 | ||
|
|
e79e06235f | ||
|
|
dce7fde7a6 | ||
|
|
8d9f3b8658 | ||
|
|
5e1adf7ea7 | ||
|
|
d71ccc46d0 | ||
|
|
284a1734ae | ||
|
|
8722c99c93 | ||
|
|
0c5932b3da | ||
|
|
332d6c9782 | ||
|
|
91f3f64bb5 | ||
|
|
599ab11656 | ||
|
|
ff707b7a58 | ||
|
|
e551319245 | ||
|
|
59430cce89 | ||
|
|
d1bbb5e3f7 | ||
|
|
6e1c07047e | ||
|
|
738270f513 | ||
|
|
9496127e88 | ||
|
|
16d43e9ee8 | ||
|
|
d0e04e0c97 | ||
|
|
658eb187c9 |
157
.flatpak-manifest.json
Normal file
157
.flatpak-manifest.json
Normal 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,6 +2,11 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-reuse.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/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
|
||||
|
||||
@@ -23,3 +23,6 @@ Dependencies:
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@stable'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux', 'Windows' ]
|
||||
|
||||
13
.reuse/dep5
13
.reuse/dep5
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ Files: .gitlab/issue_templates/bug.md
|
||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: res.qrc
|
||||
Files: res.qrc res_android.qrc res_desktop.qrc
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
||||
@@ -31,14 +31,17 @@ Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/neochatconfig.kcfg
|
||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: neochat.notifyrc
|
||||
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
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(NeoChat)
|
||||
set(PROJECT_VERSION "21.12")
|
||||
set(PROJECT_VERSION "22.09")
|
||||
|
||||
set(KF5_MIN_VERSION "5.88.0")
|
||||
set(KF5_MIN_VERSION "5.91.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
|
||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -24,28 +24,27 @@ 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(${PROJECT_VERSION}
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt5 PROPERTIES
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
@@ -59,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"
|
||||
)
|
||||
@@ -72,11 +71,12 @@ if(ANDROID)
|
||||
PURPOSE "Encrypted communications"
|
||||
)
|
||||
else()
|
||||
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
|
||||
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
)
|
||||
ecm_find_qmlmodule(org.kde.sonnet 1.0)
|
||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||
endif()
|
||||
|
||||
@@ -87,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"
|
||||
)
|
||||
@@ -111,22 +111,27 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
)
|
||||
|
||||
find_package(QCoro REQUIRED)
|
||||
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)
|
||||
|
||||
@@ -134,9 +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)
|
||||
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})
|
||||
|
||||
175
LICENSES/LGPL-2.1-only.txt
Normal file
175
LICENSES/LGPL-2.1-only.txt
Normal 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!
|
||||
@@ -14,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
|
||||
@@ -31,7 +31,7 @@ and can also directly be downloaded from the [binary factory](https://binary-fac
|
||||
|
||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
@@ -55,7 +55,7 @@ We welcome contributions in this direction.
|
||||
|
||||
## Contact
|
||||
|
||||
You can reach the maintainers at #neochat:kde.org, if you are already on Matrix.
|
||||
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
|
||||
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ ToolBar {
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
+ inputField.topPadding + inputField.bottomPadding
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
FontMetrics {
|
||||
id: fontMetrics
|
||||
font: inputField.font
|
||||
@@ -94,11 +97,11 @@ ToolBar {
|
||||
//property int lineHeight: contentHeight / lineCount
|
||||
|
||||
text: inputFieldText
|
||||
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
|
||||
placeholderText: readOnly ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : i18n("Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
horizontalAlignment: TextEdit.AlignLeft
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: currentRoom.usesEncryption
|
||||
readOnly: currentRoom.usesEncryption && !Controller.encryptionSupported
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
@@ -120,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) }
|
||||
@@ -160,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();
|
||||
@@ -244,8 +228,11 @@ ToolBar {
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
timeoutTimer.restart()
|
||||
if (!repeatTimer.running && Config.typingNotifications) {
|
||||
currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
repeatTimer.start()
|
||||
|
||||
currentRoom.cachedInput = text
|
||||
autoAppeared = false;
|
||||
|
||||
@@ -268,7 +255,7 @@ 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 {
|
||||
@@ -290,14 +277,14 @@ ToolBar {
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
visible: !chatBoxHelper.isReplying && (!chatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||
implicitWidth: uploadButton.implicitWidth
|
||||
implicitHeight: uploadButton.implicitHeight
|
||||
ToolButton {
|
||||
id: uploadButton
|
||||
anchors.fill: parent
|
||||
// Matrix does not allow sending attachments in replies
|
||||
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
visible: !chatBoxHelper.isReplying && !chatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||
icon.name: "mail-attachment"
|
||||
text: i18n("Attach an image or file")
|
||||
display: AbstractButton.IconOnly
|
||||
@@ -309,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()
|
||||
}
|
||||
@@ -392,16 +379,16 @@ ToolBar {
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
ChatBoxHelper.attachmentPath = localPath;
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
}
|
||||
|
||||
function postMessage() {
|
||||
checkForFancyEffectsReason();
|
||||
|
||||
if (ChatBoxHelper.hasAttachment) {
|
||||
if (chatBoxHelper.hasAttachment) {
|
||||
// send attachment but don't reset the text
|
||||
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||
actionsHandler.postMessage("", chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
messageSent();
|
||||
return;
|
||||
@@ -413,8 +400,8 @@ ToolBar {
|
||||
actionsHandler.postEdit(inputField.text);
|
||||
} else {
|
||||
// send normal message
|
||||
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||
actionsHandler.postMessage(inputField.text.trim(), chatBoxHelper.attachmentPath,
|
||||
chatBoxHelper.replyEventId, chatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||
}
|
||||
currentRoom.markAllMessagesAsRead();
|
||||
inputField.clear();
|
||||
|
||||
@@ -95,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 {
|
||||
@@ -120,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
|
||||
@@ -147,7 +154,7 @@ Item {
|
||||
|
||||
AttachmentPane {
|
||||
id: attachmentPane
|
||||
visible: ChatBoxHelper.hasAttachment
|
||||
visible: chatBoxHelper.hasAttachment
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
anchors.bottom: chatBarSeparator.top
|
||||
@@ -241,7 +248,7 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ChatBoxHelper
|
||||
target: chatBoxHelper
|
||||
|
||||
function onShouldClearText() {
|
||||
root.inputFieldText = "";
|
||||
@@ -270,7 +277,7 @@ Item {
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
ChatBoxHelper.clear();
|
||||
chatBoxHelper.clear();
|
||||
chatBar.emojiPaneOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ Popup {
|
||||
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
|
||||
|
||||
contentItem: ScrollView {
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ListView {
|
||||
id: completionListView
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
readonly property bool isEdit: ChatBoxHelper.isEditing
|
||||
readonly property bool isEdit: chatBoxHelper.isEditing
|
||||
property var user: null
|
||||
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||
|
||||
@@ -71,6 +71,10 @@ Loader {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
leftPadding: 0
|
||||
@@ -79,7 +83,7 @@ Loader {
|
||||
bottomPadding: 0
|
||||
text: {
|
||||
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
|
||||
const content = chatBoxHelper.isReplying ? chatBoxHelper.replyEventContent : chatBoxHelper.editContent;
|
||||
return stylesheet + content;
|
||||
}
|
||||
selectByMouse: true
|
||||
@@ -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
|
||||
|
||||
@@ -63,7 +63,7 @@ ColumnLayout {
|
||||
|
||||
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
||||
|
||||
font.family: del.label === "custom" ? "" : 'emoji'
|
||||
font.family: del.label === "custom" ? Kirigami.Theme.defaultFont.family : 'emoji'
|
||||
text: del.label === "custom" ? i18n("Custom") : del.label
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
ApplicationWindow {
|
||||
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
|
||||
|
||||
@@ -45,8 +46,6 @@ ApplicationWindow {
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
source: localPath
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: image.width
|
||||
@@ -54,6 +53,24 @@ ApplicationWindow {
|
||||
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
|
||||
visible: root.blurhash !== "" && parent.status !== Image.Ready
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: {
|
||||
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.destroy)
|
||||
contextMenu.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
@@ -13,7 +13,7 @@ import NeoChat.Component 1.0
|
||||
Kirigami.PlaceholderMessage {
|
||||
property var showContinueButton: false
|
||||
property var showBackButton: false
|
||||
property string title: i18n("Loading")
|
||||
property string title: i18n("Loading…")
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -23,5 +23,6 @@ Kirigami.PlaceholderMessage {
|
||||
|
||||
QQC2.BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
running: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -20,7 +20,7 @@ LoginStep {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
Qt.openUrlExternally(LoginHelper.ssoUrl)
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||
|
||||
@@ -11,6 +11,12 @@ 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
|
||||
@@ -18,10 +24,12 @@ QQC2.Popup {
|
||||
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
|
||||
|
||||
@@ -71,11 +79,30 @@ QQC2.Popup {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,25 @@
|
||||
|
||||
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
|
||||
|
||||
TextEdit {
|
||||
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
selectByMouse: !Kirigami.Settings.isMobile
|
||||
readOnly: true
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
77
imports/NeoChat/Component/Timeline/EventDelegate.qml
Normal file
77
imports/NeoChat/Component/Timeline/EventDelegate.qml
Normal 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 {}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtGraphicalEffects 1.15
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
@@ -14,113 +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
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
onClicked: currentRoom.cancelFileTransfer(eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "raw"
|
||||
when: true
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
|
||||
onClicked: root.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveFileAs() {
|
||||
var dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
const dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localPath)) return;
|
||||
if (UrlHelper.openUrl(progressInfo.localDir)) return;
|
||||
}
|
||||
|
||||
innerObject: RowLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: fileDelegate.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
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.neochat 1.0
|
||||
@@ -12,8 +11,14 @@ 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,75 +30,88 @@ 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
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
||||
visible: parent.status !== Image.Ready
|
||||
}
|
||||
Layout.maximumWidth: imageDelegate.contentMaxWidth
|
||||
Layout.maximumHeight: imageDelegate.contentMaxWidth / imageDelegate.info.w * imageDelegate.info.h
|
||||
Layout.preferredWidth: imageDelegate.info.w
|
||||
Layout.preferredHeight: imageDelegate.info.h
|
||||
source: model.mediaUrl
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
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
|
||||
|
||||
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: {
|
||||
fullScreenImage.createObject(parent, {
|
||||
filename: eventId,
|
||||
source: mediaUrl,
|
||||
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
||||
imageWidth: content.info.w,
|
||||
imageHeight: content.info.h,
|
||||
modelData: model
|
||||
}).showFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
64
imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml
Normal file
64
imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml
Normal 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("–", "—") + "</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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
40
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 {
|
||||
RichLabel {
|
||||
id: label
|
||||
isEmote: messageDelegate.isEmote
|
||||
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
||||
}
|
||||
Loader {
|
||||
id: linkPreviewLoader
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
height: active ? item.implicitHeight : 0
|
||||
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
|
||||
visible: active
|
||||
sourceComponent: LinkPreviewDelegate {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
116
imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml
Normal file
116
imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,11 @@ MouseArea {
|
||||
id: replyButton
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
|
||||
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||
implicitWidth: Math.min(contentMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||
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
|
||||
@@ -75,12 +75,11 @@ MouseArea {
|
||||
|
||||
Component {
|
||||
id: textComponent
|
||||
TextDelegate {
|
||||
RichLabel {
|
||||
id: replyText
|
||||
textMessage: reply.display
|
||||
textFormat: Text.RichText
|
||||
hasContextMenu: false
|
||||
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
width: Math.min(implicitWidth, contentMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
}
|
||||
@@ -95,7 +94,7 @@ MouseArea {
|
||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||
source: "image://mxc/" + mediaId
|
||||
|
||||
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||
width: contentMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||
height: reply.content.info.h / reply.content.info.w * width
|
||||
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||
}
|
||||
|
||||
@@ -18,15 +18,13 @@ TextEdit {
|
||||
property string textMessage: model.display
|
||||
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
||||
|
||||
property bool hasContextMenu: true
|
||||
|
||||
signal requestOpenMessageContext()
|
||||
|
||||
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
||||
|
||||
Layout.fillWidth: Config.compactLayout
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
persistentSelection: true
|
||||
|
||||
text: "<style>
|
||||
table {
|
||||
@@ -48,6 +46,10 @@ a{
|
||||
text-decoration: none;
|
||||
}
|
||||
" + (!spoilerRevealed ? "
|
||||
[data-mx-spoiler] a {
|
||||
color: transparent;
|
||||
background: " + Kirigami.Theme.textColor + ";
|
||||
}
|
||||
[data-mx-spoiler] {
|
||||
color: transparent;
|
||||
background: " + Kirigami.Theme.textColor + ";
|
||||
@@ -56,14 +58,19 @@ a{
|
||||
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||
selectByMouse: !Kirigami.Settings.isMobile
|
||||
readOnly: true
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.RichText
|
||||
|
||||
onLinkActivated: RoomManager.openResource(link)
|
||||
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
||||
onLinkActivated: {
|
||||
spoilerRevealed = true
|
||||
RoomManager.openResource(link)
|
||||
}
|
||||
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
|
||||
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
||||
} else {
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
@@ -77,16 +84,4 @@ a{
|
||||
enabled: !parent.hoveredLink && !spoilerRevealed
|
||||
onTapped: spoilerRevealed = true
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openMessageContext(model, parent.selectedText)
|
||||
enabled: hasContextMenu
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: requestOpenMessageContext()
|
||||
enabled: hasContextMenu
|
||||
}
|
||||
}
|
||||
@@ -7,44 +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)
|
||||
|
||||
height: label.contentHeight
|
||||
width: delegateMaxWidth
|
||||
// anchors.leftMargin: Kirigami.Units.largeSpacing
|
||||
// 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 {}
|
||||
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 {
|
||||
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> " + display
|
||||
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,25 +12,40 @@ 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
|
||||
|
||||
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
|
||||
signal openContextMenu
|
||||
|
||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !applicationWindow().wideScreen
|
||||
// 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
|
||||
width: delegateMaxWidth
|
||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
||||
background: null
|
||||
|
||||
property Item hoverComponent
|
||||
|
||||
// show hover actions
|
||||
@@ -44,13 +58,39 @@ QQC2.ItemDelegate {
|
||||
// updates the global hover component to point to this delegate, and update its position
|
||||
function updateHoverComponent() {
|
||||
if (hoverComponent) {
|
||||
hoverComponent.delegate = timelineContainer
|
||||
hoverComponent.bubble = bubble
|
||||
hoverComponent.updateFunction = updateHoverComponent;
|
||||
hoverComponent.event = model
|
||||
}
|
||||
}
|
||||
|
||||
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
||||
state: Config.compactLayout ? "alignLeft" : "alignCenter"
|
||||
// Align left when in compact mode and center when using bubbles
|
||||
states: [
|
||||
State {
|
||||
name: "alignLeft"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: undefined
|
||||
anchors.left: parent ? parent.left : undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "alignCenter"
|
||||
AnchorChanges {
|
||||
target: timelineContainer
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||
}
|
||||
]
|
||||
|
||||
SectionDelegate {
|
||||
id: sectionDelegate
|
||||
@@ -74,7 +114,9 @@ QQC2.ItemDelegate {
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
visible: model.showAuthor && Config.showAvatarInTimeline && !showUserMessageOnRight
|
||||
visible: model.showAuthor &&
|
||||
Config.showAvatarInTimeline &&
|
||||
(Config.compactLayout || !showUserMessageOnRight)
|
||||
name: model.author.name ?? model.author.displayName
|
||||
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||
color: model.author.color
|
||||
@@ -96,24 +138,21 @@ QQC2.ItemDelegate {
|
||||
|
||||
QQC2.Control {
|
||||
id: bubble
|
||||
topPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||
bottomPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||
topPadding: Config.compactLayout ? Kirigami.Units.smallSpacing / 2 : Kirigami.Units.largeSpacing
|
||||
bottomPadding: Config.compactLayout ? Kirigami.Units.mediumSpacing / 2 : Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
|
||||
hoverEnabled: true
|
||||
|
||||
// state: Config.compactLayout ? "compactLayout" : "default"
|
||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||
|
||||
anchors {
|
||||
top: avatar.top
|
||||
leftMargin: Kirigami.Units.largeSpacing
|
||||
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
|
||||
}
|
||||
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
|
||||
width: Config.compactLayout ? messageDelegate.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||
|
||||
width: Config.compactLayout ? timelineContainer.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||
|
||||
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||
// states for anchor animations on window resize
|
||||
// as setting anchors to undefined did not work reliably
|
||||
states: [
|
||||
@@ -151,7 +190,7 @@ QQC2.ItemDelegate {
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing * 2
|
||||
Layout.maximumWidth: bubbleMaxWidth
|
||||
Layout.maximumWidth: contentMaxWidth
|
||||
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
||||
|
||||
QQC2.Label {
|
||||
@@ -159,15 +198,13 @@ QQC2.ItemDelegate {
|
||||
topInset: 0
|
||||
|
||||
visible: model.showAuthor && !isEmote
|
||||
anchors.left: rowLayout.left
|
||||
anchors.right: timeLabel.left
|
||||
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||
width: Math.min(contentMaxWidth - timeLabel.width, implicitWidth)
|
||||
|
||||
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
|
||||
@@ -184,10 +221,19 @@ QQC2.ItemDelegate {
|
||||
}
|
||||
QQC2.Label {
|
||||
id: timeLabel
|
||||
anchors.right: rowLayout.right
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
anchors.left: nameLabel.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 {
|
||||
@@ -197,6 +243,7 @@ QQC2.ItemDelegate {
|
||||
visible: active
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: Config.compactLayout ? 0 : Kirigami.Units.largeSpacing
|
||||
|
||||
Connections {
|
||||
target: replyLoader.item
|
||||
@@ -209,7 +256,7 @@ QQC2.ItemDelegate {
|
||||
|
||||
background: Item {
|
||||
Rectangle {
|
||||
visible: messageDelegate.hovered
|
||||
visible: timelineContainer.hovered
|
||||
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
anchors.fill: parent
|
||||
@@ -241,7 +288,7 @@ QQC2.ItemDelegate {
|
||||
left: bubble.left
|
||||
right: parent.right
|
||||
top: bubble.bottom
|
||||
topMargin: active && !Config.compactLayout ? Kirigami.Units.smallSpacing : 0
|
||||
topMargin: active && Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
||||
}
|
||||
height: active ? item.implicitHeight : 0
|
||||
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
||||
@@ -249,4 +296,14 @@ QQC2.ItemDelegate {
|
||||
visible: active
|
||||
sourceComponent: ReactionDelegate { }
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: timelineContainer.openContextMenu()
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: timelineContainer.openContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +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
|
||||
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
|
||||
|
||||
39
imports/NeoChat/Dialog/KeyVerification/EmojiItem.qml
Normal file
39
imports/NeoChat/Dialog/KeyVerification/EmojiItem.qml
Normal 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
|
||||
}
|
||||
}
|
||||
25
imports/NeoChat/Dialog/KeyVerification/EmojiRow.qml
Normal file
25
imports/NeoChat/Dialog/KeyVerification/EmojiRow.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
50
imports/NeoChat/Dialog/KeyVerification/EmojiSas.qml
Normal file
50
imports/NeoChat/Dialog/KeyVerification/EmojiSas.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
imports/NeoChat/Dialog/KeyVerification/Message.qml
Normal file
27
imports/NeoChat/Dialog/KeyVerification/Message.qml
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
7
imports/NeoChat/Dialog/KeyVerification/qmldir
Normal file
7
imports/NeoChat/Dialog/KeyVerification/qmldir
Normal 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
|
||||
@@ -14,6 +14,8 @@ import NeoChat.Component 1.0
|
||||
Kirigami.OverlaySheet {
|
||||
id: root
|
||||
|
||||
signal closed()
|
||||
|
||||
property var room
|
||||
property var user
|
||||
|
||||
@@ -52,7 +54,7 @@ Kirigami.OverlaySheet {
|
||||
|
||||
onClicked: {
|
||||
if (avatarMediaId) {
|
||||
fullScreenImage.createObject(parent, {"filename": displayName, "localPath": room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
||||
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,5 +166,11 @@ Kirigami.OverlaySheet {
|
||||
FullScreenImage {}
|
||||
}
|
||||
}
|
||||
|
||||
onSheetOpenChanged: {
|
||||
if (!sheetOpen) {
|
||||
closed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ OpenFileDialog 1.0 OpenFileDialog.qml
|
||||
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
|
||||
StartChatDialog 1.0 StartChatDialog.qml
|
||||
EmojiDialog 1.0 EmojiDialog.qml
|
||||
KeyVerificationDialog 1.0 KeyVerificationDialog.qml
|
||||
|
||||
@@ -24,11 +24,13 @@ Labs.MenuBar {
|
||||
// text: i18nc("menu", "About NeoChat")
|
||||
// }
|
||||
Labs.MenuItem {
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
||||
text: i18nc("menu", "Preferences…")
|
||||
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")
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml", {}, {
|
||||
title: i18n("Configure")
|
||||
})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "Quit NeoChat")
|
||||
@@ -43,7 +45,7 @@ Labs.MenuBar {
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "New Private Chat…")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {connection: Controller.activeConnection})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "New Group…")
|
||||
@@ -56,7 +58,7 @@ Labs.MenuBar {
|
||||
}
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "Browse Chats…")
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
|
||||
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {connection: Controller.activeConnection})
|
||||
}
|
||||
}
|
||||
EditMenu {
|
||||
@@ -90,7 +92,7 @@ Labs.MenuBar {
|
||||
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "Matrix FAQ")
|
||||
onTriggered: Qt.openUrlExternally("https://matrix.org/faq/")
|
||||
onTriggered: UrlHelper.openUrl("https://matrix.org/faq/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,67 +4,207 @@
|
||||
|
||||
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
|
||||
|
||||
|
||||
/**
|
||||
* Context menu when clicking on a room in the room list
|
||||
*/
|
||||
Menu {
|
||||
Loader {
|
||||
id: root
|
||||
property var room
|
||||
signal closed()
|
||||
|
||||
MenuItem {
|
||||
id: newWindow
|
||||
text: i18n("Open in new window")
|
||||
onTriggered: RoomManager.openWindow(room);
|
||||
visible: !Kirigami.Settings.isMobile
|
||||
}
|
||||
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
|
||||
}
|
||||
MenuSeparator {
|
||||
visible: newWindow.visible
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
|
||||
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)
|
||||
}
|
||||
|
||||
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: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
|
||||
MenuItem {
|
||||
text: i18n("Mark as Read")
|
||||
onTriggered: room.markAllMessagesAsRead()
|
||||
}
|
||||
|
||||
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
||||
}
|
||||
MenuItem {
|
||||
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
|
||||
onTriggered: if (room.canonicalAlias.length === 0) {
|
||||
Clipboard.saveText(room.id)
|
||||
} else {
|
||||
Clipboard.saveText(room.canonicalAlias)
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Mark as Read")
|
||||
Menu {
|
||||
title: i18n("Notification State")
|
||||
|
||||
onTriggered: room.markAllMessagesAsRead()
|
||||
}
|
||||
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: i18nc("@action:inmenu", "Copy address to clipboard")
|
||||
MenuItem {
|
||||
text: i18n("Room Settings")
|
||||
onTriggered: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (room.canonicalAlias.length === 0) {
|
||||
Clipboard.saveText(room.id)
|
||||
} else {
|
||||
Clipboard.saveText(room.canonicalAlias)
|
||||
MenuSeparator {}
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Leave Room")
|
||||
onTriggered: RoomManager.leaveRoom(room)
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
root.closed()
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
Component {
|
||||
id: mobileMenu
|
||||
|
||||
MenuItem {
|
||||
text: i18n("Leave Room")
|
||||
onTriggered: RoomManager.leaveRoom(room)
|
||||
Kirigami.OverlayDrawer {
|
||||
id: drawer
|
||||
height: popupContent.implicitHeight
|
||||
edge: Qt.BottomEdge
|
||||
padding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 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()
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: destroy()
|
||||
asynchronous: true
|
||||
sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu
|
||||
|
||||
function open() {
|
||||
active = true;
|
||||
}
|
||||
|
||||
onStatusChanged: if (status == Loader.Ready) {
|
||||
if (Kirigami.Settings.isMobile) {
|
||||
item.open();
|
||||
} else {
|
||||
item.popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
imports/NeoChat/Menu/ShareAction.qml
Normal file
71
imports/NeoChat/Menu/ShareAction.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
10
imports/NeoChat/Menu/ShareActionAndroid.qml
Normal file
10
imports/NeoChat/Menu/ShareActionAndroid.qml
Normal 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
|
||||
}
|
||||
69
imports/NeoChat/Menu/ShareDialog.qml
Normal file
69
imports/NeoChat/Menu/ShareDialog.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,8 +63,18 @@ MessageDelegateContextMenu {
|
||||
icon.color: "red"
|
||||
onTriggered: {
|
||||
currentRoom.redactEvent(eventId);
|
||||
root.closeFullscreen()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
visible: author.id !== currentRoom.localUser.id
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
icon.name: "code-context"
|
||||
@@ -71,6 +85,28 @@ MessageDelegateContextMenu {
|
||||
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]
|
||||
};
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -20,18 +20,21 @@ Loader {
|
||||
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")
|
||||
@@ -43,7 +46,16 @@ Loader {
|
||||
Kirigami.Action {
|
||||
text: i18n("Copy")
|
||||
icon.name: "edit-copy"
|
||||
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.message : loadRoot.selectedText)
|
||||
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.plainMessage : loadRoot.selectedText)
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
visible: author.id !== currentRoom.localUser.id
|
||||
onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/imports/NeoChat/Menu/Timeline/ReportSheet.qml", {room: currentRoom, eventId: eventId}, {
|
||||
title: i18nc("@title", "Report Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("View Source")
|
||||
@@ -63,9 +75,36 @@ 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();
|
||||
@@ -74,13 +113,15 @@ Loader {
|
||||
QQC2.Menu {
|
||||
id: webshortcutmenu
|
||||
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||
property bool isVisible: selectedText && selectedText.length > 0 && webshortcutmodel.enabled
|
||||
Component.onCompleted: webshortcutmenu.parent.visible = isVisible
|
||||
property bool isVisible: webshortcutmodel.enabled
|
||||
Component.onCompleted: {
|
||||
webshortcutmenu.parent.visible = isVisible
|
||||
}
|
||||
onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
|
||||
Instantiator {
|
||||
model: WebShortcutModel {
|
||||
id: webshortcutmodel
|
||||
selectedText: loadRoot.selectedText
|
||||
selectedText: loadRoot.selectedText ? loadRoot.selectedText : loadRoot.plainMessage
|
||||
onOpenUrl: RoomManager.visitNonMatrix(url)
|
||||
}
|
||||
delegate: QQC2.MenuItem {
|
||||
@@ -104,7 +145,7 @@ Loader {
|
||||
|
||||
Kirigami.OverlayDrawer {
|
||||
id: drawer
|
||||
height: popupContent.implicitHeight
|
||||
height: stackView.implicitHeight
|
||||
edge: Qt.BottomEdge
|
||||
padding: 0
|
||||
leftPadding: 0
|
||||
@@ -114,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: currentRoom.htmlSafeMemberName(author.id)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ Kirigami.Page {
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
contentWidth: availableWidth
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
TextArea {
|
||||
id: sourceTextArea
|
||||
text: sourceText
|
||||
|
||||
47
imports/NeoChat/Menu/Timeline/ReportSheet.qml
Normal file
47
imports/NeoChat/Menu/Timeline/ReportSheet.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -2,3 +2,5 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,18 +3,11 @@
|
||||
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
Kirigami.Page {
|
||||
title: i18n("Loading")
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
Kirigami.LoadingPlaceholder {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
text: i18n("Loading")
|
||||
QQC2.BusyIndicator {
|
||||
running: loadingIndicator.visible
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,95 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: SpaceHierarchyCache
|
||||
function onSpaceHierarchyChanged() {
|
||||
if (spaceList.activeSpaceId !== '') {
|
||||
sortFilterRoomListModel.activeSpaceRooms = SpaceHierarchyCache.getRoomListForSpace(spaceList.activeSpaceId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.activeSpaceRooms = [];
|
||||
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 {
|
||||
id: del
|
||||
|
||||
actions.main: Kirigami.Action {
|
||||
id: enterSpaceAction
|
||||
onTriggered: {
|
||||
spaceList.activeSpaceId = id;
|
||||
sortFilterRoomListModel.activeSpaceRooms = SpaceHierarchyCache.getRoomListForSpace(id, true);
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.ToolTip {
|
||||
text: currentRoom.displayName
|
||||
}
|
||||
|
||||
source: avatar !== "" ? "image://mxc/" + avatar : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
id: page
|
||||
|
||||
title: i18n("Rooms")
|
||||
@@ -22,21 +111,10 @@ Kirigami.ScrollablePage {
|
||||
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 = "";
|
||||
if (page.contentItem && page.contentItem.flickableItem && page.contentItem.flickableItem.QQC2.ScrollBar.vertical) {
|
||||
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = false;
|
||||
}
|
||||
} else {
|
||||
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = true;
|
||||
}
|
||||
|
||||
// HACK: the scrollbar is created with a 0 timer, so we need to set the visible flag
|
||||
// after it has been created
|
||||
Timer {
|
||||
running: true
|
||||
interval: 200
|
||||
onTriggered: page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = !collapsedMode;
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -79,38 +157,39 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||
@@ -236,12 +315,16 @@ Kirigami.ScrollablePage {
|
||||
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||
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 " "
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitle: subtitleText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
onPressAndHold: {
|
||||
createRoomListContextMenu()
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse
|
||||
onTapped: createRoomListContextMenu()
|
||||
}
|
||||
|
||||
leading: Kirigami.Avatar {
|
||||
@@ -255,39 +338,45 @@ Kirigami.ScrollablePage {
|
||||
|
||||
trailing: RowLayout {
|
||||
QQC2.Label {
|
||||
text: notificationCount
|
||||
visible: notificationCount > 0
|
||||
text: notificationCount > 0 ? notificationCount : "●"
|
||||
visible: unreadCount > 0
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
||||
color: Kirigami.Theme.textColor
|
||||
Layout.minimumWidth: height
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
background: Rectangle {
|
||||
visible: notificationCount > 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
|
||||
opacity: highlightCount > 0 ? 1 : 0.3
|
||||
radius: height / 2
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: configButton
|
||||
visible: roomListItem.hovered || Kirigami.Settings.isMobile
|
||||
visible: roomListItem.hovered
|
||||
Accessible.name: i18n("Configure room")
|
||||
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +400,7 @@ Kirigami.ScrollablePage {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
text: model.connection.localUserId
|
||||
subtitle: model.connection.localUser.accountLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 +23,14 @@ 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;
|
||||
|
||||
/// 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)
|
||||
@@ -37,6 +42,8 @@ Kirigami.ScrollablePage {
|
||||
if(pageStack.lastItem == page) {
|
||||
pageStack.pop()
|
||||
}
|
||||
} else if (page.currentRoom.isInvite) {
|
||||
page.currentRoom.clearInvitationNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +53,26 @@ Kirigami.ScrollablePage {
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
hasScrolledUpBefore = false;
|
||||
ChatBoxHelper.clearEditReply()
|
||||
chatBoxHelper.clearEditReply()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: messageEventModel
|
||||
function onRowsInserted() {
|
||||
markReadIfVisibleTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: markReadIfVisibleTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
if (loading || !currentRoom.readMarkerLoaded || !applicationWindow().active) {
|
||||
restart()
|
||||
} else {
|
||||
markReadIfVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionsHandler {
|
||||
@@ -55,6 +81,16 @@ 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, invited) {
|
||||
@@ -69,17 +105,17 @@ Kirigami.ScrollablePage {
|
||||
function onShowMessage(messageType, message) {
|
||||
page.header.contentItem.text = message;
|
||||
page.header.contentItem.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : Kirigami.MessageType.Information;
|
||||
page.header.contentItem.visible = true;
|
||||
page.header.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: contentItem.visible
|
||||
visible: false
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,15 +144,10 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
Kirigami.LoadingPlaceholder {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
visible: page.currentRoom === null || (messageListView.count === 0 && !page.currentRoom.allHistoryLoaded && !page.currentRoom.isInvite)
|
||||
text: i18n("Loading")
|
||||
QQC2.BusyIndicator {
|
||||
running: loadingIndicator.visible
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
visible: loading
|
||||
}
|
||||
|
||||
focus: true
|
||||
@@ -135,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);
|
||||
@@ -146,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
|
||||
@@ -155,78 +195,9 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: hoverActions
|
||||
property var event: null
|
||||
property bool showEdit: event && (event.author.id === Controller.activeConnection.localUserId && (event.eventType === "emote" || event.eventType === "message"))
|
||||
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;
|
||||
}
|
||||
x: bubble ? (bubble.x + Kirigami.Units.largeSpacing + Math.max(bubble.width - childWidth, 0) - (Config.compactLayout ? Kirigami.Units.gridUnit * 3 : 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
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -241,7 +212,7 @@ Kirigami.ScrollablePage {
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
highlightMoveDuration: 500
|
||||
|
||||
model: !isLoaded ? undefined : sortedMessageEventModel
|
||||
model: !isLoaded ? undefined : collapseStateProxyModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
@@ -299,7 +270,7 @@ Kirigami.ScrollablePage {
|
||||
fileDialog.chosen.connect(function(path) {
|
||||
if (!path) return
|
||||
|
||||
ChatBoxHelper.attachmentPath = path;
|
||||
chatBoxHelper.attachmentPath = path;
|
||||
})
|
||||
|
||||
fileDialog.open()
|
||||
@@ -321,7 +292,7 @@ Kirigami.ScrollablePage {
|
||||
if (!Clipboard.saveImage(localPath)) {
|
||||
return;
|
||||
}
|
||||
ChatBoxHelper.attachmentPath = localPath;
|
||||
chatBoxHelper.attachmentPath = localPath;
|
||||
attachDialog.close();
|
||||
}
|
||||
}
|
||||
@@ -341,295 +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
|
||||
height: contentItem.height
|
||||
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
|
||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "message"
|
||||
delegate: TimelineContainer {
|
||||
id: messageContainer
|
||||
width: messageListView.width
|
||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||
onReplyClicked: goToEvent(eventID)
|
||||
hoverComponent: hoverActions
|
||||
|
||||
innerObject: TextDelegate {
|
||||
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "notice"
|
||||
delegate: TimelineContainer {
|
||||
id: noticeContainer
|
||||
width: messageListView.width
|
||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||
onReplyClicked: goToEvent(eventID)
|
||||
|
||||
innerObject: TextDelegate {
|
||||
Layout.fillWidth: !Config.compactLayout
|
||||
hasContextMenu: false
|
||||
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.preferredHeight: info.h / info.w * width
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onTapped: openFileContext(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
onTapped: {
|
||||
fullScreenImage.createObject(parent, {
|
||||
filename: eventId,
|
||||
localPath: currentRoom.urlToDownload(eventId),
|
||||
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
||||
imageWidth: content.info.w,
|
||||
imageHeight: content.info.h
|
||||
}).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(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, 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(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, 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(model, parent)
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: openFileContext(model, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "encrypted"
|
||||
delegate: TimelineContainer {
|
||||
id: encryptedContainer
|
||||
width: messageListView.width
|
||||
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||
|
||||
innerObject: EncryptedDelegate {
|
||||
Layout.fillWidth: Config.compactLayout
|
||||
Layout.maximumWidth: encryptedContainer.bubbleMaxWidth
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "other"
|
||||
delegate: Item {}
|
||||
}
|
||||
}
|
||||
delegate: EventDelegate {}
|
||||
|
||||
QQC2.RoundButton {
|
||||
anchors.right: parent.right
|
||||
@@ -644,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"
|
||||
}
|
||||
@@ -678,19 +361,13 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
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 {
|
||||
@@ -739,7 +416,7 @@ Kirigami.ScrollablePage {
|
||||
currentRoom.usersTyping.length,
|
||||
currentRoom.usersTyping.map(user => user.displayName).join(", ")
|
||||
) : ""
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
height: visible ? implicitHeight : 0
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
@@ -752,6 +429,103 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
|
||||
function goToEvent(eventID) {
|
||||
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
|
||||
}
|
||||
|
||||
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 : 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -766,14 +540,14 @@ 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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,7 +591,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) {
|
||||
@@ -833,10 +607,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)
|
||||
@@ -866,29 +636,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(event, file) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
||||
author: event.author,
|
||||
message: event.message,
|
||||
eventId: event.eventId,
|
||||
source: event.toolTip,
|
||||
source: event.source,
|
||||
file: file,
|
||||
mimeType: event.mimeType,
|
||||
progressInfo: event.progressInfo,
|
||||
plainMessage: event.message,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
/// Open context menu for normal message
|
||||
function openMessageContext(event, selectedText) {
|
||||
function openMessageContext(event, selectedText, plainMessage) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||
selectedText: selectedText,
|
||||
author: event.author,
|
||||
message: event.message,
|
||||
message: event.display,
|
||||
eventId: event.eventId,
|
||||
formattedBody: event.formattedBody,
|
||||
source: event.toolTip,
|
||||
eventType: event.eventType
|
||||
source: event.source,
|
||||
eventType: event.eventType,
|
||||
plainMessage: plainMessage,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,13 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
function onInitiated() {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
|
||||
@@ -18,7 +18,8 @@ Kirigami.OverlayDrawer {
|
||||
id: roomDrawer
|
||||
readonly property var room: RoomManager.currentRoom
|
||||
|
||||
width: modal ? undefined : actualWidth
|
||||
width: actualWidth
|
||||
|
||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
||||
@@ -62,56 +63,84 @@ Kirigami.OverlayDrawer {
|
||||
|
||||
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
|
||||
property alias userSearchText: userListSearchField.text
|
||||
property alias highlightedUser: userListView.currentIndex
|
||||
spacing: 0
|
||||
|
||||
Kirigami.AbstractApplicationHeader {
|
||||
Layout.fillWidth: true
|
||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: i18n("Room information")
|
||||
level: 1
|
||||
}
|
||||
ToolButton {
|
||||
id: inviteButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: "list-add-user"
|
||||
text: i18n("Invite")
|
||||
text: i18n("Invite user to room")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
|
||||
roomDrawer.close();
|
||||
}
|
||||
}
|
||||
Item {
|
||||
// HACK otherwise rating item is not right aligned
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: inviteButton.text
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
id: favouriteButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||
checkable: true
|
||||
checked: room && room.isFavourite
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||
|
||||
ToolTip {
|
||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
text: favouriteButton.text
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
id: settingsButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
icon.name: 'settings-configure'
|
||||
text: i18n("Room settings")
|
||||
display: AbstractButton.IconOnly
|
||||
|
||||
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
||||
|
||||
ToolTip {
|
||||
text: i18n("Room settings")
|
||||
text: settingsButton.text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,14 +149,11 @@ Kirigami.OverlayDrawer {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
Kirigami.Heading {
|
||||
text: i18n("Room information")
|
||||
level: 3
|
||||
}
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Avatar {
|
||||
@@ -144,15 +170,22 @@ Kirigami.OverlayDrawer {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
||||
Layout.fillWidth: true
|
||||
level: 1
|
||||
font.bold: true
|
||||
type: Kirigami.Heading.Type.Primary
|
||||
wrapMode: Label.Wrap
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
Label {
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -166,7 +199,9 @@ Kirigami.OverlayDrawer {
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
color: Kirigami.Theme.textColor
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
onLinkActivated: UrlHelper.openUrl(link)
|
||||
readOnly: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -179,16 +214,23 @@ Kirigami.OverlayDrawer {
|
||||
Kirigami.ListSectionHeader {
|
||||
label: i18n("Members")
|
||||
activeFocusOnTab: false
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
implicitWidth: parent.width
|
||||
z: 2
|
||||
Control {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Note need to set padding individually to guarantee it will always work
|
||||
// see note - https://doc.qt.io/qt-6/qml-qtquick-controls2-control.html#padding-prop
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing
|
||||
leftPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Theme.inherit: false
|
||||
@@ -196,6 +238,7 @@ Kirigami.OverlayDrawer {
|
||||
}
|
||||
contentItem: Kirigami.SearchField {
|
||||
id: userListSearchField
|
||||
|
||||
onAccepted: sortedMessageEventModel.filterString = text;
|
||||
}
|
||||
}
|
||||
@@ -204,11 +247,12 @@ Kirigami.OverlayDrawer {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
id: userListView
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
activeFocusOnTab: true
|
||||
|
||||
model: KSortFilterProxyModel {
|
||||
@@ -223,58 +267,50 @@ Kirigami.OverlayDrawer {
|
||||
filterCaseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
|
||||
delegate: Kirigami.AbstractListItem {
|
||||
width: userListView.width
|
||||
delegate: Kirigami.BasicListItem {
|
||||
id: userListItem
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||
z: 1
|
||||
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
visible: Config.showAvatarInRoomDrawer
|
||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||
name: name
|
||||
}
|
||||
label: 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
|
||||
}
|
||||
onClicked: {
|
||||
const popup = userDetailDialog.createObject(ApplicationWindow.overlay, {room: room, user: user, displayName: name, avatarMediaId: avatar})
|
||||
popup.closed.connect(function() {
|
||||
userListItem.highlighted = false
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,6 +319,10 @@ Kirigami.OverlayDrawer {
|
||||
}
|
||||
|
||||
onRoomChanged: {
|
||||
if (loader.active) {
|
||||
loader.item.userSearchText = ""
|
||||
loader.item.highlightedUser = -1
|
||||
}
|
||||
if (room == null) {
|
||||
close()
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
import QtQuick 2.15
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Kirigami.CategorizedSettings {
|
||||
id: root
|
||||
required property var room
|
||||
property var room
|
||||
|
||||
objectName: "settingsPage"
|
||||
actions: [
|
||||
Kirigami.SettingAction {
|
||||
@@ -30,6 +30,16 @@ Kirigami.CategorizedSettings {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Notifications")
|
||||
icon.name: "notifications"
|
||||
page: Qt.resolvedUrl("PushNotification.qml")
|
||||
initialProperties: {
|
||||
return {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ Kirigami.ScrollablePage {
|
||||
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
||||
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
||||
|
||||
title: i18n('General')
|
||||
title: i18n("General")
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
|
||||
57
imports/NeoChat/RoomSettings/PushNotification.qml
Normal file
57
imports/NeoChat/RoomSettings/PushNotification.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,13 @@ Kirigami.ScrollablePage {
|
||||
|
||||
property var room
|
||||
|
||||
title: i18n('Security')
|
||||
title: i18n("Security")
|
||||
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
CheckBox {
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Private (invite only)")
|
||||
Kirigami.FormData.label: i18nc("@option:check", "Access:")
|
||||
checked: room.joinRule === "invite"
|
||||
@@ -32,7 +32,7 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Only invited people can join.")
|
||||
font: Kirigami.Theme.smallFont
|
||||
}
|
||||
CheckBox {
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Space members")
|
||||
checked: room.joinRule === "restricted"
|
||||
enabled: false
|
||||
@@ -41,7 +41,7 @@ Kirigami.ScrollablePage {
|
||||
text: i18n("Anyone in a space can find and join.")
|
||||
font: Kirigami.Theme.smallFont
|
||||
}
|
||||
CheckBox {
|
||||
RadioButton {
|
||||
text: i18nc("@option:check", "Public")
|
||||
checked: room.joinRule === "public"
|
||||
enabled: false
|
||||
|
||||
@@ -7,6 +7,6 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.AboutPage {
|
||||
title: i18nc('@title:window', 'About NeoChat')
|
||||
title: i18nc("@title:window", "About NeoChat")
|
||||
aboutData: Controller.aboutData
|
||||
}
|
||||
|
||||
130
imports/NeoChat/Settings/AccountEditorPage.qml
Normal file
130
imports/NeoChat/Settings/AccountEditorPage.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,14 +11,9 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Accounts")
|
||||
|
||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
|
||||
actions.main: Kirigami.Action {
|
||||
text: i18n("Add an account")
|
||||
icon.name: "list-add-user"
|
||||
@@ -26,84 +21,71 @@ Kirigami.Page {
|
||||
visible: !pageSettingStack.wideMode
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: pageSettingStack
|
||||
function onWideModeChanged() {
|
||||
scroll.background.visible = pageSettingStack.wideMode
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ScrollView {
|
||||
id: scroll
|
||||
Component.onCompleted: background.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"
|
||||
|
||||
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
|
||||
ListView {
|
||||
clip: true
|
||||
model: AccountRegistry
|
||||
delegate: Kirigami.SwipeListItem {
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
Kirigami.BasicListItem {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Edit this account")
|
||||
iconName: "document-edit"
|
||||
onTriggered: {
|
||||
userEditSheet.connection = model.connection
|
||||
userEditSheet.open()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
onTriggered: pageSettingStack.pushDialogLayer(Qt.resolvedUrl('./AccountEditorPage.qml'), {
|
||||
connection: model.connection
|
||||
}, {
|
||||
title: i18n("Account editor")
|
||||
});
|
||||
}
|
||||
}
|
||||
Controls.ToolButton {
|
||||
display: Controls.AbstractButton.IconOnly
|
||||
Controls.ToolTip {
|
||||
text: parent.action.text
|
||||
}
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Logout")
|
||||
iconName: "im-kick-user"
|
||||
onTriggered: {
|
||||
Controller.logout(model.connection, true)
|
||||
if(Controller.accountCount === 1)
|
||||
pageStack.layers.pop()
|
||||
Controller.logout(model.connection, true);
|
||||
if (Controller.accountCount === 1) {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: Column {
|
||||
height: visible ? implicitHeight : 0
|
||||
footer: Controls.ToolBar {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.ActionToolBar {
|
||||
alignment: Qt.AlignRight
|
||||
visible: pageSettingStack.wideMode
|
||||
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")
|
||||
}
|
||||
]
|
||||
}
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Kirigami.Units.smallSpacing
|
||||
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() {
|
||||
@@ -111,124 +93,21 @@ Kirigami.Page {
|
||||
pageStack.layers.pop()
|
||||
}
|
||||
function onPasswordStatus(status) {
|
||||
if(status == Controller.Success)
|
||||
showPassiveNotification(i18n("Password changed successfully"))
|
||||
else if(status == Controller.Wrong)
|
||||
showPassiveNotification(i18n("Wrong password entered"))
|
||||
else
|
||||
showPassiveNotification(i18n("Unknown problem while trying to change password"))
|
||||
if (status === Controller.Success) {
|
||||
showPassiveNotification(i18n("Password changed successfully"));
|
||||
} else if (status === Controller.Wrong) {
|
||||
showPassiveNotification(i18n("Wrong password entered"));
|
||||
} else {
|
||||
showPassiveNotification(i18n("Unknown problem while trying to change password"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
property Component openFileDialog: Component {
|
||||
id: openFileDialog
|
||||
|
||||
OpenFileDialog {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: userEditSheet
|
||||
|
||||
property var connection
|
||||
|
||||
title: i18n("Edit Account")
|
||||
|
||||
Kirigami.FormLayout {
|
||||
RowLayout {
|
||||
Kirigami.Avatar {
|
||||
id: avatar
|
||||
source: userEditSheet.connection && userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
property var fileDialog: null;
|
||||
onClicked: {
|
||||
if (fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||
|
||||
fileDialog.chosen.connect(function(receivedSource) {
|
||||
mouseArea.fileDialog = null;
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
parent.source = receivedSource;
|
||||
});
|
||||
fileDialog.onRejected.connect(function() {
|
||||
mouseArea.fileDialog = null;
|
||||
});
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
Controls.Button {
|
||||
visible: avatar.source.length !== 0
|
||||
icon.name: "edit-clear"
|
||||
|
||||
onClicked: avatar.source = ""
|
||||
}
|
||||
Kirigami.FormData.label: i18n("Avatar:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: name
|
||||
text: userEditSheet.connection ? userEditSheet.connection.localUser.displayName : ""
|
||||
Kirigami.FormData.label: i18n("Name:")
|
||||
}
|
||||
Controls.TextField {
|
||||
id: 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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.kde.neochat 1.0
|
||||
import NeoChat.Settings 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc('@title:window', 'Appearance')
|
||||
title: i18nc("@title:window", "Appearance")
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
@@ -175,8 +175,9 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
Kirigami.FormLayout {
|
||||
Layout.maximumWidth: parent.width
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: "Show Avatar:"
|
||||
Kirigami.FormData.label: i18n("Show Avatar:")
|
||||
text: i18n("In Chat")
|
||||
checked: Config.showAvatarInTimeline
|
||||
onToggled: {
|
||||
@@ -236,6 +237,7 @@ Kirigami.ScrollablePage {
|
||||
HoverHandler { id: sliderHover }
|
||||
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
|
||||
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.Label {
|
||||
text: Math.round(Config.transparency * 100) + "%"
|
||||
@@ -244,7 +246,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.CheckBox {
|
||||
text: i18n("Show your messages on the right")
|
||||
checked: Config.showLocalMessagesOnRight
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
|
||||
onToggled: {
|
||||
Config.showLocalMessagesOnRight = checked
|
||||
Config.save()
|
||||
|
||||
@@ -5,57 +5,33 @@ import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18n("Devices")
|
||||
|
||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
|
||||
Connections {
|
||||
target: pageSettingStack
|
||||
function onWideModeChanged() {
|
||||
scroll.background.visible = pageSettingStack.wideMode
|
||||
ListView {
|
||||
model: DevicesModel {
|
||||
id: devices
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ScrollView {
|
||||
id: scroll
|
||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
||||
anchors.fill: parent
|
||||
ListView {
|
||||
clip: true
|
||||
model: DevicesModel {
|
||||
id: devices
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
visible: parent.model.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
anchors.centerIn: parent
|
||||
text: i18n("Loading")
|
||||
Controls.BusyIndicator {
|
||||
running: parent.visible
|
||||
}
|
||||
}
|
||||
Kirigami.LoadingPlaceholder {
|
||||
visible: parent.count === 0 // We can assume 0 means loading since there is at least one device
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
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 {
|
||||
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: {
|
||||
@@ -63,8 +39,22 @@ Kirigami.Page {
|
||||
renameSheet.name = model.displayName
|
||||
renameSheet.open()
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
}
|
||||
}
|
||||
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: {
|
||||
@@ -72,7 +62,7 @@ Kirigami.Page {
|
||||
passwordSheet.open()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ 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
|
||||
@@ -13,92 +15,100 @@ import NeoChat.Settings 1.0
|
||||
import NeoChat.Component 1.0 as Components
|
||||
import NeoChat.Dialog 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
title: i18nc('@title:window', 'Custom Emojis')
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc("@title:window", "Custom Emojis")
|
||||
|
||||
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
|
||||
Connections {
|
||||
target: pageSettingStack
|
||||
function onWideModeChanged() {
|
||||
scroll.background.visible = pageSettingStack.wideMode
|
||||
}
|
||||
model: CustomEmojiModel {
|
||||
id: emojiModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
id: scroll
|
||||
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
text: i18n("No custom inline stickers found")
|
||||
visible: parent.model.count === 0
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: pageSettingStack.wideMode
|
||||
sourceComponent: addEmojiComponent
|
||||
Layout.fillWidth: true
|
||||
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 {
|
||||
id: toolbar
|
||||
width: parent.width
|
||||
visible: !pageSettingStack.wideMode
|
||||
height: visible ? implicitHeight : 0
|
||||
contentItem: Loader {
|
||||
active: !pageSettingStack.wideMode
|
||||
sourceComponent: addEmojiComponent
|
||||
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()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,48 +119,4 @@ Kirigami.Page {
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||
}
|
||||
}
|
||||
|
||||
property Component addEmojiComponent: RowLayout {
|
||||
Item {
|
||||
Layout.fillWidth: Qt.application.layoutDirection == Qt.LeftToRight
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
id: emojiField
|
||||
placeholderText: i18n("new_emoji_name_here")
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[a-zA-Z_0-9]*/
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
text: i18n("Add Emoji...")
|
||||
|
||||
enabled: emojiField.text != ""
|
||||
property var fileDialog: null
|
||||
|
||||
onClicked: {
|
||||
if (this.fileDialog != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay)
|
||||
|
||||
this.fileDialog.chosen.connect((url) => {
|
||||
emojiModel.addEmoji(emojiField.text, url)
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.onRejected.connect(() => {
|
||||
rej()
|
||||
this.fileDialog = null
|
||||
})
|
||||
this.fileDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: Qt.application.layoutDirection == Qt.RightToLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
title: i18nc('@title:window', 'General')
|
||||
title: i18nc("@title:window", "General")
|
||||
ColumnLayout {
|
||||
Kirigami.FormLayout {
|
||||
Layout.fillWidth: true
|
||||
QQC2.CheckBox {
|
||||
Kirigami.FormData.label: i18n("General settings:")
|
||||
text: i18n("Close to system tray")
|
||||
@@ -25,6 +26,16 @@ Kirigami.ScrollablePage {
|
||||
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.
|
||||
@@ -35,6 +46,7 @@ Kirigami.ScrollablePage {
|
||||
onToggled: {
|
||||
Config.showNotifications = checked
|
||||
Config.save()
|
||||
NotificationsManager.globalNotificationsEnabled = checked
|
||||
}
|
||||
}
|
||||
QQC2.CheckBox {
|
||||
@@ -46,6 +58,24 @@ Kirigami.ScrollablePage {
|
||||
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")
|
||||
@@ -66,6 +96,8 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -73,6 +105,31 @@ Kirigami.ScrollablePage {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import QtQuick 2.15
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import QtQuick.Controls 2.15 as Controls
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Kirigami.CategorizedSettings {
|
||||
@@ -25,7 +24,7 @@ Kirigami.CategorizedSettings {
|
||||
page: Qt.resolvedUrl("AccountsPage.qml")
|
||||
},
|
||||
Kirigami.SettingAction {
|
||||
text: i18n("Custom Emoji")
|
||||
text: i18n("Custom Emojis")
|
||||
icon.name: "preferences-desktop-emoticons"
|
||||
page: Qt.resolvedUrl("Emoticons.qml")
|
||||
},
|
||||
|
||||
@@ -47,7 +47,7 @@ Kirigami.Page {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
title: i18nc('@window:title', 'Spellchecking')
|
||||
title: i18nc("@window:title", "Spellchecking")
|
||||
|
||||
QQC2.Dialog {
|
||||
id: applyDialog
|
||||
@@ -190,6 +190,10 @@ Kirigami.Page {
|
||||
Layout.fillHeight: true
|
||||
enabled: autodetectLanguageCheckbox.checked
|
||||
Component.onCompleted: background.visible = wideMode
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
model: settings.dictionaryModel
|
||||
@@ -254,6 +258,10 @@ Kirigami.Page {
|
||||
}
|
||||
QQC2.ScrollView {
|
||||
anchors.fill: parent
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
ListView {
|
||||
model: settings.currentIgnoreList
|
||||
delegate: Kirigami.BasicListItem {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M1 1H15V12H5.68102L2 15.0675V12H1V1ZM2 11H3V12.9325L5.31897 11H14V2H2V11Z"/>
|
||||
<rect class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" x="3" y="4" width="9" height="1"/>
|
||||
<rect class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" x="3" y="6" width="7" height="1"/>
|
||||
<rect class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" x="3" y="8" width="5" height="1"/>
|
||||
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M12 12.2929L10.8536 11.1465L10.1465 11.8536L13 14.7071V11.5H12V12.2929Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1007 B |
@@ -10,6 +10,7 @@
|
||||
<binary>neochat</binary>
|
||||
</provides>
|
||||
<name>NeoChat</name>
|
||||
<name xml:lang="ar">نيوتشات</name>
|
||||
<name xml:lang="az">NeoChat</name>
|
||||
<name xml:lang="ca">NeoChat</name>
|
||||
<name xml:lang="ca-valencia">NeoChat</name>
|
||||
@@ -35,13 +36,15 @@
|
||||
<name xml:lang="sl">NeoChat</name>
|
||||
<name xml:lang="sv">NeoChat</name>
|
||||
<name xml:lang="ta">நியோச்சாட்</name>
|
||||
<name xml:lang="tr">NeoChat</name>
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<summary>A client for matrix, the decentralized communication protocol</summary>
|
||||
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||
@@ -63,14 +66,16 @@
|
||||
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
||||
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="tr">Merkezi olmayan iletişim protokolü Matrix için bir istemci</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
|
||||
<description>
|
||||
<p>NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix. يتيح لك إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك باستخدام بروتوكول ماتركس</p>
|
||||
<p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics utilitzant el protocol Matrix.</p>
|
||||
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
|
||||
@@ -79,6 +84,7 @@
|
||||
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
||||
<p xml:lang="hu">A NeoChat egy Matrix kliens. Szöveges üzeneteket, videókat ésaudio fájlokat küldhet családjának, kollégáinak és barátainak a Matrix protokoll használatával.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
||||
<p xml:lang="id">NeoChat adalah sebuah klien Matrix. Memungkinkan Anda untuk mengirim pesan teks, file video dan audio ke keluarga, kolega dan teman Anda menggunakan protokol Matrix.</p>
|
||||
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
||||
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
||||
@@ -88,21 +94,24 @@
|
||||
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
|
||||
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
|
||||
<p xml:lang="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p>
|
||||
<p xml:lang="tr">NeoChat, bir Matrix istemcisidir. Matrix protokolünü kullanarak ailenize, iş arkadaşlarınıza, arkadaşlarınıza ve başkalarına metin iletileri, video ve ses dosyaların göndermenize olanak verir.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт мережі обміну повідомленнями Matrix. За допомогою цієї програми ви зможете надсилати текстові повідомлення, відео та звукові файли вашій родині, колегам та друзям за допомогою протоколу Matrix.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 是一个 Matrix 客户端。 它允许您使用 Matrix 协议向您的家人、同事和朋友发送文本消息、视频和音频文件。</p>
|
||||
<p>Matrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
|
||||
<p xml:lang="fi">Matrix on hajautettu viestintäyhteyskäytäntö, joka antaa hallinnan takaisin käyttäjille. NeoChat tarjoaa nykyisellään valtaosan yhteyskäytännöstä salattuja keskustelu- ja videokeskusteluja lukuun ottamatta.</p>
|
||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions cryptées et du chat vidéo.</p>
|
||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions chiffrées et du chat vidéo.</p>
|
||||
<p xml:lang="hu">A Matrix egy decentralizált kommunikációs protokoll, amely a felhasználók kezébe adja az irányítást.</p>
|
||||
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
||||
<p xml:lang="id">Matrix adalah protokol komunikasi terdesentralisasi, menempatkan pengguna kembali dalam kendali. Saat ini NeoChat mengimplementasikan sebagian besar protokol dengan pengecualian obrolan terenkripsi dan obrolan video.</p>
|
||||
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
||||
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
||||
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
||||
@@ -112,13 +121,15 @@
|
||||
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
|
||||
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
|
||||
<p xml:lang="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p>
|
||||
<p xml:lang="tr">Matrix; tam denetimi kullanıcıya bırakan, merkezi olmayan bir iletişim protokolüdür. Şu anda NeoChat, uçtan uca şifrelenmiş metin ve video sohbetleri dışında protokolün büyük bir bölümünü bünyesinde bulundurur.</p>
|
||||
<p xml:lang="uk">Matrix — протокол децентралізованого спілкування, який передає контроль над даними користувачеві. У поточній версії NeoChat реалізовано більшу частину протоколу, окрім зашифрованого спілкування та відеоспілкування.</p>
|
||||
<p xml:lang="x-test">xxMatrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.xx</p>
|
||||
<p xml:lang="zh-CN">Matrix 是一个分布式通讯协议,使用户重新得到控制权。 目前,NeoChat 实现了协议的大部分,除了加密聊天和视频聊天。</p>
|
||||
<p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
|
||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
|
||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||
@@ -127,6 +138,7 @@
|
||||
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
||||
<p xml:lang="hu">A NeoChat mobilon és asztali számítógépen is működik, egységes felhasználói élményt nyújtva.</p>
|
||||
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
||||
<p xml:lang="id">NeoChat berfungsi baik di ponsel dan desktop sambil memberikan pengalaman pengguna yang konsisten.</p>
|
||||
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
||||
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
||||
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
||||
@@ -136,16 +148,18 @@
|
||||
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
|
||||
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
||||
<p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
|
||||
<p xml:lang="tr">NeoChat, hem masaüstü hem de taşınabilir ortamlarda çalışarak tutarlı bir kullanıcı deneyimi sunar.</p>
|
||||
<p xml:lang="uk">NeoChat працює на мобільних пристроях та звичайних комп'ютерах, маючи однорідний інтерфейс на усіх підтримуваних пристроях.</p>
|
||||
<p xml:lang="x-test">xxNeoChat works both on mobile and desktop while providing a consistent user experience.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 在移动设备和桌面上均可用,并提供一致的用户体验。</p>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
<url type="bugtracker">https://invent.kde.org/network/neochat/-/issues</url>
|
||||
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&product=NeoChat</url>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
</categories>
|
||||
<developer_name>The KDE Community</developer_name>
|
||||
<developer_name xml:lang="ar">مجتمع كدي</developer_name>
|
||||
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
@@ -170,12 +184,16 @@
|
||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
<custom>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application-mobile.png</image>
|
||||
@@ -188,6 +206,46 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="22.09" date="2022-09-27">
|
||||
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
|
||||
</release>
|
||||
<release version="22.06" date="2022-06-24">
|
||||
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
|
||||
<description>
|
||||
<p>This release brings you various small bugfixes and improvements:</p>
|
||||
<ul>
|
||||
<li>Sending of typing notifications can now be disabled.</li>
|
||||
<li>In the room list, the scrollbar will now disappear correctly when it is not needed.</li>
|
||||
<li>On wayland, NeoChat will now raise correctly when clicking on a notification.</li>
|
||||
<li>Several bugs have been fixed that would sometimes cause messages containing markdown and/or HTML elements to be sent incorrectly.</li>
|
||||
<li>The quick switcher can now be controlled using the mouse.</li>
|
||||
<li>There is now an option to disable automatic room sidebar opening when resizing the window.</li>
|
||||
<li>Creation of custom emojis has been fixed.</li>
|
||||
<li>Editing or replying to the last message using the keyboard shortcuts now works correctly.</li>
|
||||
<li>When switching between rooms using the keyboard, the switching direction is now correct.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.04" date="2022-04-26">
|
||||
<url>https://www.plasma-mobile.org/2022/04/26/plasma-mobile-gear-22-04/</url>
|
||||
<description>
|
||||
<p>NeoChat now lets you filter and enter a room directly from KRunner (Plasma Search). Aside from that there is also various bug fixes regarding the typing notifications.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.02" date="2022-02-09">
|
||||
<description>
|
||||
<p>NeoChat 22.02 focus on stability and adds a few quality of life improvements</p>
|
||||
<ul>
|
||||
<li>Add support for minimizing to system tray on startup</li>
|
||||
<li>Improved internet connectivity check</li>
|
||||
<li>Add support for sharing images and files with other apps (Nextcloud, Imgur, ...)</li>
|
||||
<li>Implement adding labels for account. This allow for an easier organization when using multiple accounts.</li>
|
||||
<li>Redesign of our config dialogs to follow the new Plasma System Settings style</li>
|
||||
<li>Fix various others issues and small feature requests. Decreasing the total amount of open issues by 20%.</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url>https://www.plasma-mobile.org/2022/02/09/plasma-mobile-gear-22-02/#neochat</url>
|
||||
</release>
|
||||
<release version="21.12" date="2021-12-07">
|
||||
<description>
|
||||
<p>NeoChat 21.12 brings lots of new features and fixes</p>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
# SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||
[Desktop Entry]
|
||||
Version=1.5
|
||||
Name=NeoChat
|
||||
Name[ar]=نيوتشات
|
||||
Name[az]=NeoChat
|
||||
Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
@@ -14,6 +16,7 @@ Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
Name[it]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
@@ -28,10 +31,12 @@ Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
GenericName=Matrix Client
|
||||
GenericName[ar]=عميل ماتركس
|
||||
GenericName[az]=Matrix Müştərisi
|
||||
GenericName[ca]=Client de Matrix
|
||||
GenericName[ca@valencia]=Client de Matrix
|
||||
@@ -44,6 +49,7 @@ GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrice
|
||||
GenericName[id]=Klien Matrix
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
GenericName[lt]=Matrix kliento programą
|
||||
@@ -58,10 +64,12 @@ GenericName[sk]=Matrix Client
|
||||
GenericName[sl]=Odjemalec Matrix
|
||||
GenericName[sv]=Matrix-klient
|
||||
GenericName[ta]=Matrix வாங்கி
|
||||
GenericName[tr]=Matrix İstemcisi
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[ar]=عميل لميفاق ماتركس
|
||||
Comment[az]=Matrix protokolu üçün müştəri
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
@@ -73,6 +81,7 @@ Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
@@ -87,6 +96,7 @@ Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
|
||||
Comment[tr]=Matrix protokolü için istemci
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
@@ -96,3 +106,4 @@ Terminal=false
|
||||
Icon=org.kde.neochat
|
||||
Type=Application
|
||||
Categories=Network;InstantMessaging;
|
||||
SingleMainWindow=true
|
||||
|
||||
1
org.kde.neochat.tray.svg
Normal file
1
org.kde.neochat.tray.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="22" height="22" fill="none" version="1.1" id="svg13" xmlns="http://www.w3.org/2000/svg"><style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M2 4h18v11H6.681L3 18.067V15H2zm1 10h1v1.933L6.319 14H19V5H3z" id="path3"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect5" d="M4 7h9v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect7" d="M4 9h7v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect9" d="M4 11h5v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="m16 15.293-1.147-1.146-.707.707 2.853 2.853V14.5h-1z" id="path11"/></svg>
|
||||
|
After Width: | Height: | Size: 928 B |
2394
po/ar/neochat.po
Normal file
2394
po/ar/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2539
po/az/neochat.po
Normal file
2539
po/az/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2409
po/ca/neochat.po
Normal file
2409
po/ca/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2410
po/ca@valencia/neochat.po
Normal file
2410
po/ca@valencia/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2361
po/cs/neochat.po
Normal file
2361
po/cs/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2469
po/da/neochat.po
Normal file
2469
po/da/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2538
po/de/neochat.po
Normal file
2538
po/de/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2537
po/en_GB/neochat.po
Normal file
2537
po/en_GB/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2598
po/es/neochat.po
Normal file
2598
po/es/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2560
po/eu/neochat.po
Normal file
2560
po/eu/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2543
po/fi/neochat.po
Normal file
2543
po/fi/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2586
po/fr/neochat.po
Normal file
2586
po/fr/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2582
po/hu/neochat.po
Normal file
2582
po/hu/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2478
po/ia/neochat.po
Normal file
2478
po/ia/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2401
po/id/neochat.po
Normal file
2401
po/id/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2554
po/it/neochat.po
Normal file
2554
po/it/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2347
po/ja/neochat.po
Normal file
2347
po/ja/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2466
po/ko/neochat.po
Normal file
2466
po/ko/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2596
po/nl/neochat.po
Normal file
2596
po/nl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2326
po/nn/neochat.po
Normal file
2326
po/nn/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2538
po/pa/neochat.po
Normal file
2538
po/pa/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2551
po/pl/neochat.po
Normal file
2551
po/pl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user