Compare commits
494 Commits
move-state
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
1ace482c74 | ||
|
010debe3e4 | ||
|
72a2cfea7b | ||
|
25cf098bb5 | ||
|
d094ffafb2 | ||
|
9d25124810 | ||
|
409992686e | ||
|
a43fc91b16 | ||
|
e1f3cfd92a | ||
|
e8c682dca7 | ||
|
c776c6fd32 | ||
|
fdad10ceab | ||
|
9078ab8a98 | ||
|
a1ea58dd95 | ||
|
3151c6a8e3 | ||
|
44c5236808 | ||
|
d52c2515bc | ||
|
0bf611beb2 | ||
|
b60d6f4d58 | ||
|
2df4c532ed | ||
|
0d0709ccd3 | ||
|
7333de19da | ||
|
ab0baf5d9e | ||
|
1321d9bcca | ||
|
986b561c34 | ||
|
7431b51d27 | ||
|
b160baba4e | ||
|
6ac10963b0 | ||
|
2d678bdcf6 | ||
|
9ab1dc253e | ||
|
6d464381e4 | ||
|
f626de0447 | ||
|
7644b6e442 | ||
|
82fa8ab292 | ||
|
620b6e8838 | ||
|
dbf23fafbf | ||
|
32962a5616 | ||
|
76a9240076 | ||
|
5b6671f063 | ||
|
788131daf0 | ||
|
1e3f0f3b26 | ||
|
2b253ead9e | ||
|
65d85768d0 | ||
|
0169193a5e | ||
|
8236f6ba72 | ||
|
72d74ac59f | ||
|
6fd485a74a | ||
|
0619120ab5 | ||
|
33ad65dc17 | ||
|
2baa0f9537 | ||
|
9f3e066414 | ||
|
3dc1c3d784 | ||
|
a6b284e12c | ||
|
7c206b1b43 | ||
|
ddb1983c63 | ||
|
b74c5a5d4f | ||
|
576cd0f569 | ||
|
70d6b7f06e | ||
|
085d064b98 | ||
|
5cdb296da2 | ||
|
721ffc9b7c | ||
|
874c057ce5 | ||
|
fdaf7aab27 | ||
|
3fb7f1b310 | ||
|
207cf9f911 | ||
|
9699e95688 | ||
|
1936749ff5 | ||
|
20c1ca2aae | ||
|
4ba4586399 | ||
|
a4aaa5d868 | ||
|
7b141ed082 | ||
|
e256f342ff | ||
|
28dfd9b02b | ||
|
95a603fd62 | ||
|
d6109b95fb | ||
|
a792ea1465 | ||
|
59b6712e28 | ||
|
c41879c7d6 | ||
|
c2e625756c | ||
|
1b0af04cc8 | ||
|
a402e85a0f | ||
|
5a5aba662e | ||
|
01ee25cee4 | ||
|
ab83c7c3a8 | ||
|
2dfa40e017 | ||
|
663c3b0014 | ||
|
fcda5ace6b | ||
|
06e12a0a16 | ||
|
eb13f7c169 | ||
|
eaa91b4e56 | ||
|
3022178334 | ||
|
84b2cf08a1 | ||
|
9934004702 | ||
|
dff5cfc3ba | ||
|
6893e3a8d5 | ||
|
ce8246238e | ||
|
603ff33ea6 | ||
|
a6c89d1362 | ||
|
3a0ad55ecc | ||
|
99314c948e | ||
|
8108d98fa7 | ||
|
18e96d5c7d | ||
|
9d97f9f705 | ||
|
2e597263a2 | ||
|
7d6bd67615 | ||
|
28074794e7 | ||
|
ff449c705c | ||
|
c8a547630b | ||
|
795b8fb7dd | ||
|
ec6f0f9296 | ||
|
6c71802680 | ||
|
007ee38b04 | ||
|
5f4ab925da | ||
|
b67520256c | ||
|
8719d59e22 | ||
|
6cf3d97ebd | ||
|
877685d66d | ||
|
fa68ae9fe9 | ||
|
edaeb3ccde | ||
|
326f48d87f | ||
|
c5f4245945 | ||
|
a8f5672715 | ||
|
4b45c61024 | ||
|
3317d4582d | ||
|
463dd20682 | ||
|
6c31bb6ddc | ||
|
ff2e7bb989 | ||
|
e7f20eeae0 | ||
|
4e6150f28e | ||
|
4201ade5ae | ||
|
38ddfabee6 | ||
|
27d6c0fb92 | ||
|
1a448ae584 | ||
|
64e29b07e2 | ||
|
39ff68c6e6 | ||
|
939f00c90d | ||
|
a898abcecb | ||
|
947b8c0291 | ||
|
583fd9b5ab | ||
|
1bd59a3939 | ||
|
ae19dd2bc2 | ||
|
32f5e35037 | ||
|
ea2fb7f8df | ||
|
44bd3376ce | ||
|
0d4ddadb15 | ||
|
e46ddbbb45 | ||
|
2108d98c6d | ||
|
21a649492f | ||
|
b31e74d9f6 | ||
|
0e60c09b19 | ||
|
f6de66576c | ||
|
98b2df43ad | ||
|
1408b1a97d | ||
|
9b9d784a82 | ||
|
21562eed75 | ||
|
95bbc559fa | ||
|
9754b94364 | ||
|
f6d2fa5ec1 | ||
|
e5d75c814b | ||
|
95026dcc62 | ||
|
41737ac22c | ||
|
95a26edad2 | ||
|
716c598f4a | ||
|
5da6ab0aec | ||
|
98b2fee71b | ||
|
64dd10a6a0 | ||
|
82bbdfb929 | ||
|
fda6d7629a | ||
|
8b33b1f08b | ||
|
3748d7853e | ||
|
2192e8bea8 | ||
|
9168c2c785 | ||
|
f578272a0d | ||
|
37acdad928 | ||
|
c693d54598 | ||
|
df998ef671 | ||
|
b57b76d948 | ||
|
39576fea96 | ||
|
d8fb4d9292 | ||
|
4a86e14d04 | ||
|
4150d75be7 | ||
|
c38c6fe49e | ||
|
dcd9b80dde | ||
|
b05657d51a | ||
|
01bbec88dd | ||
|
e630504863 | ||
|
648844089c | ||
|
09303ca49f | ||
|
ae7468a716 | ||
|
8d3e463fa6 | ||
|
3dcbac8875 | ||
|
c74e68c945 | ||
|
029ae18a07 | ||
|
dd1ed7e3fb | ||
|
cc3d32c65e | ||
|
0dc40e50f8 | ||
|
2ee31aa09e | ||
|
8ca3a8b607 | ||
|
86766b739d | ||
|
6548b84e29 | ||
|
569ea5b5f4 | ||
|
61c5dffffd | ||
|
aa0223c041 | ||
|
a4b7966d21 | ||
|
dcaebea0b0 | ||
|
e438fc1068 | ||
|
e490ef953f | ||
|
5614f70576 | ||
|
02e388e542 | ||
|
47a7adf823 | ||
|
9b8e6c7f5c | ||
|
98e0b95635 | ||
|
c47ae99805 | ||
|
eb9603f4c0 | ||
|
05c636a8d4 | ||
|
8ebb55623e | ||
|
1961312b15 | ||
|
b82c11bd79 | ||
|
7a356f3832 | ||
|
df797175ca | ||
|
ce547357b8 | ||
|
6ad4065de4 | ||
|
2a70847c80 | ||
|
27fe0a45b6 | ||
|
e78be31429 | ||
|
e84d3ffb85 | ||
|
5ffd81710a | ||
|
31cc727594 | ||
|
dea7680604 | ||
|
3da9c45df2 | ||
|
c03f716e8b | ||
|
bb6ff8cec8 | ||
|
d5e578d0e4 | ||
|
47e97d490c | ||
|
8870455f9d | ||
|
973ec13ad8 | ||
|
57a6edadcb | ||
|
22071f4ff7 | ||
|
52b38abd08 | ||
|
3846adfecc | ||
|
e5cff64460 | ||
|
2685eec6c7 | ||
|
e1c96569c1 | ||
|
02e459b4e6 | ||
|
1be42045ee | ||
|
a305c2689c | ||
|
13cdbd5227 | ||
|
f4164cc799 | ||
|
626d8bf151 | ||
|
56c44d0454 | ||
|
4b12b53133 | ||
|
a6f0d2ea7d | ||
|
87bf761dc2 | ||
|
1142fe2663 | ||
|
387ba1a1e6 | ||
|
e3803ceb9a | ||
|
3b4f8f2016 | ||
|
67ab204050 | ||
|
d6f80e9275 | ||
|
c2898623dd | ||
|
0704b3cc84 | ||
|
d28a620f42 | ||
|
1b0abe97f9 | ||
|
fe6d990ad3 | ||
|
aa04c233bd | ||
|
c710b4257c | ||
|
40de75c40b | ||
|
818fc51059 | ||
|
a0fad2513e | ||
|
a651643f6e | ||
|
d405ce7878 | ||
|
1f8a3ae1e8 | ||
|
b55e6fbae8 | ||
|
0bf3f4634d | ||
|
099207b88c | ||
|
6baa775ec8 | ||
|
12e40a13cb | ||
|
55fb00c67b | ||
|
402bd565cb | ||
|
1f373479b8 | ||
|
c62f328590 | ||
|
8846a2a013 | ||
|
4c71ca8110 | ||
|
4a5b9d014a | ||
|
043737c8cb | ||
|
345dc1e61f | ||
|
f6b5b24d64 | ||
|
efe240d609 | ||
|
68c999e5f4 | ||
|
0ce7f78446 | ||
|
096f37df1f | ||
|
29a71741f4 | ||
|
db78cec626 | ||
|
6800be2483 | ||
|
be85c51b91 | ||
|
b9f72c2098 | ||
|
2787103903 | ||
|
150281b4de | ||
|
8ca819525a | ||
|
3e80f55f4e | ||
|
28fd47bf8d | ||
|
ebb35e5cb1 | ||
|
97c2505619 | ||
|
865344c7aa | ||
|
7560972cac | ||
|
736ba5e2e8 | ||
|
78ecffb45b | ||
|
af9b66dd3e | ||
|
ee232c5c60 | ||
|
b1dec6f6ac | ||
|
3f4ad1dd8b | ||
|
70c77cdc44 | ||
|
0922a8e4c7 | ||
|
32d419d14f | ||
|
e8e88e7d79 | ||
|
c461c0aac0 | ||
|
8ccd2abc6a | ||
|
8351cc4180 | ||
|
264a85b9e4 | ||
|
ebd12a6f33 | ||
|
99efe2f06b | ||
|
3ea0e79a36 | ||
|
744feabeca | ||
|
3b26cf4ba3 | ||
|
8df10eeeca | ||
|
9f7dc5488e | ||
|
b8c6c716be | ||
|
c9393fe3f6 | ||
|
0b6c82dfff | ||
|
8aadde7885 | ||
|
7ff7e33d4a | ||
|
f76f7b7f8a | ||
|
a1cd55917b | ||
|
8c4f0a070e | ||
|
0d971f543b | ||
|
cae5531c4b | ||
|
96028f00a5 | ||
|
19dbbb2c6c | ||
|
f35e826485 | ||
|
a2dab31fd6 | ||
|
b5e351ab02 | ||
|
8400540428 | ||
|
734fb7e286 | ||
|
d43607d01c | ||
|
0d61f4bff1 | ||
|
ca237f36b9 | ||
|
8c62df1bab | ||
|
567b2d05ef | ||
|
1a406f79e6 | ||
|
9f9c499cb2 | ||
|
299c486a2b | ||
|
4996ae27a0 | ||
|
6540352123 | ||
|
f3596aed55 | ||
|
7ddcab3902 | ||
|
e2fc676c77 | ||
|
3c91b5b47b | ||
|
35aa0126ac | ||
|
192c3b7a77 | ||
|
cc1f9a079b | ||
|
f044e2d2a1 | ||
|
a7d7d18e92 | ||
|
473b14ed0f | ||
|
7401bd13b2 | ||
|
6ff8db1799 | ||
|
0db4d71ec2 | ||
|
2a5e20dc6f | ||
|
b70f37194f | ||
|
6c65136101 | ||
|
2606568376 | ||
|
6a2e8a6952 | ||
|
a62276c289 | ||
|
4a5b5f992d | ||
|
29c89b1b9e | ||
|
6d678a108f | ||
|
bdb6e6b79e | ||
|
6e2ae1d812 | ||
|
9b7d33e847 | ||
|
00fd4eecec | ||
|
faeaf9dc6b | ||
|
d6504812c7 | ||
|
2e77a1554f | ||
|
463cee7146 | ||
|
6aabe9d5f7 | ||
|
37679ac57e | ||
|
8d95532b28 | ||
|
0285bf5e4e | ||
|
50f994bd23 | ||
|
23a9306383 | ||
|
3797f585c2 | ||
|
cb704006a5 | ||
|
2fcc9c2fbb | ||
|
974c336c5e | ||
|
8d68534456 | ||
|
04b920fbee | ||
|
f1bc3ba587 | ||
|
c2a56fc233 | ||
|
375e20462b | ||
|
8d195a4d11 | ||
|
87122ffe77 | ||
|
49ce7701d3 | ||
|
4aefac08a4 | ||
|
f02342fe22 | ||
|
343c9c8116 | ||
|
777b9bf20d | ||
|
46e15218d4 | ||
|
eae09f8f14 | ||
|
7874d61c33 | ||
|
e8ff6c9486 | ||
|
cf00abc03e | ||
|
e0207ff337 | ||
|
a7150b5666 | ||
|
3433cc3be7 | ||
|
724c86e663 | ||
|
5109dc5e0f | ||
|
e466cd10ff | ||
|
f73418f368 | ||
|
aeec1e12fc | ||
|
935abee62e | ||
|
2a858d84e2 | ||
|
2bfd44755e | ||
|
2ff3c0c97e | ||
|
cd3f719e43 | ||
|
d535cc5e75 | ||
|
82e80d464d | ||
|
08dbdac3b7 | ||
|
1c3e113d4e | ||
|
00885e41f8 | ||
|
1127aa7c91 | ||
|
53c653a228 | ||
|
d59910a8f2 | ||
|
4874006501 | ||
|
2b7bd09ad3 | ||
|
c3e02240bf | ||
|
50e2b5617f | ||
|
0ebb2947ef | ||
|
f5693b56f0 | ||
|
9709d6af16 | ||
|
5930b8eb31 | ||
|
58dc79074c | ||
|
c3fa592018 | ||
|
d8ebc0b3dc | ||
|
cd998d1c35 | ||
|
e09e587796 | ||
|
9b5a287d14 | ||
|
195bb0499b | ||
|
fa7ad4f234 | ||
|
3b82b2ff97 | ||
|
b3f29f592b | ||
|
87490c29cd | ||
|
12bc7d4131 | ||
|
1a2517e829 | ||
|
105e097e02 | ||
|
50564985cc | ||
|
5fbf17f6ec | ||
|
73244afdb8 | ||
|
6c370b1f0e | ||
|
982784883c | ||
|
54cc77de0e | ||
|
9e48659016 | ||
|
d1280af2e4 | ||
|
498bfb89b3 | ||
|
b07f26bde6 | ||
|
0ac7912f50 | ||
|
a413c538e4 | ||
|
5e3f513655 | ||
|
bfeb766a91 | ||
|
cb93ac3402 | ||
|
0794f0a3fd | ||
|
e6dc1da22f | ||
|
f0a6ad6ea4 | ||
|
61cc4cc37d | ||
|
a5cd283423 | ||
|
e57dc5ea83 | ||
|
7629e9b786 | ||
|
bc7cf9ef39 | ||
|
e3f95fcdab | ||
|
0b5269bfc0 | ||
|
e5d2e2b728 | ||
|
2165eb8cb8 | ||
|
be49d184be | ||
|
6313ecb7d4 | ||
|
f0102c1e55 | ||
|
fc76a939bb | ||
|
a5944ab047 | ||
|
b76865de61 | ||
|
219ed587ca | ||
c569ab24bc | |||
|
ccc5903161 | ||
|
577d403be7 | ||
|
fdf5acac74 | ||
|
7c0b05e89c | ||
|
66be966375 | ||
|
7bc57f76f7 |
@ -7,11 +7,24 @@
|
||||
|
||||
set -eu
|
||||
|
||||
FILES=$(find src -type f -type f \( -iname "*.cpp" -o -iname "*.h" \))
|
||||
FILES=$(find src -type f \( -iname "*.cpp" -o -iname "*.h" \))
|
||||
|
||||
for f in $FILES
|
||||
do
|
||||
clang-format -i "$f"
|
||||
done;
|
||||
|
||||
QMLFORMAT_PATH=$(command -v qmlformat || true)
|
||||
|
||||
if [ -n "$QMLFORMAT_PATH" ]; then
|
||||
QML_FILES=$(find resources -type f -iname "*.qml")
|
||||
|
||||
for f in $QML_FILES
|
||||
do
|
||||
$QMLFORMAT_PATH -i "$f"
|
||||
done;
|
||||
else
|
||||
echo "qmlformat not found; skipping qml formatting"
|
||||
fi
|
||||
|
||||
git diff --exit-code
|
||||
|
14
.ci/licenses.sh
Executable file
14
.ci/licenses.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Runs the license update
|
||||
# Return codes:
|
||||
# - 1 there are files to be formatted
|
||||
# - 0 everything looks fine
|
||||
|
||||
set -eu
|
||||
|
||||
FILES=$(find src resources/qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \))
|
||||
|
||||
reuse addheader --copyright="Nheko Contributors" --license="GPL-3.0-or-later" $FILES
|
||||
|
||||
git diff --exit-code
|
@ -11,13 +11,13 @@ build-gcc7:
|
||||
variables:
|
||||
CXX: g++-7
|
||||
CC: gcc-7
|
||||
QT_PKG: 510
|
||||
QT_PKG: 512
|
||||
TRAVIS_OS_NAME: linux
|
||||
before_script:
|
||||
- apt-get update
|
||||
- apt-get install -y software-properties-common
|
||||
- add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
- add-apt-repository ppa:beineri/opt-qt-5.10.1-xenial -y
|
||||
- add-apt-repository ppa:beineri/opt-qt-5.12.9-xenial -y
|
||||
- apt-get update && apt-get -y install --no-install-recommends g++-7 build-essential ninja-build qt${QT_PKG}{base,declarative,tools,multimedia,script,quickcontrols2,svg} liblmdb-dev libgl1-mesa-dev libssl-dev git ccache
|
||||
# need recommended deps for wget
|
||||
- apt-get -y install wget
|
||||
@ -37,7 +37,7 @@ build-gcc7:
|
||||
- cmake -GNinja -H. -Bbuild
|
||||
-DCMAKE_INSTALL_PREFIX=.deps/usr
|
||||
-DHUNTER_ROOT=".hunter"
|
||||
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF
|
||||
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DUSE_BUNDLED_LMDB=OFF
|
||||
-DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
|
||||
-DCI_BUILD=ON
|
||||
- cmake --build build
|
||||
@ -88,7 +88,7 @@ build-flatpak-amd64:
|
||||
#image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master'
|
||||
tags: [docker]
|
||||
before_script:
|
||||
- apt-get update && apt-get -y install flatpak-builder git python curl
|
||||
- apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0
|
||||
- flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
- flatpak --noninteractive install --user flathub org.kde.Platform//5.15
|
||||
- flatpak --noninteractive install --user flathub org.kde.Sdk//5.15
|
||||
@ -96,9 +96,10 @@ build-flatpak-amd64:
|
||||
- export VERSION=$(git describe)
|
||||
- mkdir -p build-flatpak
|
||||
- cd build-flatpak
|
||||
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json
|
||||
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.yaml
|
||||
- flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_}
|
||||
after_script:
|
||||
- (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true
|
||||
- bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-amd64.flatpak
|
||||
cache:
|
||||
key: "$CI_JOB_NAME"
|
||||
@ -115,7 +116,7 @@ build-flatpak-arm64:
|
||||
#image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master'
|
||||
tags: [docker-arm64]
|
||||
before_script:
|
||||
- apt-get update && apt-get -y install flatpak-builder git python curl
|
||||
- apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0
|
||||
- flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
- flatpak --noninteractive install --user flathub org.kde.Platform//5.15
|
||||
- flatpak --noninteractive install --user flathub org.kde.Sdk//5.15
|
||||
@ -123,9 +124,10 @@ build-flatpak-arm64:
|
||||
- export VERSION=$(git describe)
|
||||
- mkdir -p build-flatpak
|
||||
- cd build-flatpak
|
||||
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.json
|
||||
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.yaml
|
||||
- flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_}
|
||||
after_script:
|
||||
- (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true
|
||||
- bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-arm64.flatpak
|
||||
cache:
|
||||
key: "$CI_JOB_NAME"
|
||||
@ -141,9 +143,12 @@ linting:
|
||||
image: alpine:latest
|
||||
tags: [docker]
|
||||
before_script:
|
||||
- apk update && apk add clang make git
|
||||
- apk update && apk add clang make git python3 py3-pip
|
||||
- export PATH="$PATH:/root/.local/bin"
|
||||
- pip3 install --user reuse
|
||||
script:
|
||||
- make lint
|
||||
- make license
|
||||
|
||||
appimage-amd64:
|
||||
stage: build
|
||||
|
@ -79,6 +79,7 @@ AppDir:
|
||||
- libxv1
|
||||
- libxxf86vm1
|
||||
- libzstd1
|
||||
- qml-module-qt-labs-platform
|
||||
- qml-module-qtgraphicaleffects
|
||||
- qml-module-qtmultimedia
|
||||
- qml-module-qtquick-controls2
|
||||
|
146
CHANGELOG.md
146
CHANGELOG.md
@ -1,5 +1,151 @@
|
||||
# Changelog
|
||||
|
||||
## [0.8.2] -- 2021-04-23
|
||||
|
||||
### Highlights
|
||||
|
||||
- Edits
|
||||
- If you made a typo, just press the `Up` key and edit what you wrote.
|
||||
- Messages other users edited will get updated automatically and have a small
|
||||
pen symbol next to them.
|
||||
- Privacy Screen
|
||||
- Blur your messages, when Nheko looses focus, which prevents others from
|
||||
peeking at your messages.
|
||||
- You can configure the timeout of when this happens.
|
||||
- Improved notifications (contributed by lorendb)
|
||||
- No more breakage, because the message included a > on KDE based DEs.
|
||||
- Render html and images where possible in the notification.
|
||||
- Render if a message is a reply or someone sent an emote message more nicely
|
||||
where possible.
|
||||
- Encrypted notifications now show, that the content is encrypted instead of
|
||||
being empty.
|
||||
- Screenshare support in calls on X11 (contributed by trilene)
|
||||
- Share your screen in a call!
|
||||
- Select if your mouse cursor should be shown or not and if your webcam should
|
||||
be included.
|
||||
- SEND MESSAGES AS RAINBOWS! (contributed by LordMZTE)
|
||||
- YES MESSAGES, EMOTES AND NOTICES!
|
||||
|
||||
### Features
|
||||
|
||||
- Set your displayname and avatar from Nheko either globally or per room.
|
||||
(contributed by jedi18)
|
||||
- Show room topic in the room settings.
|
||||
- Double tap a message to reply to it.
|
||||
- Leave a room using `/part` or `/leave`. (contributed by lorendb)
|
||||
- Show mxid when hovering a username or avatar.
|
||||
- Allow opening matrix: uris on Windows.
|
||||
- Disable room pings caused by replies sent via Nheko (unless you are using
|
||||
Element Web/Desktop).
|
||||
|
||||
### Improvements
|
||||
- Userprofile can be closed via the Escape key. No more hotel california!
|
||||
(contributed by lorendb)
|
||||
- Most dialogs are now centered on the Nheko window. (contributed by lorendb)
|
||||
- Update Hungarian translations. (contributed by maxigaz)
|
||||
- Update Estonian translations. (contributed by Priit)
|
||||
- Update Russian translations. (contributed by Alexey Murz and Artem)
|
||||
- Update Swedish translations. (contributed by Emilie)
|
||||
- Update French translations. (contributed by MayeulC, Nicolas Guichard and Carl Schwan)
|
||||
- Allow drag and drop of files on the whole timeline. (contributed by lorendb)
|
||||
- Enable notifications on Haiku. (contributed by kallisti5)
|
||||
- Update scheme handler to the latest matrix: scheme proposal.
|
||||
- Close completers when typing a space after the colon. (contributed by jedi18)
|
||||
- Port room settings to Qml. (contributed by jedi18)
|
||||
- Improved read marker handling. Read marker should now get stuck less often.
|
||||
- Various changes around hover and tap handling in the timeline, which hopefully
|
||||
now works more predicatably.
|
||||
- Buttons in the timeline are now rendered in a box on hover on desktop
|
||||
platforms.
|
||||
- Complete room links in the timeline after typing a # character. (contributed
|
||||
by jedi18)
|
||||
- An improved quick switcher with better rendering and search. (contributed by jedi18)
|
||||
- Some fixes around inline emoji and images.
|
||||
- Jump into new rooms, after you created them. (contrubuted by jedi18)
|
||||
- Improved search in the emoji picker.
|
||||
- Allow disabling certificate checks via the config file.
|
||||
- Use native menus where possible.
|
||||
- Fix video playback on Windows. (contrubuted by jedi18)
|
||||
- Send image messages by pressing Enter. (contributed by salahmak)
|
||||
- Escape closes the upload widget. (contributed by salahmak)
|
||||
- Improve session rotation and sharing in E2EE rooms.
|
||||
|
||||
### Bugfixes
|
||||
- Emojis joined from separate emojis with a 0xfe0f in the middle should now
|
||||
render correctly.
|
||||
- Fix a bug when logging out of a non default profile clearing the wrong
|
||||
profile. (contrubuted by lorendb)
|
||||
- Various fixed around profile handling. (contributed by lorendb)
|
||||
- Focus message input after a reaction. (contributed by jedi18)
|
||||
- Disable native rendering to prevent kerning bugs on non integer scale factors.
|
||||
- Fix duplex call devices not showing up. (contributed by trilene)
|
||||
- Fix a few crashes when leaving a room. (contributed by jedi18)
|
||||
- Fix hidden tags not updating properly. (contributed by jedi18)
|
||||
- Fix some issues with login, when a server had SSO as well as password login
|
||||
enabled (for example matrix.org).
|
||||
- Properly set the dialog flag for dialogs on most platforms. (Wayland does not
|
||||
support that.)
|
||||
- Properly add license to source files.
|
||||
- Fix fingerprint increasing the minimum window size.
|
||||
- Don't send markdown links in the plain text body of events when autocompleting
|
||||
user or room names.
|
||||
- Fix webcam not working in flatpaks.
|
||||
- Fix markdown override in replies.
|
||||
- Fix unsupported events causing errors when saving them. (contributed by
|
||||
anjanik)
|
||||
- Fix exif rotation not being respected anymore in E2EE rooms.
|
||||
- Remove unused qml plugins in the windows package.
|
||||
- Fix broken olm channels automatically when noticed.
|
||||
- Fix pasting not overwriting the selection.
|
||||
- Fix Nheko sometimes overwriting received keys with keys it requested, even if
|
||||
they have a higher minimum index.
|
||||
|
||||
### Packaging changes
|
||||
- Added xcb dependency on X11 based platforms for screensharing (optional)
|
||||
- Bumped lmdbxx version from 0.9.14.0 to 1.0.0, which is a BREAKING change. You
|
||||
can get the new version here: https://github.com/hoytech/lmdbxx/releases
|
||||
(repo changed)
|
||||
- Removed tweeny as a dependency.
|
||||
|
||||
|
||||
## [0.8.1] -- 2021-01-27
|
||||
|
||||
### Features
|
||||
|
||||
- `/plain` and `/md` commands to override the current markdown setting. (contributed by lorendb)
|
||||
- Allow persistent hiding of rooms with a specific tag (or from a community) via a context menu.
|
||||
- Allow open media messages in an external program immediately. (contributed by rnhmjoj)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Use async dbus connection for notifications. (contributed by lorendb)
|
||||
- Update Hungarian translations. (contributed by maxigaz)
|
||||
- Update Finnish translations. (contributed by Priit)
|
||||
- Update Malayalam translations. (contributed by vachan-maker)
|
||||
- Update Dutch translations. (contributed by Glael)
|
||||
- Store splitter size across restarts.
|
||||
- Add a border around the completer. (contributed by lorendb)
|
||||
- Request keys for messages with unknown message indices (once per restart, when they are shown).
|
||||
- Move the database location to XDG_DATA_DIR. (contributed by rnhmjoj)
|
||||
- Reload the timeline after key backup import.
|
||||
- Autoclose completer on `space`, when there are no matches.
|
||||
- Make completer only react, when the mouse cursor is moved.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fix unhandled exception, when a device has no keys.
|
||||
- Fix some cmake warnings regarding GNUInstallDirs.
|
||||
- Fix tags being broken. If you have no tags showing up, you may want to logout and login again.
|
||||
- Fix versionOk being called on the wrong thread. (contributed by Jedi18)
|
||||
- Fix font tags showing up in media message filenames.
|
||||
- Fix user profile in dark themes showing the wrong colors. (contributed by lorendb)
|
||||
- Fix emoji category switching on old Qt versions. (contributed by lorendb)
|
||||
- Fix old messages being replayed after a limited timeline.
|
||||
- Fix empty secrets being returned from the wallet breaking verification.
|
||||
- Make matrix link chat invites create a direct chat.
|
||||
- Fix focus handling on room change or reply button clicks.
|
||||
- Fix username completion deleting the character before it.
|
||||
|
||||
## [0.8.0] -- 2021-01-21
|
||||
|
||||
### Highlights
|
||||
|
@ -33,14 +33,12 @@ option(USE_BUNDLED_CMARK "Use the bundled version of cmark."
|
||||
option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
|
||||
${HUNTER_ENABLED})
|
||||
option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL."
|
||||
${HUNTER_ENABLED})
|
||||
OFF)
|
||||
option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED})
|
||||
option(USE_BUNDLED_LMDB "Use the bundled version of lmdb."
|
||||
${HUNTER_ENABLED})
|
||||
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
|
||||
${HUNTER_ENABLED})
|
||||
option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
|
||||
${HUNTER_ENABLED})
|
||||
option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain."
|
||||
${HUNTER_ENABLED})
|
||||
|
||||
@ -72,16 +70,16 @@ if(${CMAKE_VERSION} VERSION_LESS "3.14.0")
|
||||
endmacro()
|
||||
endif()
|
||||
|
||||
# Include Qt basic functions
|
||||
include(QtCommon)
|
||||
|
||||
project(nheko LANGUAGES CXX C)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Include Qt basic functions
|
||||
include(QtCommon)
|
||||
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "0")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "8")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "0")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "2")
|
||||
set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR})
|
||||
set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR})
|
||||
set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
|
||||
@ -129,8 +127,11 @@ endif()
|
||||
if(USE_BUNDLED_LMDB)
|
||||
hunter_add_package(lmdb)
|
||||
find_package(liblmdb CONFIG REQUIRED)
|
||||
|
||||
target_include_directories(liblmdb::lmdb INTERFACE
|
||||
"${HUNTER_INSTALL_PREFIX}/include/lmdb")
|
||||
else()
|
||||
find_package(LMDB)
|
||||
find_package(LMDB REQUIRED)
|
||||
endif()
|
||||
|
||||
#
|
||||
@ -210,7 +211,7 @@ set(SPDLOG_DEBUG_ON false)
|
||||
|
||||
# Windows doesn't handle CMAKE_BUILD_TYPE.
|
||||
if(NOT WIN32)
|
||||
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(SPDLOG_DEBUG_ON true)
|
||||
else()
|
||||
set(SPDLOG_DEBUG_ON false)
|
||||
@ -257,7 +258,6 @@ set(SRC_FILES
|
||||
src/dialogs/PreviewUploadOverlay.cpp
|
||||
src/dialogs/ReCaptcha.cpp
|
||||
src/dialogs/ReadReceipts.cpp
|
||||
src/dialogs/RoomSettings.cpp
|
||||
|
||||
# Emoji
|
||||
src/emoji/EmojiModel.cpp
|
||||
@ -271,6 +271,7 @@ set(SRC_FILES
|
||||
src/timeline/TimelineViewManager.cpp
|
||||
src/timeline/TimelineModel.cpp
|
||||
src/timeline/DelegateChooser.cpp
|
||||
src/timeline/Permissions.cpp
|
||||
|
||||
# UI components
|
||||
src/ui/Avatar.cpp
|
||||
@ -281,6 +282,7 @@ set(SRC_FILES
|
||||
src/ui/InfoMessage.cpp
|
||||
src/ui/Label.cpp
|
||||
src/ui/LoadingIndicator.cpp
|
||||
src/ui/NhekoCursorShape.cpp
|
||||
src/ui/NhekoDropArea.cpp
|
||||
src/ui/OverlayModal.cpp
|
||||
src/ui/OverlayWidget.cpp
|
||||
@ -294,12 +296,18 @@ set(SRC_FILES
|
||||
src/ui/ThemeManager.cpp
|
||||
src/ui/ToggleButton.cpp
|
||||
src/ui/UserProfile.cpp
|
||||
src/ui/RoomSettings.cpp
|
||||
|
||||
# Generic notification stuff
|
||||
src/notifications/Manager.cpp
|
||||
|
||||
src/AvatarProvider.cpp
|
||||
src/BlurhashProvider.cpp
|
||||
src/Cache.cpp
|
||||
src/CallDevices.cpp
|
||||
src/CallManager.cpp
|
||||
src/ChatPage.cpp
|
||||
src/Clipboard.cpp
|
||||
src/ColorImageProvider.cpp
|
||||
src/CommunitiesList.cpp
|
||||
src/CommunitiesListItem.cpp
|
||||
@ -313,7 +321,6 @@ set(SRC_FILES
|
||||
src/MatrixClient.cpp
|
||||
src/MxcImageProvider.cpp
|
||||
src/Olm.cpp
|
||||
src/QuickSwitcher.cpp
|
||||
src/RegisterPage.cpp
|
||||
src/RoomInfoListItem.cpp
|
||||
src/RoomList.cpp
|
||||
@ -324,6 +331,7 @@ set(SRC_FILES
|
||||
src/UserInfoWidget.cpp
|
||||
src/UserSettingsPage.cpp
|
||||
src/UsersModel.cpp
|
||||
src/RoomsModel.cpp
|
||||
src/Utils.cpp
|
||||
src/WebRTCSession.cpp
|
||||
src/WelcomePage.cpp
|
||||
@ -355,13 +363,13 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||
FetchContent_Declare(
|
||||
MatrixClient
|
||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||
GIT_TAG v0.4.0
|
||||
GIT_TAG 5d2f055ea9403770039ddf66b1900f890cd5cde7
|
||||
)
|
||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||
FetchContent_MakeAvailable(MatrixClient)
|
||||
else()
|
||||
find_package(MatrixClient 0.4.0 REQUIRED)
|
||||
find_package(MatrixClient 0.5.1 REQUIRED)
|
||||
endif()
|
||||
if(USE_BUNDLED_OLM)
|
||||
include(FetchContent)
|
||||
@ -373,7 +381,7 @@ if(USE_BUNDLED_OLM)
|
||||
set(OLM_TESTS OFF CACHE INTERNAL "")
|
||||
FetchContent_MakeAvailable(Olm)
|
||||
else()
|
||||
find_package(Olm 3)
|
||||
find_package(Olm 3 REQUIRED)
|
||||
set_package_properties(Olm PROPERTIES
|
||||
DESCRIPTION "An implementation of the Double Ratchet cryptographic ratchet"
|
||||
URL "https://git.matrix.org/git/olm/about/"
|
||||
@ -414,8 +422,18 @@ set_package_properties(nlohmann_json PROPERTIES
|
||||
)
|
||||
|
||||
if(USE_BUNDLED_LMDBXX)
|
||||
hunter_add_package(lmdbxx)
|
||||
find_package(lmdbxx CONFIG REQUIRED)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
lmdbxx
|
||||
URL "https://raw.githubusercontent.com/hoytech/lmdbxx/1.0.0/lmdb++.h"
|
||||
DOWNLOAD_NO_EXTRACT TRUE
|
||||
)
|
||||
if(NOT lmdbxx_POPULATED)
|
||||
FetchContent_Populate(lmdbxx)
|
||||
endif()
|
||||
add_library(lmdbxx INTERFACE)
|
||||
target_include_directories(lmdbxx INTERFACE ${lmdbxx_SOURCE_DIR})
|
||||
add_library(lmdbxx::lmdbxx ALIAS lmdbxx)
|
||||
else()
|
||||
if(NOT LMDBXX_INCLUDE_DIR)
|
||||
find_path(LMDBXX_INCLUDE_DIR
|
||||
@ -431,24 +449,18 @@ else()
|
||||
add_library(lmdbxx::lmdbxx ALIAS lmdbxx)
|
||||
endif()
|
||||
|
||||
if(USE_BUNDLED_TWEENY)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
Tweeny
|
||||
GIT_REPOSITORY https://github.com/mobius3/tweeny.git
|
||||
GIT_TAG 6a5033372fe53c4c731c66c8a2d56261746cd85c #v3 <- v3 has unfixed warnings
|
||||
)
|
||||
FetchContent_MakeAvailable(Tweeny)
|
||||
else()
|
||||
find_package(Tweeny REQUIRED)
|
||||
endif()
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16)
|
||||
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18)
|
||||
if (TARGET PkgConfig::GSTREAMER)
|
||||
add_feature_info(voip ON "GStreamer found. Call support is enabled automatically.")
|
||||
pkg_check_modules(XCB IMPORTED_TARGET xcb xcb-ewmh)
|
||||
if (TARGET PkgConfig::XCB)
|
||||
add_feature_info("Window selection when screen sharing (X11)" ON "XCB-EWMH found. Window selection is enabled when screen sharing (X11).")
|
||||
else()
|
||||
add_feature_info("Window selection when screen sharing (X11)" OFF "XCB-EWMH could not be found on your system. Screen sharing (X11) is limited to the entire screen only. To enable window selection, make sure xcb and xcb-ewmh can be found via pkgconfig.")
|
||||
endif()
|
||||
else()
|
||||
add_feature_info(voip OFF "GStreamer could not be found on your system. As a consequence call support has been disabled. If you don't want that, make sure gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16 can be found via pkgconfig.")
|
||||
add_feature_info(voip OFF "GStreamer could not be found on your system. As a consequence call support has been disabled. If you don't want that, make sure gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18 can be found via pkgconfig.")
|
||||
endif()
|
||||
|
||||
# single instance functionality
|
||||
@ -471,7 +483,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/dialogs/RawMessage.h
|
||||
src/dialogs/ReCaptcha.h
|
||||
src/dialogs/ReadReceipts.h
|
||||
src/dialogs/RoomSettings.h
|
||||
|
||||
# Emoji
|
||||
src/emoji/EmojiModel.h
|
||||
@ -484,6 +495,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/timeline/TimelineViewManager.h
|
||||
src/timeline/TimelineModel.h
|
||||
src/timeline/DelegateChooser.h
|
||||
src/timeline/Permissions.h
|
||||
|
||||
# UI components
|
||||
src/ui/Avatar.h
|
||||
@ -494,6 +506,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/ui/Label.h
|
||||
src/ui/FloatingButton.h
|
||||
src/ui/Menu.h
|
||||
src/ui/NhekoCursorShape.h
|
||||
src/ui/NhekoDropArea.h
|
||||
src/ui/OverlayWidget.h
|
||||
src/ui/SnackBar.h
|
||||
@ -506,14 +519,18 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/ui/Theme.h
|
||||
src/ui/ThemeManager.h
|
||||
src/ui/UserProfile.h
|
||||
src/ui/RoomSettings.h
|
||||
|
||||
src/notifications/Manager.h
|
||||
|
||||
src/AvatarProvider.h
|
||||
src/BlurhashProvider.h
|
||||
src/Cache_p.h
|
||||
src/CacheCryptoStructs.h
|
||||
src/CallDevices.h
|
||||
src/CallManager.h
|
||||
src/ChatPage.h
|
||||
src/Clipboard.h
|
||||
src/CommunitiesList.h
|
||||
src/CommunitiesListItem.h
|
||||
src/CompletionProxyModel.h
|
||||
@ -522,7 +539,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/LoginPage.h
|
||||
src/MainWindow.h
|
||||
src/MxcImageProvider.h
|
||||
src/QuickSwitcher.h
|
||||
src/RegisterPage.h
|
||||
src/RoomInfoListItem.h
|
||||
src/RoomList.h
|
||||
@ -533,6 +549,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
src/UserInfoWidget.h
|
||||
src/UserSettingsPage.h
|
||||
src/UsersModel.h
|
||||
src/RoomsModel.h
|
||||
src/WebRTCSession.h
|
||||
src/WelcomePage.h
|
||||
src/popups/PopupItem.h
|
||||
@ -548,7 +565,7 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa")
|
||||
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm)
|
||||
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp src/emoji/MacHelper.mm)
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
|
||||
endif()
|
||||
@ -591,6 +608,9 @@ if(APPLE)
|
||||
elseif(WIN32)
|
||||
target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN)
|
||||
target_link_libraries (nheko PRIVATE ${NTDLIB} Qt5::WinMain)
|
||||
if(MSVC)
|
||||
target_compile_options(nheko PUBLIC "/Zc:__cplusplus")
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries (nheko PRIVATE Qt5::DBus)
|
||||
endif()
|
||||
@ -619,7 +639,6 @@ target_link_libraries(nheko PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
lmdbxx::lmdbxx
|
||||
liblmdb::lmdb
|
||||
tweeny
|
||||
SingleApplication::SingleApplication)
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
|
||||
@ -633,6 +652,10 @@ endif()
|
||||
if (TARGET PkgConfig::GSTREAMER)
|
||||
target_link_libraries(nheko PRIVATE PkgConfig::GSTREAMER)
|
||||
target_compile_definitions(nheko PRIVATE GSTREAMER_AVAILABLE)
|
||||
if (TARGET PkgConfig::XCB)
|
||||
target_link_libraries(nheko PRIVATE PkgConfig::XCB)
|
||||
target_compile_definitions(nheko PRIVATE XCB_AVAILABLE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
@ -645,7 +668,7 @@ if(QML_DEBUGGING)
|
||||
endif()
|
||||
|
||||
|
||||
if(NOT MSVC)
|
||||
if(NOT MSVC AND NOT HAIKU)
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR CI_BUILD)
|
||||
target_compile_options(nheko PRIVATE "-Werror")
|
||||
endif()
|
||||
|
3
Makefile
3
Makefile
@ -41,6 +41,9 @@ macos-app-install:
|
||||
lint:
|
||||
./.ci/format.sh
|
||||
|
||||
license:
|
||||
./.ci/licenses.sh
|
||||
|
||||
image:
|
||||
docker build -t nheko-app-image .
|
||||
|
||||
|
38
README.md
38
README.md
@ -2,8 +2,9 @@ nheko
|
||||
----
|
||||
[![Build Status](https://nheko.im/nheko-reborn/nheko/badges/master/pipeline.svg)](https://nheko.im/nheko-reborn/nheko/-/pipelines/latest)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master)
|
||||
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.0)
|
||||
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.2-RC)
|
||||
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/)
|
||||
<a href='https://flatpak.neko.dev/repo/nightly/appstream/io.github.NhekoReborn.Nheko.flatpakref' download='nheko-nightly.flatpakref'><img alt='Download Nightly Flatpak' src='https://img.shields.io/badge/download-flatpak--nightly-green'/></a>
|
||||
[![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org)
|
||||
[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko)
|
||||
<a href='https://flathub.org/apps/details/io.github.NhekoReborn.Nheko'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
|
||||
@ -49,7 +50,7 @@ Specifically there is support for:
|
||||
### Releases
|
||||
|
||||
Releases for Linux (AppImage), macOS (disk image) & Windows (x64 installer)
|
||||
can be found in the [Github releases](https://github.com/Nheko-Reborn/nheko/releases).
|
||||
can be found in the [GitHub releases](https://github.com/Nheko-Reborn/nheko/releases).
|
||||
|
||||
### Repositories
|
||||
|
||||
@ -74,7 +75,7 @@ sudo dnf install nheko
|
||||
|
||||
#### Gentoo Linux
|
||||
```bash
|
||||
sudo eselect repository enable matrix
|
||||
sudo eselect repository enable guru
|
||||
sudo emerge -a nheko
|
||||
```
|
||||
|
||||
@ -114,14 +115,29 @@ with [homebrew](https://brew.sh/):
|
||||
brew install --cask nheko
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
with [Chocolatey](https://chocolatey.org/):
|
||||
|
||||
```posh
|
||||
choco install nheko-reborn
|
||||
```
|
||||
|
||||
### FAQ
|
||||
|
||||
##
|
||||
**Q:** Why don't videos run for me on Windows?
|
||||
|
||||
**A:** You're probably missing the required video codecs, download [K-Lite Codec Pack](https://codecguide.com/download_kl.htm).
|
||||
##
|
||||
|
||||
### Build Requirements
|
||||
|
||||
- Qt5 (5.10 or greater). Qt 5.7 adds support for color font rendering with
|
||||
Freetype, which is essential to properly support emoji, 5.8 adds some features
|
||||
to make interopability with Qml easier, 5.10 makes sliders actually visible with different palettes.
|
||||
- Qt5 (5.12 or greater). Required for overlapping hover handlers in Qml.
|
||||
- CMake 3.15 or greater. (Lower version may work, but may break boost linking)
|
||||
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
|
||||
- [LMDB](https://symas.com/lightning-memory-mapped-database/)
|
||||
- [lmdb++](https://github.com/hoytech/lmdbxx)
|
||||
- [cmark](https://github.com/commonmark/cmark) 0.29 or greater.
|
||||
- Boost 1.70 or greater.
|
||||
- [libolm](https://gitlab.matrix.org/matrix-org/olm)
|
||||
@ -142,7 +158,7 @@ brew install --cask nheko
|
||||
Nheko can use bundled version for most of those libraries automatically, if the versions in your distro are too old.
|
||||
To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`.
|
||||
It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF`
|
||||
You can select which bundled dependencies you want to use by passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter.
|
||||
You can select which bundled dependencies you want to use by passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter. (The exception to that is OpenSSL, which is always disabled by default.)
|
||||
If you experience build issues and you are trying to link `mtxclient` library without hunter, make sure the library version(commit) as mentioned in the `CMakeList.txt` is used. Sometimes we have to make breaking changes in `mtxclient` and for that period the master branch of both repos may not be compatible.
|
||||
|
||||
The bundle flags are currently:
|
||||
@ -159,6 +175,8 @@ The bundle flags are currently:
|
||||
- USE_BUNDLED_LMDBXX
|
||||
- USE_BUNDLED_TWEENY
|
||||
|
||||
A note on bundled OpenSSL: You need to explicitly enable it and it will not be using your system certificate directory by default, if you enable it. You need to override that at runtime with the SSL_CERT_FILE variable. On Windows it will still be using your system certificates though, since it loads them from the system store instead of the OpenSSL directory.
|
||||
|
||||
#### Linux
|
||||
|
||||
If you don't want to install any external dependencies, you can generate an AppImage locally using docker. It is not that well maintained though...
|
||||
@ -193,7 +211,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain
|
||||
|
||||
```bash
|
||||
# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
|
||||
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2} qt5keychain-dev
|
||||
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev
|
||||
```
|
||||
This will install all dependencies, except for tweeny (use bundled tweeny)
|
||||
and mtxclient (needs to be build separately).
|
||||
@ -205,8 +223,8 @@ and mtxclient (needs to be build separately).
|
||||
```bash
|
||||
sudo apt install cmake gcc make automake liblmdb-dev \
|
||||
qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \
|
||||
qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \
|
||||
qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts \
|
||||
qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev \
|
||||
qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform\
|
||||
qt5keychain-dev
|
||||
```
|
||||
|
||||
|
31
appveyor.yml
31
appveyor.yml
@ -15,7 +15,7 @@ environment:
|
||||
|
||||
|
||||
cache:
|
||||
- c:\hunter\
|
||||
- c:\hunter\ -> appveyor.yml
|
||||
- build\_deps -> appveyor.yml
|
||||
|
||||
build:
|
||||
@ -23,15 +23,16 @@ build:
|
||||
|
||||
install:
|
||||
- set QT_DIR=C:\Qt\5.15\msvc2019_64
|
||||
- set PATH=C:\Strawberry\perl\bin;%PATH%;%QT_DIR%\bin
|
||||
- set PATH=Path=C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin;C:\Program Files\Git\cmd;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\PowerShell\7\;C:\Program Files\7-Zip;C:\Program Files\Microsoft\Web Platform Installer\;C:\Tools\NuGet;C:\Tools\PsTools;C:\Program Files\Git\usr\bin;C:\Program Files\Git LFS;C:\Program Files\Mercurial\;C:\Program Files (x86)\Subversion\bin;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Program Files\dotnet\;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\150;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin;C:\Tools\xUnit;C:\Tools\xUnit20;C:\Tools\NUnit\bin;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Extensions\TestPlatform;C:\Ruby193\bin;C:\Tools\WebDriver;C:\Python27;C:\Python27\Scripts;C:\Program Files\erl10.7\bin;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Program Files (x86)\Microsoft DirectX SDK;C:\Program Files\Microsoft Service Fabric\bin\Fabric\Fabric.Code;C:\Program Files\Microsoft SDKs\Service Fabric\Tools\ServiceFabricLocalClusterManager;C:\Tools\Doxygen;C:\Program Files (x86)\CMake\bin;C:\ProgramData\chocolatey\bin;C:\Tools\vcpkg;C:\Tools\Coverity\bin;C:\Program Files (x86)\NSIS;C:\Tools\Octopus;C:\Program Files\Meson\;C:\Tools\GitVersion;C:\Tools\NUnit3\bin;C:\Users\appveyor\AppData\Local\Microsoft\WindowsApps;C:\Users\appveyor\.dotnet\tools;C:\Program Files\AppVeyor\BuildAgent\
|
||||
- set PATH=C:\Strawberry\perl\bin;C:\Python39-x64;%QT_DIR%\bin;%PATH%
|
||||
- call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
|
||||
|
||||
build_script:
|
||||
# VERSION format: branch-master/branch-1.2
|
||||
# INSTVERSION format: x.y.z
|
||||
# WINVERSION format: 9999.0.0.123/1.2.0.234
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.8.0
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.8.0
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.8.2
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.8.2
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" if "%APPVEYOR_REPO_BRANCH%"=="master" set INSTVERSION=9999.0
|
||||
- if "%APPVEYOR_REPO_TAG%"=="false" set WINVERSION=%INSTVERSION%.0.%APPVEYOR_BUILD_NUMBER%
|
||||
# VERSION format: v1.2.3/v1.3.4
|
||||
@ -46,10 +47,9 @@ build_script:
|
||||
- echo %DATE%
|
||||
|
||||
# Build nheko
|
||||
#- cmake -G "Visual Studio 15 2017 Win64" -H. -Bbuild
|
||||
- cmake -G "Visual Studio 16 2019" -A x64 -H. -Bbuild
|
||||
-DHUNTER_ROOT="C:\hunter"
|
||||
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF
|
||||
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON
|
||||
-DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
|
||||
|
||||
- cmake --build build --config Release
|
||||
@ -61,7 +61,7 @@ after_build:
|
||||
- mkdir NhekoRelease
|
||||
- copy build\Release\nheko.exe NhekoRelease\nheko.exe
|
||||
- copy build\_deps\cmark-build\src\Release\cmark.dll NhekoRelease\cmark.dll
|
||||
- windeployqt --qmldir %QT_DIR%\qml\ --release NhekoRelease\nheko.exe
|
||||
- windeployqt --qmldir resources\qml\ NhekoRelease\nheko.exe
|
||||
|
||||
- 7z a nheko_win_64.zip .\NhekoRelease\*
|
||||
- ls -lh build\Release\
|
||||
@ -77,25 +77,19 @@ after_build:
|
||||
- mkdir installer\packages\io.github.nhekoreborn.nheko
|
||||
- mkdir installer\packages\io.github.nhekoreborn.nheko\data
|
||||
- mkdir installer\packages\io.github.nhekoreborn.nheko\meta
|
||||
- mkdir installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
|
||||
# Copy installer data
|
||||
- copy %BUILD%\resources\nheko.ico installer\config
|
||||
- copy %BUILD%\resources\nheko.png installer\config
|
||||
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko\meta\license.txt
|
||||
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\license.txt
|
||||
- copy %BUILD%\deploy\installer\config.xml installer\config
|
||||
- copy %BUILD%\deploy\installer\controlscript.qs installer\config
|
||||
- copy %BUILD%\deploy\installer\uninstall.qs installer\packages\io.github.nhekoreborn.nheko\data
|
||||
- copy %BUILD%\deploy\installer\gui\package.xml installer\packages\io.github.nhekoreborn.nheko\meta
|
||||
- copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\io.github.nhekoreborn.nheko\meta
|
||||
- copy %BUILD%\deploy\installer\cleanup\package.xml installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
|
||||
- copy %BUILD%\deploy\installer\cleanup\installscript.qs installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
|
||||
# Amend version and date
|
||||
- sed -i "s/__VERSION__/0.8.0/" installer\config\config.xml
|
||||
- sed -i "s/__VERSION__/0.8.0/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
|
||||
- sed -i "s/__VERSION__/0.8.0/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
|
||||
- sed -i "s/__VERSION__/0.8.2/" installer\config\config.xml
|
||||
- sed -i "s/__VERSION__/0.8.2/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
|
||||
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
|
||||
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
|
||||
# Copy nheko data
|
||||
- xcopy NhekoData\*.* installer\packages\io.github.nhekoreborn.nheko\data\*.* /s /e /c /y
|
||||
- move NhekoRelease\nheko.exe installer\packages\io.github.nhekoreborn.nheko\data
|
||||
@ -107,13 +101,13 @@ after_build:
|
||||
- set PATH=%BUILD%\tools\bin;%PATH%
|
||||
- binarycreator.exe -f -c installer\config\config.xml -p installer\packages nheko-installer.exe
|
||||
|
||||
- mv nheko-installer.exe nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe
|
||||
- copy nheko-installer.exe nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe
|
||||
- copy nheko-installer.exe nheko-%APPVEYOR_PULL_REQUEST_HEAD_COMMIT%-installer.exe
|
||||
- ps: .\.ci\upload-nightly.ps1
|
||||
|
||||
on_success:
|
||||
- if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/)
|
||||
|
||||
before_deploy:
|
||||
- ps: .\.ci\upload-nightly.ps1
|
||||
deploy:
|
||||
- description: "Development builds"
|
||||
provider: GitHub
|
||||
@ -128,3 +122,4 @@ deploy:
|
||||
artifacts:
|
||||
- path: nheko_win_64.zip
|
||||
- path: nheko-$(APPVEYOR_REPO_TAG_NAME)-installer.exe
|
||||
- path: nheko-$(APPVEYOR_PULL_REQUEST_HEAD_COMMIT)-installer.exe
|
||||
|
@ -1,28 +0,0 @@
|
||||
function Component()
|
||||
{
|
||||
}
|
||||
|
||||
Component.prototype.createOperations = function()
|
||||
{
|
||||
component.createOperations();
|
||||
|
||||
try
|
||||
{
|
||||
if( installer.value("os") === "win" )
|
||||
{
|
||||
/**
|
||||
* Cleanup AppData and registry
|
||||
*/
|
||||
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C reg delete HKEY_CURRENT_USER\Software\nheko\nheko /f");
|
||||
var localappdata = installer.environmentVariable("LOCALAPPDATA");
|
||||
if( localappdata != "" )
|
||||
{
|
||||
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C rmdir "+localappdata+"\nheko /f");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( e )
|
||||
{
|
||||
print( e );
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Package>
|
||||
<DisplayName>Cleanup AppData and Registry</DisplayName>
|
||||
<Description>Cleans up AppData and Registry when selected (logs you out) - Broken</Description>
|
||||
<Version>__VERSION__</Version>
|
||||
<ReleaseDate>__DATE__</ReleaseDate>
|
||||
<SortingPriority>80</SortingPriority>
|
||||
<Script>installscript.qs</Script>
|
||||
<Checkable>false</Checkable>
|
||||
</Package>
|
@ -23,6 +23,18 @@ Component.prototype.createOperations = function()
|
||||
component.addOperation( "CreateShortcut", "@TargetDir@\\nheko.exe", "@DesktopDir@\\nheko.lnk",
|
||||
"workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\nheko.exe",
|
||||
"iconId=0", "description=Desktop client for the Matrix protocol");
|
||||
|
||||
var reg = installer.environmentVariable("SystemRoot") + "\\System32\\reg.exe";
|
||||
var key= "HKEY_CLASSES_ROOT\\matrix";
|
||||
component.addOperation("Execute", reg, "ADD", key, "/f", "/t", "REG_SZ", "/d", "URL:Matrix Protocol");
|
||||
component.addOperation("Execute", reg, "ADD", key, "/f", "/v", "URL Protocol", "/t", "REG_SZ");
|
||||
var iconkey = "HKEY_CLASSES_ROOT\\matrix\\DefaultIcon";
|
||||
component.addOperation("Execute", reg, "ADD", iconkey, "/f", "/t", "REG_SZ", "/d", "@TargetDir@\\nheko.exe,1");
|
||||
component.addOperation("Execute", reg, "ADD", "HKEY_CLASSES_ROOT\\matrix\\shell", "/f");
|
||||
component.addOperation("Execute", reg, "ADD", "HKEY_CLASSES_ROOT\\matrix\\shell\\open", "/f");
|
||||
var commandkey = "HKEY_CLASSES_ROOT\\matrix\\shell\\open\\command"
|
||||
component.addOperation("Execute", reg, "ADD", commandkey, "/f");
|
||||
component.addOperation("Execute", reg, "ADD", commandkey, "/f", "/t", "REG_SZ", "/d", "\"@TargetDir@\\nheko.exe\" \"%1\"");
|
||||
}
|
||||
}
|
||||
catch( e )
|
||||
|
@ -1,269 +0,0 @@
|
||||
{
|
||||
"id": "io.github.NhekoReborn.Nheko",
|
||||
"command": "nheko",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "5.15",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"rename-icon": "nheko",
|
||||
"rename-desktop-file": "nheko.desktop",
|
||||
"rename-appdata-file": "nheko.appdata.xml",
|
||||
"finish-args": [
|
||||
"--device=dri",
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--socket=pulseaudio",
|
||||
"--socket=wayland",
|
||||
"--socket=x11",
|
||||
"--talk-name=org.freedesktop.Notifications",
|
||||
"--talk-name=org.kde.StatusNotifierWatcher"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/bin/mdb*",
|
||||
"*.a"
|
||||
],
|
||||
"build-options" : {
|
||||
"arch": {
|
||||
"aarch64": {
|
||||
"cxxflags": "-DBOOST_ASIO_DISABLE_EPOLL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "lmdb",
|
||||
"sources": [
|
||||
{
|
||||
"sha256": "f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz"
|
||||
}
|
||||
],
|
||||
"make-install-args": [
|
||||
"prefix=/app"
|
||||
],
|
||||
"no-autogen": true,
|
||||
"subdir": "libraries/liblmdb"
|
||||
},
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMARK_TESTS=OFF"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"sha256": "2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/commonmark/cmark/archive/0.29.0.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spdlog",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DSPDLOG_BUILD_EXAMPLES=0",
|
||||
"-DSPDLOG_BUILD_BENCH=0",
|
||||
"-DSPDLOG_BUILD_TESTING=0"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"sha256": "5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"name": "olm",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "6753595300767dd70150831dbbe6f92d64e75038",
|
||||
"disable-shallow-clone": true,
|
||||
"tag": "3.1.4",
|
||||
"type": "git",
|
||||
"url": "https://gitlab.matrix.org/matrix-org/olm.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DBUILD_TEST_APPLICATION=OFF",
|
||||
"-DQTKEYCHAIN_STATIC=ON"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"name": "QtKeychain",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "815fe610353ff8ad7e2f1121c368a74df8db5eb7",
|
||||
"tag": "v0.12.0",
|
||||
"type": "git",
|
||||
"url": "https://github.com/frankosterfeld/qtkeychain.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts":[
|
||||
"-DJSON_BuildTests=OFF"
|
||||
],
|
||||
"buildsystem":"cmake",
|
||||
"name": "nlohmann",
|
||||
"sources":[
|
||||
{
|
||||
"sha256": "d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/nlohmann/json/archive/v3.7.0.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"build-commands": [
|
||||
"./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app",
|
||||
"./b2 -d0 variant=release link=static threading=multi --layout=system",
|
||||
"./b2 -d0 install"
|
||||
],
|
||||
"buildsystem": "simple",
|
||||
"name": "boost",
|
||||
"sources": [
|
||||
{
|
||||
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
|
||||
"type": "archive",
|
||||
"url": "https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"buildsystem": "meson",
|
||||
"name": "gstreamer",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "a42fe476d3ee5576921f67a331464065ec33b9a4",
|
||||
"tag": "1.18.3",
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-Dcompositor=enabled",
|
||||
"-Dgl=enabled"
|
||||
],
|
||||
"buildsystem": "meson",
|
||||
"name": "gstreamer-plugins-base",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "2cc319ee13f6b72df3d432b7c75aca81feb260e5",
|
||||
"tag": "1.18.3",
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-Dpulse=enabled",
|
||||
"-Dqt5=enabled",
|
||||
"-Drtp=enabled",
|
||||
"-Drtpmanager=enabled",
|
||||
"-Dvpx=enabled"
|
||||
],
|
||||
"buildsystem": "meson",
|
||||
"name": "gstreamer-plugins-good",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "e816c6cd73c9e0676828c9e227a049ebad3d019f",
|
||||
"tag": "1.18.3",
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-good.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-Ddtls=enabled",
|
||||
"-Dgl=enabled",
|
||||
"-Dopenh264=enabled",
|
||||
"-Dopus=enabled",
|
||||
"-Dsrtp=enabled",
|
||||
"-Dwebrtc=enabled",
|
||||
"-Dflite=disabled"
|
||||
],
|
||||
"buildsystem": "meson",
|
||||
"name": "gstreamer-plugins-bad",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "382e373d9be363f1e21b12990a4d12f1ecb6df41",
|
||||
"tag": "1.18.3",
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-DBUILD_LIB_TESTS=OFF",
|
||||
"-DBUILD_LIB_EXAMPLES=OFF",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DBUILD_SHARED_LIBS=OFF"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"name": "mtxclient",
|
||||
"sources": [
|
||||
{
|
||||
"commit": "2d6e3f79917ce2065a54ca32e6a9f9d42c0b6347",
|
||||
"tag": "v0.4.0",
|
||||
"type": "git",
|
||||
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DTWEENY_BUILD_DOCUMENTATION=OFF",
|
||||
"-DTWEENY_BUILD_EXAMPLES=OFF"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"name": "tweeny",
|
||||
"sources": [
|
||||
{
|
||||
"sha256": "482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx",
|
||||
"-DCOMPILE_QML=ON"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"name": "nheko",
|
||||
"sources": [
|
||||
{
|
||||
"path": ".",
|
||||
"type": "dir",
|
||||
"skip": ["build-flatpak"]
|
||||
},
|
||||
{
|
||||
"dest": ".deps/lmdbxx",
|
||||
"sha256": "93721132bbf5045d38ad62de2997655e9984c48ea5c9886746d42128f4b26fbd",
|
||||
"type": "archive",
|
||||
"url": "https://github.com/bendiken/lmdbxx/archive/0b43ca87d8cfabba392dfe884eb1edb83874de02.tar.gz"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
180
io.github.NhekoReborn.Nheko.yaml
Normal file
180
io.github.NhekoReborn.Nheko.yaml
Normal file
@ -0,0 +1,180 @@
|
||||
id: io.github.NhekoReborn.Nheko
|
||||
command: nheko
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: '5.15'
|
||||
sdk: org.kde.Sdk
|
||||
rename-icon: nheko
|
||||
rename-desktop-file: nheko.desktop
|
||||
rename-appdata-file: nheko.appdata.xml
|
||||
finish-args:
|
||||
- --device=dri
|
||||
# needed for webcams, see #517
|
||||
- --device=all
|
||||
- --share=ipc
|
||||
- --share=network
|
||||
- --socket=pulseaudio
|
||||
- --socket=wayland
|
||||
- --socket=x11
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
- --talk-name=org.freedesktop.secrets
|
||||
- --talk-name=org.freedesktop.StatusNotifierItem
|
||||
- --talk-name=org.kde.*
|
||||
cleanup:
|
||||
- /include
|
||||
- /bin/mdb*
|
||||
- '*.a'
|
||||
build-options:
|
||||
arch:
|
||||
aarch64:
|
||||
cxxflags: -DBOOST_ASIO_DISABLE_EPOLL
|
||||
modules:
|
||||
- name: lmdb
|
||||
sources:
|
||||
- sha256: f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28
|
||||
type: archive
|
||||
url: https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz
|
||||
make-install-args:
|
||||
- prefix=/app
|
||||
no-autogen: true
|
||||
subdir: libraries/liblmdb
|
||||
- name: cmark
|
||||
buildsystem: cmake-ninja
|
||||
builddir: true
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMARK_TESTS=OFF
|
||||
sources:
|
||||
- sha256: 2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6
|
||||
type: archive
|
||||
url: https://github.com/commonmark/cmark/archive/0.29.0.tar.gz
|
||||
- name: spdlog
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DSPDLOG_BUILD_EXAMPLES=0
|
||||
- -DSPDLOG_BUILD_BENCH=0
|
||||
- -DSPDLOG_BUILD_TESTING=0
|
||||
sources:
|
||||
- sha256: 5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb
|
||||
type: archive
|
||||
url: https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz
|
||||
- config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
buildsystem: cmake-ninja
|
||||
name: olm
|
||||
sources:
|
||||
- commit: 6753595300767dd70150831dbbe6f92d64e75038
|
||||
disable-shallow-clone: true
|
||||
tag: 3.1.4
|
||||
type: git
|
||||
url: https://gitlab.matrix.org/matrix-org/olm.git
|
||||
- config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_TEST_APPLICATION=OFF
|
||||
- -DQTKEYCHAIN_STATIC=ON
|
||||
buildsystem: cmake-ninja
|
||||
name: QtKeychain
|
||||
sources:
|
||||
- commit: 815fe610353ff8ad7e2f1121c368a74df8db5eb7
|
||||
tag: v0.12.0
|
||||
type: git
|
||||
url: https://github.com/frankosterfeld/qtkeychain.git
|
||||
- config-opts:
|
||||
- -DJSON_BuildTests=OFF
|
||||
buildsystem: cmake
|
||||
name: nlohmann
|
||||
sources:
|
||||
- sha256: d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760
|
||||
type: archive
|
||||
url: https://github.com/nlohmann/json/archive/v3.7.0.tar.gz
|
||||
- build-commands:
|
||||
- ./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app
|
||||
- ./b2 -d0 variant=release link=static threading=multi --layout=system
|
||||
- ./b2 -d0 install
|
||||
buildsystem: simple
|
||||
name: boost
|
||||
sources:
|
||||
- sha256: 59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722
|
||||
type: archive
|
||||
url: https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2
|
||||
- buildsystem: meson
|
||||
name: gstreamer
|
||||
sources:
|
||||
- commit: a42fe476d3ee5576921f67a331464065ec33b9a4
|
||||
tag: 1.18.3
|
||||
type: git
|
||||
url: https://gitlab.freedesktop.org/gstreamer/gstreamer.git
|
||||
- config-opts:
|
||||
- -Dcompositor=enabled
|
||||
- -Dgl=enabled
|
||||
buildsystem: meson
|
||||
name: gstreamer-plugins-base
|
||||
sources:
|
||||
- commit: 2cc319ee13f6b72df3d432b7c75aca81feb260e5
|
||||
tag: 1.18.3
|
||||
type: git
|
||||
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git
|
||||
- config-opts:
|
||||
- -Dpulse=enabled
|
||||
- -Dqt5=enabled
|
||||
- -Drtp=enabled
|
||||
- -Drtpmanager=enabled
|
||||
- -Dvpx=enabled
|
||||
buildsystem: meson
|
||||
name: gstreamer-plugins-good
|
||||
sources:
|
||||
- commit: e816c6cd73c9e0676828c9e227a049ebad3d019f
|
||||
tag: 1.18.3
|
||||
type: git
|
||||
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-good.git
|
||||
- config-opts:
|
||||
- -Ddtls=enabled
|
||||
- -Dgl=enabled
|
||||
- -Dopenh264=enabled
|
||||
- -Dopus=enabled
|
||||
- -Dsrtp=enabled
|
||||
- -Dwebrtc=enabled
|
||||
- -Dflite=disabled
|
||||
buildsystem: meson
|
||||
name: gstreamer-plugins-bad
|
||||
sources:
|
||||
- commit: 382e373d9be363f1e21b12990a4d12f1ecb6df41
|
||||
tag: 1.18.3
|
||||
type: git
|
||||
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git
|
||||
- config-opts:
|
||||
- -DBUILD_LIB_TESTS=OFF
|
||||
- -DBUILD_LIB_EXAMPLES=OFF
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_SHARED_LIBS=OFF
|
||||
buildsystem: cmake-ninja
|
||||
name: mtxclient
|
||||
sources:
|
||||
- commit: 5d2f055ea9403770039ddf66b1900f890cd5cde7
|
||||
type: git
|
||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||
- config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DTWEENY_BUILD_DOCUMENTATION=OFF
|
||||
- -DTWEENY_BUILD_EXAMPLES=OFF
|
||||
buildsystem: cmake-ninja
|
||||
name: tweeny
|
||||
sources:
|
||||
- sha256: 482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694
|
||||
type: archive
|
||||
url: https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz
|
||||
- config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DLMDBXX_INCLUDE_DIR=.deps/lmdbxx
|
||||
- -DCOMPILE_QML=ON
|
||||
buildsystem: cmake-ninja
|
||||
name: nheko
|
||||
sources:
|
||||
- path: .
|
||||
type: dir
|
||||
skip:
|
||||
- build-flatpak
|
||||
- dest: .deps/lmdbxx
|
||||
sha256: 5e12eb3aefe9050068af7df2c663edabc977ef34c9e7ba7b9d2c43e0ad47d8df
|
||||
type: archive
|
||||
url: https://github.com/hoytech/lmdbxx/archive/1.0.0.tar.gz
|
10
nheko-nightly.flatpakref
Normal file
10
nheko-nightly.flatpakref
Normal file
@ -0,0 +1,10 @@
|
||||
[Flatpak Ref]
|
||||
Title=Nheko Nightly
|
||||
Name=io.github.NhekoReborn.Nheko
|
||||
Branch=master
|
||||
Url=https://flatpak.neko.dev/repo/nightly
|
||||
Homepage=https://nheko-reborn.github.io/
|
||||
Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg
|
||||
RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
IsRuntime=false
|
||||
GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww=
|
8
nheko-nightly.flatpakrepo
Normal file
8
nheko-nightly.flatpakrepo
Normal file
@ -0,0 +1,8 @@
|
||||
[Flatpak Repo]
|
||||
Title=Nheko Nightly
|
||||
Url=https://flatpak.neko.dev/repo/nightly
|
||||
Homepage=https://nheko.im/
|
||||
Comment=Nheko nightly release repository
|
||||
Description=Nheko nightly release repository
|
||||
Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg
|
||||
GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww=
|
File diff suppressed because it is too large
Load Diff
BIN
resources/icons/ui/screen-share.png
Normal file
BIN
resources/icons/ui/screen-share.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 773 B |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2721
resources/langs/nheko_es.ts
Normal file
2721
resources/langs/nheko_es.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2721
resources/langs/nheko_pt_BR.ts
Normal file
2721
resources/langs/nheko_pt_BR.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -13,13 +13,23 @@
|
||||
</description>
|
||||
<translation/>
|
||||
<languages>
|
||||
<lang>cs</lang>
|
||||
<lang>de</lang>
|
||||
<lang>el</lang>
|
||||
<lang>en</lang>
|
||||
<lang>eo</lang>
|
||||
<lang>et</lang>
|
||||
<lang>fi</lang>
|
||||
<lang>fr</lang>
|
||||
<lang>hu</lang>
|
||||
<lang>it</lang>
|
||||
<lang>ja</lang>
|
||||
<lang>ml</lang>
|
||||
<lang>nl</lang>
|
||||
<lang>pl</lang>
|
||||
<lang>ro</lang>
|
||||
<lang>ru</lang>
|
||||
<lang>sv</lang>
|
||||
<lang>zh_CN</lang>
|
||||
</languages>
|
||||
<content_rating type="oars-1.0">
|
||||
@ -43,6 +53,8 @@
|
||||
<url type="homepage">https://github.com/Nheko-Reborn/nheko</url>
|
||||
<update_contact>https://github.com/Nheko-Reborn</update_contact>
|
||||
<releases>
|
||||
<release date="2021-04-23" version="0.8.2"/>
|
||||
<release date="2021-01-27" version="0.8.1"/>
|
||||
<release date="2021-01-21" version="0.8.0"/>
|
||||
<release date="2020-06-12" version="0.7.2"/>
|
||||
<release date="2020-04-24" version="0.7.1"/>
|
||||
@ -57,12 +69,11 @@
|
||||
<release date="2018-08-12" version="0.5.3"/>
|
||||
<release date="2018-07-28" version="0.5.2"/>
|
||||
</releases>
|
||||
|
||||
<developer_name>Nheko Reborn</developer_name>
|
||||
|
||||
<url type="bugtracker">https://github.com/Nheko-Reborn/nheko/issues</url>
|
||||
|
||||
<url type="help">https://github.com/Nheko-Reborn/nheko/</url>
|
||||
|
||||
<url type="translate">https://weblate.nheko.im/projects/nheko/</url>
|
||||
|
||||
<!--FIXME: where to donate to the application-->
|
||||
<url type="donation"/>
|
||||
</component>
|
||||
|
@ -2,7 +2,7 @@
|
||||
Name=nheko
|
||||
Version=1.0
|
||||
Comment=Desktop client for Matrix
|
||||
Exec=nheko
|
||||
Exec=nheko %u
|
||||
Icon=nheko
|
||||
Type=Application
|
||||
Categories=Network;InstantMessaging;Qt;
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./ui"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.6
|
||||
@ -57,6 +61,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
layer.effect: OpacityMask {
|
||||
cached: true
|
||||
|
||||
maskSource: Rectangle {
|
||||
anchors.fill: parent
|
||||
@ -90,4 +95,9 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./ui"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -11,8 +15,16 @@ Popup {
|
||||
property string completerName
|
||||
property var completer
|
||||
property bool bottomToTop: true
|
||||
property bool fullWidth: false
|
||||
property bool centerRowContent: true
|
||||
property int avatarHeight: 24
|
||||
property int avatarWidth: 24
|
||||
property int rowMargin: 0
|
||||
property int rowSpacing: 5
|
||||
property alias count: listView.count
|
||||
|
||||
signal completionClicked(string completion)
|
||||
signal completionSelected(string id)
|
||||
|
||||
function up() {
|
||||
if (bottomToTop)
|
||||
@ -49,9 +61,18 @@ Popup {
|
||||
return null;
|
||||
}
|
||||
|
||||
function finishCompletion() {
|
||||
if (popup.completerName == "room")
|
||||
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
|
||||
|
||||
}
|
||||
|
||||
onCompleterNameChanged: {
|
||||
if (completerName) {
|
||||
completer = TimelineManager.timeline.input.completerFor(completerName);
|
||||
if (completerName == "user")
|
||||
completer = TimelineManager.completerFor(completerName, TimelineManager.timeline.roomId());
|
||||
else
|
||||
completer = TimelineManager.completerFor(completerName);
|
||||
completer.setSearchString("");
|
||||
} else {
|
||||
completer = undefined;
|
||||
@ -70,22 +91,31 @@ Popup {
|
||||
id: listView
|
||||
|
||||
anchors.fill: parent
|
||||
implicitWidth: contentItem.childrenRect.width
|
||||
implicitWidth: fullWidth ? parent.width : contentItem.childrenRect.width
|
||||
model: completer
|
||||
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
|
||||
spacing: rowSpacing
|
||||
pixelAligned: true
|
||||
|
||||
delegate: Rectangle {
|
||||
property variant modelData: model
|
||||
|
||||
color: model.index == popup.currentIndex ? colors.highlight : colors.base
|
||||
height: chooser.childrenRect.height + 4
|
||||
implicitWidth: chooser.childrenRect.width + 4
|
||||
height: chooser.childrenRect.height + 2 * popup.rowMargin
|
||||
implicitWidth: fullWidth ? popup.width : chooser.childrenRect.width + 4
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: popup.currentIndex = model.index
|
||||
onClicked: popup.completionClicked(completer.completionAt(model.index))
|
||||
onPositionChanged: popup.currentIndex = model.index
|
||||
onClicked: {
|
||||
popup.completionClicked(completer.completionAt(model.index));
|
||||
if (popup.completerName == "room")
|
||||
popup.completionSelected(model.roomid);
|
||||
|
||||
}
|
||||
|
||||
Ripple {
|
||||
rippleTarget: mouseArea
|
||||
@ -98,7 +128,8 @@ Popup {
|
||||
id: chooser
|
||||
|
||||
roleValue: popup.completerName
|
||||
anchors.centerIn: parent
|
||||
anchors.fill: parent
|
||||
anchors.margins: popup.rowMargin
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "user"
|
||||
@ -107,10 +138,11 @@ Popup {
|
||||
id: del
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: 24
|
||||
width: 24
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.displayName
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
onClicked: popup.completionClicked(completer.completionAt(model.index))
|
||||
@ -137,6 +169,7 @@ Popup {
|
||||
id: del
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: rowSpacing
|
||||
|
||||
Label {
|
||||
text: model.unicode
|
||||
@ -153,6 +186,67 @@ Popup {
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "room"
|
||||
|
||||
RowLayout {
|
||||
id: del
|
||||
|
||||
anchors.centerIn: centerRowContent ? parent : undefined
|
||||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
onClicked: {
|
||||
popup.completionClicked(completer.completionAt(model.index));
|
||||
popup.completionSelected(model.roomid);
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.roomName
|
||||
font.pixelSize: popup.avatarHeight * 0.5
|
||||
color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: "roomAliases"
|
||||
|
||||
RowLayout {
|
||||
id: del
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: rowSpacing
|
||||
|
||||
Avatar {
|
||||
height: popup.avatarHeight
|
||||
width: popup.avatarWidth
|
||||
displayName: model.roomName
|
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
onClicked: popup.completionClicked(completer.completionAt(model.index))
|
||||
}
|
||||
|
||||
Label {
|
||||
text: model.roomName
|
||||
color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "(" + model.roomAlias + ")"
|
||||
color: model.index == popup.currentIndex ? colors.highlightedText : colors.buttonText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,44 +1,50 @@
|
||||
import QtQuick 2.5
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.1
|
||||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
Image {
|
||||
id: stateImg
|
||||
|
||||
property bool encrypted: false
|
||||
property int trust: Crypto.Unverified
|
||||
|
||||
function getEncryptionImage() {
|
||||
if (encrypted)
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
|
||||
else
|
||||
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
|
||||
}
|
||||
|
||||
function getEncryptionTooltip() {
|
||||
if (encrypted)
|
||||
return qsTr("Encrypted");
|
||||
else
|
||||
return qsTr("This message is not encrypted!");
|
||||
}
|
||||
|
||||
color: "transparent"
|
||||
width: 16
|
||||
height: 16
|
||||
ToolTip.visible: ma.containsMouse && indicator.visible
|
||||
ToolTip.text: getEncryptionTooltip()
|
||||
source: {
|
||||
if (encrypted) {
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?green";
|
||||
case Crypto.TOFU:
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
|
||||
default:
|
||||
return "image://colorimage/:/icons/icons/ui/lock.png?#dd3d3d";
|
||||
}
|
||||
} else {
|
||||
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
|
||||
}
|
||||
}
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.text: {
|
||||
if (!encrypted)
|
||||
return qsTr("This message is not encrypted!");
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
switch (trust) {
|
||||
case Crypto.Verified:
|
||||
return qsTr("Encrypted by a verified device");
|
||||
case Crypto.TOFU:
|
||||
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
|
||||
default:
|
||||
return qsTr("Encrypted by an unverified device");
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: stateImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: getEncryptionImage()
|
||||
HoverHandler {
|
||||
id: ma
|
||||
}
|
||||
|
||||
}
|
||||
|
117
resources/qml/ForwardCompleter.qml
Normal file
117
resources/qml/ForwardCompleter.qml
Normal file
@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./delegates/"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import im.nheko 1.0
|
||||
|
||||
Popup {
|
||||
id: forwardMessagePopup
|
||||
|
||||
property var mid
|
||||
|
||||
function setMessageEventId(mid_in) {
|
||||
mid = mid_in;
|
||||
}
|
||||
|
||||
x: Math.round(parent.width / 2 - width / 2)
|
||||
y: Math.round(parent.height / 2 - height / 2)
|
||||
modal: true
|
||||
palette: colors
|
||||
parent: Overlay.overlay
|
||||
width: implicitWidth >= (timelineRoot.width * 0.8) ? implicitWidth : (timelineRoot.width * 0.8)
|
||||
height: implicitHeight + completerPopup.height + padding * 2
|
||||
leftPadding: 10
|
||||
rightPadding: 10
|
||||
onOpened: {
|
||||
completerPopup.open();
|
||||
roomTextInput.forceActiveFocus();
|
||||
}
|
||||
onClosed: {
|
||||
completerPopup.close();
|
||||
}
|
||||
|
||||
Column {
|
||||
id: forwardColumn
|
||||
|
||||
spacing: 5
|
||||
|
||||
Label {
|
||||
id: titleLabel
|
||||
|
||||
text: qsTr("Forward Message")
|
||||
font.bold: true
|
||||
bottomPadding: 10
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
Reply {
|
||||
id: replyPreview
|
||||
|
||||
modelData: TimelineManager.timeline ? TimelineManager.timeline.getDump(mid, "") : {
|
||||
}
|
||||
userColor: TimelineManager.userColor(modelData.userId, colors.window)
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
color: colors.text
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.up();
|
||||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
completerPopup.finishCompletion();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Completer {
|
||||
id: completerPopup
|
||||
|
||||
y: titleLabel.height + replyPreview.height + roomTextInput.height + roomTextInput.bottomPadding + forwardColumn.spacing * 3
|
||||
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
|
||||
completerName: "room"
|
||||
fullWidth: true
|
||||
centerRowContent: false
|
||||
avatarHeight: 24
|
||||
avatarWidth: 24
|
||||
bottomToTop: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
}
|
||||
|
||||
Connections {
|
||||
onCompletionSelected: {
|
||||
TimelineManager.timeline.forwardMessage(messageContextMenu.eventId, id);
|
||||
forwardMessagePopup.close();
|
||||
}
|
||||
onCountChanged: {
|
||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||
completerPopup.currentIndex = 0;
|
||||
|
||||
}
|
||||
target: completerPopup
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: colors.window
|
||||
}
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7)
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./ui"
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import im.nheko 1.0 // for cursor shape
|
||||
|
||||
AbstractButton {
|
||||
id: button
|
||||
@ -23,11 +28,10 @@ AbstractButton {
|
||||
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
CursorShape {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.3
|
||||
import im.nheko 1.0
|
||||
@ -8,29 +12,15 @@ TextEdit {
|
||||
focus: false
|
||||
wrapMode: Text.Wrap
|
||||
selectByMouse: !Settings.mobileMode
|
||||
enabled: selectByMouse
|
||||
color: colors.text
|
||||
onLinkActivated: {
|
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
|
||||
chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]);
|
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) {
|
||||
TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]);
|
||||
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
|
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link);
|
||||
TimelineManager.setHistoryView(match[1]);
|
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain);
|
||||
} else {
|
||||
TimelineManager.openLink(link);
|
||||
}
|
||||
}
|
||||
onLinkActivated: TimelineManager.openLink(link)
|
||||
ToolTip.visible: hoveredLink
|
||||
ToolTip.text: hoveredLink
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
66
resources/qml/MatrixTextField.qml
Normal file
66
resources/qml/MatrixTextField.qml
Normal file
@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
TextField {
|
||||
id: input
|
||||
|
||||
palette: colors
|
||||
|
||||
Rectangle {
|
||||
id: blueBar
|
||||
|
||||
anchors.top: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: colors.highlight
|
||||
height: 1
|
||||
width: parent.width
|
||||
|
||||
Rectangle {
|
||||
id: blackBar
|
||||
|
||||
anchors.verticalCenter: blueBar.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: parent.height + 1
|
||||
width: 0
|
||||
color: colors.text
|
||||
|
||||
states: State {
|
||||
name: "focused"
|
||||
when: input.activeFocus == true
|
||||
|
||||
PropertyChanges {
|
||||
target: blackBar
|
||||
width: blueBar.width
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
from: ""
|
||||
to: "focused"
|
||||
reversible: true
|
||||
|
||||
NumberAnimation {
|
||||
target: blackBar
|
||||
properties: "width"
|
||||
duration: 500
|
||||
easing.type: Easing.InOutQuad
|
||||
alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: colors.base
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./voip"
|
||||
import QtQuick 2.9
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import im.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: inputBar
|
||||
|
||||
color: colors.window
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: textInput.height + 16
|
||||
Layout.preferredHeight: row.implicitHeight
|
||||
Layout.minimumHeight: 40
|
||||
|
||||
Component {
|
||||
@ -20,11 +26,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: inputBar
|
||||
id: row
|
||||
|
||||
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false) || messageContextMenu.isSender
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
spacing: 16
|
||||
|
||||
ImageButton {
|
||||
visible: CallManager.callsSupported
|
||||
@ -36,7 +41,7 @@ Rectangle {
|
||||
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call")
|
||||
Layout.leftMargin: 8
|
||||
Layout.margins: 8
|
||||
onClicked: {
|
||||
if (TimelineManager.timeline) {
|
||||
if (CallManager.haveCallInvite) {
|
||||
@ -44,7 +49,6 @@ Rectangle {
|
||||
} else if (CallManager.isOnCall) {
|
||||
CallManager.hangUp();
|
||||
} else {
|
||||
CallManager.refreshDevices();
|
||||
var dialog = placeCallDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
}
|
||||
@ -58,7 +62,7 @@ Rectangle {
|
||||
width: 22
|
||||
height: 22
|
||||
image: ":/icons/icons/ui/paper-clip-outline.png"
|
||||
Layout.leftMargin: CallManager.callsSupported ? 0 : 8
|
||||
Layout.margins: 8
|
||||
onClicked: TimelineManager.timeline.input.openFileSelection()
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Send a file")
|
||||
@ -77,71 +81,53 @@ Rectangle {
|
||||
|
||||
}
|
||||
|
||||
Flickable {
|
||||
ScrollView {
|
||||
id: textInput
|
||||
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX + width <= r.x + r.width)
|
||||
contentX = r.x + r.width - width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY + height <= r.y + r.height)
|
||||
contentY = r.y + r.height - height;
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
|
||||
Layout.maximumHeight: Window.height / 4
|
||||
Layout.minimumHeight: Settings.fontSize
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
implicitWidth: textArea.width
|
||||
implicitHeight: textArea.height
|
||||
contentWidth: textArea.width
|
||||
contentHeight: textArea.height
|
||||
implicitWidth: inputBar.width - 4 * (22 + 16) - 24
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
id: messageInput
|
||||
|
||||
property int completerTriggeredAt: -1
|
||||
|
||||
function insertCompletion(completion) {
|
||||
textArea.remove(completerTriggeredAt, cursorPosition);
|
||||
textArea.insert(cursorPosition, completion);
|
||||
messageInput.remove(completerTriggeredAt, cursorPosition);
|
||||
messageInput.insert(cursorPosition, completion);
|
||||
}
|
||||
|
||||
function openCompleter(pos, type) {
|
||||
completerTriggeredAt = pos;
|
||||
popup.completerName = type;
|
||||
popup.open();
|
||||
popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition));
|
||||
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
|
||||
}
|
||||
|
||||
function positionCursorAtEnd() {
|
||||
cursorPosition = messageInput.length;
|
||||
}
|
||||
|
||||
function positionCursorAtStart() {
|
||||
cursorPosition = 0;
|
||||
}
|
||||
|
||||
text: "asfkajsdf"
|
||||
selectByMouse: true
|
||||
placeholderText: qsTr("Write a message...")
|
||||
//placeholderTextColor: colors.buttonText
|
||||
// only set the anchors on Qt 5.12 or higher
|
||||
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
|
||||
Component.onCompleted: {
|
||||
if (placeholderTextColor !== undefined)
|
||||
placeholderTextColor = colors.buttonText;
|
||||
|
||||
}
|
||||
placeholderTextColor: colors.buttonText
|
||||
color: colors.text
|
||||
width: textInput.width
|
||||
wrapMode: TextEdit.Wrap
|
||||
padding: 0
|
||||
padding: 8
|
||||
focus: true
|
||||
onTextChanged: {
|
||||
if (TimelineManager.timeline)
|
||||
TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
|
||||
|
||||
forceActiveFocus();
|
||||
}
|
||||
onCursorRectangleChanged: textInput.ensureVisible(cursorRectangle)
|
||||
onCursorPositionChanged: {
|
||||
if (!TimelineManager.timeline)
|
||||
return ;
|
||||
@ -152,7 +138,7 @@ Rectangle {
|
||||
popup.close();
|
||||
}
|
||||
if (popup.opened)
|
||||
popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition));
|
||||
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition));
|
||||
|
||||
}
|
||||
onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
|
||||
@ -163,17 +149,28 @@ Rectangle {
|
||||
if (event.matches(StandardKey.Paste)) {
|
||||
TimelineManager.timeline.input.paste(false);
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_Space) {
|
||||
// close popup if user enters space after colon
|
||||
if (cursorPosition == completerTriggeredAt + 1)
|
||||
popup.close();
|
||||
|
||||
if (popup.opened && popup.count <= 0)
|
||||
popup.close();
|
||||
|
||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
|
||||
textArea.clear();
|
||||
messageInput.clear();
|
||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
|
||||
textArea.text = TimelineManager.timeline.input.previousText();
|
||||
messageInput.text = TimelineManager.timeline.input.previousText();
|
||||
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) {
|
||||
textArea.text = TimelineManager.timeline.input.nextText();
|
||||
messageInput.text = TimelineManager.timeline.input.nextText();
|
||||
} else if (event.key == Qt.Key_At) {
|
||||
textArea.openCompleter(cursorPosition, "user");
|
||||
messageInput.openCompleter(cursorPosition, "user");
|
||||
popup.open();
|
||||
} else if (event.key == Qt.Key_Colon) {
|
||||
textArea.openCompleter(cursorPosition, "emoji");
|
||||
messageInput.openCompleter(cursorPosition, "emoji");
|
||||
popup.open();
|
||||
} else if (event.key == Qt.Key_NumberSign) {
|
||||
messageInput.openCompleter(cursorPosition, "roomAliases");
|
||||
popup.open();
|
||||
} else if (event.key == Qt.Key_Escape && popup.opened) {
|
||||
completerTriggeredAt = -1;
|
||||
@ -186,13 +183,12 @@ Rectangle {
|
||||
popup.completerName = "";
|
||||
popup.close();
|
||||
if (currentCompletion) {
|
||||
textArea.insertCompletion(currentCompletion);
|
||||
messageInput.insertCompletion(currentCompletion);
|
||||
event.accepted = true;
|
||||
return ;
|
||||
}
|
||||
}
|
||||
TimelineManager.timeline.input.send();
|
||||
textArea.clear();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_Tab) {
|
||||
event.accepted = true;
|
||||
@ -201,19 +197,22 @@ Rectangle {
|
||||
} else {
|
||||
var pos = cursorPosition - 1;
|
||||
while (pos > -1) {
|
||||
var t = textArea.getText(pos, pos + 1);
|
||||
var t = messageInput.getText(pos, pos + 1);
|
||||
console.log('"' + t + '"');
|
||||
if (t == '@' || t == ' ' || t == '\t') {
|
||||
textArea.openCompleter(pos, "user");
|
||||
if (t == '@') {
|
||||
messageInput.openCompleter(pos, "user");
|
||||
return ;
|
||||
} else if (t == ' ' || t == '\t') {
|
||||
messageInput.openCompleter(pos + 1, "user");
|
||||
return ;
|
||||
} else if (t == ':') {
|
||||
textArea.openCompleter(pos, "emoji");
|
||||
messageInput.openCompleter(pos, "emoji");
|
||||
return ;
|
||||
}
|
||||
pos = pos - 1;
|
||||
}
|
||||
// At start of input
|
||||
textArea.openCompleter(0, "user");
|
||||
messageInput.openCompleter(0, "user");
|
||||
}
|
||||
} else if (event.key == Qt.Key_Up && popup.opened) {
|
||||
event.accepted = true;
|
||||
@ -221,38 +220,93 @@ Rectangle {
|
||||
} else if (event.key == Qt.Key_Down && popup.opened) {
|
||||
event.accepted = true;
|
||||
popup.down();
|
||||
} else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) {
|
||||
if (cursorPosition == 0) {
|
||||
event.accepted = true;
|
||||
var idx = TimelineManager.timeline.edit ? TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) + 1 : 0;
|
||||
while (true) {
|
||||
var id = TimelineManager.timeline.indexToId(idx);
|
||||
if (!id || TimelineManager.timeline.getDump(id, "").isEditable) {
|
||||
TimelineManager.timeline.edit = id;
|
||||
cursorPosition = 0;
|
||||
Qt.callLater(positionCursorAtEnd);
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
} else if (positionAt(0, cursorRectangle.y) === 0) {
|
||||
event.accepted = true;
|
||||
positionCursorAtStart();
|
||||
}
|
||||
} else if (event.key == Qt.Key_Down && event.modifiers == Qt.NoModifier) {
|
||||
if (cursorPosition == messageInput.length && TimelineManager.timeline.edit) {
|
||||
event.accepted = true;
|
||||
var idx = TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) - 1;
|
||||
while (true) {
|
||||
var id = TimelineManager.timeline.indexToId(idx);
|
||||
if (!id || TimelineManager.timeline.getDump(id, "").isEditable) {
|
||||
TimelineManager.timeline.edit = id;
|
||||
Qt.callLater(positionCursorAtStart);
|
||||
break;
|
||||
}
|
||||
idx--;
|
||||
}
|
||||
} else if (positionAt(width, cursorRectangle.y + 2) === messageInput.length) {
|
||||
event.accepted = true;
|
||||
positionCursorAtEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
background: null
|
||||
|
||||
Connections {
|
||||
onTimelineChanged: {
|
||||
textArea.clear();
|
||||
textArea.append(TimelineManager.timeline.input.text());
|
||||
textArea.completerTriggeredAt = -1;
|
||||
onActiveTimelineChanged: {
|
||||
messageInput.clear();
|
||||
messageInput.append(TimelineManager.timeline.input.text());
|
||||
messageInput.completerTriggeredAt = -1;
|
||||
popup.completerName = "";
|
||||
messageInput.forceActiveFocus();
|
||||
}
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
Connections {
|
||||
onCompletionClicked: textArea.insertCompletion(completion)
|
||||
onCompletionClicked: messageInput.insertCompletion(completion)
|
||||
target: popup
|
||||
}
|
||||
|
||||
Completer {
|
||||
id: popup
|
||||
|
||||
x: textArea.completerTriggeredAt >= 0 ? textArea.positionToRectangle(textArea.completerTriggeredAt).x : 0
|
||||
y: textArea.completerTriggeredAt >= 0 ? textArea.positionToRectangle(textArea.completerTriggeredAt).y - height : 0
|
||||
x: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).x : 0
|
||||
y: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height : 0
|
||||
}
|
||||
|
||||
Connections {
|
||||
ignoreUnknownSignals: true
|
||||
onInsertText: textArea.insert(textArea.cursorPosition, text)
|
||||
onInsertText: {
|
||||
messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd);
|
||||
messageInput.insert(messageInput.cursorPosition, text);
|
||||
}
|
||||
onTextChanged: {
|
||||
messageInput.text = newText;
|
||||
messageInput.cursorPosition = newText.length;
|
||||
}
|
||||
target: TimelineManager.timeline ? TimelineManager.timeline.input : null
|
||||
}
|
||||
|
||||
Connections {
|
||||
ignoreUnknownSignals: true
|
||||
onReplyChanged: messageInput.forceActiveFocus()
|
||||
onEditChanged: messageInput.forceActiveFocus()
|
||||
target: TimelineManager.timeline
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager
|
||||
onFocusInput: messageInput.forceActiveFocus()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// workaround for wrong cursor shape on some platforms
|
||||
anchors.fill: parent
|
||||
@ -261,14 +315,6 @@ Rectangle {
|
||||
onClicked: TimelineManager.timeline.input.paste(true)
|
||||
}
|
||||
|
||||
NhekoDropArea {
|
||||
anchors.fill: parent
|
||||
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
}
|
||||
|
||||
}
|
||||
@ -277,6 +323,7 @@ Rectangle {
|
||||
id: emojiButton
|
||||
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
@ -284,12 +331,14 @@ Rectangle {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Emoji")
|
||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
|
||||
textArea.insert(textArea.cursorPosition, emoji);
|
||||
messageInput.insert(messageInput.cursorPosition, emoji);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.margins: 8
|
||||
hoverEnabled: true
|
||||
width: 22
|
||||
height: 22
|
||||
@ -299,10 +348,16 @@ Rectangle {
|
||||
ToolTip.text: qsTr("Send")
|
||||
onClicked: {
|
||||
TimelineManager.timeline.input.send();
|
||||
textArea.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: TimelineManager.timeline ? (!TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage)) : false
|
||||
text: qsTr("You don't have permission to send messages in this room")
|
||||
color: colors.text
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,192 +1,416 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./delegates"
|
||||
import "./emoji"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import im.nheko 1.0
|
||||
|
||||
ListView {
|
||||
id: chat
|
||||
ScrollView {
|
||||
clip: false
|
||||
palette: colors
|
||||
padding: 8
|
||||
ScrollBar.horizontal.visible: false
|
||||
|
||||
property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2 - 8)
|
||||
ListView {
|
||||
id: chat
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
cacheBuffer: 400
|
||||
model: TimelineManager.timeline
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
pixelAligned: true
|
||||
spacing: 4
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
onCountChanged: {
|
||||
// Mark timeline as read
|
||||
if (atYEnd)
|
||||
model.currentIndex = 0;
|
||||
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2
|
||||
|
||||
}
|
||||
model: TimelineManager.timeline
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
pixelAligned: true
|
||||
spacing: 4
|
||||
verticalLayoutDirection: ListView.BottomToTop
|
||||
onCountChanged: {
|
||||
// Mark timeline as read
|
||||
if (atYEnd)
|
||||
model.currentIndex = 0;
|
||||
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
enabled: !Settings.mobileMode
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToPreviousPage
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY - chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToNextPage
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY + chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
//closePolicy: Popup.NoAutoClose
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: chat.model.reply = undefined
|
||||
}
|
||||
id: messageActions
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Up"
|
||||
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
|
||||
}
|
||||
property Item attached: null
|
||||
property alias model: row.model
|
||||
// use comma to update on scroll
|
||||
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
|
||||
readonly property int padding: 4
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Down"
|
||||
onActivated: {
|
||||
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
|
||||
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
|
||||
}
|
||||
}
|
||||
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered)
|
||||
x: attached ? attachedPos.x : 0
|
||||
y: attached ? attachedPos.y : 0
|
||||
z: 10
|
||||
height: row.implicitHeight + padding * 2
|
||||
width: row.implicitWidth + padding * 2
|
||||
color: colors.window
|
||||
border.color: colors.buttonText
|
||||
border.width: 1
|
||||
radius: padding
|
||||
|
||||
Component {
|
||||
id: sectionHeader
|
||||
HoverHandler {
|
||||
id: messageActionHover
|
||||
|
||||
Column {
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
spacing: 8
|
||||
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day)
|
||||
width: parentWidth
|
||||
height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
||||
grabPermissions: PointerHandler.CanTakeOverFromAnything
|
||||
}
|
||||
|
||||
Label {
|
||||
id: dateBubble
|
||||
Row {
|
||||
id: row
|
||||
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
visible: modelData && modelData.previousMessageDay !== modelData.day
|
||||
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : ""
|
||||
color: colors.text
|
||||
height: Math.round(fontMetrics.height * 1.4)
|
||||
width: contentWidth * 1.2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
property var model
|
||||
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.window
|
||||
anchors.centerIn: parent
|
||||
spacing: messageActions.padding
|
||||
|
||||
ImageButton {
|
||||
id: editButton
|
||||
|
||||
visible: !!row.model && row.model.isEditable
|
||||
buttonTextColor: colors.buttonText
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/edit.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Edit")
|
||||
onClicked: {
|
||||
if (row.model.isEditable)
|
||||
chat.model.editAction(row.model.id);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
EmojiButton {
|
||||
id: reactButton
|
||||
|
||||
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("React")
|
||||
emojiPicker: emojiPopup
|
||||
event_id: row.model ? row.model.id : ""
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: replyButton
|
||||
|
||||
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/mail-reply.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Reply")
|
||||
onClicked: chat.model.replyAction(row.model.id)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: optionsButton
|
||||
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Options")
|
||||
onClicked: messageContextMenu.show(row.model.id, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
height: userName.height
|
||||
spacing: 8
|
||||
}
|
||||
|
||||
Avatar {
|
||||
width: avatarSize
|
||||
height: avatarSize
|
||||
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
|
||||
displayName: modelData ? modelData.userName : ""
|
||||
userid: modelData ? modelData.userId : ""
|
||||
onClicked: chat.model.openUserProfile(modelData.userId)
|
||||
ScrollHelper {
|
||||
flickable: parent
|
||||
anchors.fill: parent
|
||||
enabled: !Settings.mobileMode
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToPreviousPage
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY - chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.MoveToNextPage
|
||||
onActivated: {
|
||||
chat.contentY = chat.contentY + chat.height / 2;
|
||||
chat.returnToBounds();
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: {
|
||||
if (chat.model.reply)
|
||||
chat.model.reply = undefined;
|
||||
else
|
||||
chat.model.edit = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Up"
|
||||
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+Down"
|
||||
onActivated: {
|
||||
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
|
||||
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Alt+F"
|
||||
onActivated: {
|
||||
if (chat.model.reply) {
|
||||
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
|
||||
forwardMess.setMessageEventId(chat.model.reply);
|
||||
forwardMess.open();
|
||||
chat.model.reply = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+E"
|
||||
onActivated: {
|
||||
chat.model.edit = chat.model.reply;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager
|
||||
onFocusChanged: readTimer.running = TimelineManager.isWindowFocused
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: readTimer
|
||||
|
||||
// force current read index to update
|
||||
onTriggered: chat.model.setCurrentIndex(chat.model.currentIndex)
|
||||
interval: 1000
|
||||
}
|
||||
|
||||
Component {
|
||||
id: sectionHeader
|
||||
|
||||
Column {
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
spacing: 8
|
||||
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day)
|
||||
width: parentWidth
|
||||
height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
||||
|
||||
Label {
|
||||
id: userName
|
||||
id: dateBubble
|
||||
|
||||
text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : ""
|
||||
color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window)
|
||||
textFormat: Text.RichText
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
visible: modelData && modelData.previousMessageDay !== modelData.day
|
||||
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : ""
|
||||
color: colors.text
|
||||
height: Math.round(fontMetrics.height * 1.4)
|
||||
width: contentWidth * 1.2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: chat.model.openUserProfile(modelData.userId)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
propagateComposedEvents: true
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.window
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
color: colors.buttonText
|
||||
text: modelData ? TimelineManager.userStatus(modelData.userId) : ""
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize
|
||||
font.italic: true
|
||||
Row {
|
||||
height: userName.height
|
||||
spacing: 8
|
||||
|
||||
Avatar {
|
||||
id: messageUserAvatar
|
||||
|
||||
width: avatarSize
|
||||
height: avatarSize
|
||||
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
|
||||
displayName: modelData ? modelData.userName : ""
|
||||
userid: modelData ? modelData.userId : ""
|
||||
onClicked: chat.model.openUserProfile(modelData.userId)
|
||||
ToolTip.visible: avatarHover.hovered
|
||||
ToolTip.text: userid
|
||||
|
||||
HoverHandler {
|
||||
id: avatarHover
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chat.model
|
||||
onRoomAvatarUrlChanged: {
|
||||
messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "";
|
||||
}
|
||||
onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible)
|
||||
}
|
||||
|
||||
Label {
|
||||
id: userName
|
||||
|
||||
text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : ""
|
||||
color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window)
|
||||
textFormat: Text.RichText
|
||||
ToolTip.visible: displayNameHover.hovered
|
||||
ToolTip.text: modelData ? modelData.userId : ""
|
||||
|
||||
TapHandler {
|
||||
onSingleTapped: chat.model.openUserProfile(modelData.userId)
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: displayNameHover
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
color: colors.buttonText
|
||||
text: modelData ? TimelineManager.userStatus(modelData.userId) : ""
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize
|
||||
font.italic: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
delegate: Item {
|
||||
id: wrapper
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
}
|
||||
property bool scrolledToThis: model.id === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
||||
|
||||
delegate: Item {
|
||||
id: wrapper
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
width: chat.delegateMaxWidth
|
||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||
|
||||
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
||||
width: chat.delegateMaxWidth
|
||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||
Rectangle {
|
||||
id: scrollHighlight
|
||||
|
||||
Loader {
|
||||
id: section
|
||||
opacity: 0
|
||||
visible: true
|
||||
anchors.fill: timelinerow
|
||||
color: colors.highlight
|
||||
|
||||
property var modelData: model
|
||||
property int parentWidth: parent.width
|
||||
states: State {
|
||||
name: "revealed"
|
||||
when: wrapper.scrolledToThis
|
||||
}
|
||||
|
||||
active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day
|
||||
//asynchronous: true
|
||||
sourceComponent: sectionHeader
|
||||
visible: status == Loader.Ready
|
||||
}
|
||||
transitions: Transition {
|
||||
from: ""
|
||||
to: "revealed"
|
||||
|
||||
TimelineRow {
|
||||
id: timelinerow
|
||||
SequentialAnimation {
|
||||
PropertyAnimation {
|
||||
target: scrollHighlight
|
||||
properties: "opacity"
|
||||
easing.type: Easing.InOutQuad
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 500
|
||||
}
|
||||
|
||||
y: section.active && section.visible ? section.y + section.height : 0
|
||||
}
|
||||
PropertyAnimation {
|
||||
target: scrollHighlight
|
||||
properties: "opacity"
|
||||
easing.type: Easing.InOutQuad
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 500
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chat
|
||||
onMovementEnded: {
|
||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
||||
chat.model.currentIndex = index;
|
||||
ScriptAction {
|
||||
script: chat.model.eventShown()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: section
|
||||
|
||||
property var modelData: model
|
||||
property int parentWidth: parent.width
|
||||
|
||||
active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day
|
||||
//asynchronous: true
|
||||
sourceComponent: sectionHeader
|
||||
visible: status == Loader.Ready
|
||||
}
|
||||
|
||||
TimelineRow {
|
||||
id: timelinerow
|
||||
|
||||
property alias hovered: hoverHandler.hovered
|
||||
|
||||
y: section.visible && section.active ? section.y + section.height : 0
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
|
||||
enabled: !Settings.mobileMode
|
||||
onHoveredChanged: {
|
||||
if (hovered) {
|
||||
if (!messageActionHover.hovered) {
|
||||
messageActions.attached = timelinerow;
|
||||
messageActions.model = model;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chat
|
||||
onMovementEnded: {
|
||||
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
|
||||
chat.model.currentIndex = index;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
footer: BusyIndicator {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
running: chat.model && chat.model.paginationInProgress
|
||||
height: 50
|
||||
width: 50
|
||||
z: 3
|
||||
}
|
||||
|
||||
footer: BusyIndicator {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
running: chat.model && chat.model.paginationInProgress
|
||||
height: 50
|
||||
width: 50
|
||||
z: 3
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
|
130
resources/qml/PrivacyScreen.qml
Normal file
130
resources/qml/PrivacyScreen.qml
Normal file
@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.12
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
id: privacyScreen
|
||||
|
||||
property var timelineRoot
|
||||
property int screenTimeout
|
||||
|
||||
Connections {
|
||||
target: TimelineManager
|
||||
onFocusChanged: {
|
||||
if (TimelineManager.isWindowFocused) {
|
||||
screenSaverTimer.stop();
|
||||
screenSaver.state = "Invisible";
|
||||
} else {
|
||||
if (timelineRoot.visible)
|
||||
screenSaverTimer.start();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: screenSaverTimer
|
||||
|
||||
interval: screenTimeout * 1000
|
||||
running: true
|
||||
onTriggered: {
|
||||
screenSaver.state = "Visible";
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: screenSaver
|
||||
|
||||
state: "Invisible"
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
states: [
|
||||
State {
|
||||
name: "Visible"
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
visible: true
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "Invisible"
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: screenSaver
|
||||
visible: false
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "Visible"
|
||||
to: "Invisible"
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: screenSaver
|
||||
property: "opacity"
|
||||
duration: 250
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: screenSaver
|
||||
property: "visible"
|
||||
duration: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
Transition {
|
||||
from: "Invisible"
|
||||
to: "Visible"
|
||||
|
||||
SequentialAnimation {
|
||||
NumberAnimation {
|
||||
target: screenSaver
|
||||
property: "visible"
|
||||
duration: 0
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: screenSaver
|
||||
property: "opacity"
|
||||
duration: 500
|
||||
easing.type: Easing.InQuad
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
FastBlur {
|
||||
id: blur
|
||||
|
||||
anchors.fill: parent
|
||||
source: timelineRoot
|
||||
radius: 50
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
91
resources/qml/QuickSwitcher.qml
Normal file
91
resources/qml/QuickSwitcher.qml
Normal file
@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import im.nheko 1.0
|
||||
|
||||
Popup {
|
||||
id: quickSwitcher
|
||||
|
||||
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
|
||||
property int textMargin: Math.round(textHeight / 8)
|
||||
|
||||
background: null
|
||||
width: Math.round(parent.width / 2)
|
||||
x: Math.round(parent.width / 2 - width / 2)
|
||||
y: Math.round(parent.height / 4 - height / 2)
|
||||
modal: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
parent: Overlay.overlay
|
||||
palette: colors
|
||||
onOpened: {
|
||||
completerPopup.open();
|
||||
roomTextInput.forceActiveFocus();
|
||||
}
|
||||
onClosed: {
|
||||
completerPopup.close();
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
id: roomTextInput
|
||||
|
||||
anchors.fill: parent
|
||||
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
|
||||
padding: textMargin
|
||||
color: colors.text
|
||||
onTextEdited: {
|
||||
completerPopup.completer.searchString = text;
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Up && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.up();
|
||||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
completerPopup.finishCompletion();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Completer {
|
||||
id: completerPopup
|
||||
|
||||
x: roomTextInput.x
|
||||
y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
|
||||
visible: roomTextInput.length > 0
|
||||
width: parent.width
|
||||
completerName: "room"
|
||||
bottomToTop: false
|
||||
fullWidth: true
|
||||
avatarHeight: quickSwitcher.textHeight
|
||||
avatarWidth: quickSwitcher.textHeight
|
||||
centerRowContent: false
|
||||
rowMargin: Math.round(quickSwitcher.textMargin / 2)
|
||||
rowSpacing: quickSwitcher.textMargin
|
||||
closePolicy: Popup.NoAutoClose
|
||||
}
|
||||
|
||||
Connections {
|
||||
onCompletionSelected: {
|
||||
TimelineManager.setHistoryView(id);
|
||||
TimelineManager.highlightRoom(id);
|
||||
quickSwitcher.close();
|
||||
}
|
||||
onCountChanged: {
|
||||
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
|
||||
completerPopup.currentIndex = 0;
|
||||
|
||||
}
|
||||
target: completerPopup
|
||||
}
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: "#aa1E1E1E"
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import im.nheko 1.0
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./delegates/"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -10,15 +14,16 @@ Rectangle {
|
||||
property var room: TimelineManager.timeline
|
||||
|
||||
Layout.fillWidth: true
|
||||
visible: room && room.reply
|
||||
visible: room && (room.reply || room.edit)
|
||||
// Height of child, plus margins, plus border
|
||||
implicitHeight: replyPreview.height + 10
|
||||
implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + 10
|
||||
color: colors.window
|
||||
z: 3
|
||||
|
||||
Reply {
|
||||
id: replyPreview
|
||||
|
||||
visible: room && room.reply
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2 * 22 + 3 * 16
|
||||
anchors.right: closeReplyButton.left
|
||||
@ -32,8 +37,9 @@ Rectangle {
|
||||
ImageButton {
|
||||
id: closeReplyButton
|
||||
|
||||
visible: room && room.reply
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.rightMargin: 16
|
||||
anchors.top: replyPreview.top
|
||||
hoverEnabled: true
|
||||
width: 16
|
||||
@ -44,4 +50,17 @@ Rectangle {
|
||||
onClicked: room.reply = undefined
|
||||
}
|
||||
|
||||
Button {
|
||||
id: closeEditButton
|
||||
|
||||
visible: room && room.edit
|
||||
anchors.left: parent.left
|
||||
anchors.rightMargin: 16
|
||||
anchors.topMargin: 10
|
||||
anchors.top: parent.top
|
||||
//height: 16
|
||||
text: qsTr("Cancel edit")
|
||||
onClicked: room.edit = undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
287
resources/qml/RoomSettings.qml
Normal file
287
resources/qml/RoomSettings.qml
Normal file
@ -0,0 +1,287 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.3
|
||||
import im.nheko 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: roomSettingsDialog
|
||||
|
||||
property var roomSettings
|
||||
|
||||
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
|
||||
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
|
||||
minimumWidth: 420
|
||||
minimumHeight: 650
|
||||
palette: colors
|
||||
color: colors.window
|
||||
modality: Qt.WindowModal
|
||||
flags: Qt.Dialog
|
||||
title: qsTr("Room Settings")
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: roomSettingsDialog.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout1
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 10
|
||||
|
||||
Avatar {
|
||||
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: roomSettings.roomName
|
||||
height: 130
|
||||
width: 130
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
if (roomSettings.canChangeAvatar)
|
||||
roomSettings.updateAvatar();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
running: roomSettings.isLoading
|
||||
visible: roomSettings.isLoading
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorText
|
||||
|
||||
color: "red"
|
||||
visible: opacity > 0
|
||||
opacity: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: hideErrorAnimation
|
||||
|
||||
running: false
|
||||
|
||||
PauseAnimation {
|
||||
duration: 4000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: errorText
|
||||
property: 'opacity'
|
||||
to: 0
|
||||
duration: 1000
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: roomSettings
|
||||
onDisplayError: {
|
||||
errorText.text = errorMessage;
|
||||
errorText.opacity = 1;
|
||||
hideErrorAnimation.restart();
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
MatrixText {
|
||||
text: roomSettings.roomName
|
||||
font.pixelSize: 24
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("%1 member(s)").arg(roomSettings.memberCount)
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
image: ":/icons/icons/ui/edit.png"
|
||||
visible: roomSettings.canChangeNameAndTopic
|
||||
onClicked: roomSettings.openEditModal()
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.maximumHeight: 75
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: parent.width
|
||||
|
||||
TextArea {
|
||||
text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
background: null
|
||||
selectByMouse: true
|
||||
color: colors.text
|
||||
horizontalAlignment: TextEdit.AlignHCenter
|
||||
onLinkActivated: TimelineManager.openLink(link)
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: 10
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("SETTINGS")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Notifications")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
|
||||
currentIndex: roomSettings.notifications
|
||||
onActivated: {
|
||||
roomSettings.changeNotifications(index);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: "Room access"
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
enabled: roomSettings.canChangeJoinRules
|
||||
model: [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]
|
||||
currentIndex: roomSettings.accessJoinRules
|
||||
onActivated: {
|
||||
roomSettings.changeAccessRules(index);
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Encryption")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: encryptionToggle
|
||||
|
||||
checked: roomSettings.isEncryptionEnabled
|
||||
onClicked: {
|
||||
if (roomSettings.isEncryptionEnabled) {
|
||||
checked = true;
|
||||
return ;
|
||||
}
|
||||
confirmEncryptionDialog.open();
|
||||
}
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Platform.MessageDialog {
|
||||
id: confirmEncryptionDialog
|
||||
|
||||
title: qsTr("End-to-End Encryption")
|
||||
text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br>
|
||||
Please take note that it can't be disabled afterwards.")
|
||||
modality: Qt.WindowModal
|
||||
onAccepted: {
|
||||
if (roomSettings.isEncryptionEnabled)
|
||||
return ;
|
||||
|
||||
roomSettings.enableEncryption();
|
||||
}
|
||||
onRejected: {
|
||||
encryptionToggle.checked = false;
|
||||
}
|
||||
buttons: Dialog.Ok | Dialog.Cancel
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
visible: roomSettings.isEncryptionEnabled
|
||||
text: qsTr("Respond to key requests")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
visible: roomSettings.isEncryptionEnabled
|
||||
ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys
|
||||
upon request. Use with caution, this is a temporary measure to test the
|
||||
E2E implementation until device verification is completed.")
|
||||
checked: roomSettings.respondsToKeyRequests
|
||||
onClicked: {
|
||||
roomSettings.changeKeyRequestsPreference(checked);
|
||||
}
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
Item {
|
||||
// for adding extra space between sections
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item {
|
||||
// for adding extra space between sections
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("INFO")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Internal ID")
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: roomSettings.roomId
|
||||
font.pixelSize: 14
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Room Version")
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: roomSettings.roomVersion
|
||||
font.pixelSize: 14
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("OK")
|
||||
onClicked: close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,9 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
|
||||
* Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
// Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
|
||||
// Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/*
|
||||
* Shamelessly stolen from:
|
||||
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
|
||||
@ -90,7 +78,7 @@ MouseArea {
|
||||
// Show the scrollbars
|
||||
flickable.flick(0, 0);
|
||||
flickable.contentY = newPos;
|
||||
cancelFlickStateTimer.start();
|
||||
cancelFlickStateTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import im.nheko 1.0
|
||||
|
@ -1,6 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./delegates"
|
||||
import "./emoji"
|
||||
import QtQuick 2.6
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
@ -12,32 +16,32 @@ Item {
|
||||
height: row.height
|
||||
|
||||
Rectangle {
|
||||
color: (Settings.messageHoverHighlight && hoverHandler.containsMouse) ? colors.alternateBase : "transparent"
|
||||
color: (Settings.messageHoverHighlight && hoverHandler.hovered) ? colors.alternateBase : "transparent"
|
||||
anchors.fill: row
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
preventStealing: false
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton)
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row);
|
||||
else
|
||||
event.accepted = false;
|
||||
}
|
||||
onPressAndHold: {
|
||||
messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y));
|
||||
}
|
||||
acceptedDevices: PointerDevice.GenericPointer
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onSingleTapped: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onLongPressed: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
|
||||
onDoubleTapped: chat.model.reply = model.id
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
anchors.rightMargin: 1
|
||||
anchors.leftMargin: avatarSize + 16
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@ -46,6 +50,8 @@ Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 4
|
||||
Layout.topMargin: 1
|
||||
Layout.bottomMargin: 1
|
||||
|
||||
// fancy reply, if this is a reply
|
||||
Reply {
|
||||
@ -80,67 +86,41 @@ Item {
|
||||
EncryptionIndicator {
|
||||
visible: model.isRoomEncrypted
|
||||
encrypted: model.isEncrypted
|
||||
trust: model.trustlevel
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
Layout.preferredWidth: 16
|
||||
}
|
||||
|
||||
EmojiButton {
|
||||
id: reactButton
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Image {
|
||||
visible: model.isEdited || model.id == chat.model.edit
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
height: 16
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("React")
|
||||
emojiPicker: emojiPopup
|
||||
event_id: model.id
|
||||
}
|
||||
sourceSize.width: 16
|
||||
sourceSize.height: 16
|
||||
source: "image://colorimage/:/icons/icons/ui/edit.png?" + ((model.id == chat.model.edit) ? colors.highlight : colors.buttonText)
|
||||
ToolTip.visible: editHovered.hovered
|
||||
ToolTip.text: qsTr("Edited")
|
||||
|
||||
ImageButton {
|
||||
id: replyButton
|
||||
HoverHandler {
|
||||
id: editHovered
|
||||
}
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/mail-reply.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Reply")
|
||||
onClicked: chat.model.replyAction(model.id)
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
id: optionsButton
|
||||
|
||||
visible: Settings.buttonsInTimeline
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
width: 16
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Options")
|
||||
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
text: model.timestamp.toLocaleTimeString("HH:mm")
|
||||
text: model.timestamp.toLocaleTimeString(Locale.ShortFormat)
|
||||
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
|
||||
color: inactiveColors.text
|
||||
ToolTip.visible: ma.containsMouse
|
||||
ToolTip.visible: ma.hovered
|
||||
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
|
||||
|
||||
MouseArea {
|
||||
HoverHandler {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./delegates"
|
||||
import "./device-verification"
|
||||
import "./emoji"
|
||||
import "./voip"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -16,7 +21,7 @@ Page {
|
||||
property var colors: currentActivePalette
|
||||
property var systemInactive
|
||||
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
|
||||
property int avatarSize: 40
|
||||
readonly property int avatarSize: 40
|
||||
property real highlightHue: colors.highlight.hslHue
|
||||
property real highlightSat: colors.highlight.hslSaturation
|
||||
property real highlightLight: colors.highlight.hslLightness
|
||||
@ -30,18 +35,8 @@ Page {
|
||||
EmojiPicker {
|
||||
id: emojiPopup
|
||||
|
||||
width: 7 * 52 + 20
|
||||
height: 6 * 52
|
||||
colors: palette
|
||||
|
||||
model: EmojiProxyModel {
|
||||
category: EmojiCategory.People
|
||||
|
||||
sourceModel: EmojiModel {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
model: TimelineManager.completerFor("allemoji", "")
|
||||
}
|
||||
|
||||
Component {
|
||||
@ -52,6 +47,14 @@ Page {
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: roomSettingsComponent
|
||||
|
||||
RoomSettings {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mobileCallInviteDialog
|
||||
|
||||
@ -60,71 +63,158 @@ Page {
|
||||
|
||||
}
|
||||
|
||||
Menu {
|
||||
Component {
|
||||
id: quickSwitcherComponent
|
||||
|
||||
QuickSwitcher {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: forwardCompleterComponent
|
||||
|
||||
ForwardCompleter {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
onActivated: {
|
||||
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
|
||||
TimelineManager.focusTimeline();
|
||||
quickSwitch.open();
|
||||
}
|
||||
}
|
||||
|
||||
Platform.Menu {
|
||||
id: messageContextMenu
|
||||
|
||||
property string eventId
|
||||
property string link
|
||||
property string text
|
||||
property int eventType
|
||||
property bool isEncrypted
|
||||
property bool isEditable
|
||||
property bool isSender
|
||||
|
||||
function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
|
||||
function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
|
||||
eventId = eventId_;
|
||||
eventType = eventType_;
|
||||
isEncrypted = isEncrypted_;
|
||||
if (position)
|
||||
popup(position, showAt_);
|
||||
isEditable = isEditable_;
|
||||
isSender = isSender_;
|
||||
if (text_)
|
||||
text = text_;
|
||||
else
|
||||
popup(showAt_);
|
||||
text = "";
|
||||
if (link_)
|
||||
link = link_;
|
||||
else
|
||||
link = "";
|
||||
if (showAt_)
|
||||
open(showAt_);
|
||||
else
|
||||
open();
|
||||
}
|
||||
|
||||
modal: true
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.text
|
||||
enabled: visible
|
||||
text: qsTr("&Copy")
|
||||
onTriggered: Clipboard.text = messageContextMenu.text
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("React")
|
||||
onClicked: emojiPopup.show(messageContextMenu.parent, function(emoji) {
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.link
|
||||
enabled: visible
|
||||
text: qsTr("Copy &link location")
|
||||
onTriggered: Clipboard.text = messageContextMenu.link
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
id: reactionOption
|
||||
|
||||
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.Reaction) : false
|
||||
text: qsTr("Re&act")
|
||||
onTriggered: emojiPopup.show(null, function(emoji) {
|
||||
TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji);
|
||||
})
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Reply")
|
||||
onClicked: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
|
||||
Platform.MenuItem {
|
||||
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false
|
||||
text: qsTr("Repl&y")
|
||||
onTriggered: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Read receipts")
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.isEditable && (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false)
|
||||
enabled: visible
|
||||
text: qsTr("&Edit")
|
||||
onTriggered: TimelineManager.timeline.editAction(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Read receip&ts")
|
||||
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Mark as read")
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
|
||||
text: qsTr("&Forward")
|
||||
onTriggered: {
|
||||
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
|
||||
forwardMess.setMessageEventId(messageContextMenu.eventId);
|
||||
forwardMess.open();
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
text: qsTr("&Mark as read")
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("View raw message")
|
||||
onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
|
||||
visible: messageContextMenu.isEncrypted
|
||||
height: visible ? implicitHeight : 0
|
||||
enabled: visible
|
||||
text: qsTr("View decrypted raw message")
|
||||
onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Remove message")
|
||||
Platform.MenuItem {
|
||||
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canRedact() : false) || messageContextMenu.isSender
|
||||
text: qsTr("Remo&ve message")
|
||||
onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
|
||||
height: visible ? implicitHeight : 0
|
||||
text: qsTr("Save as")
|
||||
enabled: visible
|
||||
text: qsTr("&Save as")
|
||||
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
|
||||
enabled: visible
|
||||
text: qsTr("&Open in external program")
|
||||
onTriggered: TimelineManager.timeline.openMedia(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
visible: messageContextMenu.eventId
|
||||
enabled: visible
|
||||
text: qsTr("Copy link to eve&nt")
|
||||
onTriggered: TimelineManager.timeline.copyLinkToEvent(messageContextMenu.eventId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@ -147,10 +237,6 @@ Page {
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager.timeline
|
||||
onOpenProfile: {
|
||||
var userProfile = userProfileComponent.createObject(timelineRoot, {
|
||||
"profile": profile
|
||||
@ -159,6 +245,16 @@ Page {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TimelineManager.timeline
|
||||
onOpenRoomSettingsDialog: {
|
||||
var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
|
||||
"roomSettings": settings
|
||||
});
|
||||
roomSettings.show();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CallManager
|
||||
onNewInviteState: {
|
||||
@ -187,6 +283,8 @@ Page {
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: timelineLayout
|
||||
|
||||
visible: TimelineManager.timeline != null
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
@ -231,7 +329,7 @@ Page {
|
||||
}
|
||||
|
||||
Loader {
|
||||
source: CallManager.isOnCall && CallManager.isVideo ? "voip/VideoCall.qml" : ""
|
||||
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
|
||||
onLoaded: TimelineManager.setVideoCallItem()
|
||||
}
|
||||
|
||||
@ -271,6 +369,18 @@ Page {
|
||||
|
||||
}
|
||||
|
||||
NhekoDropArea {
|
||||
anchors.fill: parent
|
||||
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PrivacyScreen {
|
||||
anchors.fill: parent
|
||||
visible: Settings.privacyScreen
|
||||
screenTimeout: Settings.privacyScreenTimeout
|
||||
timelineRoot: timelineLayout
|
||||
}
|
||||
|
||||
systemInactive: SystemPalette {
|
||||
|
44
resources/qml/ToggleButton.qml
Normal file
44
resources/qml/ToggleButton.qml
Normal file
@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import im.nheko 1.0
|
||||
|
||||
Switch {
|
||||
id: toggleButton
|
||||
|
||||
implicitWidth: indicatorItem.width
|
||||
|
||||
indicator: Item {
|
||||
id: indicatorItem
|
||||
|
||||
implicitWidth: 48
|
||||
implicitHeight: 24
|
||||
y: parent.height / 2 - height / 2
|
||||
|
||||
Rectangle {
|
||||
height: 3 * parent.height / 4
|
||||
radius: height / 2
|
||||
width: parent.width - height
|
||||
x: radius
|
||||
y: parent.height / 2 - height / 2
|
||||
color: toggleButton.checked ? "skyblue" : "grey"
|
||||
border.color: "#cccccc"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: toggleButton.checked ? parent.width - width : 0
|
||||
y: parent.height / 2 - height / 2
|
||||
width: parent.height
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: toggleButton.down ? "whitesmoke" : "whitesmoke"
|
||||
border.color: "#ebebeb"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.2
|
||||
import im.nheko 1.0
|
||||
|
||||
@ -13,9 +18,12 @@ Rectangle {
|
||||
z: 3
|
||||
color: colors.window
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
TapHandler {
|
||||
onSingleTapped: {
|
||||
TimelineManager.timeline.openRoomSettings();
|
||||
eventPoint.accepted = true;
|
||||
}
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
@ -53,7 +61,7 @@ Rectangle {
|
||||
height: avatarSize
|
||||
url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
|
||||
displayName: room ? room.roomName : qsTr("No room selected")
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
onClicked: TimelineManager.timeline.openRoomSettings()
|
||||
}
|
||||
|
||||
Label {
|
||||
@ -65,12 +73,7 @@ Rectangle {
|
||||
text: room ? room.roomName : qsTr("No room selected")
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openRoomSettings()
|
||||
}
|
||||
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
@ -92,29 +95,30 @@ Rectangle {
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Room options")
|
||||
onClicked: roomOptionsMenu.popup(roomOptionsButton)
|
||||
onClicked: roomOptionsMenu.open(roomOptionsButton)
|
||||
|
||||
Menu {
|
||||
Platform.Menu {
|
||||
id: roomOptionsMenu
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canInvite() : false
|
||||
text: qsTr("Invite users")
|
||||
onTriggered: TimelineManager.openInviteUsersDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Members")
|
||||
onTriggered: TimelineManager.openMemberListDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Leave room")
|
||||
onTriggered: TimelineManager.openLeaveRoomDialog()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Settings")
|
||||
onTriggered: TimelineManager.openRoomSettings()
|
||||
onTriggered: TimelineManager.timeline.openRoomSettings()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./device-verification"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -10,11 +14,21 @@ ApplicationWindow {
|
||||
|
||||
property var profile
|
||||
|
||||
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
|
||||
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
|
||||
height: 650
|
||||
width: 420
|
||||
minimumHeight: 420
|
||||
palette: colors
|
||||
color: colors.window
|
||||
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
|
||||
modality: Qt.WindowModal
|
||||
flags: Qt.Dialog
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: userProfileDialog.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: contentL
|
||||
@ -30,16 +44,86 @@ ApplicationWindow {
|
||||
displayName: profile.displayName
|
||||
userid: profile.userid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id)
|
||||
onClicked: profile.isSelf ? profile.changeAvatar() : TimelineManager.openImageOverlay(profile.avatarUrl, "")
|
||||
}
|
||||
|
||||
Label {
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
running: profile.isLoading
|
||||
visible: profile.isLoading
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorText
|
||||
|
||||
color: "red"
|
||||
visible: opacity > 0
|
||||
opacity: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: hideErrorAnimation
|
||||
|
||||
running: false
|
||||
|
||||
PauseAnimation {
|
||||
duration: 4000
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: errorText
|
||||
property: 'opacity'
|
||||
to: 0
|
||||
duration: 1000
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: profile
|
||||
onDisplayError: {
|
||||
errorText.text = errorMessage;
|
||||
errorText.opacity = 1;
|
||||
hideErrorAnimation.restart();
|
||||
}
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: displayUsername
|
||||
|
||||
property bool isUsernameEditingAllowed
|
||||
|
||||
readOnly: !isUsernameEditingAllowed
|
||||
text: profile.displayName
|
||||
fontSizeMode: Text.HorizontalFit
|
||||
font.pixelSize: 20
|
||||
color: TimelineManager.userColor(profile.userid, colors.window)
|
||||
font.bold: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
selectByMouse: true
|
||||
onAccepted: {
|
||||
profile.changeUsername(displayUsername.text);
|
||||
displayUsername.isUsernameEditingAllowed = false;
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
visible: profile.isSelf
|
||||
anchors.leftMargin: 5
|
||||
anchors.left: displayUsername.right
|
||||
anchors.verticalCenter: displayUsername.verticalCenter
|
||||
image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
|
||||
onClicked: {
|
||||
if (displayUsername.isUsernameEditingAllowed) {
|
||||
profile.changeUsername(displayUsername.text);
|
||||
displayUsername.isUsernameEditingAllowed = false;
|
||||
} else {
|
||||
displayUsername.isUsernameEditingAllowed = true;
|
||||
displayUsername.focus = true;
|
||||
displayUsername.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
@ -53,32 +137,21 @@ ApplicationWindow {
|
||||
|
||||
text: qsTr("Verify")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
enabled: !profile.isUserVerified
|
||||
visible: !profile.isUserVerified && !profile.isSelf && profile.userVerificationEnabled
|
||||
enabled: profile.userVerified != Crypto.Verified
|
||||
visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
|
||||
onClicked: profile.verify()
|
||||
}
|
||||
|
||||
Image {
|
||||
Layout.preferredHeight: 16
|
||||
Layout.preferredWidth: 16
|
||||
source: "image://colorimage/:/icons/icons/ui/lock.png?green"
|
||||
visible: profile.isUserVerified
|
||||
source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : colors.buttonText)
|
||||
visible: profile.userVerified != Crypto.Unverified
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 8
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Ban the user")
|
||||
onClicked: profile.banUser()
|
||||
}
|
||||
// ImageButton{
|
||||
|
||||
// image:":/icons/icons/ui/volume-off-indicator.png"
|
||||
// Layout.margins: {
|
||||
// left: 5
|
||||
@ -90,6 +163,10 @@ ApplicationWindow {
|
||||
// profile.ignoreUser()
|
||||
// }
|
||||
// }
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 8
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/black-bubble-speech.png"
|
||||
hoverEnabled: true
|
||||
@ -104,6 +181,16 @@ ApplicationWindow {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Kick the user")
|
||||
onClicked: profile.kickUser()
|
||||
visible: profile.room ? profile.room.permissions.canKick() : false
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Ban the user")
|
||||
onClicked: profile.banUser()
|
||||
visible: profile.room ? profile.room.permissions.canBan() : false
|
||||
}
|
||||
|
||||
}
|
||||
@ -155,7 +242,7 @@ ApplicationWindow {
|
||||
id: verifyButton
|
||||
|
||||
visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled))
|
||||
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
|
||||
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
|
||||
onClicked: {
|
||||
if (model.verificationStatus == VerificationStatus.VERIFIED)
|
||||
profile.unverify(model.deviceId);
|
||||
|
@ -1,4 +1,8 @@
|
||||
import QtQuick 2.6
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.2
|
||||
import im.nheko 1.0
|
||||
|
||||
@ -29,9 +33,13 @@ Item {
|
||||
fillMode: Image.Pad
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
TapHandler {
|
||||
onSingleTapped: TimelineManager.timeline.saveMedia(model.data.id)
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.timeline.saveMedia(model.data.id)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
import QtQuick 2.6
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
|
||||
property double tempHeight: tempWidth * model.data.proportionalHeight
|
||||
property double divisor: model.isReply ? 4 : 2
|
||||
property double divisor: model.isReply ? 5 : 3
|
||||
property bool tooHigh: tempHeight > timelineRoot.height / divisor
|
||||
|
||||
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)
|
||||
@ -32,20 +36,24 @@ Item {
|
||||
smooth: true
|
||||
mipmap: true
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
TapHandler {
|
||||
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
|
||||
onSingleTapped: {
|
||||
TimelineManager.openImageOverlay(model.data.url, model.data.id);
|
||||
eventPoint.accepted = true;
|
||||
}
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: mouseArea
|
||||
}
|
||||
|
||||
Item {
|
||||
id: overlay
|
||||
|
||||
anchors.fill: parent
|
||||
visible: mouseArea.containsMouse
|
||||
visible: mouseArea.hovered
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
|
@ -1,9 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.6
|
||||
import im.nheko 1.0
|
||||
|
||||
Item {
|
||||
property alias modelData: model.data
|
||||
property alias isReply: model.isReply
|
||||
property alias child: chooser.child
|
||||
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
|
||||
|
||||
height: chooser.childrenRect.height
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
TextMessage {
|
||||
font.italic: true
|
||||
color: colors.buttonText
|
||||
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
|
||||
clip: isReply
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import ".."
|
||||
|
||||
MatrixText {
|
||||
|
@ -1,5 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtMultimedia 5.6
|
||||
import QtQuick 2.6
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import im.nheko 1.0
|
||||
@ -103,20 +108,40 @@ Rectangle {
|
||||
width: parent.width
|
||||
spacing: 15
|
||||
|
||||
Rectangle {
|
||||
ImageButton {
|
||||
id: button
|
||||
|
||||
color: colors.window
|
||||
radius: 22
|
||||
height: 44
|
||||
width: 44
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
//color: colors.window
|
||||
//radius: 22
|
||||
height: 32
|
||||
width: 32
|
||||
z: 3
|
||||
image: ":/icons/icons/ui/arrow-pointing-down.png"
|
||||
onClicked: {
|
||||
switch (button.state) {
|
||||
case "":
|
||||
TimelineManager.timeline.cacheMedia(model.data.id);
|
||||
break;
|
||||
case "stopped":
|
||||
media.play();
|
||||
console.log("play");
|
||||
button.state = "playing";
|
||||
break;
|
||||
case "playing":
|
||||
media.pause();
|
||||
console.log("pause");
|
||||
button.state = "stopped";
|
||||
break;
|
||||
}
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "stopped"
|
||||
|
||||
PropertyChanges {
|
||||
target: img
|
||||
source: "image://colorimage/:/icons/icons/ui/play-sign.png?" + colors.text
|
||||
target: button
|
||||
image: ":/icons/icons/ui/play-sign.png"
|
||||
}
|
||||
|
||||
},
|
||||
@ -124,41 +149,15 @@ Rectangle {
|
||||
name: "playing"
|
||||
|
||||
PropertyChanges {
|
||||
target: img
|
||||
source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + colors.text
|
||||
target: button
|
||||
image: ":/icons/icons/ui/pause-symbol.png"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
anchors.centerIn: parent
|
||||
z: 3
|
||||
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + colors.text
|
||||
fillMode: Image.Pad
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
switch (button.state) {
|
||||
case "":
|
||||
TimelineManager.timeline.cacheMedia(model.data.id);
|
||||
break;
|
||||
case "stopped":
|
||||
media.play();
|
||||
console.log("play");
|
||||
button.state = "playing";
|
||||
break;
|
||||
case "playing":
|
||||
media.pause();
|
||||
console.log("pause");
|
||||
button.state = "stopped";
|
||||
break;
|
||||
}
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
@ -178,7 +177,7 @@ Rectangle {
|
||||
target: TimelineManager.timeline
|
||||
onMediaCached: {
|
||||
if (mxcUrl == model.data.url) {
|
||||
media.source = "file://" + cacheUrl;
|
||||
media.source = cacheUrl;
|
||||
button.state = "stopped";
|
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
import QtQuick 2.6
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
@ -13,10 +17,13 @@ Item {
|
||||
width: parent.width
|
||||
height: replyContainer.height
|
||||
|
||||
MouseArea {
|
||||
TapHandler {
|
||||
onSingleTapped: chat.model.showEvent(modelData.id)
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
CursorShape {
|
||||
anchors.fill: parent
|
||||
preventStealing: false
|
||||
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
@ -43,10 +50,9 @@ Item {
|
||||
color: replyComponent.userColor
|
||||
textFormat: Text.RichText
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: chat.model.openUserProfile(reply.modelData.userId)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
TapHandler {
|
||||
onSingleTapped: chat.model.openUserProfile(reply.modelData.userId)
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import ".."
|
||||
import im.nheko 1.0
|
||||
|
||||
MatrixText {
|
||||
property string formatted: model.data.formattedBody
|
||||
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : model.data.body
|
||||
|
||||
text: "<span style='white-space: pre-wrap'><style type=\"text/css\">a { color:" + colors.link + ";}\ncode { background-color: " + colors.alternateBase + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + colors.alternateBase + "'>") + "</span>"
|
||||
text: "<style type=\"text/css\">a { color:" + colors.link + ";}\ncode { background-color: " + colors.alternateBase + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + colors.alternateBase + "'>")
|
||||
width: parent ? parent.width : undefined
|
||||
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
|
||||
clip: isReply
|
||||
selectByMouse: !Settings.mobileMode && !isReply
|
||||
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Window 2.10
|
||||
@ -11,9 +15,12 @@ ApplicationWindow {
|
||||
onClosing: TimelineManager.removeVerificationFlow(flow)
|
||||
title: stack.currentItem.title
|
||||
flags: Qt.Dialog
|
||||
modality: Qt.WindowModal
|
||||
palette: colors
|
||||
height: stack.implicitHeight
|
||||
width: stack.implicitWidth
|
||||
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
|
||||
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.10
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.1
|
||||
@ -14,5 +18,6 @@ ImageButton {
|
||||
image: ":/icons/icons/ui/smile.png"
|
||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) {
|
||||
TimelineManager.queueReactionMessage(event_id, emoji);
|
||||
TimelineManager.focusMessageInput();
|
||||
})
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
@ -6,7 +10,7 @@ import QtQuick.Layouts 1.3
|
||||
import im.nheko 1.0
|
||||
import im.nheko.EmojiModel 1.0
|
||||
|
||||
Popup {
|
||||
Menu {
|
||||
id: emojiPopup
|
||||
|
||||
property var callback
|
||||
@ -20,13 +24,8 @@ Popup {
|
||||
|
||||
function show(showAt, callback) {
|
||||
console.debug("Showing emojiPicker");
|
||||
if (showAt) {
|
||||
parent = showAt;
|
||||
x = Math.round((showAt.width - width) / 2);
|
||||
y = showAt.height;
|
||||
}
|
||||
emojiPopup.callback = callback;
|
||||
open();
|
||||
popup(showAt ? showAt : null);
|
||||
}
|
||||
|
||||
margins: 0
|
||||
@ -36,24 +35,74 @@ Popup {
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
height: columnView.implicitHeight + 4
|
||||
width: columnView.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: columnView
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
Layout.bottomMargin: 0
|
||||
Layout.leftMargin: 3
|
||||
Layout.rightMargin: 3
|
||||
Layout.topMargin: 2
|
||||
anchors.leftMargin: 3
|
||||
anchors.rightMargin: 3
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 2
|
||||
|
||||
// Search field
|
||||
TextField {
|
||||
id: emojiSearch
|
||||
|
||||
//width: gridView.width - 6
|
||||
Layout.topMargin: 3
|
||||
Layout.preferredWidth: 7 * 52 + 20 - 6
|
||||
placeholderText: qsTr("Search")
|
||||
selectByMouse: true
|
||||
rightPadding: clearSearch.width
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchTimer
|
||||
|
||||
interval: 350 // tweak as needed?
|
||||
onTriggered: {
|
||||
emojiPopup.model.searchString = emojiSearch.text;
|
||||
emojiPopup.model.category = Emoji.Category.Search;
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: clearSearch
|
||||
|
||||
visible: emojiSearch.text !== ''
|
||||
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
}
|
||||
// clear the default hover effects.
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// emoji grid
|
||||
GridView {
|
||||
id: gridView
|
||||
|
||||
Layout.preferredHeight: emojiPopup.height
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: cellHeight * 5
|
||||
Layout.preferredWidth: 7 * 52 + 20
|
||||
Layout.leftMargin: 4
|
||||
cellWidth: 52
|
||||
cellHeight: 52
|
||||
@ -103,54 +152,6 @@ Popup {
|
||||
|
||||
}
|
||||
|
||||
// Search field
|
||||
header: TextField {
|
||||
id: emojiSearch
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: emojiScroll.width + 4
|
||||
placeholderText: qsTr("Search")
|
||||
selectByMouse: true
|
||||
rightPadding: clearSearch.width
|
||||
onTextChanged: searchTimer.restart()
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchTimer
|
||||
|
||||
interval: 350 // tweak as needed?
|
||||
onTriggered: {
|
||||
emojiPopup.model.filter = emojiSearch.text;
|
||||
emojiPopup.model.category = EmojiCategory.Search;
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: clearSearch
|
||||
|
||||
visible: emojiSearch.text !== ''
|
||||
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
|
||||
focusPolicy: Qt.NoFocus
|
||||
onClicked: emojiSearch.clear()
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
}
|
||||
// clear the default hover effects.
|
||||
|
||||
background: Item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: emojiScroll
|
||||
}
|
||||
@ -159,6 +160,7 @@ Popup {
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
visible: emojiSearch.text === ''
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: emojiPopup.colors.alternateBase
|
||||
@ -166,6 +168,7 @@ Popup {
|
||||
|
||||
// Category picker row
|
||||
RowLayout {
|
||||
visible: emojiSearch.text === ''
|
||||
Layout.bottomMargin: 0
|
||||
Layout.preferredHeight: 42
|
||||
implicitHeight: 42
|
||||
@ -178,42 +181,42 @@ Popup {
|
||||
// TODO: Would like to get 'simple' icons for the categories
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/people.png"
|
||||
category: EmojiCategory.People
|
||||
category: Emoji.Category.People
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/nature.png"
|
||||
category: EmojiCategory.Nature
|
||||
category: Emoji.Category.Nature
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/foods.png"
|
||||
category: EmojiCategory.Food
|
||||
category: Emoji.Category.Food
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/activity.png"
|
||||
category: EmojiCategory.Activity
|
||||
category: Emoji.Category.Activity
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/travel.png"
|
||||
category: EmojiCategory.Travel
|
||||
category: Emoji.Category.Travel
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/objects.png"
|
||||
category: EmojiCategory.Objects
|
||||
category: Emoji.Category.Objects
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/symbols.png"
|
||||
category: EmojiCategory.Symbols
|
||||
category: Emoji.Category.Symbols
|
||||
}
|
||||
|
||||
ListElement {
|
||||
image: ":/icons/icons/emoji-categories/flags.png"
|
||||
category: EmojiCategory.Flags
|
||||
category: Emoji.Category.Flags
|
||||
}
|
||||
|
||||
}
|
||||
@ -224,27 +227,28 @@ Popup {
|
||||
hoverEnabled: true
|
||||
ToolTip.text: {
|
||||
switch (model.category) {
|
||||
case EmojiCategory.People:
|
||||
case Emoji.Category.People:
|
||||
return qsTr('People');
|
||||
case EmojiCategory.Nature:
|
||||
case Emoji.Category.Nature:
|
||||
return qsTr('Nature');
|
||||
case EmojiCategory.Food:
|
||||
case Emoji.Category.Food:
|
||||
return qsTr('Food');
|
||||
case EmojiCategory.Activity:
|
||||
case Emoji.Category.Activity:
|
||||
return qsTr('Activity');
|
||||
case EmojiCategory.Travel:
|
||||
case Emoji.Category.Travel:
|
||||
return qsTr('Travel');
|
||||
case EmojiCategory.Objects:
|
||||
case Emoji.Category.Objects:
|
||||
return qsTr('Objects');
|
||||
case EmojiCategory.Symbols:
|
||||
case Emoji.Category.Symbols:
|
||||
return qsTr('Symbols');
|
||||
case EmojiCategory.Flags:
|
||||
case Emoji.Category.Flags:
|
||||
return qsTr('Flags');
|
||||
}
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
onClicked: {
|
||||
emojiPopup.model.category = model.category;
|
||||
//emojiPopup.model.category = model.category;
|
||||
gridView.positionViewAtIndex(emojiPopup.model.sourceModel.categoryToIndex(model.category), GridView.Beginning);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@ -275,56 +279,6 @@ Popup {
|
||||
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 1
|
||||
implicitWidth: 1
|
||||
height: parent.height
|
||||
color: emojiPopup.colors.alternateBase
|
||||
}
|
||||
|
||||
// Search Button is special
|
||||
AbstractButton {
|
||||
id: searchBtn
|
||||
|
||||
hoverEnabled: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.bottomMargin: 0
|
||||
ToolTip.text: qsTr("Search")
|
||||
ToolTip.visible: hovered
|
||||
onClicked: {
|
||||
// clear any filters
|
||||
emojiPopup.model.category = EmojiCategory.Search;
|
||||
gridView.positionViewAtBeginning();
|
||||
emojiSearch.forceActiveFocus();
|
||||
}
|
||||
Layout.preferredWidth: 36
|
||||
Layout.preferredHeight: 36
|
||||
implicitWidth: 36
|
||||
implicitHeight: 36
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
anchors.right: parent.right
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
verticalAlignment: Image.AlignVCenter
|
||||
sourceSize.width: 32
|
||||
sourceSize.height: 32
|
||||
fillMode: Image.Pad
|
||||
smooth: true
|
||||
source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
@ -9,9 +13,9 @@ Item {
|
||||
property real radius: 0
|
||||
property color color: "#22000000"
|
||||
property real maxRadius: Math.max(width, height)
|
||||
property real radiusAnimationRate: 0.05
|
||||
property real radiusTailAnimationRate: 0.5
|
||||
property real opacityAnimationDuration: 300
|
||||
readonly property real radiusAnimationRate: 0.05
|
||||
readonly property real radiusTailAnimationRate: 0.5
|
||||
readonly property real opacityAnimationDuration: 300
|
||||
readonly property real diameter: radius * 2
|
||||
property real centerX
|
||||
property real centerY
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -12,7 +16,7 @@ Rectangle {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (CallManager.isVideo)
|
||||
if (CallManager.callType != CallType.VOICE)
|
||||
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
|
||||
|
||||
}
|
||||
@ -42,10 +46,46 @@ Rectangle {
|
||||
}
|
||||
|
||||
Image {
|
||||
id: callTypeIcon
|
||||
|
||||
Layout.leftMargin: 4
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||
}
|
||||
|
||||
Item {
|
||||
states: [
|
||||
State {
|
||||
name: "VOICE"
|
||||
when: CallManager.callType == CallType.VOICE
|
||||
|
||||
PropertyChanges {
|
||||
target: callTypeIcon
|
||||
source: "qrc:/icons/icons/ui/place-call.png"
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "VIDEO"
|
||||
when: CallManager.callType == CallType.VIDEO
|
||||
|
||||
PropertyChanges {
|
||||
target: callTypeIcon
|
||||
source: "qrc:/icons/icons/ui/video-call.png"
|
||||
}
|
||||
|
||||
},
|
||||
State {
|
||||
name: "SCREEN"
|
||||
when: CallManager.callType == CallType.SCREEN
|
||||
|
||||
PropertyChanges {
|
||||
target: callTypeIcon
|
||||
source: "qrc:/icons/icons/ui/screen-share.png"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Label {
|
||||
@ -103,7 +143,7 @@ Rectangle {
|
||||
|
||||
PropertyChanges {
|
||||
target: stackLayout
|
||||
currentIndex: CallManager.isVideo ? 1 : 0
|
||||
currentIndex: CallManager.callType != CallType.VOICE ? 1 : 0
|
||||
}
|
||||
|
||||
},
|
||||
@ -147,20 +187,28 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.leftMargin: 16
|
||||
visible: CallManager.callType == CallType.SCREEN && CallManager.callState == WebRTCState.CONNECTED
|
||||
text: qsTr("You are screen sharing")
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
color: "#000000"
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
visible: CallManager.haveLocalVideo
|
||||
visible: CallManager.haveLocalPiP
|
||||
width: 24
|
||||
height: 24
|
||||
buttonTextColor: "#000000"
|
||||
image: ":/icons/icons/ui/toggle-camera-view.png"
|
||||
hoverEnabled: true
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Toggle camera view")
|
||||
onClicked: CallManager.toggleCameraView()
|
||||
ToolTip.text: qsTr("Hide/Show Picture-in-Picture")
|
||||
onClicked: CallManager.toggleLocalPiP()
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
@ -40,7 +44,7 @@ Popup {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: CallManager.isVideo && CallManager.cameras.length > 0
|
||||
visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0
|
||||
|
||||
Image {
|
||||
Layout.preferredWidth: 22
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -53,7 +57,7 @@ Popup {
|
||||
Layout.bottomMargin: msgView.height / 25
|
||||
|
||||
Image {
|
||||
property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
|
||||
property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.preferredWidth: msgView.height / 10
|
||||
@ -63,7 +67,7 @@ Popup {
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
font.pointSize: fontMetrics.font.pointSize * 2
|
||||
color: colors.windowText
|
||||
}
|
||||
@ -97,7 +101,7 @@ Popup {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: CallManager.isVideo && CallManager.cameras.length > 0
|
||||
visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Image {
|
||||
@ -159,7 +163,7 @@ Popup {
|
||||
RoundButton {
|
||||
id: acceptButton
|
||||
|
||||
property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
|
||||
property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
|
||||
|
||||
implicitWidth: buttonLayout.buttonSize
|
||||
implicitHeight: buttonLayout.buttonSize
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -52,12 +56,12 @@ Rectangle {
|
||||
Layout.leftMargin: 4
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||
source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||
}
|
||||
|
||||
Label {
|
||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||
text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call")
|
||||
color: "#000000"
|
||||
}
|
||||
|
||||
@ -75,7 +79,6 @@ Rectangle {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Devices")
|
||||
onClicked: {
|
||||
CallManager.refreshDevices();
|
||||
var dialog = devicesDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
}
|
||||
@ -83,7 +86,7 @@ Rectangle {
|
||||
|
||||
Button {
|
||||
Layout.rightMargin: 4
|
||||
icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||
icon.source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
|
||||
text: qsTr("Accept")
|
||||
palette: colors
|
||||
onClicked: {
|
||||
@ -102,7 +105,7 @@ Rectangle {
|
||||
dialog.open();
|
||||
return ;
|
||||
}
|
||||
if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
|
||||
if (CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
|
||||
var dialog = deviceError.createObject(timelineRoot, {
|
||||
"errorString": qsTr("Unknown camera: %1").arg(Settings.camera),
|
||||
"image": ":/icons/icons/ui/video-call.png"
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
@ -23,6 +27,14 @@ Popup {
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: screenShareDialog
|
||||
|
||||
ScreenShare {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
|
||||
@ -76,7 +88,7 @@ Popup {
|
||||
onClicked: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
CallManager.sendInvite(TimelineManager.timeline.roomId(), false);
|
||||
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VOICE);
|
||||
close();
|
||||
}
|
||||
}
|
||||
@ -90,12 +102,23 @@ Popup {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
Settings.camera = cameraCombo.currentText;
|
||||
CallManager.sendInvite(TimelineManager.timeline.roomId(), true);
|
||||
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VIDEO);
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: CallManager.screenShareSupported
|
||||
text: qsTr("Screen")
|
||||
icon.source: "qrc:/icons/icons/ui/screen-share.png"
|
||||
onClicked: {
|
||||
var dialog = screenShareDialog.createObject(timelineRoot);
|
||||
dialog.open();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
|
168
resources/qml/voip/ScreenShare.qml
Normal file
168
resources/qml/voip/ScreenShare.qml
Normal file
@ -0,0 +1,168 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "../"
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import im.nheko 1.0
|
||||
|
||||
Popup {
|
||||
modal: true
|
||||
// only set the anchors on Qt 5.12 or higher
|
||||
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
|
||||
Component.onCompleted: {
|
||||
if (anchors)
|
||||
anchors.centerIn = parent;
|
||||
|
||||
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
|
||||
}
|
||||
palette: colors
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Share desktop with %1?").arg(TimelineManager.timeline.roomName)
|
||||
color: colors.windowText
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Window:")
|
||||
color: colors.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: windowCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: CallManager.windowList()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Frame rate:")
|
||||
color: colors.windowText
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: frameRateCombo
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: ["25", "20", "15", "10", "5", "2", "1"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
columns: 2
|
||||
rowSpacing: 10
|
||||
Layout.margins: 8
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Include your camera picture-in-picture")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: pipCheckBox
|
||||
|
||||
enabled: CallManager.cameras.length > 0
|
||||
checked: Settings.screenSharePiP
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Request remote camera")
|
||||
ToolTip.text: qsTr("View your callee's camera like a regular video call")
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: remoteVideoCheckBox
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: Settings.screenShareRemoteVideo
|
||||
ToolTip.text: qsTr("View your callee's camera like a regular video call")
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Hide mouse cursor")
|
||||
}
|
||||
|
||||
ToggleButton {
|
||||
id: hideCursorCheckBox
|
||||
|
||||
Layout.alignment: Qt.AlignRight
|
||||
checked: Settings.screenShareHideCursor
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 8
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Share")
|
||||
icon.source: "qrc:/icons/icons/ui/screen-share.png"
|
||||
onClicked: {
|
||||
if (buttonLayout.validateMic()) {
|
||||
Settings.microphone = micCombo.currentText;
|
||||
if (pipCheckBox.checked)
|
||||
Settings.camera = cameraCombo.currentText;
|
||||
|
||||
Settings.screenShareFrameRate = frameRateCombo.currentText;
|
||||
Settings.screenSharePiP = pipCheckBox.checked;
|
||||
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
|
||||
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
|
||||
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.SCREEN, windowCombo.currentIndex);
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Preview")
|
||||
onClicked: {
|
||||
CallManager.previewWindow(windowCombo.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: colors.window
|
||||
border.color: colors.windowText
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.9
|
||||
import org.freedesktop.gstreamer.GLVideoItem 1.0
|
||||
|
||||
|
@ -74,6 +74,7 @@
|
||||
<file>icons/ui/end-call.png</file>
|
||||
<file>icons/ui/microphone-mute.png</file>
|
||||
<file>icons/ui/microphone-unmute.png</file>
|
||||
<file>icons/ui/screen-share.png</file>
|
||||
<file>icons/ui/toggle-camera-view.png</file>
|
||||
<file>icons/ui/video-call.png</file>
|
||||
|
||||
@ -128,16 +129,22 @@
|
||||
<file>qml/EncryptionIndicator.qml</file>
|
||||
<file>qml/ImageButton.qml</file>
|
||||
<file>qml/MatrixText.qml</file>
|
||||
<file>qml/MatrixTextField.qml</file>
|
||||
<file>qml/ToggleButton.qml</file>
|
||||
<file>qml/MessageInput.qml</file>
|
||||
<file>qml/MessageView.qml</file>
|
||||
<file>qml/NhekoBusyIndicator.qml</file>
|
||||
<file>qml/PrivacyScreen.qml</file>
|
||||
<file>qml/Reactions.qml</file>
|
||||
<file>qml/ReplyPopup.qml</file>
|
||||
<file>qml/ScrollHelper.qml</file>
|
||||
<file>qml/StatusIndicator.qml</file>
|
||||
<file>qml/TimelineRow.qml</file>
|
||||
<file>qml/TopBar.qml</file>
|
||||
<file>qml/QuickSwitcher.qml</file>
|
||||
<file>qml/ForwardCompleter.qml</file>
|
||||
<file>qml/TypingIndicator.qml</file>
|
||||
<file>qml/RoomSettings.qml</file>
|
||||
<file>qml/emoji/EmojiButton.qml</file>
|
||||
<file>qml/emoji/EmojiPicker.qml</file>
|
||||
<file>qml/UserProfile.qml</file>
|
||||
@ -164,6 +171,7 @@
|
||||
<file>qml/voip/CallInviteBar.qml</file>
|
||||
<file>qml/voip/DeviceError.qml</file>
|
||||
<file>qml/voip/PlaceCall.qml</file>
|
||||
<file>qml/voip/ScreenShare.qml</file>
|
||||
<file>qml/voip/VideoCall.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/media">
|
||||
|
@ -11,26 +11,13 @@ class Emoji(object):
|
||||
self.code = repr(code.encode('utf-8'))[1:].strip("'")
|
||||
self.shortname = shortname
|
||||
|
||||
def generate_code(emojis, category):
|
||||
tmpl = Template('''
|
||||
const std::vector<Emoji> emoji::Provider::{{ category }} = {
|
||||
// {{ category.capitalize() }}
|
||||
{%- for e in emoji %}
|
||||
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ category.capitalize() }}},
|
||||
{%- endfor %}
|
||||
};
|
||||
''')
|
||||
|
||||
d = dict(category=category, emoji=emojis)
|
||||
print(tmpl.render(d))
|
||||
|
||||
def generate_qml_list(**kwargs):
|
||||
tmpl = Template('''
|
||||
const QVector<Emoji> emoji::Provider::emoji = {
|
||||
{%- for c in kwargs.items() %}
|
||||
// {{ c[0].capitalize() }}
|
||||
{%- for e in c[1] %}
|
||||
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ c[0].capitalize() }}},
|
||||
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::Emoji::Category::{{ c[0].capitalize() }}},
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
};
|
||||
@ -93,12 +80,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Use xclip to pipe the output to clipboard.
|
||||
# e.g ./codegen.py emoji.json | xclip -sel clip
|
||||
generate_code(people, 'people')
|
||||
generate_code(nature, 'nature')
|
||||
generate_code(food, 'food')
|
||||
generate_code(activity, 'activity')
|
||||
generate_code(travel, 'travel')
|
||||
generate_code(objects, 'objects')
|
||||
generate_code(symbols, 'symbols')
|
||||
generate_code(flags, 'flags')
|
||||
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags)
|
||||
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags)
|
||||
|
772
scripts/flat-manager-client
Executable file
772
scripts/flat-manager-client
Executable file
@ -0,0 +1,772 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import binascii
|
||||
import errno
|
||||
import fnmatch
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from argparse import ArgumentParser
|
||||
from functools import reduce
|
||||
from urllib.parse import urljoin, urlparse, urlsplit, urlunparse, urlunsplit
|
||||
|
||||
import aiohttp
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
|
||||
|
||||
import gi
|
||||
gi.require_version('OSTree', '1.0')
|
||||
from gi.repository import Gio, GLib, OSTree
|
||||
|
||||
UPLOAD_CHUNK_LIMIT = 4 * 1024 * 1024
|
||||
DEFAULT_LIMIT = 2 ** 16
|
||||
|
||||
def eprint(*args, **kwargs):
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
class UsageException(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class ApiError(Exception):
|
||||
def __init__(self, response, body):
|
||||
self.url = str(response.url)
|
||||
self.status = response.status
|
||||
|
||||
try:
|
||||
self.body = json.loads(response);
|
||||
except:
|
||||
self.body = {"status": self.status, "error-type": "no-error", "message": "No json error details from server"}
|
||||
|
||||
def repr(self):
|
||||
return {
|
||||
"type": "api",
|
||||
"url": self.url,
|
||||
"status_code": self.status,
|
||||
"details": self.body
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "Api call to %s failed with status %d, details: %s" % (self.url, self.status, self.body)
|
||||
|
||||
|
||||
# This is similar to the regular payload, but opens the file lazily
|
||||
class AsyncNamedFilePart(aiohttp.payload.Payload):
|
||||
def __init__(self,
|
||||
value,
|
||||
disposition='attachment',
|
||||
*args,
|
||||
**kwargs):
|
||||
self._file = None
|
||||
if 'filename' not in kwargs:
|
||||
kwargs['filename'] = os.path.basename(value)
|
||||
|
||||
super().__init__(value, *args, **kwargs)
|
||||
|
||||
if self._filename is not None and disposition is not None:
|
||||
self.set_content_disposition(disposition, filename=self._filename, quote_fields=False)
|
||||
|
||||
self._size = os.stat(value).st_size
|
||||
|
||||
async def write(self, writer):
|
||||
if self._file is None or self._file.closed:
|
||||
self._file = open(self._value, 'rb')
|
||||
try:
|
||||
chunk = self._file.read(DEFAULT_LIMIT)
|
||||
while chunk:
|
||||
await writer.write(chunk)
|
||||
chunk = self._file.read(DEFAULT_LIMIT)
|
||||
finally:
|
||||
self._file.close()
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._size
|
||||
|
||||
def ostree_object_path(repo, obj):
|
||||
repodir = repo.get_path().get_path()
|
||||
return os.path.join(repodir, 'objects', obj[0:2], obj[2:])
|
||||
|
||||
def ostree_get_dir_files(repo, objects, dirtree):
|
||||
if dirtree.endswith(".dirtree"):
|
||||
dirtree = dirtree[:-8]
|
||||
dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree)[1]
|
||||
iter = OSTree.RepoCommitTraverseIter()
|
||||
iter.init_dirtree(repo, dirtreev, 0)
|
||||
while True:
|
||||
type = iter.next()
|
||||
if type == OSTree.RepoCommitIterResult.END:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.ERROR:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.FILE:
|
||||
d = iter.get_file()
|
||||
objects.add(d.out_checksum + ".filez")
|
||||
if type == OSTree.RepoCommitIterResult.DIR:
|
||||
pass
|
||||
|
||||
def local_needed_files(repo, metadata_objects):
|
||||
objects = set()
|
||||
for c in metadata_objects:
|
||||
if c.endswith(".dirtree"):
|
||||
ostree_get_dir_files(repo, objects, c)
|
||||
return objects
|
||||
|
||||
def local_needed_metadata_dirtree(repo, objects, dirtree_content, dirtree_meta):
|
||||
objects.add(dirtree_meta + ".dirmeta")
|
||||
dirtree_content_name = dirtree_content + ".dirtree"
|
||||
if dirtree_content_name in objects:
|
||||
return
|
||||
objects.add(dirtree_content_name)
|
||||
|
||||
dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree_content)[1]
|
||||
iter = OSTree.RepoCommitTraverseIter()
|
||||
iter.init_dirtree(repo, dirtreev, 0)
|
||||
while True:
|
||||
type = iter.next()
|
||||
if type == OSTree.RepoCommitIterResult.END:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.ERROR:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.FILE:
|
||||
pass
|
||||
if type == OSTree.RepoCommitIterResult.DIR:
|
||||
d = iter.get_dir()
|
||||
local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum)
|
||||
|
||||
def local_needed_metadata(repo, commits):
|
||||
objects = set()
|
||||
for rev in commits:
|
||||
objects.add(rev + ".commit")
|
||||
commitv = repo.load_variant(OSTree.ObjectType.COMMIT, rev)[1]
|
||||
iter = OSTree.RepoCommitTraverseIter()
|
||||
iter.init_commit(repo, commitv, 0)
|
||||
while True:
|
||||
type = iter.next()
|
||||
if type == OSTree.RepoCommitIterResult.END:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.ERROR:
|
||||
break
|
||||
if type == OSTree.RepoCommitIterResult.FILE:
|
||||
pass
|
||||
if type == OSTree.RepoCommitIterResult.DIR:
|
||||
d = iter.get_dir()
|
||||
local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum)
|
||||
return objects
|
||||
|
||||
|
||||
def chunks(l, n):
|
||||
"""Yield successive n-sized chunks from l."""
|
||||
for i in range(0, len(l), n):
|
||||
yield l[i:i + n]
|
||||
|
||||
async def missing_objects(session, build_url, token, wanted):
|
||||
missing=[]
|
||||
for chunk in chunks(wanted, 2000):
|
||||
wanted_json=json.dumps({'wanted': chunk}).encode('utf-8')
|
||||
data=gzip.compress(wanted_json)
|
||||
headers = {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Encoding': 'gzip',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
resp = await session.get(build_url + "/missing_objects", data=data, headers=headers)
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
data = await resp.json()
|
||||
missing.extend(data["missing"])
|
||||
return missing
|
||||
|
||||
async def upload_files(session, build_url, token, files):
|
||||
if len(files) == 0:
|
||||
return
|
||||
print("Uploading %d files (%d bytes)" % (len(files), reduce(lambda x, y: x + y, map(lambda f: f.size, files))))
|
||||
with aiohttp.MultipartWriter() as writer:
|
||||
for f in files:
|
||||
writer.append(f)
|
||||
writer.headers['Authorization'] = 'Bearer ' + token
|
||||
resp = await session.request("post", build_url + '/upload', data=writer, headers=writer.headers)
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
|
||||
async def upload_deltas(session, repo_path, build_url, token, deltas, refs, ignore_delta):
|
||||
if not len(deltas):
|
||||
return
|
||||
|
||||
req = []
|
||||
for ref, commit in refs.items():
|
||||
# Skip screenshots here
|
||||
parts = ref.split("/")
|
||||
if len(parts) == 4 and (parts[0] == "app" or parts[0] =="runtime") and not should_skip_delta(parts[1], ignore_delta):
|
||||
for delta in deltas:
|
||||
# Only upload from-scratch deltas, as these are the only reused ones
|
||||
if delta == commit:
|
||||
print(" %s: %s" % (ref, delta))
|
||||
delta_name = delta_name_encode (delta)
|
||||
delta_dir = repo_path + "/deltas/" + delta_name[:2] + "/" + delta_name[2:]
|
||||
parts = os.listdir(delta_dir)
|
||||
for part in parts:
|
||||
req.append(AsyncNamedFilePart(delta_dir + "/" + part, filename = delta_name + "." + part + ".delta"))
|
||||
|
||||
if len(req):
|
||||
await upload_files(session, build_url, token, req)
|
||||
|
||||
|
||||
async def upload_objects(session, repo_path, build_url, token, objects):
|
||||
req = []
|
||||
total_size = 0
|
||||
for file_obj in objects:
|
||||
named = get_object_multipart(repo_path, file_obj)
|
||||
file_size = named.size
|
||||
if total_size + file_size > UPLOAD_CHUNK_LIMIT: # The new object would bring us over the chunk limit
|
||||
if len(req) > 0: # We already have some objects, upload those first
|
||||
next_req = [named]
|
||||
total_size = file_size
|
||||
else:
|
||||
next_req = []
|
||||
req.append(named)
|
||||
total_size = 0
|
||||
await upload_files(session, build_url, token, req)
|
||||
req = next_req
|
||||
else:
|
||||
total_size = total_size + file_size
|
||||
req.append(named)
|
||||
|
||||
# Upload any remainder
|
||||
await upload_files(session, build_url, token, req)
|
||||
|
||||
async def create_ref(session, build_url, token, ref, commit):
|
||||
print("Creating ref %s with commit %s" % (ref, commit))
|
||||
resp = await session.post(build_url + "/build_ref", headers={'Authorization': 'Bearer ' + token}, json= { "ref": ref, "commit": commit} )
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
|
||||
data = await resp.json()
|
||||
return data
|
||||
|
||||
async def add_extra_ids(session, build_url, token, extra_ids):
|
||||
print("Adding extra ids %s" % (extra_ids))
|
||||
resp = await session.post(build_url + "/add_extra_ids", headers={'Authorization': 'Bearer ' + token}, json= { "ids": extra_ids} )
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
|
||||
data = await resp.json()
|
||||
return data
|
||||
|
||||
async def get_build(session, build_url, token):
|
||||
resp = await session.get(build_url, headers={'Authorization': 'Bearer ' + token})
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
data = await resp.json()
|
||||
return data
|
||||
|
||||
# For stupid reasons this is a string with json, lets expand it
|
||||
def reparse_job_results(job):
|
||||
job["results"] = json.loads(job.get("results", "{}"))
|
||||
return job
|
||||
|
||||
async def get_job(session, job_url, token):
|
||||
resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={})
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
data = await resp.json()
|
||||
return data
|
||||
|
||||
async def wait_for_job(session, job_url, token):
|
||||
reported_delay = False
|
||||
old_job_status = 0
|
||||
printed_len = 0
|
||||
iterations_since_change=0
|
||||
error_iterations = 0
|
||||
while True:
|
||||
try:
|
||||
resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={'log-offset': printed_len})
|
||||
async with resp:
|
||||
if resp.status == 200:
|
||||
error_iterations = 0
|
||||
job = await resp.json()
|
||||
job_status = job['status']
|
||||
if job_status == 0 and not reported_delay:
|
||||
reported_delay = True
|
||||
start_after_struct = job.get("start_after", None)
|
||||
if start_after_struct:
|
||||
start_after = start_after_struct.get("secs_since_epoch", None)
|
||||
now = time.time()
|
||||
if start_after and start_after > now:
|
||||
print("Waiting %d seconds before starting job" % (int(start_after - now)))
|
||||
if job_status > 0 and old_job_status == 0:
|
||||
print("/ Job was started");
|
||||
old_job_status = job_status
|
||||
log = job['log']
|
||||
if len(log) > 0:
|
||||
iterations_since_change=0
|
||||
for line in log.splitlines(True):
|
||||
print("| %s" % line, end="")
|
||||
printed_len = printed_len + len(log)
|
||||
else:
|
||||
iterations_since_change=iterations_since_change+1
|
||||
if job_status > 1:
|
||||
if job_status == 2:
|
||||
print("\ Job completed successfully")
|
||||
else:
|
||||
print("\ Job failed")
|
||||
return job
|
||||
else:
|
||||
iterations_since_change=4 # Start at 4 so we ramp up the delay faster
|
||||
error_iterations=error_iterations + 1
|
||||
if error_iterations <= 5:
|
||||
print("Unexpected response %s getting job log, ignoring" % resp.status)
|
||||
else:
|
||||
raise ApiError(resp, await resp.text())
|
||||
except OSError as e:
|
||||
if e.args[0] == errno.ECONNRESET:
|
||||
# Client disconnected, retry
|
||||
# Not sure exactly why, but i got a lot of ConnectionResetErrors here
|
||||
# in tests. I guess the server stops reusing a http2 session after a bit
|
||||
# Should be fine to retry with the backof
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
# Some polling backoff to avoid loading the server
|
||||
if iterations_since_change <= 1:
|
||||
sleep_time=1
|
||||
elif iterations_since_change < 5:
|
||||
sleep_time=3
|
||||
elif iterations_since_change < 15:
|
||||
sleep_time=5
|
||||
elif iterations_since_change < 30:
|
||||
sleep_time=10
|
||||
else:
|
||||
sleep_time=60
|
||||
time.sleep(sleep_time)
|
||||
|
||||
async def commit_build(session, build_url, eol, eol_rebase, token_type, wait, token):
|
||||
print("Committing build %s" % (build_url))
|
||||
json = {
|
||||
"endoflife": eol,
|
||||
"endoflife_rebase": eol_rebase
|
||||
}
|
||||
if token_type != None:
|
||||
json['token_type'] = token_type
|
||||
resp = await session.post(build_url + "/commit", headers={'Authorization': 'Bearer ' + token}, json=json)
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
|
||||
job = await resp.json()
|
||||
job_url = resp.headers['location'];
|
||||
|
||||
if wait:
|
||||
print("Waiting for commit job")
|
||||
job = await wait_for_job(session, job_url, token);
|
||||
|
||||
reparse_job_results(job)
|
||||
job["location"] = job_url
|
||||
return job
|
||||
|
||||
async def publish_build(session, build_url, wait, token):
|
||||
print("Publishing build %s" % (build_url))
|
||||
resp = await session.post(build_url + "/publish", headers={'Authorization': 'Bearer ' + token}, json= { } )
|
||||
async with resp:
|
||||
if resp.status == 400:
|
||||
body = await resp.text()
|
||||
try:
|
||||
msg = json.loads(body)
|
||||
if msg.get("current-state", "") == "published":
|
||||
print("the build has been already published")
|
||||
return {}
|
||||
except:
|
||||
pass
|
||||
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
|
||||
job = await resp.json()
|
||||
job_url = resp.headers['location'];
|
||||
|
||||
if wait:
|
||||
print("Waiting for publish job")
|
||||
job = await wait_for_job(session, job_url, token);
|
||||
|
||||
reparse_job_results(job)
|
||||
job["location"] = job_url
|
||||
return job
|
||||
|
||||
async def purge_build(session, build_url, token):
|
||||
print("Purging build %s" % (build_url))
|
||||
resp = await session.post(build_url + "/purge", headers={'Authorization': 'Bearer ' + token}, json= {} )
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
return await resp.json()
|
||||
|
||||
async def create_token(session, manager_url, token, name, subject, scope, duration):
|
||||
token_url = urljoin(manager_url, "api/v1/token_subset")
|
||||
resp = await session.post(token_url, headers={'Authorization': 'Bearer ' + token}, json = {
|
||||
"name": name,
|
||||
"sub": subject,
|
||||
"scope": scope,
|
||||
"duration": duration,
|
||||
})
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
return await resp.json()
|
||||
|
||||
def get_object_multipart(repo_path, object):
|
||||
return AsyncNamedFilePart(repo_path + "/objects/" + object[:2] + "/" + object[2:], filename=object)
|
||||
|
||||
async def create_command(session, args):
|
||||
build_url = urljoin(args.manager_url, "api/v1/build")
|
||||
resp = await session.post(build_url, headers={'Authorization': 'Bearer ' + args.token}, json={
|
||||
"repo": args.repo
|
||||
})
|
||||
async with resp:
|
||||
if resp.status != 200:
|
||||
raise ApiError(resp, await resp.text())
|
||||
data = await resp.json()
|
||||
data["location"] = resp.headers['location']
|
||||
if not args.print_output:
|
||||
print(resp.headers['location'])
|
||||
return data
|
||||
|
||||
def delta_name_part_encode(commit):
|
||||
return base64.b64encode(binascii.unhexlify(commit), b"+_")[:-1].decode("utf-8")
|
||||
|
||||
def delta_name_encode (delta):
|
||||
return "-".join(map(delta_name_part_encode, delta.split("-")))
|
||||
|
||||
def should_skip_delta(id, globs):
|
||||
if globs:
|
||||
for glob in globs:
|
||||
if fnmatch.fnmatch(id, glob):
|
||||
return True
|
||||
return False
|
||||
|
||||
def build_url_to_api(build_url):
|
||||
parts = urlparse(build_url)
|
||||
path = os.path.dirname(os.path.dirname(parts.path))
|
||||
return urlunparse((parts.scheme, parts.netloc, path, None, None, None))
|
||||
|
||||
@retry(
|
||||
stop=stop_after_attempt(6),
|
||||
wait=wait_fixed(10),
|
||||
retry=retry_if_exception_type(ApiError),
|
||||
reraise=True,
|
||||
)
|
||||
async def push_command(session, args):
|
||||
local_repo = OSTree.Repo.new(Gio.File.new_for_path(args.repo_path))
|
||||
try:
|
||||
local_repo.open(None)
|
||||
except GLib.Error as err:
|
||||
raise UsageException("Can't open repo %s: %s" % (args.repo_path, err.message)) from err
|
||||
|
||||
refs = {}
|
||||
if len(args.branches) == 0:
|
||||
_, all_refs = local_repo.list_refs(None, None)
|
||||
for ref in all_refs:
|
||||
if ref.startswith("app/") or ref.startswith("runtime/") or ref.startswith("screenshots/"):
|
||||
refs[ref] = all_refs[ref]
|
||||
else:
|
||||
for branch in args.branches:
|
||||
_, rev = local_repo.resolve_rev(branch, False)
|
||||
refs[branch] = rev
|
||||
|
||||
if (args.minimal_token):
|
||||
id = os.path.basename(urlparse(args.build_url).path)
|
||||
token = create_token(args.build_url, args.token, "minimal-upload", "build/%s" % (id), ["upload"], 60*60)["token"]
|
||||
else:
|
||||
token = args.token
|
||||
|
||||
print("Uploading refs to %s: %s"% (args.build_url, list(refs)))
|
||||
|
||||
metadata_objects = local_needed_metadata(local_repo, refs.values())
|
||||
|
||||
print("Refs contain %d metadata objects" % (len(metadata_objects)))
|
||||
|
||||
missing_metadata_objects = await missing_objects(session, args.build_url, token, list(metadata_objects))
|
||||
|
||||
print("Remote missing %d of those" % (len(missing_metadata_objects)))
|
||||
|
||||
file_objects = local_needed_files(local_repo, missing_metadata_objects)
|
||||
print("Has %d file objects for those" % (len(file_objects)))
|
||||
|
||||
missing_file_objects = await missing_objects(session, args.build_url, token, list(file_objects))
|
||||
print("Remote missing %d of those" % (len(missing_file_objects)))
|
||||
|
||||
# First upload all missing file objects
|
||||
print("Uploading file objects")
|
||||
await upload_objects(session, args.repo_path, args.build_url, token, missing_file_objects)
|
||||
|
||||
# Then all the metadata
|
||||
print("Uploading metadata objects")
|
||||
await upload_objects(session, args.repo_path, args.build_url, token, missing_metadata_objects)
|
||||
|
||||
_, deltas = local_repo.list_static_delta_names()
|
||||
print("Uploading deltas")
|
||||
await upload_deltas(session, args.repo_path, args.build_url, token, deltas, refs, args.ignore_delta)
|
||||
|
||||
# Then the refs
|
||||
for ref, commit in refs.items():
|
||||
await create_ref(session, args.build_url, token, ref, commit)
|
||||
|
||||
# Then any extra ids
|
||||
if args.extra_id:
|
||||
await add_extra_ids(session, args.build_url, token, args.extra_id)
|
||||
|
||||
commit_job = None
|
||||
publish_job = None
|
||||
update_job = None
|
||||
|
||||
# Note, this always uses the full token, as the minimal one only has upload permissions
|
||||
if args.commit or args.publish:
|
||||
commit_job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.publish or args.wait, args.token)
|
||||
|
||||
if args.publish:
|
||||
publish_job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token)
|
||||
update_job_id = publish_job.get("results", {}).get("update-repo-job", None)
|
||||
if update_job_id:
|
||||
print("Queued repo update job %d" %(update_job_id))
|
||||
update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id)
|
||||
if args.wait_update:
|
||||
print("Waiting for repo update job")
|
||||
update_job = await wait_for_job (session, update_job_url, token);
|
||||
else:
|
||||
update_job = await get_job(session, update_job_url, token)
|
||||
reparse_job_results(update_job)
|
||||
update_job["location"] = update_job_url
|
||||
|
||||
data = await get_build(session, args.build_url, args.token)
|
||||
if commit_job:
|
||||
data["commit_job"] = commit_job
|
||||
if publish_job:
|
||||
data["publish_job"] = publish_job
|
||||
if update_job:
|
||||
data["update_job"] = update_job
|
||||
return data
|
||||
|
||||
async def commit_command(session, args):
|
||||
job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.wait, args.token)
|
||||
return job
|
||||
|
||||
async def publish_command(session, args):
|
||||
job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token)
|
||||
update_job_id = job.get("results", {}).get("update-repo-job", None)
|
||||
if update_job_id:
|
||||
print("Queued repo update job %d" %(update_job_id))
|
||||
update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id)
|
||||
if args.wait_update:
|
||||
print("Waiting for repo update job")
|
||||
update_job = await wait_for_job(session, update_job_url, args.token);
|
||||
else:
|
||||
update_job = await get_job(session, update_job_url, args.token)
|
||||
reparse_job_results(update_job)
|
||||
update_job["location"] = update_job_url
|
||||
return job
|
||||
|
||||
async def purge_command(session, args):
|
||||
job = await purge_build(session, args.build_url, args.token)
|
||||
return job
|
||||
|
||||
async def create_token_command(session, args):
|
||||
data = await create_token(session, args.manager_url, args.token, args.name, args.subject, args.scope, args.duration)
|
||||
if not args.print_output:
|
||||
print(data['token'])
|
||||
return data
|
||||
|
||||
async def follow_job_command(session, args):
|
||||
job = await wait_for_job(session, args.job_url, args.token)
|
||||
return job
|
||||
|
||||
async def run_with_session(args):
|
||||
timeout = aiohttp.ClientTimeout(total=90*60)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
result = await args.func(session, args)
|
||||
return result
|
||||
|
||||
if __name__ == '__main__':
|
||||
progname = os.path.basename(sys.argv[0])
|
||||
|
||||
parser = ArgumentParser(prog=progname)
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='enable verbose output')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
help='enable debugging output')
|
||||
parser.add_argument('--output', help='Write output json to file')
|
||||
parser.add_argument('--print-output', action='store_true', help='Print output json')
|
||||
parser.add_argument('--token', help='use this token')
|
||||
parser.add_argument('--token-file', help='use token from file')
|
||||
subparsers = parser.add_subparsers(title='subcommands',
|
||||
dest='subparser_name',
|
||||
description='valid subcommands',
|
||||
help='additional help')
|
||||
|
||||
create_parser = subparsers.add_parser('create', help='Create new build')
|
||||
create_parser.add_argument('manager_url', help='remote repo manager url')
|
||||
create_parser.add_argument('repo', help='repo name')
|
||||
create_parser.set_defaults(func=create_command)
|
||||
|
||||
push_parser = subparsers.add_parser('push', help='Push to repo manager')
|
||||
push_parser.add_argument('build_url', help='remote build url')
|
||||
push_parser.add_argument('repo_path', help='local repository')
|
||||
push_parser.add_argument('branches', nargs='*', help='branches to push')
|
||||
push_parser.add_argument('--commit', action='store_true',
|
||||
help='commit build after pushing')
|
||||
push_parser.add_argument('--publish', action='store_true',
|
||||
help='publish build after committing')
|
||||
push_parser.add_argument('--extra-id', action='append', help='add extra collection-id')
|
||||
push_parser.add_argument('--ignore-delta', action='append', help='don\'t upload deltas matching this glob')
|
||||
push_parser.add_argument('--wait', action='store_true',
|
||||
help='wait for commit/publish to finish')
|
||||
push_parser.add_argument('--wait-update', action='store_true',
|
||||
help='wait for update-repo to finish')
|
||||
push_parser.add_argument('--minimal-token', action='store_true',
|
||||
help='Create minimal token for the upload')
|
||||
push_parser.add_argument('--end-of-life', help='Set end of life')
|
||||
push_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one')
|
||||
push_parser.add_argument('--token-type', help='Set token type', type=int)
|
||||
push_parser.set_defaults(func=push_command)
|
||||
|
||||
commit_parser = subparsers.add_parser('commit', help='Commit build')
|
||||
commit_parser.add_argument('--wait', action='store_true',
|
||||
help='wait for commit to finish')
|
||||
commit_parser.add_argument('--end-of-life', help='Set end of life')
|
||||
commit_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one')
|
||||
commit_parser.add_argument('--token-type', help='Set token type', type=int)
|
||||
commit_parser.add_argument('build_url', help='remote build url')
|
||||
commit_parser.set_defaults(func=commit_command)
|
||||
|
||||
publish_parser = subparsers.add_parser('publish', help='Publish build')
|
||||
publish_parser.add_argument('--wait', action='store_true',
|
||||
help='wait for publish to finish')
|
||||
publish_parser.add_argument('--wait-update', action='store_true',
|
||||
help='wait for update-repo to finish')
|
||||
publish_parser.add_argument('build_url', help='remote build url')
|
||||
publish_parser.set_defaults(func=publish_command)
|
||||
|
||||
purge_parser = subparsers.add_parser('purge', help='Purge build')
|
||||
purge_parser.add_argument('build_url', help='remote build url')
|
||||
purge_parser.set_defaults(func=purge_command)
|
||||
|
||||
create_token_parser = subparsers.add_parser('create-token', help='Create subset token')
|
||||
create_token_parser.add_argument('manager_url', help='remote repo manager url')
|
||||
create_token_parser.add_argument('name', help='Name')
|
||||
create_token_parser.add_argument('subject', help='Subject')
|
||||
create_token_parser.add_argument('scope', nargs='*', help='Scope')
|
||||
create_token_parser.add_argument('--duration', help='Duration until expires, in seconds',
|
||||
default=60*60*24, # Default duration is one day
|
||||
type=int)
|
||||
create_token_parser.set_defaults(func=create_token_command)
|
||||
|
||||
follow_job_parser = subparsers.add_parser('follow-job', help='Follow existing job log')
|
||||
follow_job_parser.add_argument('job_url', help='url of job')
|
||||
follow_job_parser.set_defaults(func=follow_job_command)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
loglevel = logging.WARNING
|
||||
if args.verbose:
|
||||
loglevel = logging.INFO
|
||||
if args.debug:
|
||||
loglevel = logging.DEBUG
|
||||
|
||||
logging.basicConfig(format='%(module)s: %(levelname)s: %(message)s',
|
||||
level=loglevel, stream=sys.stderr)
|
||||
|
||||
if not args.subparser_name:
|
||||
print("No subcommand specified, see --help for usage")
|
||||
exit(1)
|
||||
|
||||
if not args.token:
|
||||
if args.token_file:
|
||||
file = open(args.token_file, 'rb')
|
||||
args.token = file.read().splitlines()[0].decode("utf-8").strip()
|
||||
elif "REPO_TOKEN" in os.environ:
|
||||
args.token = os.environ["REPO_TOKEN"]
|
||||
else:
|
||||
print("No token available, pass with --token, --token-file or $REPO_TOKEN")
|
||||
exit(1)
|
||||
|
||||
|
||||
res = 1
|
||||
output = None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
result = loop.run_until_complete(run_with_session(args))
|
||||
|
||||
output = {
|
||||
"command": args.subparser_name,
|
||||
"result": result,
|
||||
}
|
||||
res = 0
|
||||
except SystemExit:
|
||||
# Something called sys.exit(), lets just exit
|
||||
res = 1
|
||||
raise # Pass on regular exit callse
|
||||
except ApiError as e:
|
||||
eprint(str(e))
|
||||
output = {
|
||||
"command": args.subparser_name,
|
||||
"error": e.repr(),
|
||||
}
|
||||
except UsageException as e:
|
||||
eprint(str(e))
|
||||
output = {
|
||||
"error": {
|
||||
"type": "usage",
|
||||
"details": {
|
||||
"message": str(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
except:
|
||||
ei = sys.exc_info()
|
||||
eprint("Unexpected %s exception in %s: %s" % (ei[0].__name__, args.subparser_name, ei[1]))
|
||||
eprint(traceback.format_exc())
|
||||
output = {
|
||||
"command": args.subparser_name,
|
||||
"error": {
|
||||
"type": "exception",
|
||||
"details": {
|
||||
"error-type": ei[0].__name__,
|
||||
"message": str(ei[1]),
|
||||
}
|
||||
}
|
||||
}
|
||||
res = 1
|
||||
|
||||
if output:
|
||||
if args.print_output:
|
||||
print(json.dumps(output, indent=4))
|
||||
if args.output:
|
||||
f = open(args.output,"w+")
|
||||
f.write(json.dumps(output, indent=4))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
exit(res)
|
24
scripts/upload-to-flatpak-repo.sh
Executable file
24
scripts/upload-to-flatpak-repo.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Missing repo to upload!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "${CI_COMMIT_TAG}" ]; then
|
||||
BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev stable)
|
||||
elif [ "master" = "${CI_COMMIT_REF_NAME}" ]; then
|
||||
BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev nightly)
|
||||
fi
|
||||
|
||||
if [ -z "${BUILD_URL}" ]; then
|
||||
echo "No upload to repo."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BUILD_URL=${BUILD_URL/http:/https:}
|
||||
|
||||
./flat-manager-client push $BUILD_URL $1
|
||||
./flat-manager-client commit --wait $BUILD_URL
|
||||
./flat-manager-client publish --wait $BUILD_URL
|
||||
|
@ -1,22 +1,11 @@
|
||||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QPixmapCache>
|
||||
#include <QPointer>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -24,13 +13,14 @@
|
||||
#include "Cache.h"
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "Utils.h"
|
||||
|
||||
static QPixmapCache avatar_cache;
|
||||
|
||||
namespace AvatarProvider {
|
||||
void
|
||||
resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback)
|
||||
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
|
||||
{
|
||||
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
|
||||
|
||||
@ -45,44 +35,32 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = cache::image(cacheKey);
|
||||
if (!data.isNull()) {
|
||||
pixmap = QPixmap::fromImage(utils::readImage(&data));
|
||||
avatar_cache.insert(cacheKey, pixmap);
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
|
||||
QSize(size, size),
|
||||
[callback, cacheKey, recv = QPointer<QObject>(receiver)](
|
||||
QString, QSize, QImage img, QString) {
|
||||
if (!recv)
|
||||
return;
|
||||
|
||||
auto proxy = std::make_shared<AvatarProxy>();
|
||||
QObject::connect(proxy.get(),
|
||||
&AvatarProxy::avatarDownloaded,
|
||||
receiver,
|
||||
[callback, cacheKey](QByteArray data) {
|
||||
QPixmap pm = QPixmap::fromImage(utils::readImage(&data));
|
||||
avatar_cache.insert(cacheKey, pm);
|
||||
callback(pm);
|
||||
});
|
||||
auto proxy = std::make_shared<AvatarProxy>();
|
||||
QObject::connect(proxy.get(),
|
||||
&AvatarProxy::avatarDownloaded,
|
||||
recv,
|
||||
[callback, cacheKey](QPixmap pm) {
|
||||
if (!pm.isNull())
|
||||
avatar_cache.insert(
|
||||
cacheKey, pm);
|
||||
callback(pm);
|
||||
});
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.width = size;
|
||||
opts.height = size;
|
||||
opts.mxc_url = avatarUrl.toStdString();
|
||||
if (img.isNull()) {
|
||||
emit proxy->avatarDownloaded(QPixmap{});
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->get_thumbnail(
|
||||
opts,
|
||||
[opts, cacheKey, proxy = std::move(proxy)](const std::string &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to download avatar: {} - ({} {})",
|
||||
opts.mxc_url,
|
||||
mtx::errors::to_string(err->matrix_error.errcode),
|
||||
err->matrix_error.error);
|
||||
} else {
|
||||
cache::saveImage(cacheKey.toStdString(), res);
|
||||
}
|
||||
|
||||
emit proxy->avatarDownloaded(QByteArray(res.data(), (int)res.size()));
|
||||
});
|
||||
auto pm = QPixmap::fromImage(std::move(img));
|
||||
emit proxy->avatarDownloaded(pm);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@ -92,8 +70,8 @@ resolve(const QString &room_id,
|
||||
QObject *receiver,
|
||||
AvatarCallback callback)
|
||||
{
|
||||
const auto avatarUrl = cache::avatarUrl(room_id, user_id);
|
||||
auto avatarUrl = cache::avatarUrl(room_id, user_id);
|
||||
|
||||
resolve(avatarUrl, size, receiver, callback);
|
||||
resolve(std::move(avatarUrl), size, receiver, callback);
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,26 @@
|
||||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPixmap>
|
||||
#include <functional>
|
||||
|
||||
using AvatarCallback = std::function<void(QPixmap)>;
|
||||
|
||||
class AvatarProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void avatarDownloaded(const QByteArray &data);
|
||||
void avatarDownloaded(QPixmap pm);
|
||||
};
|
||||
|
||||
using AvatarCallback = std::function<void(QPixmap)>;
|
||||
|
||||
namespace AvatarProvider {
|
||||
void
|
||||
resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb);
|
||||
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback cb);
|
||||
void
|
||||
resolve(const QString &room_id,
|
||||
const QString &user_id,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user