Compare commits
407 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45444253fd | ||
|
|
308ade6a7e | ||
|
|
fc67a801e3 | ||
|
|
6a3657ca87 | ||
|
|
0537c5f273 | ||
|
|
cc4a5f30b6 | ||
|
|
b0d7c3e9b1 | ||
|
|
3bf7c44fc9 | ||
|
|
4ff4e63a11 | ||
|
|
72a35ba58b | ||
|
|
b6a31979f2 | ||
|
|
7c710e22cc | ||
|
|
ab58e7a225 | ||
|
|
c9fb97cd7c | ||
|
|
789f3e1584 | ||
|
|
0fc8229be1 | ||
|
|
a1e555267e | ||
|
|
0ac88c0cb5 | ||
|
|
d43a6da62b | ||
|
|
940455f786 | ||
|
|
0f74456f30 | ||
|
|
7840fa6d90 | ||
|
|
95ccc99fee | ||
|
|
7b0a156bba | ||
|
|
0d8ae7bb37 | ||
|
|
9491cff1df | ||
|
|
51dc5d6e37 | ||
|
|
f4c739ab92 | ||
|
|
0dd8ae3d77 | ||
|
|
7d2878d81c | ||
|
|
bd70a05861 | ||
|
|
0605c7b2bc | ||
|
|
8e83a55143 | ||
|
|
4ab4eb8ef2 | ||
|
|
d1e6150874 | ||
|
|
4121c99f36 | ||
|
|
827040f487 | ||
|
|
9032489786 | ||
|
|
8ea7bd4913 | ||
|
|
97b021efaf | ||
|
|
b3f9a77ba7 | ||
|
|
63fdc1f876 | ||
|
|
17cf354c58 | ||
|
|
c6fd8bcb99 | ||
|
|
1684465e04 | ||
|
|
fe2df96953 | ||
|
|
ee9d0cfd99 | ||
|
|
7b7e18e752 | ||
|
|
928be4151b | ||
|
|
37dd648686 | ||
|
|
93a590774e | ||
|
|
22b99b6d3e | ||
|
|
101d626d4f | ||
|
|
3633c19208 | ||
|
|
e302f328f7 | ||
|
|
f74ba95e95 | ||
|
|
c38982d286 | ||
|
|
fe9bac096b | ||
|
|
5b809c4fc6 | ||
|
|
4729e51e14 | ||
|
|
960cf7a34b | ||
|
|
852ab19760 | ||
|
|
2e45d9fc6b | ||
|
|
74b71b92b6 | ||
|
|
bbc14ba74f | ||
|
|
45c7829cd8 | ||
|
|
f2aa3afbbb | ||
|
|
909b01241b | ||
|
|
abb58c58a0 | ||
|
|
700e10d32c | ||
|
|
4ac48d0e4a | ||
|
|
345b2cb835 | ||
|
|
b962309498 | ||
|
|
e99cb9bfb8 | ||
|
|
9e12e18f90 | ||
|
|
66fc9b38df | ||
|
|
5dbe429e6b | ||
|
|
b2481ea6c1 | ||
|
|
86a294ce4b | ||
|
|
a8d1eadfbf | ||
|
|
b07d3c5403 | ||
|
|
892db55ae1 | ||
|
|
93615fef65 | ||
|
|
87452706ef | ||
|
|
3569615b21 | ||
|
|
86f7d09d31 | ||
|
|
d5d1254393 | ||
|
|
4df90cfb9e | ||
|
|
ec6862d31a | ||
|
|
6f23010382 | ||
|
|
c672f105d3 | ||
|
|
e60d501e4a | ||
|
|
0d7175058b | ||
|
|
3b0bd9d1d1 | ||
|
|
bd28ac6e1f | ||
|
|
0c2d00c792 | ||
|
|
140ba653b9 | ||
|
|
f64f008f77 | ||
|
|
a6315bef05 | ||
|
|
f810d7c82a | ||
|
|
cf61dedc79 | ||
|
|
2ab9587f5f | ||
|
|
4950b52359 | ||
|
|
03af444735 | ||
|
|
7f6221b409 | ||
|
|
ef859d77e9 | ||
|
|
0fd752657a | ||
|
|
f1451a1de3 | ||
|
|
fa96f25683 | ||
|
|
9e447383df | ||
|
|
5e762be32b | ||
|
|
75de81a3ab | ||
|
|
d26b64a5bb | ||
|
|
cb8d40eaf2 | ||
|
|
ded0936bc4 | ||
|
|
16830a410c | ||
|
|
9f79dda463 | ||
|
|
15dc7c74d7 | ||
|
|
f9abef9e05 | ||
|
|
ba84499f00 | ||
|
|
79ce24222a | ||
|
|
6cb9264864 | ||
|
|
247a070405 | ||
|
|
e05bb75b8a | ||
|
|
6a415cf232 | ||
|
|
e8034189df | ||
|
|
107f329b4f | ||
|
|
999a13358e | ||
|
|
2077f51084 | ||
|
|
1e77a3df20 | ||
|
|
141a291523 | ||
|
|
cb03d5a9d3 | ||
|
|
b618d1e56a | ||
|
|
23ae638512 | ||
|
|
eda749d7cb | ||
|
|
08d0186e53 | ||
|
|
443981ba31 | ||
|
|
f3ed7c5e19 | ||
|
|
dd2378b591 | ||
|
|
b885779365 | ||
|
|
32b95f0d9a | ||
|
|
2b8eec8666 | ||
|
|
3f24627f54 | ||
|
|
0c4bca312e | ||
|
|
e36afc675e | ||
|
|
d754014321 | ||
|
|
9f73242cc5 | ||
|
|
fae9649773 | ||
|
|
4b43f4cbec | ||
|
|
b61befa210 | ||
|
|
5e2bc337bc | ||
|
|
af1608cbfa | ||
|
|
9aa80976ff | ||
|
|
c9cfe9e90f | ||
|
|
482e337762 | ||
|
|
a0821f5a01 | ||
|
|
5ebdf3ed39 | ||
|
|
86096db02d | ||
|
|
a93e01b896 | ||
|
|
0585e72c35 | ||
|
|
c82fbefcfc | ||
|
|
23542a1db1 | ||
|
|
1bf50d60d8 | ||
|
|
8596b0309e | ||
|
|
7f6e871b26 | ||
|
|
8912d4d55a | ||
|
|
7ac849ab12 | ||
|
|
4cab699b04 | ||
|
|
03aa05e4d2 | ||
|
|
428a3cf0ce | ||
|
|
1c8b165a64 | ||
|
|
1869071ef7 | ||
|
|
33ca5ee39f | ||
|
|
e46d5a86d3 | ||
|
|
2729bcac3b | ||
|
|
d60ce41fa9 | ||
|
|
958db945f3 | ||
|
|
057f906ca4 | ||
|
|
8df7a45e29 | ||
|
|
e4af1570cb | ||
|
|
049ebf9027 | ||
|
|
f2f0c7df92 | ||
|
|
bee4118513 | ||
|
|
bf48025d12 | ||
|
|
5e624605cf | ||
|
|
026490acc6 | ||
|
|
709ce3adb8 | ||
|
|
a8d23489c4 | ||
|
|
296df113e3 | ||
|
|
97c4e79e96 | ||
|
|
f89bac7781 | ||
|
|
1d8a7f8fd3 | ||
|
|
ef9f7ab27a | ||
|
|
a676138745 | ||
|
|
7442ea7a16 | ||
|
|
0089cad740 | ||
|
|
cf1fa718a8 | ||
|
|
d6ba6ac41e | ||
|
|
bbdd5feaa4 | ||
|
|
678527254b | ||
|
|
46bf7781aa | ||
|
|
99c547b625 | ||
|
|
6976e97de3 | ||
|
|
f8e3e70273 | ||
|
|
23c9f7a957 | ||
|
|
2ca763cc77 | ||
|
|
2adc811351 | ||
|
|
be18be4a86 | ||
|
|
8d7abb1b8a | ||
|
|
1cc5988c40 | ||
|
|
da426ae03b | ||
|
|
3cebd6d923 | ||
|
|
f1019c8ca4 | ||
|
|
edfb7b6b24 | ||
|
|
157a928f5a | ||
|
|
5f5a2a3ef2 | ||
|
|
8534cf3756 | ||
|
|
39b90092ff | ||
|
|
d3142ebe6d | ||
|
|
d914c6be2e | ||
|
|
05c3e968df | ||
|
|
7756cce123 | ||
|
|
8287d717f8 | ||
|
|
db9e60b4b5 | ||
|
|
28a79bfccb | ||
|
|
321490e528 | ||
|
|
6f752357d7 | ||
|
|
e1f71baed6 | ||
|
|
df377cd5bb | ||
|
|
691a0acdab | ||
|
|
5e2eda6af3 | ||
|
|
17cdc2b585 | ||
|
|
c08266f81b | ||
|
|
f90a4db569 | ||
|
|
22ec7a6d75 | ||
|
|
1b16a84810 | ||
|
|
d778276f5d | ||
|
|
e3030a168f | ||
|
|
dfd07a4f4f | ||
|
|
f9fc65d7de | ||
|
|
46cf7db242 | ||
|
|
2b94cffe7e | ||
|
|
2e74ad6fbe | ||
|
|
df7dc1583d | ||
|
|
88e80b4fae | ||
|
|
aea90f4b65 | ||
|
|
80db076f38 | ||
|
|
27bba8250a | ||
|
|
3fb0fa6892 | ||
|
|
c3195cfcbe | ||
|
|
51661a872c | ||
|
|
64706ea103 | ||
|
|
ec69d557dc | ||
|
|
addd37fb1f | ||
|
|
9dc947ecb6 | ||
|
|
7d74d3da3a | ||
|
|
aa0c56876c | ||
|
|
37c7b0c6d1 | ||
|
|
0d07d238bc | ||
|
|
fa4e74ffef | ||
|
|
c22d76e5be | ||
|
|
18850ebd83 | ||
|
|
17abef95eb | ||
|
|
d135151477 | ||
|
|
07fd9b3074 | ||
|
|
0523ae705a | ||
|
|
9db2502cd0 | ||
|
|
a174119877 | ||
|
|
569dd19932 | ||
|
|
530e2a1feb | ||
|
|
de732ba692 | ||
|
|
c6649e84a6 | ||
|
|
e3517aceab | ||
|
|
6d7abd1718 | ||
|
|
e9e493707b | ||
|
|
c25adf8b57 | ||
|
|
d2be10cd4e | ||
|
|
006ecf9a56 | ||
|
|
a53cc52241 | ||
|
|
961d283325 | ||
|
|
4e46529eb6 | ||
|
|
81001e04e9 | ||
|
|
2fd174ab9c | ||
|
|
6ff5e221ea | ||
|
|
232077b919 | ||
|
|
fecddb5203 | ||
|
|
d0132c0f7b | ||
|
|
37d32b32f8 | ||
|
|
51213b499f | ||
|
|
dcb98ce0fb | ||
|
|
428e90a844 | ||
|
|
017535cf7b | ||
|
|
409389a994 | ||
|
|
ba34d92cd3 | ||
|
|
b412ee258d | ||
|
|
f1ffe2a641 | ||
|
|
a13ca95894 | ||
|
|
0989a80a57 | ||
|
|
d72c15e9d3 | ||
|
|
7084cf9526 | ||
|
|
67cc0ef75c | ||
|
|
75714cc358 | ||
|
|
75e454f3fd | ||
|
|
97afb4e01a | ||
|
|
8fcbf43410 | ||
|
|
15a834b883 | ||
|
|
3d8396e586 | ||
|
|
3257fd364a | ||
|
|
d690af99fc | ||
|
|
1c28495162 | ||
|
|
ec82d5674f | ||
|
|
4a22e76bdb | ||
|
|
527be95618 | ||
|
|
41985c0a5f | ||
|
|
522e45ce92 | ||
|
|
48fb0f3b1e | ||
|
|
798093c58f | ||
|
|
e795dad616 | ||
|
|
c3587ff46f | ||
|
|
dc9fa9ccf2 | ||
|
|
97c259d928 | ||
|
|
afffdd5bbf | ||
|
|
199e7a1d46 | ||
|
|
a1aa315187 | ||
|
|
8c56fddc55 | ||
|
|
277d76df3e | ||
|
|
1ac33d30bd | ||
|
|
658cb438f8 | ||
|
|
2b71625ffe | ||
|
|
2b13fc9a24 | ||
|
|
9e18964e7f | ||
|
|
43dfe559a6 | ||
|
|
aab7ba264c | ||
|
|
b7162b5fad | ||
|
|
ce4a081155 | ||
|
|
5df2a048e1 | ||
|
|
1b6a7fafa8 | ||
|
|
2ab725e5e1 | ||
|
|
88a310a86e | ||
|
|
86319be256 | ||
|
|
9d68ef6421 | ||
|
|
2bb1c5d39b | ||
|
|
3aa15c979d | ||
|
|
c062ba3426 | ||
|
|
343560225c | ||
|
|
e0dd77f0c3 | ||
|
|
92ff07f723 | ||
|
|
a23dca080a | ||
|
|
6844f88567 | ||
|
|
e6060ea277 | ||
|
|
549de7fa54 | ||
|
|
ecf9faa21d | ||
|
|
a87ebd41e7 | ||
|
|
183dd40f39 | ||
|
|
c722c5c46f | ||
|
|
865200db5e | ||
|
|
c3e15de759 | ||
|
|
44bfdbdc83 | ||
|
|
5f10c1875c | ||
|
|
a7ae7a8cda | ||
|
|
706f142a98 | ||
|
|
08df3b2dff | ||
|
|
14672ff145 | ||
|
|
7fcd84d08e | ||
|
|
12f8686326 | ||
|
|
aa445adfff | ||
|
|
603aa5db5f | ||
|
|
c34289036f | ||
|
|
5b6bec775b | ||
|
|
3b0fe3043f | ||
|
|
c99165891f | ||
|
|
4938b18f9d | ||
|
|
8895b4e8a3 | ||
|
|
a7321c9beb | ||
|
|
c23b533704 | ||
|
|
de34c75788 | ||
|
|
06341efe0d | ||
|
|
c810005f86 | ||
|
|
cdedf283ac | ||
|
|
acfd92e2e6 | ||
|
|
51b81dba87 | ||
|
|
7f6dfcf52f | ||
|
|
92582d8434 | ||
|
|
e2bff474db | ||
|
|
4f702e12b7 | ||
|
|
083400d1c2 | ||
|
|
7491337bfd | ||
|
|
d6b833fbb2 | ||
|
|
2113a2b634 | ||
|
|
5df632264f | ||
|
|
42c350243a | ||
|
|
522ca3b04a | ||
|
|
2d53ec5d34 | ||
|
|
13d2f70c3a | ||
|
|
a87d19998e | ||
|
|
6ddf241293 | ||
|
|
e43ec6c4ea | ||
|
|
5f3db95cbd | ||
|
|
d874829b06 | ||
|
|
6cfbccd955 | ||
|
|
0d821c3630 | ||
|
|
b61e3b580d | ||
|
|
5c301353ec | ||
|
|
0363421862 | ||
|
|
6f18b9b691 | ||
|
|
35e40be550 | ||
|
|
c1528f532e |
32
.devcontainer.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "CentOS",
|
||||
"image": "tdesktop:centos_env",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"C_Cpp.intelliSenseEngine": "disabled",
|
||||
"clangd.arguments": [
|
||||
"--compile-commands-dir=${workspaceFolder}/out"
|
||||
],
|
||||
"cmake.generator": "Ninja Multi-Config",
|
||||
"cmake.buildDirectory": "${workspaceFolder}/out"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"TheQtCompany.qt",
|
||||
"ms-python.python",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"capAdd": [
|
||||
"SYS_PTRACE"
|
||||
],
|
||||
"securityOpt": [
|
||||
"seccomp=unconfined"
|
||||
],
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/usr/src/tdesktop,type=bind,consistency=cached",
|
||||
"workspaceFolder": "/usr/src/tdesktop"
|
||||
}
|
||||
5
.github/workflows/linux.yml
vendored
@@ -83,6 +83,7 @@ jobs:
|
||||
fi
|
||||
|
||||
docker run --rm \
|
||||
-u $(id -u) \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e CONFIG=Debug \
|
||||
tdesktop:centos_env \
|
||||
@@ -114,8 +115,8 @@ jobs:
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
cd $REPO_NAME/out/Debug
|
||||
sudo mkdir artifact
|
||||
sudo mv {Telegram,Updater} artifact/
|
||||
mkdir artifact
|
||||
mv {Telegram,Updater} artifact/
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
name: Upload artifact.
|
||||
|
||||
1
.gitignore
vendored
@@ -19,6 +19,7 @@ Release/
|
||||
ipch/
|
||||
.vs/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
/Telegram/log.txt
|
||||
/Telegram/data
|
||||
|
||||
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2024 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2025 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -246,6 +246,8 @@ PRIVATE
|
||||
boxes/peers/prepare_short_info_box.h
|
||||
boxes/peers/replace_boost_box.cpp
|
||||
boxes/peers/replace_boost_box.h
|
||||
boxes/peers/verify_peers_box.cpp
|
||||
boxes/peers/verify_peers_box.h
|
||||
boxes/about_box.cpp
|
||||
boxes/about_box.h
|
||||
boxes/about_sponsored_box.cpp
|
||||
@@ -330,6 +332,8 @@ PRIVATE
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
boxes/stickers_box.h
|
||||
boxes/transfer_gift_box.cpp
|
||||
boxes/transfer_gift_box.h
|
||||
boxes/translate_box.cpp
|
||||
boxes/translate_box.h
|
||||
boxes/url_auth_box.cpp
|
||||
@@ -626,6 +630,7 @@ PRIVATE
|
||||
data/data_shared_media.h
|
||||
data/data_sparse_ids.cpp
|
||||
data/data_sparse_ids.h
|
||||
data/data_star_gift.h
|
||||
data/data_statistics.h
|
||||
data/data_stories.cpp
|
||||
data/data_stories.h
|
||||
@@ -796,6 +801,8 @@ PRIVATE
|
||||
history/view/media/history_view_story_mention.h
|
||||
history/view/media/history_view_theme_document.cpp
|
||||
history/view/media/history_view_theme_document.h
|
||||
history/view/media/history_view_unique_gift.cpp
|
||||
history/view/media/history_view_unique_gift.h
|
||||
history/view/media/history_view_userpic_suggestion.cpp
|
||||
history/view/media/history_view_userpic_suggestion.h
|
||||
history/view/media/history_view_web_page.cpp
|
||||
@@ -952,6 +959,7 @@ PRIVATE
|
||||
info/global_media/info_global_media_inner_widget.h
|
||||
info/global_media/info_global_media_provider.cpp
|
||||
info/global_media/info_global_media_provider.h
|
||||
info/media/info_media_buttons.cpp
|
||||
info/media/info_media_buttons.h
|
||||
info/media/info_media_common.cpp
|
||||
info/media/info_media_common.h
|
||||
@@ -1007,8 +1015,8 @@ PRIVATE
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/similar_channels/info_similar_channels_widget.cpp
|
||||
info/similar_channels/info_similar_channels_widget.h
|
||||
info/similar_peers/info_similar_peers_widget.cpp
|
||||
info/similar_peers/info_similar_peers_widget.h
|
||||
info/statistics/info_statistics_common.h
|
||||
info/statistics/info_statistics_inner_widget.cpp
|
||||
info/statistics/info_statistics_inner_widget.h
|
||||
@@ -1128,6 +1136,8 @@ PRIVATE
|
||||
media/audio/media_audio_loader.h
|
||||
media/audio/media_audio_loaders.cpp
|
||||
media/audio/media_audio_loaders.h
|
||||
media/audio/media_audio_local_cache.cpp
|
||||
media/audio/media_audio_local_cache.h
|
||||
media/audio/media_audio_track.cpp
|
||||
media/audio/media_audio_track.h
|
||||
media/audio/media_child_ffmpeg_loader.cpp
|
||||
@@ -1196,6 +1206,8 @@ PRIVATE
|
||||
media/streaming/media_streaming_video_track.h
|
||||
media/view/media_view_group_thumbs.cpp
|
||||
media/view/media_view_group_thumbs.h
|
||||
media/view/media_view_open_common.cpp
|
||||
media/view/media_view_open_common.h
|
||||
media/view/media_view_overlay_opengl.cpp
|
||||
media/view/media_view_overlay_opengl.h
|
||||
media/view/media_view_overlay_raster.cpp
|
||||
@@ -1214,7 +1226,6 @@ PRIVATE
|
||||
media/view/media_view_playback_controls.h
|
||||
media/view/media_view_playback_progress.cpp
|
||||
media/view/media_view_playback_progress.h
|
||||
media/view/media_view_open_common.h
|
||||
media/system_media_controls_manager.h
|
||||
media/system_media_controls_manager.cpp
|
||||
menu/menu_antispam_validator.cpp
|
||||
@@ -1402,8 +1413,6 @@ PRIVATE
|
||||
settings/business/settings_recipients_helper.h
|
||||
settings/business/settings_working_hours.cpp
|
||||
settings/business/settings_working_hours.h
|
||||
settings/cloud_password/settings_cloud_password_common.cpp
|
||||
settings/cloud_password/settings_cloud_password_common.h
|
||||
settings/cloud_password/settings_cloud_password_email.cpp
|
||||
settings/cloud_password/settings_cloud_password_email.h
|
||||
settings/cloud_password/settings_cloud_password_email_confirm.cpp
|
||||
@@ -1416,6 +1425,8 @@ PRIVATE
|
||||
settings/cloud_password/settings_cloud_password_manage.h
|
||||
settings/cloud_password/settings_cloud_password_start.cpp
|
||||
settings/cloud_password/settings_cloud_password_start.h
|
||||
settings/cloud_password/settings_cloud_password_step.cpp
|
||||
settings/cloud_password/settings_cloud_password_step.h
|
||||
settings/settings_active_sessions.cpp
|
||||
settings/settings_active_sessions.h
|
||||
settings/settings_advanced.cpp
|
||||
@@ -1464,6 +1475,8 @@ PRIVATE
|
||||
settings/settings_privacy_security.h
|
||||
settings/settings_scale_preview.cpp
|
||||
settings/settings_scale_preview.h
|
||||
settings/settings_shortcuts.cpp
|
||||
settings/settings_shortcuts.h
|
||||
settings/settings_type.h
|
||||
settings/settings_websites.cpp
|
||||
settings/settings_websites.h
|
||||
|
||||
BIN
Telegram/Resources/art/verified_bg.webp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/art/verified_fg.webp
Normal file
|
After Width: | Height: | Size: 500 B |
@@ -1,6 +1,7 @@
|
||||
// This is a list of your own shortcuts for Telegram Desktop
|
||||
// You can see full list of commands in the 'shortcuts-default.json' file
|
||||
// Place a null value instead of a command string to switch the shortcut off
|
||||
// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts.
|
||||
|
||||
[
|
||||
// {
|
||||
|
||||
BIN
Telegram/Resources/icons/chat/markup_webview.png
Normal file
|
After Width: | Height: | Size: 663 B |
BIN
Telegram/Resources/icons/chat/markup_webview@2x.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
Telegram/Resources/icons/chat/markup_webview@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/chat/mini_info_alert.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@2x.png
Normal file
|
After Width: | Height: | Size: 578 B |
BIN
Telegram/Resources/icons/chat/mini_info_alert@3x.png
Normal file
|
After Width: | Height: | Size: 829 B |
BIN
Telegram/Resources/icons/menu/nft_takeoff.png
Normal file
|
After Width: | Height: | Size: 730 B |
BIN
Telegram/Resources/icons/menu/nft_takeoff@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/nft_takeoff@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Resources/icons/menu/nft_wear.png
Normal file
|
After Width: | Height: | Size: 716 B |
BIN
Telegram/Resources/icons/menu/nft_wear@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/nft_wear@3x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Telegram/Resources/icons/menu/shortcut.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
Telegram/Resources/icons/menu/shortcut@2x.png
Normal file
|
After Width: | Height: | Size: 671 B |
BIN
Telegram/Resources/icons/menu/shortcut@3x.png
Normal file
|
After Width: | Height: | Size: 1004 B |
BIN
Telegram/Resources/icons/menu/tradable.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
Telegram/Resources/icons/menu/tradable@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/tradable@3x.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/Resources/icons/menu/unique.png
Normal file
|
After Width: | Height: | Size: 868 B |
BIN
Telegram/Resources/icons/menu/unique@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/unique@3x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Telegram/Resources/icons/payments/premium_emoji.png
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@2x.png
Normal file
|
After Width: | Height: | Size: 712 B |
BIN
Telegram/Resources/icons/payments/premium_emoji@3x.png
Normal file
|
After Width: | Height: | Size: 926 B |
@@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_window_close" = "When window closed";
|
||||
"lng_settings_run_in_background" = "Run in the background";
|
||||
"lng_settings_quit_on_close" = "Quit the application";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||
"lng_settings_window_system" = "Window title bar";
|
||||
@@ -639,6 +642,46 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_chat_quick_action_react" = "Send reaction with double click";
|
||||
"lng_settings_chat_corner_reaction" = "Reaction button on messages";
|
||||
|
||||
"lng_settings_shortcuts" = "Keyboard shortcuts";
|
||||
|
||||
"lng_shortcuts_reset" = "Reset to default";
|
||||
"lng_shortcuts_recording" = "Recording...";
|
||||
"lng_shortcuts_add_another" = "Add another";
|
||||
|
||||
"lng_shortcuts_close" = "Close the window";
|
||||
"lng_shortcuts_lock" = "Lock the application";
|
||||
"lng_shortcuts_minimize" = "Minimize the window";
|
||||
"lng_shortcuts_quit" = "Quit the application";
|
||||
"lng_shortcuts_media_play" = "Play the media";
|
||||
"lng_shortcuts_media_pause" = "Pause the media";
|
||||
"lng_shortcuts_media_play_pause" = "Toggle media playback";
|
||||
"lng_shortcuts_media_stop" = "Stop media playback";
|
||||
"lng_shortcuts_media_previous" = "Previous track";
|
||||
"lng_shortcuts_media_next" = "Next track";
|
||||
"lng_shortcuts_search" = "Search messages";
|
||||
"lng_shortcuts_chat_previous" = "Previous chat";
|
||||
"lng_shortcuts_chat_next" = "Next chat";
|
||||
"lng_shortcuts_chat_first" = "First chat";
|
||||
"lng_shortcuts_chat_last" = "Last chat";
|
||||
"lng_shortcuts_chat_self" = "Saved Messages";
|
||||
"lng_shortcuts_chat_pinned_n" = "Pinned chat #{index}";
|
||||
"lng_shortcuts_show_account_n" = "Account #{index}";
|
||||
"lng_shortcuts_show_all_chats" = "All Chats folder";
|
||||
"lng_shortcuts_show_folder_n" = "Folder #{index}";
|
||||
"lng_shortcuts_show_folder_last" = "Last folder";
|
||||
"lng_shortcuts_folder_next" = "Next folder";
|
||||
"lng_shortcuts_folder_previous" = "Previous folder";
|
||||
"lng_shortcuts_scheduled" = "Scheduled messages";
|
||||
"lng_shortcuts_archive" = "Archived chats";
|
||||
"lng_shortcuts_contacts" = "Contacts list";
|
||||
"lng_shortcuts_just_send" = "Just send";
|
||||
"lng_shortcuts_silent_send" = "Silent send";
|
||||
"lng_shortcuts_schedule" = "Schedule";
|
||||
"lng_shortcuts_read_chat" = "Mark chat as read";
|
||||
"lng_shortcuts_archive_chat" = "Archive chat";
|
||||
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
|
||||
"lng_shortcuts_show_chat_menu" = "Show chat menu";
|
||||
|
||||
"lng_settings_chat_reactions_title" = "Quick Reaction";
|
||||
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
|
||||
"lng_settings_chat_message_reply_from" = "Bob Harris";
|
||||
@@ -1175,6 +1218,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
"lng_edit_privacy_close_friends" = "Close friends";
|
||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||
"lng_edit_privacy_paid" = "Paid";
|
||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||
"lng_edit_privacy_nobody" = "Nobody";
|
||||
"lng_edit_privacy_premium" = "Premium users";
|
||||
@@ -1313,6 +1357,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
|
||||
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
|
||||
"lng_messages_privacy_premium_link" = "Telegram Premium";
|
||||
"lng_messages_privacy_charge" = "Charge for messages";
|
||||
"lng_messages_privacy_charge_about" = "Charge a fee for messages from people outside your contacts or those you haven't messaged first.";
|
||||
"lng_messages_privacy_price" = "Set your price per message";
|
||||
"lng_messages_privacy_price_about" = "You will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||
"lng_messages_privacy_exceptions" = "Exceptions";
|
||||
"lng_messages_privacy_remove_fee" = "Remove Fee";
|
||||
"lng_messages_privacy_remove_about" = "Add users or entire groups who won't be charged for sending messages to you.";
|
||||
|
||||
"lng_self_destruct_title" = "Account self-destruction";
|
||||
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
|
||||
@@ -1346,6 +1397,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_similar_bots#one" = "{count} similar bot";
|
||||
"lng_profile_similar_bots#other" = "{count} similar bots";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_peer_gifts#one" = "{count} gift";
|
||||
@@ -1628,11 +1681,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_bot_star_ref" = "Affiliate Program";
|
||||
"lng_manage_peer_bot_star_ref_off" = "Off";
|
||||
"lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
|
||||
"lng_manage_peer_bot_verify" = "Verify Accounts";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
"lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
|
||||
|
||||
"lng_bot_verify_title" = "Choose Chat to Verify";
|
||||
"lng_bot_verify_bot_title" = "Verify Bot";
|
||||
"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_bot_about" = "You can customize your description for each bot.";
|
||||
"lng_bot_verify_bot_submit" = "Verify Bot";
|
||||
"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_user_title" = "Verify User";
|
||||
"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_user_about" = "You can customize your description for each account.";
|
||||
"lng_bot_verify_user_submit" = "Verify User";
|
||||
"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_channel_title" = "Verify Channel";
|
||||
"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_channel_about" = "You can customize your description for each channel.";
|
||||
"lng_bot_verify_channel_submit" = "Verify Channel";
|
||||
"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_group_title" = "Verify Group";
|
||||
"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?";
|
||||
"lng_bot_verify_group_about" = "You can customize your description for each group.";
|
||||
"lng_bot_verify_group_submit" = "Verify Group";
|
||||
"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
|
||||
"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?";
|
||||
"lng_bot_verify_description_label" = "Description";
|
||||
"lng_bot_verify_remove_title" = "Remove verification";
|
||||
"lng_bot_verify_remove_submit" = "Remove";
|
||||
"lng_bot_verify_remove_done" = "You've removed this verification.";
|
||||
|
||||
"lng_star_ref_title" = "Affiliate Program";
|
||||
"lng_star_ref_about" = "Reward those who help grow your user base.";
|
||||
"lng_star_ref_share_title" = "Share revenue with affiliates";
|
||||
@@ -1985,21 +2069,50 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_proximity_distance_km#other" = "{count} km";
|
||||
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_unique_received" = "{user} sent you a unique collectible item";
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
|
||||
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
|
||||
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
|
||||
"lng_action_gift_transferred" = "{user} transferred you a gift";
|
||||
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_unknown" = "Someone transferred you a gift";
|
||||
"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_self" = "You transferred a unique collectible";
|
||||
"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
|
||||
"lng_action_gift_self_subtitle" = "Saved Gift";
|
||||
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others.";
|
||||
"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star.";
|
||||
"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible.";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts.";
|
||||
"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
"lng_action_gift_sent_upgradable" = "{user} can upgrade this gift to a unique collectible.";
|
||||
"lng_action_gift_premium_months#one" = "{count} Month Premium";
|
||||
"lng_action_gift_premium_months#other" = "{count} Months Premium";
|
||||
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
|
||||
"lng_action_gift_refunded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
|
||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -2048,10 +2161,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_giveaway_results_credits#other" = "{count} winners of the giveaway were randomly selected by Telegram and received their prize.";
|
||||
"lng_action_giveaway_results_credits_some" = "Some winners of the giveaway were randomly selected by Telegram and received their prize.";
|
||||
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
|
||||
"lng_action_boost_apply_me" = "You boosted the group";
|
||||
"lng_action_boost_apply#one" = "{from} boosted the group";
|
||||
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
|
||||
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
|
||||
"lng_action_payment_refunded" = "{peer} refunded {amount}";
|
||||
"lng_action_paid_message_sent#one" = "You paid {count} Star to {action}";
|
||||
"lng_action_paid_message_sent#other" = "You paid {count} Stars to {action}";
|
||||
"lng_action_paid_message_one" = "send a message";
|
||||
"lng_action_paid_message_some#one" = "send {count} message";
|
||||
"lng_action_paid_message_some#other" = "send {count} messages";
|
||||
"lng_action_paid_message_got#one" = "You received {count} Star from {name}";
|
||||
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
|
||||
"lng_you_paid_stars#one" = "You paid {count} Star.";
|
||||
"lng_you_paid_stars#other" = "You paid {count} Stars.";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
"lng_similar_channels_view_all" = "View all";
|
||||
@@ -2061,9 +2184,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_similar_channels_premium_all_link" = "Telegram Premium";
|
||||
"lng_similar_channels_show_more" = "Show more channels";
|
||||
|
||||
"lng_similar_bots_title" = "Similar bots";
|
||||
"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot.";
|
||||
"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots.";
|
||||
"lng_similar_bots_show_more" = "Show more bots";
|
||||
|
||||
"lng_peer_gifts_title" = "Gifts";
|
||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
||||
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
|
||||
"lng_peer_gifts_notify" = "Notify About New Gifts";
|
||||
"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
|
||||
"lng_peer_gifts_filter_by_value" = "Sort by Value";
|
||||
"lng_peer_gifts_filter_by_date" = "Sort by Date";
|
||||
"lng_peer_gifts_filter_unlimited" = "Unlimited";
|
||||
"lng_peer_gifts_filter_limited" = "Limited";
|
||||
"lng_peer_gifts_filter_unique" = "Unique";
|
||||
"lng_peer_gifts_filter_saved" = "Displayed";
|
||||
"lng_peer_gifts_filter_unsaved" = "Hidden";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
@@ -2326,6 +2463,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_stickers_add" = "Choose sticker set";
|
||||
"lng_group_emoji" = "Select Emoji Pack";
|
||||
"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group.";
|
||||
"lng_collectible_emoji" = "Collectibles";
|
||||
|
||||
"lng_premium" = "Premium";
|
||||
"lng_premium_free" = "Free";
|
||||
@@ -2403,6 +2541,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
|
||||
"lng_premium_summary_new_badge" = "NEW";
|
||||
"lng_soon_badge" = "Soon";
|
||||
|
||||
"lng_premium_success" = "You've successfully subscribed to Telegram Premium!";
|
||||
"lng_premium_unavailable" = "This feature requires subscription to **Telegram Premium**.\n\nUnfortunately, **Telegram Premium** is not available in your region.";
|
||||
@@ -2469,6 +2608,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_double_limits_subtitle_accounts" = "Connected Accounts";
|
||||
"lng_premium_double_limits_about_accounts#one" = "Connect {count} account with different mobile numbers";
|
||||
"lng_premium_double_limits_about_accounts#other" = "Connect {count} accounts with different mobile numbers";
|
||||
|
||||
"lng_premium_double_limits_subtitle_similar_channels" = "Similar Channel";
|
||||
"lng_premium_double_limits_about_similar_channels#one" = "View up to {count} similar channel";
|
||||
"lng_premium_double_limits_about_similar_channels#other" = "View up to {count} similar channels";
|
||||
//
|
||||
|
||||
"lng_premium_gift_title" = "Gift Telegram Premium";
|
||||
@@ -2538,6 +2681,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_commission" = "{amount} commission";
|
||||
"lng_credits_paid_messages_fee#one" = "Fee for {count} Message";
|
||||
"lng_credits_paid_messages_fee#other" = "Fee for {count} Messages";
|
||||
"lng_credits_paid_messages_fee_about" = "You receive {percent} of the price that you charge for each incoming message. {link}";
|
||||
"lng_credits_paid_messages_fee_about_link" = "Change Fee {emoji}";
|
||||
"lng_credits_paid_messages_full" = "Full Price";
|
||||
"lng_credits_premium_gift_duration" = "Duration";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
@@ -2576,6 +2725,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_peer_in" = "From";
|
||||
"lng_credits_box_history_entry_gift_from" = "Gift From";
|
||||
"lng_credits_box_history_entry_via" = "Via";
|
||||
"lng_credits_box_history_entry_play_market" = "Play Store";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
@@ -2585,6 +2735,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_transfer" = "Gift Transfer";
|
||||
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
|
||||
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
@@ -2612,6 +2763,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
||||
"lng_credits_box_history_entry_gift_upgrade" = "Collectible Upgrade";
|
||||
|
||||
"lng_credits_subscription_section" = "My subscriptions";
|
||||
"lng_credits_box_subscription_title" = "Subscription";
|
||||
@@ -2648,6 +2800,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
|
||||
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||
@@ -2909,11 +3063,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
|
||||
"lng_boost_channel_title_wear" = "Wear Item";
|
||||
"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
|
||||
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
|
||||
"lng_boost_channel_ask_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
//"lng_boost_channel_or" = "or";
|
||||
//"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
//"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
"lng_boost_group_ask" = "Ask your **Premium** members to boost your group with this link:";
|
||||
//"lng_boost_group_gifting" = "Boost your group by gifting your members Telegram Premium. {link}";
|
||||
|
||||
"lng_feature_stories#one" = "**{count}** Story Per Day";
|
||||
"lng_feature_stories#other" = "**{count}** Stories Per Day";
|
||||
@@ -3175,6 +3335,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
||||
"lng_gift_premium_features" = "See Features >";
|
||||
"lng_gift_premium_label" = "Premium";
|
||||
"lng_gift_premium_by_stars" = "or {amount}";
|
||||
"lng_gift_stars_subtitle" = "Gift Stars";
|
||||
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
||||
"lng_gift_stars_link" = "What are Stars >";
|
||||
@@ -3186,24 +3347,69 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_pay_with_stars" = "Pay with {amount}";
|
||||
"lng_gift_send_stars_balance" = "Your balance is {amount}. {link}";
|
||||
"lng_gift_send_stars_balance_link" = "Get More Stars >";
|
||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_anonymous_about_paid" = "You can hide your name from visitors to {user}'s profile. {recipient} will still see your name.";
|
||||
"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
||||
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_link" = "Learn More >";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_limited_sold#one" = "{count} sold";
|
||||
"lng_gift_send_limited_sold#other" = "{count} sold";
|
||||
"lng_gift_send_limited_left#one" = "{count} left";
|
||||
"lng_gift_send_limited_left#other" = "{count} left";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
|
||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||
"lng_gift_limited_of_one" = "unique";
|
||||
"lng_gift_limited_of_count" = "1 of {amount}";
|
||||
"lng_gift_collectible_tag" = "gift";
|
||||
"lng_gift_price_unique" = "Unique";
|
||||
"lng_gift_view_unpack" = "Unpack";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";
|
||||
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
"lng_gift_hidden_unique" = "This gift is not displayed on your page.";
|
||||
"lng_gift_visible_hint" = "This gift is visible on your page.";
|
||||
"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel.";
|
||||
"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts.";
|
||||
"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}";
|
||||
"lng_gift_in_blockchain_link" = "View >";
|
||||
"lng_gift_visible_hide" = "Hide >";
|
||||
"lng_gift_show_on_page" = "Display on my Page";
|
||||
"lng_gift_show_on_channel" = "Display in channel's Gifts";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_visibility" = "Visibility";
|
||||
"lng_gift_visibility_shown" = "Visible on your page";
|
||||
"lng_gift_visibility_hidden" = "Not visible on your page";
|
||||
"lng_gift_visibility_show" = "show";
|
||||
"lng_gift_visibility_hide" = "hide";
|
||||
"lng_gift_self_status" = "buy yourself a gift";
|
||||
"lng_gift_self_title" = "Buy a Gift";
|
||||
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
|
||||
"lng_gift_channel_title" = "Send a Gift";
|
||||
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
|
||||
"lng_gift_unique_owner" = "Owner";
|
||||
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
|
||||
"lng_gift_unique_status" = "Status";
|
||||
"lng_gift_unique_status_non" = "Non-Unique";
|
||||
"lng_gift_unique_status_upgrade" = "upgrade";
|
||||
"lng_gift_unique_number" = "Collectible #{index}";
|
||||
"lng_gift_unique_model" = "Model";
|
||||
"lng_gift_unique_backdrop" = "Backdrop";
|
||||
"lng_gift_unique_symbol" = "Symbol";
|
||||
"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute.";
|
||||
"lng_gift_unique_availability_label" = "Quantity";
|
||||
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
|
||||
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
|
||||
"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}.";
|
||||
"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\".";
|
||||
"lng_gift_availability_left#one" = "{count} of {amount} left";
|
||||
"lng_gift_availability_left#other" = "{count} of {amount} left";
|
||||
"lng_gift_availability_none" = "None of {amount} left";
|
||||
@@ -3212,20 +3418,89 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
|
||||
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
|
||||
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
||||
"lng_gift_pinned_done" = "The gift will always be shown on top.";
|
||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
||||
"lng_gift_channel_got#other" = "Channel got **{count} Stars** for this gift.";
|
||||
"lng_gift_sold_out_title" = "Sold Out!";
|
||||
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
|
||||
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
|
||||
"lng_gift_send_small" = "send a gift";
|
||||
"lng_gift_sell_small#one" = "sell for {count} Star";
|
||||
"lng_gift_sell_small#other" = "sell for {count} Stars";
|
||||
"lng_gift_upgrade_title" = "Upgrade Gift";
|
||||
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
|
||||
"lng_gift_upgrade_preview_title" = "Make Unique";
|
||||
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_unique_title" = "Unique";
|
||||
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
|
||||
"lng_gift_upgrade_transferable_title" = "Transferable";
|
||||
"lng_gift_upgrade_transferable_about" = "Send your upgraded gift to any of your friends on Telegram.";
|
||||
"lng_gift_upgrade_tradable_title" = "Tradable";
|
||||
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
|
||||
"lng_gift_upgrade_button" = "Upgrade for {price}";
|
||||
"lng_gift_upgrade_free" = "Upgrade for Free";
|
||||
"lng_gift_upgrade_confirm" = "Confirm";
|
||||
"lng_gift_upgrade_add_my" = "Add my name to the gift";
|
||||
"lng_gift_upgrade_add_my_comment" = "Add my name and comment";
|
||||
"lng_gift_upgrade_add_sender" = "Add sender's name to the gift";
|
||||
"lng_gift_upgrade_add_comment" = "Add sender's name and comment";
|
||||
"lng_gift_upgraded_title" = "Gift Upgraded";
|
||||
"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others";
|
||||
"lng_gift_transferred_title" = "Gift Transferred";
|
||||
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
|
||||
"lng_gift_transfer_title" = "Transfer {name}";
|
||||
"lng_gift_transfer_via_blockchain" = "Send via Blockchain";
|
||||
"lng_gift_transfer_password_title" = "Two-step verification";
|
||||
"lng_gift_transfer_password_description" = "Please enter your password to transfer.";
|
||||
"lng_gift_transfer_password_about" = "You can withdraw only if you have:";
|
||||
"lng_gift_transfer_confirm_title" = "Manage with Fragment";
|
||||
"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment.";
|
||||
"lng_gift_transfer_confirm_button" = "Open Fragment";
|
||||
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
|
||||
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
|
||||
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";
|
||||
"lng_gift_transfer_unlocks_hours#other" = "unlocks in {count} hours";
|
||||
"lng_gift_transfer_unlocks_title" = "Unlocking in progress";
|
||||
"lng_gift_transfer_unlocks_about" = "{when}, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction.";
|
||||
"lng_gift_transfer_unlocks_when_days#one" = "In {count} day";
|
||||
"lng_gift_transfer_unlocks_when_days#other" = "In {count} days";
|
||||
"lng_gift_transfer_unlocks_when_hours#one" = "In {count} hour";
|
||||
"lng_gift_transfer_unlocks_when_hours#other" = "In {count} hours";
|
||||
"lng_gift_transfer_unlocks_update_title" = "Update required";
|
||||
"lng_gift_transfer_unlocks_update_about" = "Please update your Telegram application to the latest version.";
|
||||
"lng_gift_transfer_sure" = "Do you want to transfer ownership of {name} to {recipient}?";
|
||||
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
|
||||
"lng_gift_transfer_button" = "Transfer";
|
||||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
"lng_gift_transfer_wear" = "Wear";
|
||||
"lng_gift_transfer_take_off" = "Take Off";
|
||||
"lng_gift_menu_show" = "Show";
|
||||
"lng_gift_menu_hide" = "Hide";
|
||||
"lng_gift_wear_title" = "Wear {name}";
|
||||
"lng_gift_wear_about" = "and get these benefits:";
|
||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||
"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name.";
|
||||
"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name.";
|
||||
"lng_gift_wear_proof_title" = "Proof of Ownership";
|
||||
"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner.";
|
||||
"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner.";
|
||||
"lng_gift_wear_start" = "Start Wearing";
|
||||
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
|
||||
"lng_gift_wear_start_toast" = "You put on {name}";
|
||||
"lng_gift_wear_end_toast" = "You took off {name}";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
@@ -3367,6 +3642,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join.";
|
||||
"lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}.";
|
||||
"lng_new_contact_about_status_link" = "Telegram Premium";
|
||||
"lng_new_contact_not_contact" = "Not a contact";
|
||||
"lng_new_contact_phone_number" = "Phone number";
|
||||
"lng_new_contact_registration" = "Registration";
|
||||
"lng_new_contact_common_groups" = "Common groups";
|
||||
"lng_new_contact_groups#one" = "{count} group {emoji} {arrow}";
|
||||
"lng_new_contact_groups#other" = "{count} groups {emoji} {arrow}";
|
||||
"lng_new_contact_not_official" = "Not an official account";
|
||||
"lng_new_contact_updated_name" = "User updated name {when}";
|
||||
"lng_new_contact_updated_photo" = "User updated photo {when}";
|
||||
"lng_new_contact_updated_now" = "less than an hour ago";
|
||||
"lng_new_contact_updated_hours#one" = "{count} hour ago";
|
||||
"lng_new_contact_updated_hours#other" = "{count} hours ago";
|
||||
"lng_new_contact_updated_days#one" = "{count} day ago";
|
||||
"lng_new_contact_updated_days#other" = "{count} days ago";
|
||||
"lng_new_contact_updated_months#one" = "{count} month ago";
|
||||
"lng_new_contact_updated_months#other" = "{count} months ago";
|
||||
"lng_from_request_title_channel" = "Response to your join request";
|
||||
"lng_from_request_title_group" = "Response to your join request";
|
||||
"lng_from_request_body" = "You received this message because you requested to join {name} on {date}.";
|
||||
@@ -3395,6 +3686,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_story_reply_ph" = "Reply privately...";
|
||||
"lng_story_comment_ph" = "Comment story...";
|
||||
"lng_message_paid_ph" = "Message for {amount}";
|
||||
"lng_send_text_no" = "Text not allowed.";
|
||||
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
|
||||
"lng_send_text_type_and_last" = "{types} and {last}";
|
||||
@@ -3641,6 +3933,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_choose_image" = "Choose an image";
|
||||
"lng_choose_file" = "Choose a file";
|
||||
"lng_choose_files" = "Choose Files";
|
||||
"lng_choose_cover" = "Choose video cover";
|
||||
"lng_choose_cover_bad" = "Can't use this file as a caption.";
|
||||
"lng_game_tag" = "Game";
|
||||
|
||||
"lng_context_new_window" = "Open in new window";
|
||||
@@ -3757,6 +4051,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_read_show" = "show when";
|
||||
"lng_context_edit_shortcut" = "Edit Shortcut";
|
||||
"lng_context_delete_shortcut" = "Delete Quick Reply";
|
||||
"lng_context_gift_send" = "Send Another Gift";
|
||||
|
||||
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
|
||||
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
|
||||
@@ -3785,6 +4080,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
"lng_context_edit_cover" = "Edit Cover";
|
||||
"lng_context_clear_cover" = "Clear Cover";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
@@ -3914,6 +4211,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||
|
||||
"lng_share_title" = "Share to";
|
||||
"lng_share_at_time_title" = "Share at {time} to";
|
||||
"lng_share_copy_link" = "Copy share link";
|
||||
"lng_share_confirm" = "Send";
|
||||
"lng_share_wrong_user" = "This game was opened from a different user.";
|
||||
@@ -4029,11 +4327,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_search_messages_from" = "Show messages from";
|
||||
"lng_search_messages_n_of_amount" = "{n} of {amount}";
|
||||
"lng_search_messages_none" = "No results";
|
||||
"lng_search_filter_all" = "All chats";
|
||||
"lng_search_filter_private" = "Private chats";
|
||||
"lng_search_filter_group" = "Group chats";
|
||||
"lng_search_filter_channel" = "Channels";
|
||||
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
"lng_mediaview_copy" = "Copy";
|
||||
"lng_mediaview_copy_frame" = "Copy Frame";
|
||||
"lng_mediaview_forward" = "Forward";
|
||||
"lng_mediaview_share_at_time" = "Share at {time}";
|
||||
"lng_mediaview_delete" = "Delete";
|
||||
"lng_mediaview_save_to_profile" = "Post to Profile";
|
||||
"lng_mediaview_pin_story_done" = "Story pinned";
|
||||
@@ -4544,6 +4848,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
|
||||
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
|
||||
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
|
||||
"lng_rights_charge_stars" = "Charge Stars for Messages";
|
||||
"lng_rights_charge_stars_about" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
|
||||
"lng_rights_charge_price" = "Set price per message";
|
||||
"lng_rights_charge_price_about" = "Your group will receive {percent} of the selected fee ({amount}) for each incoming message.";
|
||||
|
||||
"lng_slowmode_enabled" = "Slow Mode is active.\nYou can send your next message in {left}.";
|
||||
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
|
||||
@@ -4551,6 +4859,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_slowmode_seconds#one" = "{count} second";
|
||||
"lng_slowmode_seconds#other" = "{count} seconds";
|
||||
|
||||
"lng_payment_confirm_title" = "Confirm payment";
|
||||
"lng_payment_confirm_text#one" = "{name} charges **{count}** Star per message.";
|
||||
"lng_payment_confirm_text#other" = "{name} charges **{count}** Stars per message.";
|
||||
"lng_payment_confirm_amount#one" = "**{count}** Star";
|
||||
"lng_payment_confirm_amount#other" = "**{count}** Stars";
|
||||
"lng_payment_confirm_users#one" = "You selected **{count}** user who charge Stars for messages.";
|
||||
"lng_payment_confirm_users#other" = "You selected **{count}** users who charge Stars for messages.";
|
||||
"lng_payment_confirm_chats#one" = "You selected **{count}** chat where you pay Stars for messages.";
|
||||
"lng_payment_confirm_chats#other" = "You selected **{count}** chats where you pay Stars for messages.";
|
||||
"lng_payment_confirm_sure#one" = "Would you like to pay {amount} to send **{count}** message?";
|
||||
"lng_payment_confirm_sure#other" = "Would you like to pay {amount} to send **{count}** messages?";
|
||||
"lng_payment_confirm_dont_ask" = "Don't ask me again";
|
||||
"lng_payment_confirm_button#one" = "Pay for {count} Message";
|
||||
"lng_payment_confirm_button#other" = "Pay for {count} Messages";
|
||||
"lng_payment_bar_text" = "{name} must pay {cost} for each message to you.";
|
||||
"lng_payment_bar_button" = "Remove Fee";
|
||||
"lng_payment_refund_title" = "Remove Fee";
|
||||
"lng_payment_refund_text" = "Are you sure you want to allow {name} to message you for free?";
|
||||
"lng_payment_refund_also#one" = "Refund already paid {count} Star";
|
||||
"lng_payment_refund_also#other" = "Refund already paid {count} Stars";
|
||||
"lng_payment_refund_confirm" = "Confirm";
|
||||
|
||||
"lng_rights_gigagroup_title" = "Broadcast group";
|
||||
"lng_rights_gigagroup_convert" = "Convert to Broadcast Group";
|
||||
"lng_rights_gigagroup_about" = "Broadcast groups can have over 200,000 members, but only admins can send messages in them.";
|
||||
@@ -4682,6 +5012,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
|
||||
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
|
||||
|
||||
"lng_send_charges_stars_text" = "{user} charges {amount} for each message.";
|
||||
"lng_send_charges_stars_go" = "Buy Stars";
|
||||
|
||||
"lng_exceptions_list_title" = "Exceptions";
|
||||
"lng_removed_list_title" = "Removed users";
|
||||
|
||||
@@ -5237,6 +5570,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_filters_edit" = "Edit Folder";
|
||||
"lng_filters_setup_menu" = "Edit Folders";
|
||||
"lng_filters_new_name" = "Folder name";
|
||||
"lng_filters_enable_animations" = "Enable animations";
|
||||
"lng_filters_disable_animations" = "Disable animations";
|
||||
"lng_filters_add_chats" = "Add Chats";
|
||||
"lng_filters_remove_chats" = "Add Chats to Exclude";
|
||||
"lng_filters_include" = "Included chats";
|
||||
@@ -5396,6 +5731,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_view_button_iv" = "Instant View";
|
||||
"lng_view_button_stickerset" = "View stickers";
|
||||
"lng_view_button_emojipack" = "View emoji";
|
||||
"lng_view_button_collectible" = "View collectible";
|
||||
|
||||
"lng_sponsored_hide_ads" = "Hide";
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
@@ -5712,6 +6048,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
|
||||
|
||||
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
|
||||
"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up.";
|
||||
"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway";
|
||||
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
|
||||
"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium";
|
||||
@@ -5771,6 +6108,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
|
||||
"lng_channel_earn_chart_top_hours" = "Ad impressions";
|
||||
"lng_channel_earn_chart_revenue" = "Ad rewards";
|
||||
"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars";
|
||||
"lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON";
|
||||
"lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD";
|
||||
"lng_channel_earn_currency_history" = "TON Transactions";
|
||||
@@ -5879,6 +6217,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
|
||||
"lng_search_tab_no_results_retry" = "Try another hashtag.";
|
||||
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
|
||||
"lng_search_tab_try_in_all" = "Search in All Messages";
|
||||
|
||||
"lng_contact_details_button" = "View Contact";
|
||||
"lng_contact_details_title" = "Contact details";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.9.2.0" />
|
||||
Version="5.12.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEVERSION 5,12,2,0
|
||||
PRODUCTVERSION 5,12,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "FileVersion", "5.12.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
VALUE "ProductVersion", "5.12.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,9,2,0
|
||||
PRODUCTVERSION 5,9,2,0
|
||||
FILEVERSION 5,12,2,0
|
||||
PRODUCTVERSION 5,12,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "5.9.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "FileVersion", "5.12.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.9.2.0"
|
||||
VALUE "ProductVersion", "5.12.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -217,7 +217,11 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
|
||||
MTP_bool(disabled)
|
||||
)).done([=] {
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: toggle calls %1. Hash: %2. %3.")
|
||||
.arg(disabled ? u"disabled"_q : u"enabled"_q)
|
||||
.arg(hash)
|
||||
.arg(error.type()));
|
||||
_toggleCallsDisabledRequests.remove(hash);
|
||||
}).send();
|
||||
_toggleCallsDisabledRequests.emplace(hash, id);
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_chat_filters.h"
|
||||
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
@@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -25,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/filter_link_header.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/vertical_list.h"
|
||||
@@ -49,7 +52,7 @@ public:
|
||||
ToggleChatsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
ToggleAction action,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
std::vector<not_null<PeerData*>> chats,
|
||||
std::vector<not_null<PeerData*>> additional);
|
||||
|
||||
@@ -75,7 +78,6 @@ private:
|
||||
Ui::RpWidget *_addedBottomWidget = nullptr;
|
||||
|
||||
ToggleAction _action = ToggleAction::Adding;
|
||||
QString _filterTitle;
|
||||
base::flat_set<not_null<PeerData*>> _checkable;
|
||||
std::vector<not_null<PeerData*>> _chats;
|
||||
std::vector<not_null<PeerData*>> _additional;
|
||||
@@ -106,9 +108,9 @@ private:
|
||||
|
||||
[[nodiscard]] TextWithEntities AboutText(
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title) {
|
||||
TextWithEntities title) {
|
||||
using Type = Ui::FilterLinkHeaderType;
|
||||
auto boldTitle = Ui::Text::Bold(title);
|
||||
auto boldTitle = Ui::Text::Wrapped(title, EntityType::Bold);
|
||||
return (type == Type::AddingFilter)
|
||||
? tr::lng_filters_by_link_sure(
|
||||
tr::now,
|
||||
@@ -138,19 +140,24 @@ void InitFilterLinkHeader(
|
||||
not_null<PeerListBox*> box,
|
||||
Fn<void(int minHeight, int maxHeight, int addedTopHeight)> adjust,
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
rpl::producer<int> count,
|
||||
bool horizontalFilters) {
|
||||
const auto icon = Ui::LookupFilterIcon(
|
||||
Ui::LookupFilterIconByEmoji(
|
||||
iconEmoji
|
||||
).value_or(Ui::FilterIcon::Custom)).active;
|
||||
const auto isStatic = title.isStatic;
|
||||
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||
.type = type,
|
||||
.title = TitleText(type)(tr::now),
|
||||
.about = AboutText(type, title),
|
||||
.folderTitle = title,
|
||||
.about = AboutText(type, title.text),
|
||||
.aboutContext = Core::TextContext({
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
.folderTitle = title.text,
|
||||
.folderIcon = icon,
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
@@ -248,12 +255,11 @@ void ImportInvite(
|
||||
ToggleChatsController::ToggleChatsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
ToggleAction action,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
std::vector<not_null<PeerData*>> chats,
|
||||
std::vector<not_null<PeerData*>> additional)
|
||||
: _window(window)
|
||||
, _action(action)
|
||||
, _filterTitle(title)
|
||||
, _chats(std::move(chats))
|
||||
, _additional(std::move(additional)) {
|
||||
setStyleOverrides(&st::filterLinkChatsList);
|
||||
@@ -529,7 +535,7 @@ void ShowImportError(
|
||||
|
||||
void ShowImportToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
Data::ChatFilterTitle title,
|
||||
Ui::FilterLinkHeaderType type,
|
||||
int added) {
|
||||
const auto strong = weak.get();
|
||||
@@ -540,14 +546,23 @@ void ShowImportToast(
|
||||
const auto phrase = created
|
||||
? tr::lng_filters_added_title
|
||||
: tr::lng_filters_updated_title;
|
||||
auto text = Ui::Text::Bold(phrase(tr::now, lt_folder, title));
|
||||
auto text = Ui::Text::Wrapped(
|
||||
phrase(tr::now, lt_folder, title.text, Ui::Text::WithEntities),
|
||||
EntityType::Bold);
|
||||
if (added > 0) {
|
||||
const auto phrase = created
|
||||
? tr::lng_filters_added_also
|
||||
: tr::lng_filters_updated_also;
|
||||
text.append('\n').append(phrase(tr::now, lt_count, added));
|
||||
}
|
||||
strong->showToast(std::move(text));
|
||||
const auto isStatic = title.isStatic;
|
||||
strong->showToast({
|
||||
.text = std::move(text),
|
||||
.textContext = Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
void HandleEnterInBox(not_null<Ui::BoxContent*> box) {
|
||||
@@ -574,8 +589,8 @@ void ProcessFilterInvite(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &slug,
|
||||
FilterId filterId,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> peers,
|
||||
std::vector<not_null<PeerData*>> already) {
|
||||
const auto strong = weak.get();
|
||||
@@ -616,10 +631,15 @@ void ProcessFilterInvite(
|
||||
|
||||
raw->setRealContentHeight(box->heightValue());
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title,
|
||||
title.text,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
@@ -720,7 +740,7 @@ void CheckFilterInvite(
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
auto title = QString();
|
||||
auto title = Data::ChatFilterTitle();
|
||||
auto iconEmoji = QString();
|
||||
auto filterId = FilterId();
|
||||
auto peers = std::vector<not_null<PeerData*>>();
|
||||
@@ -739,7 +759,8 @@ void CheckFilterInvite(
|
||||
return result;
|
||||
};
|
||||
result.match([&](const MTPDchatlists_chatlistInvite &data) {
|
||||
title = qs(data.vtitle());
|
||||
title.text = ParseTextWithEntities(session, data.vtitle());
|
||||
title.isStatic = data.is_title_noanimate();
|
||||
iconEmoji = data.vemoticon().value_or_empty();
|
||||
peers = parseList(data.vpeers());
|
||||
}, [&](const MTPDchatlists_chatlistInviteAlready &data) {
|
||||
@@ -804,8 +825,8 @@ void ProcessFilterUpdate(
|
||||
|
||||
void ProcessFilterRemove(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> all,
|
||||
std::vector<not_null<PeerData*>> suggest,
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done) {
|
||||
@@ -839,10 +860,15 @@ void ProcessFilterRemove(
|
||||
raw->adjust(min, max, addedTop);
|
||||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title,
|
||||
title.text,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
|
||||
@@ -17,6 +17,7 @@ class SessionController;
|
||||
|
||||
namespace Data {
|
||||
class ChatFilter;
|
||||
struct ChatFilterTitle;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
@@ -36,8 +37,8 @@ void ProcessFilterUpdate(
|
||||
|
||||
void ProcessFilterRemove(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &title,
|
||||
const QString &iconEmoji,
|
||||
Data::ChatFilterTitle title,
|
||||
QString iconEmoji,
|
||||
std::vector<not_null<PeerData*>> all,
|
||||
std::vector<not_null<PeerData*>> suggest,
|
||||
Fn<void(std::vector<not_null<PeerData*>>)> done);
|
||||
|
||||
@@ -211,11 +211,10 @@ void ApplyBotsList(
|
||||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Chats &chats) {
|
||||
auto result = ChatParticipants::Channels();
|
||||
std::vector<not_null<ChannelData*>>();
|
||||
auto result = ChatParticipants::Peers();
|
||||
chats.match([&](const auto &data) {
|
||||
const auto &list = data.vchats().v;
|
||||
result.list.reserve(list.size());
|
||||
@@ -234,10 +233,29 @@ void ApplyBotsList(
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPmessages_Chats &chats) {
|
||||
return ParseSimilar(&channel->session(), chats);
|
||||
return ParseSimilarChannels(&channel->session(), chats);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarBots(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPusers_Users &users) {
|
||||
auto result = ChatParticipants::Peers();
|
||||
users.match([&](const auto &data) {
|
||||
const auto &list = data.vusers().v;
|
||||
result.list.reserve(list.size());
|
||||
for (const auto &user : list) {
|
||||
result.list.push_back(session->data().processUser(user));
|
||||
}
|
||||
if constexpr (MTPDusers_usersSlice::Is<decltype(data)>()) {
|
||||
if (session->premiumPossible()) {
|
||||
result.more = data.vcount().v - data.vusers().v.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -782,52 +800,65 @@ void ChatParticipants::unblock(
|
||||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
|
||||
if (!channel->isBroadcast()) {
|
||||
return;
|
||||
} else if (const auto i = _similar.find(channel); i != end(_similar)) {
|
||||
void ChatParticipants::loadSimilarPeers(not_null<PeerData*> peer) {
|
||||
if (const auto i = _similar.find(peer); i != end(_similar)) {
|
||||
if (i->second.requestId
|
||||
|| !i->second.channels.more
|
||||
|| !channel->session().premium()) {
|
||||
|| !i->second.peers.more
|
||||
|| !peer->session().premium()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[channel].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilar(channel, result);
|
||||
if (similar.channels == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.channels = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarChannels(channel, result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
similar.peers = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
} else if (const auto bot = peer->asBot()) {
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPbots_GetBotRecommendations(bot->inputUser)
|
||||
).done([=](const MTPusers_Users &result) {
|
||||
auto &similar = _similar[peer];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarBots(&peer->session(), result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.peers = std::move(parsed);
|
||||
_similarLoaded.fire_copy(peer);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
auto ChatParticipants::similar(not_null<ChannelData*> channel)
|
||||
-> const Channels & {
|
||||
const auto i = channel->isBroadcast()
|
||||
? _similar.find(channel)
|
||||
auto ChatParticipants::similar(not_null<PeerData*> peer)
|
||||
-> const Peers & {
|
||||
const auto i = (peer->isBroadcast() || peer->isBot())
|
||||
? _similar.find(peer)
|
||||
: end(_similar);
|
||||
if (i != end(_similar)) {
|
||||
return i->second.channels;
|
||||
return i->second.peers;
|
||||
}
|
||||
static const auto empty = Channels();
|
||||
static const auto empty = Peers();
|
||||
return empty;
|
||||
}
|
||||
|
||||
auto ChatParticipants::similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>> {
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _similarLoaded.events();
|
||||
}
|
||||
|
||||
@@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() {
|
||||
MTP_inputChannelEmpty())
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_recommendations.requestId = 0;
|
||||
auto parsed = ParseSimilar(_session, result);
|
||||
_recommendations.channels = std::move(parsed);
|
||||
_recommendations.channels.more = 0;
|
||||
auto parsed = ParseSimilarChannels(_session, result);
|
||||
_recommendations.peers = std::move(parsed);
|
||||
_recommendations.peers.more = 0;
|
||||
_recommendationsLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
|
||||
return _recommendations.channels;
|
||||
const ChatParticipants::Peers &ChatParticipants::recommendations() const {
|
||||
return _recommendations.peers;
|
||||
}
|
||||
|
||||
rpl::producer<> ChatParticipants::recommendationsLoaded() const {
|
||||
|
||||
@@ -138,27 +138,27 @@ public:
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant);
|
||||
|
||||
void loadSimilarChannels(not_null<ChannelData*> channel);
|
||||
void loadSimilarPeers(not_null<PeerData*> peer);
|
||||
|
||||
struct Channels {
|
||||
std::vector<not_null<ChannelData*>> list;
|
||||
struct Peers {
|
||||
std::vector<not_null<PeerData*>> list;
|
||||
int more = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Channels &,
|
||||
const Channels &) = default;
|
||||
const Peers &,
|
||||
const Peers &) = default;
|
||||
};
|
||||
[[nodiscard]] const Channels &similar(not_null<ChannelData*> channel);
|
||||
[[nodiscard]] const Peers &similar(not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>>;
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
|
||||
void loadRecommendations();
|
||||
[[nodiscard]] const Channels &recommendations() const;
|
||||
[[nodiscard]] const Peers &recommendations() const;
|
||||
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
|
||||
|
||||
private:
|
||||
struct SimilarChannels {
|
||||
Channels channels;
|
||||
struct SimilarPeers {
|
||||
Peers peers;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
@@ -186,10 +186,10 @@ private:
|
||||
not_null<PeerData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||
base::flat_map<not_null<PeerData*>, SimilarPeers> _similar;
|
||||
rpl::event_stream<not_null<PeerData*>> _similarLoaded;
|
||||
|
||||
SimilarChannels _recommendations;
|
||||
SimilarPeers _recommendations;
|
||||
rpl::variable<bool> _recommendationsLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SendOptions {
|
||||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
EffectId effectId = 0;
|
||||
int starsApproved = 0;
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool invertCaption = false;
|
||||
@@ -74,6 +75,7 @@ struct MessageToSend {
|
||||
struct RemoteFileInfo {
|
||||
MTPInputFile file;
|
||||
std::optional<MTPInputFile> thumb;
|
||||
std::optional<MTPInputPhoto> videoCover;
|
||||
std::vector<MTPInputDocument> attachedStickers;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_credits.h"
|
||||
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -73,6 +74,11 @@ constexpr auto kTransactionsLimit = 100;
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto nonUniqueGift = stargift
|
||||
? stargift->match([&](const MTPDstarGift &data) {
|
||||
return &data;
|
||||
}, [](const auto &) { return (const MTPDstarGift*)nullptr; })
|
||||
: nullptr;
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto amount = Data::FromTL(tl.data().vstars());
|
||||
const auto starrefAmount = tl.data().vstarref_amount()
|
||||
@@ -84,7 +90,17 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? peerFromMTP(*tl.data().vstarref_peer()).value
|
||||
: 0;
|
||||
const auto incoming = (amount >= StarsAmount());
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
const auto paidMessagesCount
|
||||
= tl.data().vpaid_messages().value_or_empty();
|
||||
const auto premiumMonthsForStars
|
||||
= tl.data().vpremium_gift_months().value_or_empty();
|
||||
const auto saveActorId = (reaction
|
||||
|| !extended.empty()
|
||||
|| paidMessagesCount) && incoming;
|
||||
const auto parsedGift = stargift
|
||||
? FromTL(&peer->session(), *stargift)
|
||||
: std::optional<Data::StarGift>();
|
||||
const auto giftStickerId = parsedGift ? parsedGift->document->id : 0;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
@@ -97,13 +113,12 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareGiftStickerId = giftStickerId,
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.starrefAmount = starrefAmount,
|
||||
.starrefCommission = starrefCommission,
|
||||
.starrefRecipientId = starrefBarePeerId,
|
||||
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
||||
.starrefAmount = paidMessagesCount ? StarsAmount() : starrefAmount,
|
||||
.starrefCommission = paidMessagesCount ? 0 : starrefCommission,
|
||||
.starrefRecipientId = paidMessagesCount ? 0 : starrefBarePeerId,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -129,12 +144,19 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
.paidMessagesCount = paidMessagesCount,
|
||||
.paidMessagesAmount = (paidMessagesCount
|
||||
? starrefAmount
|
||||
: StarsAmount()),
|
||||
.paidMessagesCommission = paidMessagesCount ? starrefCommission : 0,
|
||||
.starsConverted = int(nonUniqueGift
|
||||
? nonUniqueGift->vconvert_stars().v
|
||||
: 0),
|
||||
.premiumMonthsForStars = premiumMonthsForStars,
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.giftUpgraded = tl.data().is_stargift_upgrade(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
@@ -503,4 +525,12 @@ void EditCreditsSubscription(
|
||||
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
||||
}
|
||||
|
||||
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
|
||||
return id.isUser()
|
||||
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
||||
: MTP_inputSavedStarGiftChat(
|
||||
id.chat()->input,
|
||||
MTP_long(id.chatSavedId()));
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_credits_earn.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Data {
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -116,4 +120,7 @@ void EditCreditsSubscription(
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
||||
const Data::SavedStarGiftId &id);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -276,14 +276,22 @@ mtpRequestId EditTextMessage(
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto videoCover = media->videoCover();
|
||||
const auto videoTimestamp = media->videoTimestamp();
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
| (spoilered ? Flag::f_spoiler : Flag())
|
||||
| (videoTimestamp ? Flag::f_video_timestamp : Flag())
|
||||
| (videoCover ? Flag::f_video_cover : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
(videoCover
|
||||
? videoCover->mtpInput()
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTP_int(videoTimestamp),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
|
||||
@@ -13,6 +13,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace Api {
|
||||
|
||||
PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value) {
|
||||
return value.match([&](const MTPDpaidReactionPrivacyDefault &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDpaidReactionPrivacyAnonymous &) {
|
||||
return PeerId();
|
||||
}, [&](const MTPDpaidReactionPrivacyPeer &data) {
|
||||
return data.vpeer().match([&](const MTPDinputPeerSelf &) {
|
||||
return session->userPeerId();
|
||||
}, [](const MTPDinputPeerUser &data) {
|
||||
return peerFromUser(data.vuser_id());
|
||||
}, [](const MTPDinputPeerChat &data) {
|
||||
return peerFromChat(data.vchat_id());
|
||||
}, [](const MTPDinputPeerChannel &data) {
|
||||
return peerFromChannel(data.vchannel_id());
|
||||
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
|
||||
Unexpected("From message peer in ParsePaidReactionShownPeer.");
|
||||
}, [](const MTPDinputPeerEmpty &) -> PeerId {
|
||||
Unexpected("Empty peer in ParsePaidReactionShownPeer.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
GlobalPrivacy::GlobalPrivacy(not_null<ApiWrap*> api)
|
||||
: _session(&api->session())
|
||||
, _api(&api->instance()) {
|
||||
@@ -88,7 +114,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
|
||||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hide,
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::hideReadTimeCurrent() const {
|
||||
@@ -99,14 +126,6 @@ rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
|
||||
return _hideReadTime.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateNewRequirePremium(bool value) {
|
||||
update(
|
||||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
value);
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::newRequirePremiumCurrent() const {
|
||||
return _newRequirePremium.current();
|
||||
}
|
||||
@@ -115,27 +134,46 @@ rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
|
||||
return _newRequirePremium.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionAnonymous() {
|
||||
if (_paidReactionAnonymousLoaded) {
|
||||
int GlobalPrivacy::newChargeStarsCurrent() const {
|
||||
return _newChargeStars.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> GlobalPrivacy::newChargeStars() const {
|
||||
return _newChargeStars.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateMessagesPrivacy(
|
||||
bool requirePremium,
|
||||
int chargeStars) {
|
||||
update(
|
||||
archiveAndMuteCurrent(),
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
requirePremium,
|
||||
chargeStars);
|
||||
}
|
||||
|
||||
void GlobalPrivacy::loadPaidReactionShownPeer() {
|
||||
if (_paidReactionShownPeerLoaded) {
|
||||
return;
|
||||
}
|
||||
_paidReactionAnonymousLoaded = true;
|
||||
_paidReactionShownPeerLoaded = true;
|
||||
_api.request(MTPmessages_GetPaidReactionPrivacy(
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updatePaidReactionAnonymous(bool value) {
|
||||
_paidReactionAnonymous = value;
|
||||
void GlobalPrivacy::updatePaidReactionShownPeer(PeerId shownPeer) {
|
||||
_paidReactionShownPeer = shownPeer;
|
||||
}
|
||||
|
||||
bool GlobalPrivacy::paidReactionAnonymousCurrent() const {
|
||||
return _paidReactionAnonymous.current();
|
||||
PeerId GlobalPrivacy::paidReactionShownPeerCurrent() const {
|
||||
return _paidReactionShownPeer.current();
|
||||
}
|
||||
|
||||
rpl::producer<bool> GlobalPrivacy::paidReactionAnonymous() const {
|
||||
return _paidReactionAnonymous.value();
|
||||
rpl::producer<PeerId> GlobalPrivacy::paidReactionShownPeer() const {
|
||||
return _paidReactionShownPeer.value();
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
@@ -143,7 +181,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
|
||||
value,
|
||||
unarchiveOnNewMessageCurrent(),
|
||||
hideReadTimeCurrent(),
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
||||
@@ -152,14 +191,16 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
|
||||
archiveAndMuteCurrent(),
|
||||
value,
|
||||
hideReadTimeCurrent(),
|
||||
newRequirePremiumCurrent());
|
||||
newRequirePremiumCurrent(),
|
||||
newChargeStarsCurrent());
|
||||
}
|
||||
|
||||
void GlobalPrivacy::update(
|
||||
bool archiveAndMute,
|
||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||
bool hideReadTime,
|
||||
bool newRequirePremium) {
|
||||
bool newRequirePremium,
|
||||
int newChargeStars) {
|
||||
using Flag = MTPDglobalPrivacySettings::Flag;
|
||||
|
||||
_api.request(_requestId).cancel();
|
||||
@@ -178,35 +219,44 @@ void GlobalPrivacy::update(
|
||||
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
|
||||
| ((newRequirePremium && newRequirePremiumAllowed)
|
||||
? Flag::f_new_noncontact_peers_require_premium
|
||||
: Flag());
|
||||
: Flag())
|
||||
| Flag::f_noncontact_peers_paid_stars;
|
||||
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
|
||||
MTP_globalPrivacySettings(MTP_flags(flags))
|
||||
MTP_globalPrivacySettings(
|
||||
MTP_flags(flags),
|
||||
MTP_long(newChargeStars))
|
||||
)).done([=](const MTPGlobalPrivacySettings &result) {
|
||||
_requestId = 0;
|
||||
apply(result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requestId = 0;
|
||||
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
|
||||
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {});
|
||||
update(
|
||||
archiveAndMute,
|
||||
unarchiveOnNewMessage,
|
||||
hideReadTime,
|
||||
false,
|
||||
0);
|
||||
}
|
||||
}).send();
|
||||
_archiveAndMute = archiveAndMute;
|
||||
_unarchiveOnNewMessage = unarchiveOnNewMessage;
|
||||
_hideReadTime = hideReadTime;
|
||||
_newRequirePremium = newRequirePremium;
|
||||
_newChargeStars = newChargeStars;
|
||||
}
|
||||
|
||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
|
||||
data.match([&](const MTPDglobalPrivacySettings &data) {
|
||||
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
||||
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
||||
? UnarchiveOnNewMessage::None
|
||||
: data.is_keep_archived_folders()
|
||||
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
||||
: UnarchiveOnNewMessage::AnyUnmuted;
|
||||
_hideReadTime = data.is_hide_read_marks();
|
||||
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
||||
});
|
||||
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
|
||||
const auto &data = settings.data();
|
||||
_archiveAndMute = data.is_archive_and_mute_new_noncontact_peers();
|
||||
_unarchiveOnNewMessage = data.is_keep_archived_unmuted()
|
||||
? UnarchiveOnNewMessage::None
|
||||
: data.is_keep_archived_folders()
|
||||
? UnarchiveOnNewMessage::NotInFoldersUnmuted
|
||||
: UnarchiveOnNewMessage::AnyUnmuted;
|
||||
_hideReadTime = data.is_hide_read_marks();
|
||||
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
|
||||
_newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -23,6 +23,10 @@ enum class UnarchiveOnNewMessage {
|
||||
AnyUnmuted,
|
||||
};
|
||||
|
||||
[[nodiscard]] PeerId ParsePaidReactionShownPeer(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPPaidReactionPrivacy &value);
|
||||
|
||||
class GlobalPrivacy final {
|
||||
public:
|
||||
explicit GlobalPrivacy(not_null<ApiWrap*> api);
|
||||
@@ -45,23 +49,28 @@ public:
|
||||
[[nodiscard]] bool hideReadTimeCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
|
||||
|
||||
void updateNewRequirePremium(bool value);
|
||||
[[nodiscard]] bool newRequirePremiumCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
|
||||
|
||||
void loadPaidReactionAnonymous();
|
||||
void updatePaidReactionAnonymous(bool value);
|
||||
[[nodiscard]] bool paidReactionAnonymousCurrent() const;
|
||||
[[nodiscard]] rpl::producer<bool> paidReactionAnonymous() const;
|
||||
[[nodiscard]] int newChargeStarsCurrent() const;
|
||||
[[nodiscard]] rpl::producer<int> newChargeStars() const;
|
||||
|
||||
void updateMessagesPrivacy(bool requirePremium, int chargeStars);
|
||||
|
||||
void loadPaidReactionShownPeer();
|
||||
void updatePaidReactionShownPeer(PeerId shownPeer);
|
||||
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
|
||||
[[nodiscard]] rpl::producer<PeerId> paidReactionShownPeer() const;
|
||||
|
||||
private:
|
||||
void apply(const MTPGlobalPrivacySettings &data);
|
||||
void apply(const MTPGlobalPrivacySettings &settings);
|
||||
|
||||
void update(
|
||||
bool archiveAndMute,
|
||||
UnarchiveOnNewMessage unarchiveOnNewMessage,
|
||||
bool hideReadTime,
|
||||
bool newRequirePremium);
|
||||
bool newRequirePremium,
|
||||
int newChargeStars);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
@@ -72,9 +81,10 @@ private:
|
||||
rpl::variable<bool> _showArchiveAndMute = false;
|
||||
rpl::variable<bool> _hideReadTime = false;
|
||||
rpl::variable<bool> _newRequirePremium = false;
|
||||
rpl::variable<bool> _paidReactionAnonymous = false;
|
||||
rpl::variable<int> _newChargeStars = 0;
|
||||
rpl::variable<PeerId> _paidReactionShownPeer = false;
|
||||
std::vector<Fn<void()>> _callbacks;
|
||||
bool _paidReactionAnonymousLoaded = false;
|
||||
bool _paidReactionShownPeerLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
| (info.thumb ? Flag::f_thumb : Flag())
|
||||
| (item->groupId() ? Flag::f_nosound_video : Flag())
|
||||
| (info.attachedStickers.empty() ? Flag::f_stickers : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag());
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (info.videoCover ? Flag::f_video_cover : Flag());
|
||||
const auto document = item->media()->document();
|
||||
return MTP_inputMediaUploadedDocument(
|
||||
MTP_flags(flags),
|
||||
@@ -121,6 +122,8 @@ MTPInputMedia PrepareUploadedDocument(
|
||||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
info.videoCover.value_or(MTPInputPhoto()),
|
||||
MTP_int(0), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() {
|
||||
_requestId = _history->session().api().request(MTPmessages_Search(
|
||||
MTP_flags((fromPeer ? Flag::f_from_id : Flag())
|
||||
| (savedPeer ? Flag::f_saved_peer_id : Flag())
|
||||
| (_request.topMsgId ? Flag::f_top_msg_id : Flag())
|
||||
| (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
|
||||
_history->peer->input,
|
||||
MTP_string(_request.query),
|
||||
@@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() {
|
||||
MTP_vector_from_range(_request.tags | ranges::views::transform(
|
||||
Data::ReactionToMTP
|
||||
)),
|
||||
MTPint(), // top_msg_id
|
||||
MTP_int(_request.topMsgId), // top_msg_id
|
||||
MTP_inputMessagesFilterEmpty(),
|
||||
MTP_int(0), // min_date
|
||||
MTP_int(0), // max_date
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
QString query;
|
||||
PeerData *from = nullptr;
|
||||
std::vector<Data::ReactionId> tags;
|
||||
MsgId topMsgId;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Request &,
|
||||
|
||||
@@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
|
||||
}
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::disableMigrated() {
|
||||
_migratedSearch = std::nullopt;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::addFound(const FoundMessages &data) {
|
||||
for (const auto &message : data.messages) {
|
||||
_concatedFound.messages.push_back(message);
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
void clear();
|
||||
void search(const Request &search);
|
||||
void searchMore();
|
||||
void disableMigrated();
|
||||
|
||||
[[nodiscard]] const FoundMessages &messages() const;
|
||||
[[nodiscard]] const Request &request() const;
|
||||
|
||||
@@ -37,7 +37,7 @@ Polls::Polls(not_null<ApiWrap*> api)
|
||||
|
||||
void Polls::create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
_session->api().sendAction(action);
|
||||
@@ -59,6 +59,9 @@ void Polls::create(
|
||||
history->startSavingCloudDraft(topicRootId);
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
@@ -71,6 +74,10 @@ void Polls::create(
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
@@ -93,7 +100,8 @@ void Polls::create(
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
|
||||
void create(
|
||||
const PollData &data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail);
|
||||
void sendVotes(
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_text_entities.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_peer_values.h"
|
||||
@@ -377,15 +378,15 @@ const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
rpl::producer<> Premium::somePremiumRequiredResolved() const {
|
||||
return _somePremiumRequiredResolved.events();
|
||||
rpl::producer<> Premium::someMessageMoneyRestrictionsResolved() const {
|
||||
return _someMessageMoneyRestrictionsResolved.events();
|
||||
}
|
||||
|
||||
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
||||
_resolvePremiumRequiredUsers.emplace(user);
|
||||
if (!_premiumRequiredRequestScheduled
|
||||
&& _resolvePremiumRequestedUsers.empty()) {
|
||||
_premiumRequiredRequestScheduled = true;
|
||||
void Premium::resolveMessageMoneyRestrictions(not_null<UserData*> user) {
|
||||
_resolveMessageMoneyRequiredUsers.emplace(user);
|
||||
if (!_messageMoneyRequestScheduled
|
||||
&& _resolveMessageMoneyRequestedUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = true;
|
||||
crl::on_main(_session, [=] {
|
||||
requestPremiumRequiredSlice();
|
||||
});
|
||||
@@ -393,50 +394,65 @@ void Premium::resolvePremiumRequired(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
void Premium::requestPremiumRequiredSlice() {
|
||||
_premiumRequiredRequestScheduled = false;
|
||||
if (!_resolvePremiumRequestedUsers.empty()
|
||||
|| _resolvePremiumRequiredUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = false;
|
||||
if (!_resolveMessageMoneyRequestedUsers.empty()
|
||||
|| _resolveMessageMoneyRequiredUsers.empty()) {
|
||||
return;
|
||||
}
|
||||
constexpr auto kPerRequest = 100;
|
||||
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
|
||||
auto users = MTP_vector_from_range(_resolveMessageMoneyRequiredUsers
|
||||
| ranges::views::transform(&UserData::inputUser));
|
||||
if (users.v.size() > kPerRequest) {
|
||||
auto shortened = users.v;
|
||||
shortened.resize(kPerRequest);
|
||||
users = MTP_vector<MTPInputUser>(std::move(shortened));
|
||||
const auto from = begin(_resolvePremiumRequiredUsers);
|
||||
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
|
||||
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
|
||||
const auto from = begin(_resolveMessageMoneyRequiredUsers);
|
||||
_resolveMessageMoneyRequestedUsers = { from, from + kPerRequest };
|
||||
_resolveMessageMoneyRequiredUsers.erase(from, from + kPerRequest);
|
||||
} else {
|
||||
_resolvePremiumRequestedUsers
|
||||
= base::take(_resolvePremiumRequiredUsers);
|
||||
_resolveMessageMoneyRequestedUsers
|
||||
= base::take(_resolveMessageMoneyRequiredUsers);
|
||||
}
|
||||
const auto finish = [=](const QVector<MTPBool> &list) {
|
||||
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
|
||||
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
|
||||
constexpr auto mask = me | known;
|
||||
const auto finish = [=](const QVector<MTPRequirementToContact> &list) {
|
||||
|
||||
auto index = 0;
|
||||
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
|
||||
const auto require = (index < list.size())
|
||||
&& mtpIsTrue(list[index++]);
|
||||
user->setFlags((user->flags() & ~mask)
|
||||
| known
|
||||
| (require ? me : UserDataFlag()));
|
||||
for (const auto &user : base::take(_resolveMessageMoneyRequestedUsers)) {
|
||||
const auto set = [&](bool requirePremium, int stars) {
|
||||
using Flag = UserDataFlag;
|
||||
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||
constexpr auto hasPrem = Flag::HasRequirePremiumToWrite;
|
||||
constexpr auto hasStars = Flag::HasStarsPerMessage;
|
||||
user->setStarsPerMessage(stars);
|
||||
user->setFlags((user->flags() & ~(me | hasPrem | hasStars))
|
||||
| known
|
||||
| (requirePremium ? (me | hasPrem) : Flag())
|
||||
| (stars ? hasStars : Flag()));
|
||||
};
|
||||
if (index >= list.size()) {
|
||||
set(false, 0);
|
||||
continue;
|
||||
}
|
||||
list[index++].match([&](const MTPDrequirementToContactEmpty &) {
|
||||
set(false, 0);
|
||||
}, [&](const MTPDrequirementToContactPremium &) {
|
||||
set(true, 0);
|
||||
}, [&](const MTPDrequirementToContactPaidMessages &data) {
|
||||
set(false, data.vstars_amount().v);
|
||||
});
|
||||
}
|
||||
if (!_premiumRequiredRequestScheduled
|
||||
&& !_resolvePremiumRequiredUsers.empty()) {
|
||||
_premiumRequiredRequestScheduled = true;
|
||||
if (!_messageMoneyRequestScheduled
|
||||
&& !_resolveMessageMoneyRequiredUsers.empty()) {
|
||||
_messageMoneyRequestScheduled = true;
|
||||
crl::on_main(_session, [=] {
|
||||
requestPremiumRequiredSlice();
|
||||
});
|
||||
}
|
||||
_somePremiumRequiredResolved.fire({});
|
||||
_someMessageMoneyRestrictionsResolved.fire({});
|
||||
};
|
||||
_session->api().request(
|
||||
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
|
||||
).done([=](const MTPVector<MTPBool> &result) {
|
||||
MTPusers_GetRequirementsToContact(std::move(users))
|
||||
).done([=](const MTPVector<MTPRequirementToContact> &result) {
|
||||
finish(result.v);
|
||||
}).fail([=] {
|
||||
finish({});
|
||||
@@ -463,10 +479,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
||||
for (const auto &tlOption : result.v) {
|
||||
const auto &data = tlOption.data();
|
||||
tlMapOptions[data.vusers().v].push_back(tlOption);
|
||||
if (qs(data.vcurrency()) == Ui::kCreditsCurrency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto token = Token{ data.vusers().v, data.vmonths().v };
|
||||
_stores[token] = Store{
|
||||
.amount = data.vamount().v,
|
||||
.currency = qs(data.vcurrency()),
|
||||
.product = qs(data.vstore_product().value_or_empty()),
|
||||
.quantity = data.vstore_quantity().value_or_empty(),
|
||||
};
|
||||
@@ -475,14 +495,14 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
|
||||
}
|
||||
}
|
||||
for (const auto &[amount, tlOptions] : tlMapOptions) {
|
||||
if (amount == 1 && _optionsForOnePerson.currency.isEmpty()) {
|
||||
_optionsForOnePerson.currency = qs(
|
||||
tlOptions.front().data().vcurrency());
|
||||
if (amount == 1 && _optionsForOnePerson.currencies.empty()) {
|
||||
for (const auto &option : tlOptions) {
|
||||
_optionsForOnePerson.months.push_back(
|
||||
option.data().vmonths().v);
|
||||
_optionsForOnePerson.totalCosts.push_back(
|
||||
option.data().vamount().v);
|
||||
_optionsForOnePerson.currencies.push_back(
|
||||
qs(option.data().vcurrency()));
|
||||
}
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
@@ -509,7 +529,7 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::applyPrepaid(
|
||||
_api.request(MTPpayments_LaunchPrepaidGiveaway(
|
||||
_peer->input,
|
||||
MTP_long(prepaidId),
|
||||
invoice.creditsAmount
|
||||
invoice.giveawayCredits
|
||||
? Payments::InvoiceCreditsGiveawayToTL(invoice)
|
||||
: Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
@@ -540,7 +560,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
const auto token = Token{ users, months };
|
||||
const auto &store = _stores[token];
|
||||
return Payments::InvoicePremiumGiftCode{
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.currency = store.currency,
|
||||
.storeProduct = store.product,
|
||||
.randomId = randomId,
|
||||
.amount = store.amount,
|
||||
@@ -553,14 +573,15 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
if (!_optionsForOnePerson.currencies.empty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
Assert(i < _optionsForOnePerson.currencies.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.currency = _optionsForOnePerson.currencies[i],
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
@@ -581,7 +602,7 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
MTP_int(_optionsForOnePerson.months[i]),
|
||||
MTPstring(),
|
||||
MTPint(),
|
||||
MTP_string(_optionsForOnePerson.currency),
|
||||
MTP_string(_optionsForOnePerson.currencies[i]),
|
||||
MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
|
||||
}
|
||||
_subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
|
||||
@@ -601,7 +622,7 @@ auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
auto gifts = std::vector<Data::StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
@@ -620,7 +641,8 @@ auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
auto PremiumGiftCodeOptions::starGifts() const
|
||||
-> const std::vector<Data::StarGift> & {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
@@ -693,28 +715,38 @@ rpl::producer<rpl::no_value, QString> SponsoredToggle::setToggled(bool v) {
|
||||
};
|
||||
}
|
||||
|
||||
RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||
not_null<PeerData*> peer,
|
||||
History *maybeHistory) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user
|
||||
|| !user->someRequirePremiumToWrite()
|
||||
|| user->session().premium()) {
|
||||
return RequirePremiumState::No;
|
||||
} else if (user->requirePremiumToWriteKnown()) {
|
||||
return user->meRequiresPremiumToWrite()
|
||||
? RequirePremiumState::Yes
|
||||
: RequirePremiumState::No;
|
||||
} else if (user->flags() & UserDataFlag::MutualContact) {
|
||||
return RequirePremiumState::No;
|
||||
} else if (!maybeHistory) {
|
||||
return RequirePremiumState::Unknown;
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
return {
|
||||
.starsPerMessage = channel->starsPerMessageChecked(),
|
||||
.known = true,
|
||||
};
|
||||
}
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return { .known = true };
|
||||
} else if (user->messageMoneyRestrictionsKnown()) {
|
||||
return {
|
||||
.starsPerMessage = user->starsPerMessageChecked(),
|
||||
.premiumRequired = (user->requiresPremiumToWrite()
|
||||
&& !user->session().premium()),
|
||||
.known = true,
|
||||
};
|
||||
} else if (user->hasStarsPerMessage()) {
|
||||
return {};
|
||||
} else if (!user->hasRequirePremiumToWrite()) {
|
||||
return { .known = true };
|
||||
} else if (user->flags() & UserDataFlag::MutualContact) {
|
||||
return { .known = true };
|
||||
} else if (!maybeHistory) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto update = [&](bool require) {
|
||||
using Flag = UserDataFlag;
|
||||
constexpr auto known = Flag::RequirePremiumToWriteKnown;
|
||||
constexpr auto me = Flag::MeRequiresPremiumToWrite;
|
||||
constexpr auto known = Flag::MessageMoneyRestrictionsKnown;
|
||||
constexpr auto me = Flag::RequiresPremiumToWrite;
|
||||
user->setFlags((user->flags() & ~me)
|
||||
| known
|
||||
| (require ? me : Flag()));
|
||||
@@ -726,16 +758,19 @@ RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
const auto item = view->data();
|
||||
if (!item->out() && !item->isService()) {
|
||||
update(false);
|
||||
return RequirePremiumState::No;
|
||||
return { .known = true };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (user->isContact() // Here we know, that we're not in his contacts.
|
||||
&& maybeHistory->loadedAtTop() // And no incoming messages.
|
||||
&& maybeHistory->loadedAtBottom()) {
|
||||
update(true);
|
||||
return {
|
||||
.premiumRequired = !user->session().premium(),
|
||||
.known = true,
|
||||
};
|
||||
}
|
||||
return RequirePremiumState::Unknown;
|
||||
return {};
|
||||
}
|
||||
|
||||
rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
@@ -758,41 +793,100 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.birthday = data.is_birthday(),
|
||||
};
|
||||
return gift.match([&](const MTPDstarGift &data) {
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return std::optional<Data::StarGift>();
|
||||
}
|
||||
return std::optional<Data::StarGift>(Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.starsToUpgrade = int64(data.vupgrade_stars().value_or_empty()),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.upgradable = data.vupgrade_stars().has_value(),
|
||||
.birthday = data.is_birthday(),
|
||||
});
|
||||
}, [&](const MTPDstarGiftUnique &data) {
|
||||
const auto total = data.vavailability_total().v;
|
||||
auto model = std::optional<Data::UniqueGiftModel>();
|
||||
auto pattern = std::optional<Data::UniqueGiftPattern>();
|
||||
for (const auto &attribute : data.vattributes().v) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
model = FromTL(session, data);
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
pattern = FromTL(session, data);
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
});
|
||||
}
|
||||
if (!model
|
||||
|| !model->document->sticker()
|
||||
|| !pattern
|
||||
|| !pattern->document->sticker()) {
|
||||
return std::optional<Data::StarGift>();
|
||||
}
|
||||
auto result = Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
|
||||
.id = data.vid().v,
|
||||
.slug = qs(data.vslug()),
|
||||
.title = qs(data.vtitle()),
|
||||
.ownerAddress = qs(data.vowner_address().value_or_empty()),
|
||||
.ownerName = qs(data.vowner_name().value_or_empty()),
|
||||
.ownerId = (data.vowner_id()
|
||||
? peerFromMTP(*data.vowner_id())
|
||||
: PeerId()),
|
||||
.number = data.vnum().v,
|
||||
.model = *model,
|
||||
.pattern = *pattern,
|
||||
}),
|
||||
.document = model->document,
|
||||
.limitedLeft = (total - data.vavailability_issued().v),
|
||||
.limitedCount = total,
|
||||
};
|
||||
const auto unique = result.unique.get();
|
||||
for (const auto &attribute : data.vattributes().v) {
|
||||
attribute.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
unique->backdrop = FromTL(data);
|
||||
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
unique->originalDetails = FromTL(session, data);
|
||||
});
|
||||
}
|
||||
return std::make_optional(result);
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
} else if (const auto unique = parsed->unique.get()) {
|
||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
}
|
||||
return UserStarGift{
|
||||
using Id = Data::SavedStarGiftId;
|
||||
return Data::SavedStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.manageId = (to->isUser()
|
||||
? Id::User(data.vmsg_id().value_or_empty())
|
||||
: Id::Chat(to, data.vsaved_id().value_or_empty())),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
@@ -802,15 +896,71 @@ std::optional<UserStarGift> FromTL(
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsUpgradedBySender = int64(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
? peerFromMTP(*data.vfrom_id())
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.pinned = data.is_pinned_to_top(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeModel &data) {
|
||||
auto result = Data::UniqueGiftModel{
|
||||
.document = session->data().processDocument(data.vdocument()),
|
||||
};
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftPattern FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributePattern &data) {
|
||||
auto result = Data::UniqueGiftPattern{
|
||||
.document = session->data().processDocument(data.vdocument()),
|
||||
};
|
||||
result.document->overrideEmojiUsesTextColor(true);
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftBackdrop FromTL(const MTPDstarGiftAttributeBackdrop &data) {
|
||||
auto result = Data::UniqueGiftBackdrop();
|
||||
result.name = qs(data.vname());
|
||||
result.rarityPermille = data.vrarity_permille().v;
|
||||
result.centerColor = Ui::ColorFromSerialized(
|
||||
data.vcenter_color());
|
||||
result.edgeColor = Ui::ColorFromSerialized(
|
||||
data.vedge_color());
|
||||
result.patternColor = Ui::ColorFromSerialized(
|
||||
data.vpattern_color());
|
||||
result.textColor = Ui::ColorFromSerialized(
|
||||
data.vtext_color());
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::UniqueGiftOriginalDetails FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
auto result = Data::UniqueGiftOriginalDetails();
|
||||
result.date = data.vdate().v;
|
||||
result.senderId = data.vsender_id()
|
||||
? peerFromMTP(*data.vsender_id())
|
||||
: PeerId();
|
||||
result.recipientId = peerFromMTP(data.vrecipient_id());
|
||||
result.message = data.vmessage()
|
||||
? ParseTextWithEntities(session, *data.vmessage())
|
||||
: TextWithEntities();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#pragma once
|
||||
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class History;
|
||||
@@ -73,34 +74,6 @@ struct GiftOptionData {
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 starsConverted = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
@@ -143,8 +116,9 @@ public:
|
||||
[[nodiscard]] auto subscriptionOptions() const
|
||||
-> const Data::PremiumSubscriptionOptions &;
|
||||
|
||||
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
|
||||
void resolvePremiumRequired(not_null<UserData*> user);
|
||||
[[nodiscard]] auto someMessageMoneyRestrictionsResolved() const
|
||||
-> rpl::producer<>;
|
||||
void resolveMessageMoneyRestrictions(not_null<UserData*> user);
|
||||
|
||||
private:
|
||||
void reloadPromo();
|
||||
@@ -193,10 +167,10 @@ private:
|
||||
|
||||
Data::PremiumSubscriptionOptions _subscriptionOptions;
|
||||
|
||||
rpl::event_stream<> _somePremiumRequiredResolved;
|
||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
|
||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
|
||||
bool _premiumRequiredRequestScheduled = false;
|
||||
rpl::event_stream<> _someMessageMoneyRestrictionsResolved;
|
||||
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequiredUsers;
|
||||
base::flat_set<not_null<UserData*>> _resolveMessageMoneyRequestedUsers;
|
||||
bool _messageMoneyRequestScheduled = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -223,7 +197,7 @@ public:
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
[[nodiscard]] const std::vector<Data::StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
@@ -235,6 +209,7 @@ private:
|
||||
};
|
||||
struct Store final {
|
||||
uint64 amount = 0;
|
||||
QString currency;
|
||||
QString product;
|
||||
int quantity = 0;
|
||||
};
|
||||
@@ -245,7 +220,7 @@ private:
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
std::vector<QString> currencies;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
std::vector<int> _availablePresets;
|
||||
@@ -253,7 +228,7 @@ private:
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
std::vector<Data::StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
@@ -271,23 +246,43 @@ private:
|
||||
|
||||
};
|
||||
|
||||
enum class RequirePremiumState {
|
||||
Unknown,
|
||||
Yes,
|
||||
No,
|
||||
struct MessageMoneyRestriction {
|
||||
int starsPerMessage = 0;
|
||||
bool premiumRequired = false;
|
||||
bool known = false;
|
||||
|
||||
explicit operator bool() const {
|
||||
return starsPerMessage != 0 || premiumRequired;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const MessageMoneyRestriction &,
|
||||
const MessageMoneyRestriction &) = default;
|
||||
};
|
||||
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
|
||||
[[nodiscard]] MessageMoneyRestriction ResolveMessageMoneyRestrictions(
|
||||
not_null<PeerData*> peer,
|
||||
History *maybeHistory);
|
||||
|
||||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
[[nodiscard]] std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift);
|
||||
|
||||
[[nodiscard]] Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeModel &data);
|
||||
[[nodiscard]] Data::UniqueGiftPattern FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributePattern &data);
|
||||
[[nodiscard]] Data::UniqueGiftBackdrop FromTL(
|
||||
const MTPDstarGiftAttributeBackdrop &data);
|
||||
[[nodiscard]] Data::UniqueGiftOriginalDetails FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPDstarGiftAttributeOriginalDetails &data);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -26,7 +26,7 @@ Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||
}();
|
||||
return {
|
||||
.duration = Ui::FormatTTL(months * 86400 * 31),
|
||||
.discount = discount
|
||||
.discount = (discount > 0)
|
||||
? QString::fromUtf8("\xe2\x88\x92%1%").arg(discount)
|
||||
: QString(),
|
||||
.costPerMonth = Ui::FillAmountAndCurrency(
|
||||
|
||||
@@ -24,15 +24,26 @@ template<typename Option>
|
||||
if (tlOpts.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto monthlyAmountPerCurrency = base::flat_map<QString, int>();
|
||||
auto result = Data::PremiumSubscriptionOptions();
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto monthlyAmount = [&](const QString ¤cy) -> int {
|
||||
const auto it = monthlyAmountPerCurrency.find(currency);
|
||||
if (it != end(monthlyAmountPerCurrency)) {
|
||||
return it->second;
|
||||
}
|
||||
const auto &min = ranges::min_element(
|
||||
tlOpts,
|
||||
ranges::less(),
|
||||
[](const Option &o) { return o.data().vamount().v; }
|
||||
[&](const Option &o) {
|
||||
return currency == qs(o.data().vcurrency())
|
||||
? o.data().vamount().v
|
||||
: std::numeric_limits<int64_t>::max();
|
||||
}
|
||||
)->data();
|
||||
return min.vamount().v / float64(min.vmonths().v);
|
||||
}();
|
||||
const auto monthly = min.vamount().v / float64(min.vmonths().v);
|
||||
monthlyAmountPerCurrency.emplace(currency, monthly);
|
||||
return monthly;
|
||||
};
|
||||
result.reserve(tlOpts.size());
|
||||
for (const auto &tlOption : tlOpts) {
|
||||
const auto &option = tlOption.data();
|
||||
@@ -45,7 +56,7 @@ template<typename Option>
|
||||
const auto currency = qs(option.vcurrency());
|
||||
result.push_back(CreateSubscriptionOption(
|
||||
months,
|
||||
monthlyAmount,
|
||||
monthlyAmount(currency),
|
||||
amount,
|
||||
currency,
|
||||
botUrl));
|
||||
|
||||
@@ -95,7 +95,9 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
@@ -111,6 +113,10 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
histories.sendPreparedMessage(
|
||||
@@ -129,7 +135,8 @@ void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId);
|
||||
@@ -160,7 +167,7 @@ void SendExistingMedia(
|
||||
? (*localMessageId)
|
||||
: session->data().nextLocalMessageId());
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
const auto &action = message.action;
|
||||
auto &action = message.action;
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
@@ -190,7 +197,9 @@ void SendExistingMedia(
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||
}
|
||||
const auto captionText = caption.text;
|
||||
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
@@ -206,6 +215,10 @@ void SendExistingMedia(
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -216,6 +229,7 @@ void SendExistingMedia(
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, media, caption);
|
||||
@@ -240,7 +254,8 @@ void SendExistingMedia(
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.code() == 400
|
||||
@@ -272,7 +287,9 @@ void SendExistingDocument(
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTPint(), // ttl_seconds
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
};
|
||||
SendExistingMedia(
|
||||
@@ -339,7 +356,7 @@ bool SendDice(MessageToSend &message) {
|
||||
message.action.generateLocal = true;
|
||||
|
||||
|
||||
const auto &action = message.action;
|
||||
auto &action = message.action;
|
||||
api->sendAction(action);
|
||||
|
||||
const auto newId = FullMsgId(
|
||||
@@ -378,6 +395,13 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -388,6 +412,7 @@ bool SendDice(MessageToSend &message) {
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaDice(
|
||||
@@ -409,7 +434,8 @@ bool SendDice(MessageToSend &message) {
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
@@ -547,9 +573,12 @@ void SendConfirmedFile(
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
@@ -557,9 +586,12 @@ void SendConfirmedFile(
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->videoCover ? Flag::f_video_cover : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
file->videoCover ? file->videoCover->photo : MTPPhoto(),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
@@ -571,6 +603,8 @@ void SendConfirmedFile(
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
@@ -600,6 +634,9 @@ void SendConfirmedFile(
|
||||
.replyTo = file->to.replyTo,
|
||||
.date = NewMessageDate(file->to.options),
|
||||
.shortcutId = file->to.options.shortcutId,
|
||||
.starsPaid = std::min(
|
||||
history->peer->starsPerMessageChecked(),
|
||||
file->to.options.starsApproved),
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.groupedId = groupId,
|
||||
.effectId = file->to.options.effectId,
|
||||
|
||||
@@ -229,7 +229,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
}
|
||||
|
||||
MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
not_null<Main::Session*> session,
|
||||
Main::Session *session,
|
||||
const EntitiesInText &entities,
|
||||
ConvertOption option) {
|
||||
auto v = QVector<MTPMessageEntity>();
|
||||
@@ -283,6 +283,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
v.push_back(MTP_messageEntityMention(offset, length));
|
||||
} break;
|
||||
case EntityType::MentionName: {
|
||||
Assert(session != nullptr);
|
||||
const auto valid = MentionNameEntity(
|
||||
session,
|
||||
offset,
|
||||
@@ -344,4 +345,14 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
return MTP_vector<MTPMessageEntity>(std::move(v));
|
||||
}
|
||||
|
||||
TextWithEntities ParseTextWithEntities(
|
||||
Main::Session *session,
|
||||
const MTPTextWithEntities &text) {
|
||||
const auto &data = text.data();
|
||||
return {
|
||||
.text = qs(data.vtext()),
|
||||
.entities = EntitiesFromMTP(session, data.ventities().v),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -25,8 +25,12 @@ enum class ConvertOption {
|
||||
const QVector<MTPMessageEntity> &entities);
|
||||
|
||||
[[nodiscard]] MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
not_null<Main::Session*> session,
|
||||
Main::Session *session,
|
||||
const EntitiesInText &entities,
|
||||
ConvertOption option = ConvertOption::WithLocal);
|
||||
|
||||
[[nodiscard]] TextWithEntities ParseTextWithEntities(
|
||||
Main::Session *session,
|
||||
const MTPTextWithEntities &text);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -1218,7 +1218,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1255,7 +1257,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MTPFactCheck(),
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -2707,8 +2711,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updatePaidReactionPrivacy: {
|
||||
const auto &data = update.c_updatePaidReactionPrivacy();
|
||||
_session->api().globalPrivacy().updatePaidReactionAnonymous(
|
||||
mtpIsTrue(data.vprivate()));
|
||||
_session->api().globalPrivacy().updatePaidReactionShownPeer(
|
||||
Api::ParsePaidReactionShownPeer(_session, data.vprivate()));
|
||||
} break;
|
||||
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
case Key::NoPaidMessages: return MTP_inputPrivacyKeyNoPaidMessages();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
@@ -241,6 +242,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
case mtpc_privacyKeyNoPaidMessages:
|
||||
case mtpc_inputPrivacyKeyNoPaidMessages: return Key::NoPaidMessages;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
NoPaidMessages,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
|
||||
@@ -142,6 +142,16 @@ void ShowChannelsLimitBox(not_null<PeerData*> peer) {
|
||||
action.replaceMediaOf);
|
||||
}
|
||||
|
||||
[[nodiscard]] QString FormatVideoTimestamp(TimeId seconds) {
|
||||
const auto minutes = seconds / 60;
|
||||
const auto hours = minutes / 60;
|
||||
return hours
|
||||
? u"%1h%2m%3s"_q.arg(hours).arg(minutes % 60).arg(seconds % 60)
|
||||
: minutes
|
||||
? u"%1m%2s"_q.arg(minutes).arg(seconds % 60)
|
||||
: QString::number(seconds);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ApiWrap::ApiWrap(not_null<Main::Session*> session)
|
||||
@@ -513,6 +523,7 @@ void ApiWrap::sendMessageFail(
|
||||
uint64 randomId,
|
||||
FullMsgId itemId) {
|
||||
const auto show = ShowForPeer(peer);
|
||||
const auto paidStarsPrefix = u"ALLOW_PAYMENT_REQUIRED_"_q;
|
||||
if (show && error == u"PEER_FLOOD"_q) {
|
||||
show->showBox(
|
||||
Ui::MakeInformBox(
|
||||
@@ -567,6 +578,19 @@ void ApiWrap::sendMessageFail(
|
||||
if (show) {
|
||||
show->showToast(tr::lng_error_schedule_limit(tr::now));
|
||||
}
|
||||
} else if (error.startsWith(paidStarsPrefix)) {
|
||||
if (show) {
|
||||
show->showToast(
|
||||
u"Payment requirements changed. Please, try again."_q);
|
||||
}
|
||||
if (const auto stars = error.mid(paidStarsPrefix.size()).toInt()) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
user->setStarsPerMessage(stars);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
channel->setStarsPerMessage(stars);
|
||||
}
|
||||
}
|
||||
peer->updateFull();
|
||||
}
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
Assert(randomId != 0);
|
||||
@@ -717,7 +741,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
bool forceNonPublicLink,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
@@ -769,19 +794,6 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
: linkThreadId
|
||||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto document = media->document()) {
|
||||
if (document->isVideoMessage()) {
|
||||
return u"https://telesco.pe/"_q + query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
@@ -803,7 +815,14 @@ QString ApiWrap::exportDirectMessageLink(
|
||||
_unlikelyMessageLinks.emplace_or_assign(itemId, link);
|
||||
}
|
||||
}).send();
|
||||
return current;
|
||||
const auto addTimestamp = channel->hasUsername()
|
||||
&& !inRepliesContext
|
||||
&& videoTimestamp.has_value();
|
||||
const auto addedSeparator = (current.indexOf('?') >= 0) ? '&' : '?';
|
||||
const auto addedTimestamp = addTimestamp
|
||||
? (addedSeparator + u"t="_q + FormatVideoTimestamp(*videoTimestamp))
|
||||
: QString();
|
||||
return current + addedTimestamp;
|
||||
}
|
||||
|
||||
QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
|
||||
@@ -1772,7 +1791,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
||||
_channelAmInRequests.emplace(channel, requestId);
|
||||
|
||||
using Flag = ChannelDataFlag;
|
||||
chatParticipants().loadSimilarChannels(channel);
|
||||
chatParticipants().loadSimilarPeers(channel);
|
||||
channel->setFlags(channel->flags() | Flag::SimilarExpanded);
|
||||
}
|
||||
}
|
||||
@@ -3267,13 +3286,13 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
const auto topicRootId = action.replyTo.topicRootId;
|
||||
auto toForward = history->resolveForwardDraft(topicRootId);
|
||||
if (!toForward.items.empty()) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
const auto error = GetErrorForSending(
|
||||
history->peer,
|
||||
{
|
||||
.topicRootId = topicRootId,
|
||||
.forward = &toForward.items,
|
||||
});
|
||||
if (!error.isEmpty()) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3293,7 +3312,7 @@ void ApiWrap::finishForwarding(const SendAction &action) {
|
||||
|
||||
void ApiWrap::forwardMessages(
|
||||
Data::ResolvedForwardDraft &&draft,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
FnMut<void()> &&successCallback) {
|
||||
Expects(!draft.items.empty());
|
||||
|
||||
@@ -3368,9 +3387,17 @@ void ApiWrap::forwardMessages(
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
const auto starsPaid = std::min(
|
||||
action.options.starsApproved,
|
||||
int(ids.size() * peer->starsPerMessageChecked()));
|
||||
auto oneFlags = sendFlags;
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
oneFlags |= SendFlag::f_allow_paid_stars;
|
||||
}
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
MTP_flags(oneFlags),
|
||||
forwardFrom->input,
|
||||
MTP_vector<MTPint>(ids),
|
||||
MTP_vector<MTPlong>(randomIds),
|
||||
@@ -3378,7 +3405,9 @@ void ApiWrap::forwardMessages(
|
||||
MTP_int(topMsgId),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTPint(), // video_timestamp
|
||||
MTP_long(starsPaid)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
@@ -3423,6 +3452,7 @@ void ApiWrap::forwardMessages(
|
||||
.replyTo = { .topicRootId = topMsgId },
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = action.options.starsApproved,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
|
||||
// forwarded messages don't have effects
|
||||
@@ -3516,6 +3546,7 @@ void ApiWrap::sendSharedContact(
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = action.options.starsApproved,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaContact(
|
||||
@@ -3572,6 +3603,18 @@ void ApiWrap::editMedia(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false)
|
||||
: nullptr),
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
@@ -3613,6 +3656,19 @@ void ApiWrap::sendFiles(
|
||||
file.path,
|
||||
file.content,
|
||||
std::move(file.information),
|
||||
(file.videoCover
|
||||
? std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
file.videoCover->path,
|
||||
file.videoCover->content,
|
||||
std::move(file.videoCover->information),
|
||||
nullptr,
|
||||
SendMediaType::Photo,
|
||||
to,
|
||||
TextWithTags(),
|
||||
false,
|
||||
nullptr)
|
||||
: nullptr),
|
||||
uploadWithType,
|
||||
to,
|
||||
caption,
|
||||
@@ -3638,11 +3694,13 @@ void ApiWrap::sendFile(
|
||||
auto caption = TextWithTags();
|
||||
const auto spoiler = false;
|
||||
const auto information = nullptr;
|
||||
const auto videoCover = nullptr;
|
||||
_fileLoader->addTask(std::make_unique<FileLoadTask>(
|
||||
&session(),
|
||||
QString(),
|
||||
fileContent,
|
||||
information,
|
||||
videoCover,
|
||||
type,
|
||||
to,
|
||||
caption,
|
||||
@@ -3849,6 +3907,14 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_allow_paid_stars;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_allow_paid_stars;
|
||||
}
|
||||
lastMessage = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
@@ -3856,6 +3922,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.postAuthor = NewMessagePostAuthor(action),
|
||||
.effectId = action.options.effectId,
|
||||
}, sending, media);
|
||||
@@ -3904,7 +3971,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), done, fail);
|
||||
} else {
|
||||
histories.sendPreparedMessage(
|
||||
@@ -3922,7 +3990,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
MTP_long(action.options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), done, fail);
|
||||
}
|
||||
isFirst = false;
|
||||
@@ -3979,7 +4048,7 @@ void ApiWrap::sendBotStart(
|
||||
void ApiWrap::sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
@@ -4019,6 +4088,13 @@ void ApiWrap::sendInlineResult(
|
||||
if (action.options.hideViaBot) {
|
||||
sendFlags |= SendFlag::f_hide_via;
|
||||
}
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
action.options.starsApproved);
|
||||
if (starsPaid) {
|
||||
action.options.starsApproved -= starsPaid;
|
||||
sendFlags |= SendFlag::f_allow_paid_stars;
|
||||
}
|
||||
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
@@ -4033,6 +4109,7 @@ void ApiWrap::sendInlineResult(
|
||||
.replyTo = action.replyTo,
|
||||
.date = NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.starsPaid = starsPaid,
|
||||
.viaBotId = ((bot && !action.options.hideViaBot)
|
||||
? peerToUser(bot->id)
|
||||
: UserId()),
|
||||
@@ -4056,7 +4133,8 @@ void ApiWrap::sendInlineResult(
|
||||
MTP_string(data->getId()),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
@@ -4136,16 +4214,29 @@ void ApiWrap::uploadAlbumMedia(
|
||||
return;
|
||||
}
|
||||
const auto &fields = document->c_document();
|
||||
const auto mtpCover = data.vvideo_cover();
|
||||
const auto cover = (mtpCover && mtpCover->type() == mtpc_photo)
|
||||
? &(mtpCover->c_photo())
|
||||
: (const MTPDphoto*)nullptr;
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (data.vttl_seconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
| (spoiler ? Flag::f_spoiler : Flag())
|
||||
| (data.vvideo_timestamp() ? Flag::f_video_timestamp : Flag())
|
||||
| (cover ? Flag::f_video_cover : Flag());
|
||||
const auto media = MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
MTP_inputDocument(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
(cover
|
||||
? MTP_inputPhoto(
|
||||
cover->vid(),
|
||||
cover->vaccess_hash(),
|
||||
cover->vfile_reference())
|
||||
: MTPInputPhoto()),
|
||||
MTP_int(data.vvideo_timestamp().value_or_empty()),
|
||||
MTP_int(data.vttl_seconds().value_or_empty()),
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
@@ -4175,6 +4266,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Fn<void(bool)> done) {
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
const auto peer = history->peer;
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
@@ -4184,6 +4276,12 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
const auto updateRecentStickers = Api::HasAttachedStickers(media);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
@@ -4196,10 +4294,10 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
@@ -4223,7 +4321,8 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
MTP_long(options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
@@ -4242,7 +4341,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
Expects(album->options.price > 0);
|
||||
|
||||
const auto groupId = album->groupId;
|
||||
const auto &options = album->options;
|
||||
auto &options = album->options;
|
||||
const auto randomId = album->items.front().randomId;
|
||||
auto medias = album->items | ranges::view::transform([](
|
||||
const SendingAlbum::Item &part) {
|
||||
@@ -4252,6 +4351,7 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
const auto peer = history->peer;
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
@@ -4259,6 +4359,12 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
_session,
|
||||
caption.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
@@ -4271,10 +4377,10 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
@@ -4297,7 +4403,8 @@ void ApiWrap::sendMultiPaidMedia(
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
MTP_long(options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
const auto copy = (*album)->items;
|
||||
@@ -4387,6 +4494,12 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
const auto history = sample->history();
|
||||
const auto replyTo = sample->replyTo();
|
||||
const auto sendAs = album->options.sendAs;
|
||||
const auto starsPaid = std::min(
|
||||
history->peer->starsPerMessageChecked() * int(medias.size()),
|
||||
album->options.starsApproved);
|
||||
if (starsPaid) {
|
||||
album->options.starsApproved -= starsPaid;
|
||||
}
|
||||
using Flag = MTPmessages_SendMultiMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (replyTo ? Flag::f_reply_to : Flag(0))
|
||||
@@ -4399,7 +4512,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0))
|
||||
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
@@ -4415,7 +4529,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
MTP_int(album->options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||
MTP_long(album->options.effectId)
|
||||
MTP_long(album->options.effectId),
|
||||
MTP_long(starsPaid)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
|
||||
@@ -166,7 +166,8 @@ public:
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
bool forceNonPublicLink = false,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
@@ -305,7 +306,7 @@ public:
|
||||
void finishForwarding(const SendAction &action);
|
||||
void forwardMessages(
|
||||
Data::ResolvedForwardDraft &&draft,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
FnMut<void()> &&successCallback = nullptr);
|
||||
void shareContact(
|
||||
const QString &phone,
|
||||
@@ -367,7 +368,7 @@ public:
|
||||
void sendInlineResult(
|
||||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
SendAction action,
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
|
||||
@@ -1078,7 +1078,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
||||
? tr::lng_background_other_group(tr::now)
|
||||
: forChannel()
|
||||
? tr::lng_background_other_channel(tr::now)
|
||||
: (_forPeer && !_fromMessageId)
|
||||
: (_forPeer
|
||||
&& !_fromMessageId
|
||||
&& !_forPeer->starsPerMessageChecked())
|
||||
? tr::lng_background_other_info(
|
||||
tr::now,
|
||||
lt_user,
|
||||
|
||||
@@ -1122,3 +1122,10 @@ profileQrBackgroundRadius: 12px;
|
||||
profileQrIcon: icon{{ "qr_mini", windowActiveTextFg }};
|
||||
profileQrBackgroundMargins: margins(36px, 12px, 36px, 12px);
|
||||
profileQrBackgroundPadding: margins(0px, 24px, 0px, 24px);
|
||||
|
||||
foldersMenu: PopupMenu(popupMenuWithIcons) {
|
||||
maxHeight: 320px;
|
||||
menu: Menu(menuWithIcons) {
|
||||
itemPadding: margins(54px, 8px, 44px, 8px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/filters/edit_filter_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/application.h" // primaryWindow
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -22,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::Bold
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -169,15 +171,22 @@ void ChangeFilterById(
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()] {
|
||||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
controller->showToast((add
|
||||
? tr::lng_filters_toast_add
|
||||
: tr::lng_filters_toast_remove)(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(chat),
|
||||
lt_folder,
|
||||
Ui::Text::Bold(name),
|
||||
Ui::Text::WithEntities));
|
||||
const auto isStatic = name.isStatic;
|
||||
controller->showToast({
|
||||
.text = (add
|
||||
? tr::lng_filters_toast_add
|
||||
: tr::lng_filters_toast_remove)(
|
||||
tr::now,
|
||||
lt_chat,
|
||||
Ui::Text::Bold(chat),
|
||||
lt_folder,
|
||||
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
||||
Ui::Text::WithEntities),
|
||||
.textContext = Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
@@ -274,19 +283,22 @@ void FillChooseFilterMenu(
|
||||
};
|
||||
|
||||
const auto contains = filter.contains(history);
|
||||
const auto title = filter.title();
|
||||
auto item = base::make_unique_q<FilterAction>(
|
||||
menu.get(),
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
Ui::Text::FixAmpersandInAction(filter.title()),
|
||||
Ui::Text::FixAmpersandInAction(title.text.text),
|
||||
std::move(callback)),
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
item->setMarkedText(title.text, QString(), Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
||||
}));
|
||||
|
||||
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
||||
const auto &p = st::menuWithIcons.itemPadding;
|
||||
item->setMinWidth(item->minWidth() + p.left() - p.right() - p.top());
|
||||
const auto action = menu->addAction(std::move(item));
|
||||
action->setEnabled(contains
|
||||
? validator.canRemove(id)
|
||||
|
||||
@@ -817,13 +817,15 @@ CreatePollBox::CreatePollBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
rpl::producer<int> starsRequired,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: _controller(controller)
|
||||
, _chosen(chosen)
|
||||
, _disabled(disabled)
|
||||
, _sendType(sendType)
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; })
|
||||
, _starsRequired(std::move(starsRequired)) {
|
||||
}
|
||||
|
||||
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
||||
@@ -1226,10 +1228,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
_sendMenuDetails());
|
||||
};
|
||||
const auto submit = addButton(
|
||||
(isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button()),
|
||||
tr::lng_polls_create_button(),
|
||||
[=] { isNormal ? send({}) : schedule(); });
|
||||
submit->setText(PaidSendButtonText(_starsRequired.value(), isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button()));
|
||||
const auto sendMenuDetails = [=] {
|
||||
collectError();
|
||||
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
rpl::producer<int> starsRequired,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Details sendMenuDetails);
|
||||
|
||||
@@ -76,6 +77,7 @@ private:
|
||||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
const Fn<SendMenu::Details()> _sendMenuDetails;
|
||||
rpl::variable<int> _starsRequired;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
|
||||
@@ -467,13 +467,16 @@ void EditCaptionBox::rebuildPreview() {
|
||||
}
|
||||
} else {
|
||||
const auto &file = _preparedList.files.front();
|
||||
|
||||
const auto isVideoFile = file.isVideoFile();
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
this,
|
||||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
[=](Ui::AttachActionType type) {
|
||||
return (type != Ui::AttachActionType::EditCover)
|
||||
|| isVideoFile;
|
||||
},
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
@@ -719,7 +722,7 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
controller->uiShow(),
|
||||
&_preparedList.files.front(),
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { rebuildPreview(); });
|
||||
[=](bool ok) { if (ok) rebuildPreview(); });
|
||||
} else {
|
||||
EditPhotoImage(_controller, _photoMedia, hasSpoiler(), [=](
|
||||
Ui::PreparedList &&list) {
|
||||
|
||||
@@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
@@ -21,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "settings/settings_privacy_controllers.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -42,6 +45,8 @@ namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
constexpr auto kStarsMin = 1;
|
||||
constexpr auto kDefaultChargeStars = 10;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
@@ -452,6 +457,143 @@ auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeChargeStarsSlider(
|
||||
QWidget *parent,
|
||||
not_null<const style::MediaSlider*> sliderStyle,
|
||||
not_null<const style::FlatLabel*> labelStyle,
|
||||
int valuesCount,
|
||||
Fn<int(int)> valueByIndex,
|
||||
int value,
|
||||
int maxValue,
|
||||
Fn<void(int)> valueProgress,
|
||||
Fn<void(int)> valueFinished) {
|
||||
auto result = object_ptr<Ui::VerticalLayout>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto labels = raw->add(object_ptr<Ui::RpWidget>(raw));
|
||||
const auto min = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(kStarsMin),
|
||||
*labelStyle);
|
||||
const auto max = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(maxValue),
|
||||
*labelStyle);
|
||||
const auto current = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString::number(value),
|
||||
*labelStyle);
|
||||
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||
const auto slider = raw->add(object_ptr<Ui::MediaSliderWheelless>(
|
||||
raw,
|
||||
*sliderStyle));
|
||||
labels->resize(
|
||||
labels->width(),
|
||||
current->height() + st::defaultVerticalListSkip);
|
||||
struct State {
|
||||
int indexMin = 0;
|
||||
int index = 0;
|
||||
};
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
const auto updateByIndex = [=] {
|
||||
const auto outer = labels->width();
|
||||
const auto minWidth = min->width();
|
||||
const auto maxWidth = max->width();
|
||||
const auto currentWidth = current->width();
|
||||
if (minWidth + maxWidth + currentWidth > outer) {
|
||||
return;
|
||||
}
|
||||
|
||||
min->moveToLeft(0, 0, outer);
|
||||
max->moveToRight(0, 0, outer);
|
||||
current->moveToLeft((outer - current->width()) / 2, 0, outer);
|
||||
};
|
||||
const auto updateByValue = [=](int value) {
|
||||
current->setText(
|
||||
tr::lng_action_gift_for_stars(tr::now, lt_count, value));
|
||||
|
||||
state->index = 0;
|
||||
auto maxIndex = valuesCount - 1;
|
||||
while (state->index < maxIndex) {
|
||||
const auto mid = (state->index + maxIndex) / 2;
|
||||
const auto midValue = valueByIndex(mid);
|
||||
if (midValue == value) {
|
||||
state->index = mid;
|
||||
break;
|
||||
} else if (midValue < value) {
|
||||
state->index = mid + 1;
|
||||
} else {
|
||||
maxIndex = mid - 1;
|
||||
}
|
||||
}
|
||||
updateByIndex();
|
||||
};
|
||||
const auto progress = [=](int value) {
|
||||
updateByValue(value);
|
||||
valueProgress(value);
|
||||
};
|
||||
const auto finished = [=](int value) {
|
||||
updateByValue(value);
|
||||
valueFinished(value);
|
||||
};
|
||||
style::PaletteChanged() | rpl::start_with_next([=] {
|
||||
min->setTextColorOverride(st::windowSubTextFg->c);
|
||||
max->setTextColorOverride(st::windowSubTextFg->c);
|
||||
}, raw->lifetime());
|
||||
updateByValue(value);
|
||||
state->indexMin = 0;
|
||||
|
||||
slider->setPseudoDiscrete(
|
||||
valuesCount,
|
||||
valueByIndex,
|
||||
value,
|
||||
progress,
|
||||
finished,
|
||||
state->indexMin);
|
||||
slider->resize(slider->width(), sliderStyle->seekSize.height());
|
||||
|
||||
raw->widthValue() | rpl::start_with_next([=](int width) {
|
||||
labels->resizeToWidth(width);
|
||||
updateByIndex();
|
||||
}, slider->lifetime());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void EditNoPaidMessagesExceptions(
|
||||
not_null<Window::SessionController*> window,
|
||||
const Api::UserPrivacy::Rule &value) {
|
||||
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
|
||||
&window->session(),
|
||||
tr::lng_messages_privacy_remove_fee(),
|
||||
value.always,
|
||||
std::optional<SpecialRowType>());
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
auto copy = value;
|
||||
auto &setTo = copy.always;
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = false;
|
||||
setTo.miniapps = false;
|
||||
auto &removeFrom = copy.never;
|
||||
for (const auto peer : setTo.peers) {
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
window->session().api().userPrivacy().save(
|
||||
Api::UserPrivacy::Key::NoPaidMessages,
|
||||
copy);
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
window->show(
|
||||
Box<PeerListBox>(std::move(controller), std::move(initBox)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EditPrivacyController::hasOption(Option option) const {
|
||||
@@ -812,19 +954,27 @@ void EditMessagesPrivacyBox(
|
||||
|
||||
constexpr auto kOptionAll = 0;
|
||||
constexpr auto kOptionPremium = 1;
|
||||
constexpr auto kOptionCharge = 2;
|
||||
|
||||
const auto session = &controller->session();
|
||||
const auto allowed = [=] {
|
||||
return controller->session().premium()
|
||||
|| controller->session().appConfig().newRequirePremiumFree();
|
||||
return session->premium()
|
||||
|| session->appConfig().newRequirePremiumFree();
|
||||
};
|
||||
const auto privacy = &controller->session().api().globalPrivacy();
|
||||
const auto privacy = &session->api().globalPrivacy();
|
||||
const auto inner = box->verticalLayout();
|
||||
inner->add(object_ptr<Ui::PlainShadow>(box));
|
||||
|
||||
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
|
||||
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
|
||||
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll);
|
||||
(!allowed()
|
||||
? kOptionAll
|
||||
: privacy->newRequirePremiumCurrent()
|
||||
? kOptionPremium
|
||||
: privacy->newChargeStarsCurrent()
|
||||
? kOptionCharge
|
||||
: kOptionAll));
|
||||
inner->add(
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
inner,
|
||||
@@ -846,6 +996,92 @@ void EditMessagesPrivacyBox(
|
||||
0,
|
||||
st::messagePrivacyBottomSkip));
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
||||
|
||||
const auto available = session->appConfig().paidMessagesAvailable();
|
||||
|
||||
const auto charged = available
|
||||
? inner->add(
|
||||
object_ptr<Ui::Radiobutton>(
|
||||
inner,
|
||||
group,
|
||||
kOptionCharge,
|
||||
tr::lng_messages_privacy_charge(tr::now),
|
||||
st::messagePrivacyCheck),
|
||||
st::settingsSendTypePadding + style::margins(
|
||||
0,
|
||||
st::messagePrivacyBottomSkip,
|
||||
0,
|
||||
st::messagePrivacyBottomSkip))
|
||||
: nullptr;
|
||||
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto savedValue = privacy->newChargeStarsCurrent();
|
||||
|
||||
if (available) {
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_charge_about());
|
||||
|
||||
const auto chargeWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
const auto chargeInner = chargeWrap->entity();
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
|
||||
state->stars = SetupChargeSlider(
|
||||
chargeInner,
|
||||
session->user(),
|
||||
savedValue);
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
Ui::AddSubsectionTitle(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_exceptions());
|
||||
|
||||
const auto key = Api::UserPrivacy::Key::NoPaidMessages;
|
||||
session->api().userPrivacy().reload(key);
|
||||
auto label = session->api().userPrivacy().value(
|
||||
key
|
||||
) | rpl::map([=](const Api::UserPrivacy::Rule &value) {
|
||||
using namespace Settings;
|
||||
const auto always = ExceptionUsersCount(value.always.peers);
|
||||
return always
|
||||
? tr::lng_edit_privacy_exceptions_count(
|
||||
tr::now,
|
||||
lt_count,
|
||||
always)
|
||||
: QString();
|
||||
});
|
||||
|
||||
const auto exceptions = Settings::AddButtonWithLabel(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_remove_fee(),
|
||||
std::move(label),
|
||||
st::settingsButtonNoIcon);
|
||||
|
||||
const auto shower = exceptions->lifetime().make_state<rpl::lifetime>();
|
||||
exceptions->setClickedCallback([=] {
|
||||
*shower = session->api().userPrivacy().value(
|
||||
key
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Api::UserPrivacy::Rule &value) {
|
||||
EditNoPaidMessagesExceptions(controller, value);
|
||||
});
|
||||
});
|
||||
Ui::AddSkip(chargeInner);
|
||||
Ui::AddDividerText(
|
||||
chargeInner,
|
||||
tr::lng_messages_privacy_remove_about());
|
||||
|
||||
using namespace rpl::mappers;
|
||||
chargeWrap->toggleOn(group->value() | rpl::map(_1 == kOptionCharge));
|
||||
chargeWrap->finishAnimating();
|
||||
}
|
||||
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
|
||||
const auto toast = std::make_shared<WeakToast>();
|
||||
const auto showToast = [=] {
|
||||
@@ -875,19 +1111,20 @@ void EditMessagesPrivacyBox(
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
if (!allowed()) {
|
||||
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
|
||||
if (charged) {
|
||||
CreateRadiobuttonLock(charged, st::messagePrivacyCheck);
|
||||
}
|
||||
|
||||
group->setChangedCallback([=](int value) {
|
||||
if (value == kOptionPremium) {
|
||||
if (value == kOptionPremium || value == kOptionCharge) {
|
||||
group->setValue(kOptionAll);
|
||||
showToast();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
|
||||
if (!allowed()) {
|
||||
Ui::AddSkip(inner);
|
||||
Settings::AddButtonWithIcon(
|
||||
inner,
|
||||
@@ -907,8 +1144,12 @@ void EditMessagesPrivacyBox(
|
||||
} else {
|
||||
box->addButton(tr::lng_settings_save(), [=] {
|
||||
if (allowed()) {
|
||||
privacy->updateNewRequirePremium(
|
||||
group->current() == kOptionPremium);
|
||||
const auto value = group->current();
|
||||
const auto premiumRequired = (value == kOptionPremium);
|
||||
const auto chargeStars = (value == kOptionCharge)
|
||||
? state->stars.current()
|
||||
: 0;
|
||||
privacy->updateMessagesPrivacy(premiumRequired, chargeStars);
|
||||
box->closeBox();
|
||||
} else {
|
||||
showToast();
|
||||
@@ -919,3 +1160,78 @@ void EditMessagesPrivacyBox(
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue) {
|
||||
struct State {
|
||||
rpl::variable<int> stars;
|
||||
};
|
||||
const auto group = !peer->isUser();
|
||||
const auto state = container->lifetime().make_state<State>();
|
||||
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
|
||||
state->stars = chargeStars;
|
||||
|
||||
Ui::AddSubsectionTitle(container, group
|
||||
? tr::lng_rights_charge_price()
|
||||
: tr::lng_messages_privacy_price());
|
||||
|
||||
auto values = std::vector<int>();
|
||||
const auto maxStars = peer->session().appConfig().paidMessageStarsMax();
|
||||
if (chargeStars < kStarsMin) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
for (auto i = kStarsMin; i < std::min(100, maxStars); ++i) {
|
||||
values.push_back(i);
|
||||
}
|
||||
for (auto i = 100; i < std::min(1000, maxStars); i += 10) {
|
||||
if (i < chargeStars + 10 && chargeStars < i) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
values.push_back(i);
|
||||
}
|
||||
for (auto i = 1000; i < maxStars + 1; i += 100) {
|
||||
if (i < chargeStars + 100 && chargeStars < i) {
|
||||
values.push_back(chargeStars);
|
||||
}
|
||||
values.push_back(i);
|
||||
}
|
||||
const auto valuesCount = int(values.size());
|
||||
const auto setStars = [=](int value) {
|
||||
state->stars = value;
|
||||
};
|
||||
container->add(
|
||||
MakeChargeStarsSlider(
|
||||
container,
|
||||
&st::settingsScale,
|
||||
&st::settingsScaleLabel,
|
||||
valuesCount,
|
||||
[=](int index) { return values[index]; },
|
||||
chargeStars,
|
||||
maxStars,
|
||||
setStars,
|
||||
setStars),
|
||||
st::boxRowPadding);
|
||||
|
||||
const auto skip = 2 * st::defaultVerticalListSkip;
|
||||
Ui::AddSkip(container, skip);
|
||||
|
||||
auto dollars = state->stars.value() | rpl::map([=](int stars) {
|
||||
const auto ratio = peer->session().appConfig().starsWithdrawRate();
|
||||
const auto dollars = int(base::SafeRound(stars * ratio));
|
||||
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
|
||||
});
|
||||
const auto percent = peer->session().appConfig().paidMessageCommission();
|
||||
Ui::AddDividerText(
|
||||
container,
|
||||
(group
|
||||
? tr::lng_rights_charge_price_about
|
||||
: tr::lng_messages_privacy_price_about)(
|
||||
lt_percent,
|
||||
rpl::single(QString::number(percent / 10.) + '%'),
|
||||
lt_amount,
|
||||
std::move(dollars)));
|
||||
|
||||
return state->stars.value();
|
||||
}
|
||||
|
||||
@@ -169,3 +169,8 @@ private:
|
||||
void EditMessagesPrivacyBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
int savedValue);
|
||||
|
||||
@@ -15,8 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_peer.h"
|
||||
@@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/filter_icons.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
@@ -350,13 +353,17 @@ void EditFilterBox(
|
||||
rpl::variable<bool> hasLinks;
|
||||
rpl::variable<bool> chatlist;
|
||||
rpl::variable<bool> creating;
|
||||
rpl::variable<TextWithEntities> title;
|
||||
rpl::variable<bool> staticTitle;
|
||||
rpl::variable<int> colorIndex;
|
||||
};
|
||||
const auto owner = &window->session().data();
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.rules = filter,
|
||||
.chatlist = filter.chatlist(),
|
||||
.creating = filter.title().isEmpty(),
|
||||
.creating = filter.title().empty(),
|
||||
.title = filter.titleText(),
|
||||
.staticTitle = filter.staticTitle(),
|
||||
});
|
||||
state->colorIndex = filter.colorIndex().value_or(kNoTag);
|
||||
state->links = owner->chatsFilters().chatlistLinks(filter.id()),
|
||||
@@ -394,32 +401,67 @@ void EditFilterBox(
|
||||
tr::lng_filters_edit()));
|
||||
box->setCloseByOutsideClick(false);
|
||||
|
||||
const auto session = &window->session();
|
||||
Data::AmPremiumValue(
|
||||
&window->session()
|
||||
session
|
||||
) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
const auto current = state->title.current();
|
||||
const auto name = content->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::windowFilterNameInput,
|
||||
tr::lng_filters_new_name(),
|
||||
filter.title()),
|
||||
Ui::InputField::Mode::SingleLine,
|
||||
tr::lng_filters_new_name()),
|
||||
st::markdownLinkFieldPadding);
|
||||
InitMessageFieldHandlers(window, name, ChatHelpers::PauseReason::Layer);
|
||||
name->setTextWithTags({
|
||||
current.text,
|
||||
TextUtilities::ConvertEntitiesToTextTags(current.entities),
|
||||
}, Ui::InputField::HistoryAction::Clear);
|
||||
name->setMaxLength(kMaxFilterTitleLength);
|
||||
name->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
name->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
box->getDelegate()->outerContainer(),
|
||||
name,
|
||||
&window->session());
|
||||
|
||||
const auto nameEditing = box->lifetime().make_state<NameEditing>(
|
||||
NameEditing{ name });
|
||||
|
||||
const auto staticTitle = Ui::CreateChild<Ui::LinkButton>(
|
||||
name,
|
||||
QString());
|
||||
staticTitle->setClickedCallback([=] {
|
||||
state->staticTitle = !state->staticTitle.current();
|
||||
});
|
||||
state->staticTitle.value() | rpl::start_with_next([=](bool value) {
|
||||
staticTitle->setText(value
|
||||
? tr::lng_filters_enable_animations(tr::now)
|
||||
: tr::lng_filters_disable_animations(tr::now));
|
||||
const auto paused = [=] {
|
||||
using namespace Window;
|
||||
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
||||
};
|
||||
name->setCustomTextContext(Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
}), [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
});
|
||||
name->update();
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
staticTitle->widthValue(),
|
||||
name->widthValue()
|
||||
) | rpl::start_with_next([=](int inner, int outer) {
|
||||
staticTitle->moveToRight(
|
||||
st::windowFilterStaticTitlePosition.x(),
|
||||
st::windowFilterStaticTitlePosition.y(),
|
||||
outer);
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
state->creating.value(
|
||||
) | rpl::filter(!_1) | rpl::start_with_next([=] {
|
||||
nameEditing->custom = true;
|
||||
@@ -430,7 +472,13 @@ void EditFilterBox(
|
||||
if (!nameEditing->settingDefault) {
|
||||
nameEditing->custom = true;
|
||||
}
|
||||
auto entered = name->getTextWithTags();
|
||||
state->title = TextWithEntities{
|
||||
std::move(entered.text),
|
||||
TextUtilities::ConvertTextTagsToEntities(entered.tags),
|
||||
};
|
||||
}, name->lifetime());
|
||||
|
||||
const auto updateDefaultTitle = [=](const Data::ChatFilter &filter) {
|
||||
if (nameEditing->custom) {
|
||||
return;
|
||||
@@ -443,6 +491,11 @@ void EditFilterBox(
|
||||
}
|
||||
};
|
||||
|
||||
state->title.value(
|
||||
) | rpl::start_with_next([=](const TextWithEntities &value) {
|
||||
staticTitle->setVisible(!value.entities.isEmpty());
|
||||
}, staticTitle->lifetime());
|
||||
|
||||
const auto outer = box->getDelegate()->outerContainer();
|
||||
CreateIconSelector(
|
||||
outer,
|
||||
@@ -545,18 +598,25 @@ void EditFilterBox(
|
||||
colors->width(),
|
||||
h);
|
||||
}, preview->lifetime());
|
||||
const auto previewTag = preview->lifetime().make_state<QImage>();
|
||||
const auto previewAlpha = preview->lifetime().make_state<float64>(1);
|
||||
|
||||
struct TagState {
|
||||
Ui::Animations::Simple animation;
|
||||
Ui::ChatsFilterTagContext context;
|
||||
QImage frame;
|
||||
float64 alpha = 1.;
|
||||
};
|
||||
const auto tag = preview->lifetime().make_state<TagState>();
|
||||
tag->context.textContext = Core::TextContext({ .session = session });
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.setOpacity(*previewAlpha);
|
||||
const auto size = previewTag->size() / style::DevicePixelRatio();
|
||||
p.setOpacity(tag->alpha);
|
||||
const auto size = tag->frame.size() / style::DevicePixelRatio();
|
||||
const auto rect = QRect(
|
||||
preview->width() - size.width() - st::boxRowPadding.right(),
|
||||
(st::normalFont->height - size.height()) / 2,
|
||||
size.width(),
|
||||
size.height());
|
||||
p.drawImage(rect.topLeft(), *previewTag);
|
||||
p.drawImage(rect.topLeft(), tag->frame);
|
||||
if (p.opacity() < 1) {
|
||||
p.setOpacity(1. - p.opacity());
|
||||
p.setFont(st::normalFont);
|
||||
@@ -573,16 +633,20 @@ void EditFilterBox(
|
||||
Ui::CreateSkipWidget(colors, side),
|
||||
st::boxRowPadding);
|
||||
auto buttons = std::vector<not_null<UserpicBuilder::CircleButton*>>();
|
||||
const auto animation
|
||||
= line->lifetime().make_state<Ui::Animations::Simple>();
|
||||
const auto palette = [](int i) {
|
||||
return Ui::EmptyUserpic::UserpicColor(i).color2;
|
||||
};
|
||||
name->changes() | rpl::start_with_next([=] {
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
palette(state->colorIndex.current())->c,
|
||||
false);
|
||||
const auto upperTitle = [=] {
|
||||
auto value = state->title.current();
|
||||
value.text = value.text.toUpper();
|
||||
return value;
|
||||
};
|
||||
state->title.changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
tag->context.color = palette(state->colorIndex.current())->c;
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
preview->update();
|
||||
}, preview->lifetime());
|
||||
for (auto i = 0; i < kColorsCount; ++i) {
|
||||
@@ -596,12 +660,12 @@ void EditFilterBox(
|
||||
const auto color = palette(i);
|
||||
button->setBrush(color);
|
||||
if (progress == 1) {
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
color->c,
|
||||
false);
|
||||
tag->context.color = color->c;
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
if (i == kNoTag) {
|
||||
*previewAlpha = 0.;
|
||||
tag->alpha = 0.;
|
||||
}
|
||||
}
|
||||
buttons.push_back(button);
|
||||
@@ -616,17 +680,17 @@ void EditFilterBox(
|
||||
const auto c2 = palette(now);
|
||||
const auto a1 = (was == kNoTag) ? 0. : 1.;
|
||||
const auto a2 = (now == kNoTag) ? 0. : 1.;
|
||||
animation->stop();
|
||||
animation->start([=](float64 progress) {
|
||||
tag->animation.stop();
|
||||
tag->animation.start([=](float64 progress) {
|
||||
if (was >= 0) {
|
||||
buttons[was]->setSelectedProgress(1. - progress);
|
||||
}
|
||||
buttons[now]->setSelectedProgress(progress);
|
||||
*previewTag = Ui::ChatsFilterTag(
|
||||
name->getLastText().toUpper(),
|
||||
anim::color(c1, c2, progress),
|
||||
false);
|
||||
*previewAlpha = anim::interpolateF(a1, a2, progress);
|
||||
tag->context.color = anim::color(c1, c2, progress);
|
||||
tag->frame = Ui::ChatsFilterTag(
|
||||
upperTitle(),
|
||||
tag->context);
|
||||
tag->alpha = anim::interpolateF(a1, a2, progress);
|
||||
preview->update();
|
||||
}, 0., 1., st::universalDuration);
|
||||
}
|
||||
@@ -672,9 +736,11 @@ void EditFilterBox(
|
||||
}
|
||||
|
||||
const auto collect = [=]() -> std::optional<Data::ChatFilter> {
|
||||
const auto title = name->getLastText().trimmed();
|
||||
auto title = state->title.current();
|
||||
const auto staticTitle = !title.entities.isEmpty()
|
||||
&& state->staticTitle.current();
|
||||
const auto rules = data->current();
|
||||
if (title.isEmpty()) {
|
||||
if (title.empty()) {
|
||||
name->showError();
|
||||
box->scrollToY(0);
|
||||
return {};
|
||||
@@ -691,7 +757,9 @@ void EditFilterBox(
|
||||
const auto colorIndex = (rawColorIndex >= kNoTag
|
||||
? std::nullopt
|
||||
: std::make_optional(rawColorIndex));
|
||||
return rules.withTitle(title).withColorIndex(colorIndex);
|
||||
return rules.withTitle(
|
||||
{ std::move(title), staticTitle }
|
||||
).withColorIndex(colorIndex);
|
||||
};
|
||||
|
||||
Ui::AddSubsectionTitle(
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -63,13 +64,27 @@ private:
|
||||
|
||||
class ExceptionRow final : public ChatsListBoxController::Row {
|
||||
public:
|
||||
explicit ExceptionRow(not_null<History*> history);
|
||||
ExceptionRow(
|
||||
not_null<History*> history,
|
||||
not_null<PeerListDelegate*> delegate);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
void paintStatusText(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) override;
|
||||
|
||||
private:
|
||||
Ui::Text::String _filtersText;
|
||||
|
||||
};
|
||||
|
||||
class TypeController final : public PeerListController {
|
||||
@@ -126,15 +141,32 @@ Flag TypeRow::flag() const {
|
||||
return static_cast<Flag>(id() & 0xFFFF);
|
||||
}
|
||||
|
||||
ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
|
||||
auto filters = QStringList();
|
||||
ExceptionRow::ExceptionRow(
|
||||
not_null<History*> history,
|
||||
not_null<PeerListDelegate*> delegate)
|
||||
: Row(history) {
|
||||
auto filters = TextWithEntities();
|
||||
for (const auto &filter : history->owner().chatsFilters().list()) {
|
||||
if (filter.contains(history) && filter.id()) {
|
||||
filters << filter.title();
|
||||
if (!filters.empty()) {
|
||||
filters.append(u", "_q);
|
||||
}
|
||||
auto title = filter.title();
|
||||
filters.append(title.isStatic
|
||||
? Data::ForceCustomEmojiStatic(std::move(title.text))
|
||||
: std::move(title.text));
|
||||
}
|
||||
}
|
||||
if (!filters.isEmpty()) {
|
||||
setCustomStatus(filters.join(", "));
|
||||
if (!filters.empty()) {
|
||||
const auto repaint = [=] { delegate->peerListUpdateRow(this); };
|
||||
_filtersText.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
filters,
|
||||
kMarkupTextOptions,
|
||||
Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.repaint = repaint,
|
||||
}));
|
||||
} else if (peer()->isSelf()) {
|
||||
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
||||
}
|
||||
@@ -176,6 +208,37 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
|
||||
};
|
||||
}
|
||||
|
||||
void ExceptionRow::paintStatusText(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (_filtersText.isEmpty()) {
|
||||
Row::paintStatusText(
|
||||
p,
|
||||
st,
|
||||
x,
|
||||
y,
|
||||
availableWidth,
|
||||
outerWidth,
|
||||
selected);
|
||||
} else {
|
||||
p.setPen(selected ? st.statusFgOver : st.statusFg);
|
||||
_filtersText.draw(p, {
|
||||
.position = { x, y },
|
||||
.outerWidth = outerWidth,
|
||||
.availableWidth = availableWidth,
|
||||
.palette = &st::defaultTextPalette,
|
||||
.now = crl::now(),
|
||||
.pausedEmoji = false,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TypeController::TypeController(
|
||||
not_null<Main::Session*> session,
|
||||
Flags options,
|
||||
@@ -418,7 +481,7 @@ void EditFilterChatsListController::prepareViewHook() {
|
||||
const auto rows = std::make_unique<std::optional<ExceptionRow>[]>(count);
|
||||
auto i = 0;
|
||||
for (const auto &history : _peers) {
|
||||
rows[i++].emplace(history);
|
||||
rows[i++].emplace(history, delegate());
|
||||
}
|
||||
auto pointers = std::vector<ExceptionRow*>();
|
||||
pointers.reserve(count);
|
||||
@@ -499,7 +562,7 @@ auto EditFilterChatsListController::createRow(not_null<History*> history)
|
||||
return nullptr;
|
||||
}
|
||||
return history->inChatList()
|
||||
? std::make_unique<ExceptionRow>(history)
|
||||
? std::make_unique<ExceptionRow>(history, delegate())
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // InviteLinkQrBox.
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
@@ -482,7 +483,7 @@ private:
|
||||
const not_null<Window::SessionController*> _window;
|
||||
InviteLinkData _data;
|
||||
|
||||
QString _filterTitle;
|
||||
Data::ChatFilterTitle _filterTitle;
|
||||
base::flat_set<not_null<History*>> _filterChats;
|
||||
base::flat_map<not_null<PeerData*>, QString> _denied;
|
||||
rpl::variable<base::flat_set<not_null<PeerData*>>> _selected;
|
||||
@@ -535,6 +536,7 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
}, verticalLayout->lifetime());
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
const auto isStatic = _filterTitle.isStatic;
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
@@ -544,9 +546,16 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
|
||||
: tr::lng_filters_link_share_about(
|
||||
lt_folder,
|
||||
rpl::single(Ui::Text::Bold(_filterTitle)),
|
||||
rpl::single(Ui::Text::Wrapped(
|
||||
_filterTitle.text,
|
||||
EntityType::Bold)),
|
||||
Ui::Text::WithEntities)),
|
||||
st::settingsFilterDividerLabel)),
|
||||
st::settingsFilterDividerLabel,
|
||||
st::defaultPopupMenu,
|
||||
Core::TextContext({
|
||||
.session = &_window->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}))),
|
||||
st::filterLinkDividerLabelPadding);
|
||||
|
||||
verticalLayout->geometryValue(
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "core/ui_integration.h" // TextContext.
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -67,14 +68,9 @@ void GiftCreditsBox(
|
||||
2.);
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
true));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
|
||||
Ui::Text::RichLangValue
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
@@ -92,7 +88,7 @@ void GiftCreditsBox(
|
||||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue),
|
||||
{ .session = &peer->session() },
|
||||
Core::TextContext({ .session = &peer->session() }),
|
||||
st::creditsBoxAbout)),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
@@ -21,6 +25,14 @@ struct GiveawayResults;
|
||||
struct SubscriptionEntry;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Settings {
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
@@ -54,28 +66,37 @@ void ResolveGiveawayInfo(
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
[[nodiscard]] QString TonAddressUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &address);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars);
|
||||
Fn<void()> convertToStars,
|
||||
Fn<void()> startUpgrade);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::SubscriptionEntry &s);
|
||||
void AddSubscriberEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::Boost &boost);
|
||||
|
||||
@@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
||||
if (peer != item->history()->peer) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
const auto author = item->author();
|
||||
if (author == peer) {
|
||||
return {};
|
||||
} else if (const auto channel = author->asChannel()) {
|
||||
if (channel->linkedChat() == peer) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item->suggestBanReport()) {
|
||||
result.allCanBan = false;
|
||||
}
|
||||
@@ -443,10 +453,7 @@ void CreateModerateMessagesBox(
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
raw->setMarkedText(
|
||||
Ui::Text::Link(text, u"internal:"_q),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { raw->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = session }));
|
||||
}, label->lifetime());
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
@@ -171,8 +172,9 @@ PasscodeBox::PasscodeBox(
|
||||
bool turningOff)
|
||||
: _session(session)
|
||||
, _api(&_session->mtp())
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _turningOff(turningOff)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _about(_textWidth)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_passcode_enter_old())
|
||||
, _newPasscode(
|
||||
this,
|
||||
@@ -193,10 +195,11 @@ PasscodeBox::PasscodeBox(
|
||||
const CloudFields &fields)
|
||||
: _session(session)
|
||||
, _api(mtp)
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _turningOff(fields.turningOff)
|
||||
, _cloudPwd(true)
|
||||
, _cloudFields(fields)
|
||||
, _about(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _about(_textWidth)
|
||||
, _oldPasscode(this, st::defaultInputField, tr::lng_cloud_password_enter_old())
|
||||
, _newPasscode(
|
||||
this,
|
||||
@@ -274,7 +277,7 @@ void PasscodeBox::prepare() {
|
||||
: _cloudPwd
|
||||
? tr::lng_cloud_password_about(tr::now)
|
||||
: tr::lng_passcode_about(tr::now)));
|
||||
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
|
||||
_aboutHeight = _about.countHeight(_textWidth);
|
||||
const auto onlyCheck = onlyCheckCurrent();
|
||||
if (onlyCheck) {
|
||||
_oldPasscode->show();
|
||||
@@ -382,28 +385,27 @@ void PasscodeBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
Painter p(this);
|
||||
|
||||
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
||||
int32 abouty = (_passwordHint->isHidden() ? ((_reenterPasscode->isHidden() ? (_oldPasscode->y() + (_showRecoverLink && !_hintText.isEmpty() ? st::passcodeTextLine : 0)) : _reenterPasscode->y()) + st::passcodeSkip) : _passwordHint->y()) + _oldPasscode->height() + st::passcodeLittleSkip + st::passcodeAboutSkip;
|
||||
p.setPen(st::boxTextFg);
|
||||
_about.drawLeft(p, st::boxPadding.left(), abouty, w, width());
|
||||
_about.drawLeft(p, st::boxPadding.left(), abouty, _textWidth, width());
|
||||
|
||||
if (!_hintText.isEmpty() && _oldError.isEmpty()) {
|
||||
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), w, width(), 1, style::al_topleft);
|
||||
_hintText.drawLeftElided(p, st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height() + ((st::passcodeTextLine - st::normalFont->height) / 2), _textWidth, width(), 1, style::al_topleft);
|
||||
}
|
||||
|
||||
if (!_oldError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), w, st::passcodeTextLine), _oldError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _oldPasscode->y() + _oldPasscode->height(), _textWidth, st::passcodeTextLine), _oldError, style::al_left);
|
||||
}
|
||||
|
||||
if (!_newError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), w, st::passcodeTextLine), _newError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _reenterPasscode->y() + _reenterPasscode->height(), _textWidth, st::passcodeTextLine), _newError, style::al_left);
|
||||
}
|
||||
|
||||
if (!_emailError.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), w, st::passcodeTextLine), _emailError, style::al_left);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverEmail->y() + _recoverEmail->height(), _textWidth, st::passcodeTextLine), _emailError, style::al_left);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,11 +1143,20 @@ RecoverBox::RecoverBox(
|
||||
Fn<void()> closeParent)
|
||||
: _session(session)
|
||||
, _api(mtp)
|
||||
, _pattern(st::normalFont->elided(tr::lng_signin_recover_hint(tr::now, lt_recover_email, pattern), st::boxWidth - st::boxPadding.left() * 1.5))
|
||||
, _textWidth(st::boxWidth - st::boxPadding.left() * 1.5)
|
||||
, _cloudFields(fields)
|
||||
, _recoverCode(this, st::defaultInputField, tr::lng_signin_code())
|
||||
, _noEmailAccess(this, tr::lng_signin_try_password(tr::now))
|
||||
, _patternLabel(
|
||||
this,
|
||||
tr::lng_signin_recover_hint(
|
||||
lt_recover_email,
|
||||
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
||||
Ui::Text::WithEntities),
|
||||
st::termsContent,
|
||||
st::defaultPopupMenu)
|
||||
, _closeParent(std::move(closeParent)) {
|
||||
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (_cloudFields.pendingResetDate != 0 || !session) {
|
||||
_noEmailAccess.destroy();
|
||||
} else {
|
||||
@@ -1176,19 +1187,24 @@ rpl::producer<> RecoverBox::recoveryExpired() const {
|
||||
return _recoveryExpired.events();
|
||||
}
|
||||
|
||||
void RecoverBox::prepare() {
|
||||
setTitle(tr::lng_signin_recover_title());
|
||||
|
||||
addButton(tr::lng_passcode_submit(), [=] { submit(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
void RecoverBox::updateHeight() {
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
(st::passcodePadding.top()
|
||||
+ st::passcodePadding.bottom()
|
||||
+ st::passcodeTextLine
|
||||
+ _recoverCode->height()
|
||||
+ _patternLabel->height()
|
||||
+ st::passcodeTextLine));
|
||||
}
|
||||
|
||||
void RecoverBox::prepare() {
|
||||
setTitle(tr::lng_signin_recover_title());
|
||||
|
||||
addButton(tr::lng_passcode_submit(), [=] { submit(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
updateHeight();
|
||||
|
||||
_recoverCode->changes(
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -1205,23 +1221,42 @@ void RecoverBox::paintEvent(QPaintEvent *e) {
|
||||
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(st::boxTextFg);
|
||||
int32 w = st::boxWidth - st::boxPadding.left() * 1.5;
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() - st::passcodeTextLine - st::passcodePadding.top(), w, st::passcodePadding.top() + st::passcodeTextLine), _pattern, style::al_left);
|
||||
|
||||
if (!_error.isEmpty()) {
|
||||
p.setPen(st::boxTextFgError);
|
||||
p.drawText(QRect(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height(), w, st::passcodeTextLine), _error, style::al_left);
|
||||
p.drawText(
|
||||
QRect(
|
||||
st::boxPadding.left(),
|
||||
_recoverCode->y() + _recoverCode->height(),
|
||||
_textWidth,
|
||||
st::passcodeTextLine),
|
||||
_error,
|
||||
style::al_left);
|
||||
}
|
||||
}
|
||||
|
||||
void RecoverBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_recoverCode->resize(st::boxWidth - st::boxPadding.left() - st::boxPadding.right(), _recoverCode->height());
|
||||
_recoverCode->moveToLeft(st::boxPadding.left(), st::passcodePadding.top() + st::passcodePadding.bottom() + st::passcodeTextLine);
|
||||
_patternLabel->resizeToWidth(_textWidth);
|
||||
_patternLabel->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
st::passcodePadding.top());
|
||||
|
||||
_recoverCode->resize(
|
||||
st::boxWidth - st::boxPadding.left() - st::boxPadding.right(),
|
||||
_recoverCode->height());
|
||||
_recoverCode->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
rect::m::sum::v(st::passcodePadding) + _patternLabel->height());
|
||||
if (_noEmailAccess) {
|
||||
_noEmailAccess->moveToLeft(st::boxPadding.left(), _recoverCode->y() + _recoverCode->height() + (st::passcodeTextLine - _noEmailAccess->height()) / 2);
|
||||
_noEmailAccess->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
rect::bottom(_recoverCode)
|
||||
+ (st::passcodeTextLine - _noEmailAccess->height()) / 2);
|
||||
}
|
||||
|
||||
updateHeight();
|
||||
}
|
||||
|
||||
void RecoverBox::setInnerFocus() {
|
||||
|
||||
@@ -154,6 +154,7 @@ private:
|
||||
|
||||
Main::Session *_session = nullptr;
|
||||
MTP::Sender _api;
|
||||
const int _textWidth;
|
||||
|
||||
QString _pattern;
|
||||
|
||||
@@ -219,17 +220,18 @@ private:
|
||||
void proceedToChange(const QString &code);
|
||||
void checkSubmitFail(const MTP::Error &error);
|
||||
void setError(const QString &error);
|
||||
void updateHeight();
|
||||
|
||||
Main::Session *_session = nullptr;
|
||||
MTP::Sender _api;
|
||||
const int _textWidth;
|
||||
mtpRequestId _submitRequest = 0;
|
||||
|
||||
QString _pattern;
|
||||
|
||||
PasscodeBox::CloudFields _cloudFields;
|
||||
|
||||
object_ptr<Ui::InputField> _recoverCode;
|
||||
object_ptr<Ui::LinkButton> _noEmailAccess;
|
||||
object_ptr<Ui::FlatLabel> _patternLabel;
|
||||
Fn<void()> _closeParent;
|
||||
|
||||
QString _error;
|
||||
|
||||
@@ -788,31 +788,29 @@ int PeerListRow::paintNameIconGetWidth(
|
||||
|| _isVerifyCodesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _badge.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
return _badge.drawGetWidth(p, {
|
||||
.peer = peer(),
|
||||
.rectForName = QRect(
|
||||
nameLeft,
|
||||
nameTop,
|
||||
availableWidth,
|
||||
st::semiboldFont->height),
|
||||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = peer(),
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = repaint,
|
||||
.now = now,
|
||||
.paused = false,
|
||||
});
|
||||
.nameWidth = nameWidth,
|
||||
.outerWidth = outerWidth,
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
.premium = &(selected
|
||||
? st::dialogsPremiumIcon.over
|
||||
: st::dialogsPremiumIcon.icon),
|
||||
.scam = &(selected ? st::dialogsScamFgOver : st::dialogsScamFg),
|
||||
.premiumFg = &(selected
|
||||
? st::dialogsVerifiedIconBgOver
|
||||
: st::dialogsVerifiedIconBg),
|
||||
.customEmojiRepaint = repaint,
|
||||
.now = now,
|
||||
.paused = false,
|
||||
});
|
||||
}
|
||||
|
||||
void PeerListRow::paintStatusText(
|
||||
@@ -875,6 +873,7 @@ void PeerListRow::paintUserpic(
|
||||
} else if (const auto callback = generatePaintUserpicCallback(false)) {
|
||||
callback(p, x, y, outerWidth, st.photoSize);
|
||||
}
|
||||
paintUserpicOverlay(p, st, x, y, outerWidth);
|
||||
}
|
||||
|
||||
// Emulates Ui::RoundImageCheckbox::paint() in a checked state.
|
||||
@@ -914,7 +913,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
|
||||
p.setPen(userpicBorderPen);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawEllipse(userpicEllipse);
|
||||
if (peer()->forum()) {
|
||||
const auto radius = userpicDiameter
|
||||
* Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(userpicEllipse, radius, radius);
|
||||
} else {
|
||||
p.drawEllipse(userpicEllipse);
|
||||
}
|
||||
|
||||
p.setPen(iconBorderPen);
|
||||
p.setBrush(st.disabledCheckFg);
|
||||
@@ -1682,6 +1687,10 @@ void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
_controller->rowClicked(row);
|
||||
}
|
||||
}
|
||||
} else if (button == Qt::MiddleButton && pressed == _selected) {
|
||||
if (auto row = getRow(pressed.index)) {
|
||||
_controller->rowMiddleClicked(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,13 @@ public:
|
||||
[[nodiscard]] virtual QString generateShortName();
|
||||
[[nodiscard]] virtual auto generatePaintUserpicCallback(
|
||||
bool forceRound) -> PaintRoundImageCallback;
|
||||
virtual void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual auto generateNameFirstLetters() const
|
||||
-> const base::flat_set<QChar> &;
|
||||
@@ -482,6 +489,8 @@ public:
|
||||
}
|
||||
|
||||
virtual void rowClicked(not_null<PeerListRow*> row) = 0;
|
||||
virtual void rowMiddleClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_premium.h" // MessageMoneyRestriction.
|
||||
#include "base/random.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "settings/settings_premium.h"
|
||||
@@ -41,8 +41,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "payments/ui/payments_reaction_box.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "window/window_separate_id.h"
|
||||
#include "window/window_session_controller.h" // showAddContact()
|
||||
#include "base/unixtime.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -64,6 +66,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
public:
|
||||
using ContactsBoxController::ContactsBoxController;
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> wheelClicks() const {
|
||||
return _wheelClicks.events();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
@@ -72,6 +78,14 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void rowMiddleClicked(
|
||||
not_null<PeerListRow*> row) override {
|
||||
_wheelClicks.fire(row->peer());
|
||||
}
|
||||
|
||||
private:
|
||||
rpl::event_stream<not_null<PeerData*>> _wheelClicks;
|
||||
|
||||
};
|
||||
auto controller = std::make_unique<Controller>(
|
||||
&sessionController->session());
|
||||
@@ -100,6 +114,10 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||
online ? &st::contactsSortOnlineIconOver : nullptr);
|
||||
});
|
||||
raw->setSortMode(Mode::Online);
|
||||
|
||||
raw->wheelClicks() | rpl::start_with_next([=](not_null<PeerData*> p) {
|
||||
sessionController->showInNewWindow(p);
|
||||
}, box->lifetime());
|
||||
};
|
||||
return Box<PeerListBox>(std::move(controller), std::move(init));
|
||||
}
|
||||
@@ -258,40 +276,71 @@ bool PeerListGlobalSearchController::isLoading() {
|
||||
return _timer.isActive() || _requestId;
|
||||
}
|
||||
|
||||
struct RecipientRow::Restriction {
|
||||
Api::MessageMoneyRestriction value;
|
||||
RestrictionBadgeCache cache;
|
||||
};
|
||||
|
||||
RecipientRow::RecipientRow(
|
||||
not_null<PeerData*> peer,
|
||||
const style::PeerListItem *maybeLockedSt,
|
||||
History *maybeHistory)
|
||||
: PeerListRow(peer)
|
||||
, _maybeHistory(maybeHistory)
|
||||
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
|
||||
if (maybeLockedSt
|
||||
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
|
||||
== Api::RequirePremiumState::Yes)) {
|
||||
_lockedSt = maybeLockedSt;
|
||||
, _maybeLockedSt(maybeLockedSt) {
|
||||
if (_maybeLockedSt) {
|
||||
setRestriction(Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
maybeHistory));
|
||||
}
|
||||
}
|
||||
|
||||
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
|
||||
if (const auto st = _lockedSt) {
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) {
|
||||
result(p, x, y, outerWidth, size);
|
||||
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
|
||||
};
|
||||
Api::MessageMoneyRestriction RecipientRow::restriction() const {
|
||||
return _restriction
|
||||
? _restriction->value
|
||||
: Api::MessageMoneyRestriction();
|
||||
}
|
||||
|
||||
void RecipientRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||
if (!restriction) {
|
||||
_restriction = nullptr;
|
||||
return;
|
||||
} else if (!_restriction) {
|
||||
_restriction = std::make_unique<Restriction>();
|
||||
}
|
||||
_restriction->value = restriction;
|
||||
}
|
||||
|
||||
void RecipientRow::paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
if (const auto &r = _restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
_maybeLockedSt,
|
||||
r->value.starsPerMessage,
|
||||
r->cache,
|
||||
x,
|
||||
y,
|
||||
outerWidth,
|
||||
st.photoSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RecipientRow::refreshLock(
|
||||
not_null<const style::PeerListItem*> maybeLockedSt) {
|
||||
if (const auto user = peer()->asUser()) {
|
||||
const auto locked = _resolvePremiumRequired
|
||||
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
|
||||
== Api::RequirePremiumState::Yes);
|
||||
if (this->locked() != locked) {
|
||||
setLocked(locked ? maybeLockedSt.get() : nullptr);
|
||||
using Restriction = Api::MessageMoneyRestriction;
|
||||
const auto r = _maybeLockedSt
|
||||
? Api::ResolveMessageMoneyRestrictions(
|
||||
user,
|
||||
_maybeHistory)
|
||||
: Restriction();
|
||||
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||
setRestriction(r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -301,22 +350,30 @@ bool RecipientRow::refreshLock(
|
||||
void RecipientRow::preloadUserpic() {
|
||||
PeerListRow::preloadUserpic();
|
||||
|
||||
if (!_resolvePremiumRequired) {
|
||||
if (!_maybeLockedSt) {
|
||||
return;
|
||||
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
|
||||
== Api::RequirePremiumState::Unknown) {
|
||||
const auto user = peer()->asUser();
|
||||
user->session().api().premium().resolvePremiumRequired(user);
|
||||
}
|
||||
const auto peer = this->peer();
|
||||
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
_maybeHistory).known;
|
||||
if (known) {
|
||||
return;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
const auto api = &user->session().api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
} else if (const auto group = peer->asChannel()) {
|
||||
group->updateFull();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackPremiumRequiredChanges(
|
||||
void TrackMessageMoneyRestrictionsChanges(
|
||||
not_null<PeerListController*> controller,
|
||||
rpl::lifetime &lifetime) {
|
||||
const auto session = &controller->session();
|
||||
rpl::merge(
|
||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||
session->api().premium().somePremiumRequiredResolved()
|
||||
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto st = &controller->computeListSt().item;
|
||||
const auto delegate = controller->delegate();
|
||||
@@ -709,7 +766,7 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
|
||||
return std::make_unique<PeerListRow>(user);
|
||||
}
|
||||
|
||||
RecipientPremiumRequiredError WritePremiumRequiredError(
|
||||
RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||
not_null<UserData*> user) {
|
||||
return {
|
||||
.text = tr::lng_send_non_premium_message_toast(
|
||||
@@ -742,7 +799,7 @@ ChooseRecipientBoxController::ChooseRecipientBoxController(
|
||||
, _session(args.session)
|
||||
, _callback(std::move(args.callback))
|
||||
, _filter(std::move(args.filter))
|
||||
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
|
||||
, _moneyRestrictionError(std::move(args.moneyRestrictionError)) {
|
||||
}
|
||||
|
||||
Main::Session &ChooseRecipientBoxController::session() const {
|
||||
@@ -752,14 +809,17 @@ Main::Session &ChooseRecipientBoxController::session() const {
|
||||
void ChooseRecipientBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(tr::lng_forward_choose());
|
||||
|
||||
if (_premiumRequiredError) {
|
||||
TrackPremiumRequiredChanges(this, lifetime());
|
||||
if (_moneyRestrictionError) {
|
||||
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
bool ChooseRecipientBoxController::showLockedError(
|
||||
not_null<PeerListRow*> row) {
|
||||
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
|
||||
return RecipientRow::ShowLockedError(
|
||||
this,
|
||||
row,
|
||||
_moneyRestrictionError);
|
||||
}
|
||||
|
||||
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
@@ -819,8 +879,9 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
bool RecipientRow::ShowLockedError(
|
||||
not_null<PeerListController*> controller,
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
|
||||
if (!static_cast<RecipientRow*>(row.get())->locked()) {
|
||||
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error) {
|
||||
const auto recipient = static_cast<RecipientRow*>(row.get());
|
||||
if (!recipient->restriction().premiumRequired) {
|
||||
return false;
|
||||
}
|
||||
::Settings::ShowPremiumPromoToast(
|
||||
@@ -843,15 +904,15 @@ auto ChooseRecipientBoxController::createRow(
|
||||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isVerifyCodes()
|
||||
|| (peer->isUser() && (_premiumRequiredError
|
||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
||||
|| (peer->isUser() && (_moneyRestrictionError
|
||||
? !peer->asUser()->canSendIgnoreMoneyRestrictions()
|
||||
: !Data::CanSendAnything(peer))));
|
||||
if (skip) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(
|
||||
history,
|
||||
_premiumRequiredError ? &computeListSt().item : nullptr);
|
||||
_moneyRestrictionError ? &computeListSt().item : nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1076,25 +1137,61 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
|
||||
return skip ? nullptr : std::make_unique<Row>(topic);
|
||||
};
|
||||
|
||||
void PaintPremiumRequiredLock(
|
||||
void PaintRestrictionBadge(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int stars,
|
||||
RestrictionBadgeCache &cache,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size) {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto paletteVersion = style::PaletteVersion();
|
||||
const auto good = !cache.badge.isNull()
|
||||
&& (cache.stars == stars)
|
||||
&& (cache.paletteVersion == paletteVersion);
|
||||
const auto &check = st->checkbox.check;
|
||||
auto pen = check.border->p;
|
||||
pen.setWidthF(check.width);
|
||||
p.setPen(pen);
|
||||
p.setBrush(st::premiumButtonBg2);
|
||||
const auto &icon = st::stickersPremiumLock;
|
||||
const auto width = icon.width();
|
||||
const auto height = icon.height();
|
||||
const auto rect = QRect(
|
||||
QPoint(x + size - width, y + size - height),
|
||||
icon.size());
|
||||
p.drawEllipse(rect);
|
||||
icon.paintInCenter(p, rect);
|
||||
const auto add = check.width;
|
||||
if (!good) {
|
||||
cache.stars = stars;
|
||||
cache.paletteVersion = paletteVersion;
|
||||
if (stars) {
|
||||
const auto text = (stars >= 1000)
|
||||
? (QString::number(stars / 1000) + 'K')
|
||||
: QString::number(stars);
|
||||
cache.badge = Ui::GenerateSmallBadgeImage(
|
||||
text,
|
||||
st::paidReactTopStarIcon,
|
||||
check.bgActive->c,
|
||||
st::premiumButtonFg->c,
|
||||
&check);
|
||||
} else {
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto &icon = st::stickersPremiumLock;
|
||||
const auto width = icon.width();
|
||||
const auto height = icon.height();
|
||||
const auto rect = QRect(
|
||||
QPoint(x + size - width, y + size - height),
|
||||
icon.size());
|
||||
const auto added = QMargins(add, add, add, add);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
cache.badge = QImage(
|
||||
(rect + added).size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
cache.badge.setDevicePixelRatio(ratio);
|
||||
cache.badge.fill(Qt::transparent);
|
||||
const auto inner = QRect(add, add, rect.width(), rect.height());
|
||||
auto q = QPainter(&cache.badge);
|
||||
auto pen = check.border->p;
|
||||
pen.setWidthF(check.width);
|
||||
q.setPen(pen);
|
||||
q.setBrush(st::premiumButtonBg2);
|
||||
q.drawEllipse(inner);
|
||||
icon.paintInCenter(q, inner);
|
||||
}
|
||||
}
|
||||
const auto cached = cache.badge.size() / cache.badge.devicePixelRatio();
|
||||
const auto left = x + size + add - cached.width();
|
||||
const auto top = stars ? (y - add) : (y + size + add - cached.height());
|
||||
p.drawImage(left, top, cache.badge);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace style {
|
||||
struct PeerListItem;
|
||||
} // namespace style
|
||||
|
||||
namespace Api {
|
||||
struct MessageMoneyRestriction;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
class Forum;
|
||||
@@ -93,13 +97,28 @@ private:
|
||||
|
||||
};
|
||||
|
||||
struct RecipientPremiumRequiredError {
|
||||
struct RecipientMoneyRestrictionError {
|
||||
TextWithEntities text;
|
||||
};
|
||||
|
||||
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
|
||||
[[nodiscard]] RecipientMoneyRestrictionError WriteMoneyRestrictionError(
|
||||
not_null<UserData*> user);
|
||||
|
||||
struct RestrictionBadgeCache {
|
||||
int paletteVersion = 0;
|
||||
int stars = 0;
|
||||
QImage badge;
|
||||
};
|
||||
void PaintRestrictionBadge(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int stars,
|
||||
RestrictionBadgeCache &cache,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size);
|
||||
|
||||
class RecipientRow : public PeerListRow {
|
||||
public:
|
||||
explicit RecipientRow(
|
||||
@@ -112,30 +131,33 @@ public:
|
||||
[[nodiscard]] static bool ShowLockedError(
|
||||
not_null<PeerListController*> controller,
|
||||
not_null<PeerListRow*> row,
|
||||
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error);
|
||||
Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> error);
|
||||
|
||||
[[nodiscard]] History *maybeHistory() const {
|
||||
return _maybeHistory;
|
||||
}
|
||||
[[nodiscard]] bool locked() const {
|
||||
return _lockedSt != nullptr;
|
||||
}
|
||||
void setLocked(const style::PeerListItem *lockedSt) {
|
||||
_lockedSt = lockedSt;
|
||||
}
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) override;
|
||||
|
||||
void preloadUserpic() override;
|
||||
|
||||
[[nodiscard]] Api::MessageMoneyRestriction restriction() const;
|
||||
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||
|
||||
private:
|
||||
struct Restriction;
|
||||
|
||||
History *_maybeHistory = nullptr;
|
||||
const style::PeerListItem *_lockedSt = nullptr;
|
||||
bool _resolvePremiumRequired = false;
|
||||
const style::PeerListItem *_maybeLockedSt = nullptr;
|
||||
std::shared_ptr<Restriction> _restriction;
|
||||
|
||||
};
|
||||
|
||||
void TrackPremiumRequiredChanges(
|
||||
void TrackMessageMoneyRestrictionsChanges(
|
||||
not_null<PeerListController*> controller,
|
||||
rpl::lifetime &lifetime);
|
||||
|
||||
@@ -261,8 +283,8 @@ struct ChooseRecipientArgs {
|
||||
FnMut<void(not_null<Data::Thread*>)> callback;
|
||||
Fn<bool(not_null<Data::Thread*>)> filter;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||
Fn<MoneyRestrictionError(not_null<UserData*>)> moneyRestrictionError;
|
||||
};
|
||||
|
||||
class ChooseRecipientBoxController
|
||||
@@ -290,8 +312,8 @@ private:
|
||||
const not_null<Main::Session*> _session;
|
||||
FnMut<void(not_null<Data::Thread*>)> _callback;
|
||||
Fn<bool(not_null<Data::Thread*>)> _filter;
|
||||
Fn<RecipientPremiumRequiredError(
|
||||
not_null<UserData*>)> _premiumRequiredError;
|
||||
Fn<RecipientMoneyRestrictionError(
|
||||
not_null<UserData*>)> _moneyRestrictionError;
|
||||
|
||||
};
|
||||
|
||||
@@ -371,11 +393,3 @@ private:
|
||||
Fn<bool(not_null<Data::ForumTopic*>)> _filter;
|
||||
|
||||
};
|
||||
|
||||
void PaintPremiumRequiredLock(
|
||||
Painter &p,
|
||||
not_null<const style::PeerListItem*> st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
int size);
|
||||
|
||||
@@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "boxes/peers/edit_peer_type_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
@@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/show_or_premium_box.h"
|
||||
@@ -52,16 +55,39 @@ constexpr auto kUserpicsLimit = 3;
|
||||
|
||||
class ForbiddenRow final : public PeerListRow {
|
||||
public:
|
||||
ForbiddenRow(not_null<PeerData*> peer, bool locked);
|
||||
ForbiddenRow(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const style::PeerListItem*> lockSt,
|
||||
bool locked);
|
||||
|
||||
PaintRoundImageCallback generatePaintUserpicCallback(
|
||||
bool forceRound) override;
|
||||
|
||||
Api::MessageMoneyRestriction restriction() const;
|
||||
void setRestriction(Api::MessageMoneyRestriction restriction);
|
||||
|
||||
void preloadUserpic() override;
|
||||
void paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) override;
|
||||
|
||||
bool refreshLock();
|
||||
|
||||
private:
|
||||
struct Restriction {
|
||||
Api::MessageMoneyRestriction value;
|
||||
RestrictionBadgeCache cache;
|
||||
};
|
||||
|
||||
const bool _locked = false;
|
||||
const not_null<const style::PeerListItem*> _lockSt;
|
||||
QImage _disabledFrame;
|
||||
InMemoryKey _userpicKey;
|
||||
int _paletteVersion = 0;
|
||||
std::shared_ptr<Restriction> _restriction;
|
||||
|
||||
};
|
||||
|
||||
@@ -81,6 +107,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> selectedValue() const {
|
||||
return _selected.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<int> starsToSend() const {
|
||||
return _starsToSend.value();
|
||||
}
|
||||
|
||||
void send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
@@ -89,10 +118,16 @@ public:
|
||||
|
||||
private:
|
||||
void appendRow(not_null<UserData*> user);
|
||||
[[nodiscard]] std::unique_ptr<PeerListRow> createRow(
|
||||
[[nodiscard]] std::unique_ptr<ForbiddenRow> createRow(
|
||||
not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canInvite(not_null<PeerData*> peer) const;
|
||||
|
||||
void send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close,
|
||||
Api::SendOptions options);
|
||||
|
||||
void setSimpleCover();
|
||||
void setComplexCover();
|
||||
|
||||
@@ -101,8 +136,11 @@ private:
|
||||
const std::vector<not_null<UserData*>> &_users;
|
||||
const bool _can = false;
|
||||
rpl::variable<int> _selected;
|
||||
rpl::variable<int> _starsToSend;
|
||||
bool _sending = false;
|
||||
|
||||
rpl::lifetime _paymentCheckLifetime;
|
||||
|
||||
};
|
||||
|
||||
base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
|
||||
@@ -256,11 +294,17 @@ Main::Session &InviteForbiddenController::session() const {
|
||||
return _peer->session();
|
||||
}
|
||||
|
||||
ForbiddenRow::ForbiddenRow(not_null<PeerData*> peer, bool locked)
|
||||
ForbiddenRow::ForbiddenRow(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const style::PeerListItem*> lockSt,
|
||||
bool locked)
|
||||
: PeerListRow(peer)
|
||||
, _locked(locked) {
|
||||
, _locked(locked)
|
||||
, _lockSt(lockSt) {
|
||||
if (_locked) {
|
||||
setCustomStatus(tr::lng_invite_status_disabled(tr::now));
|
||||
} else {
|
||||
setRestriction(Api::ResolveMessageMoneyRestrictions(peer, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +383,76 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Api::MessageMoneyRestriction ForbiddenRow::restriction() const {
|
||||
return _restriction
|
||||
? _restriction->value
|
||||
: Api::MessageMoneyRestriction();
|
||||
}
|
||||
|
||||
void ForbiddenRow::setRestriction(Api::MessageMoneyRestriction restriction) {
|
||||
if (!restriction || !restriction.starsPerMessage) {
|
||||
_restriction = nullptr;
|
||||
return;
|
||||
} else if (!_restriction) {
|
||||
_restriction = std::make_unique<Restriction>();
|
||||
}
|
||||
_restriction->value = restriction;
|
||||
}
|
||||
|
||||
void ForbiddenRow::paintUserpicOverlay(
|
||||
Painter &p,
|
||||
const style::PeerListItem &st,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth) {
|
||||
if (const auto &r = _restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
_lockSt,
|
||||
r->value.starsPerMessage,
|
||||
r->cache,
|
||||
x,
|
||||
y,
|
||||
outerWidth,
|
||||
st.photoSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool ForbiddenRow::refreshLock() {
|
||||
if (_locked) {
|
||||
return false;
|
||||
} else if (const auto user = peer()->asUser()) {
|
||||
using Restriction = Api::MessageMoneyRestriction;
|
||||
auto r = Api::ResolveMessageMoneyRestrictions(user, nullptr);
|
||||
if (!r || !r.starsPerMessage) {
|
||||
r = Restriction();
|
||||
}
|
||||
if ((_restriction ? _restriction->value : Restriction()) != r) {
|
||||
setRestriction(r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ForbiddenRow::preloadUserpic() {
|
||||
PeerListRow::preloadUserpic();
|
||||
|
||||
const auto peer = this->peer();
|
||||
const auto known = Api::ResolveMessageMoneyRestrictions(
|
||||
peer,
|
||||
nullptr).known;
|
||||
if (known) {
|
||||
return;
|
||||
} else if (const auto user = peer->asUser()) {
|
||||
const auto api = &user->session().api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
} else if (const auto group = peer->asChannel()) {
|
||||
group->updateFull();
|
||||
}
|
||||
}
|
||||
|
||||
void InviteForbiddenController::setSimpleCover() {
|
||||
delegate()->peerListSetTitle(
|
||||
_can ? tr::lng_profile_add_via_link() : tr::lng_via_link_cant());
|
||||
@@ -435,6 +549,30 @@ void InviteForbiddenController::setComplexCover() {
|
||||
}
|
||||
|
||||
void InviteForbiddenController::prepare() {
|
||||
session().api().premium().someMessageMoneyRestrictionsResolved(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto stars = 0;
|
||||
const auto process = [&](not_null<PeerListRow*> raw) {
|
||||
const auto row = static_cast<ForbiddenRow*>(raw.get());
|
||||
if (row->refreshLock()) {
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}
|
||||
if (const auto r = row->restriction()) {
|
||||
stars += r.starsPerMessage;
|
||||
}
|
||||
};
|
||||
auto count = delegate()->peerListFullRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
process(delegate()->peerListRowAt(i));
|
||||
}
|
||||
_starsToSend = stars;
|
||||
|
||||
count = delegate()->peerListSearchRowsCount();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
process(delegate()->peerListSearchRowAt(i));
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
if (session().premium()
|
||||
|| (_forbidden.premiumAllowsInvite.empty()
|
||||
&& _forbidden.premiumAllowsWrite.empty())) {
|
||||
@@ -464,6 +602,11 @@ void InviteForbiddenController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto checked = row->checked();
|
||||
delegate()->peerListSetRowChecked(row, !checked);
|
||||
_selected = _selected.current() + (checked ? -1 : 1);
|
||||
const auto r = static_cast<ForbiddenRow*>(row.get())->restriction();
|
||||
if (r.starsPerMessage) {
|
||||
_starsToSend = _starsToSend.current()
|
||||
+ (checked ? -r.starsPerMessage : r.starsPerMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
||||
@@ -473,6 +616,9 @@ void InviteForbiddenController::appendRow(not_null<UserData*> user) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
if (canInvite(user)) {
|
||||
delegate()->peerListSetRowChecked(raw, true);
|
||||
if (const auto r = raw->restriction()) {
|
||||
_starsToSend = _starsToSend.current() + r.starsPerMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -481,7 +627,64 @@ void InviteForbiddenController::send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close) {
|
||||
if (_sending || list.empty()) {
|
||||
send(list, show, close, {});
|
||||
}
|
||||
|
||||
void InviteForbiddenController::send(
|
||||
std::vector<not_null<PeerData*>> list,
|
||||
Ui::ShowPtr show,
|
||||
Fn<void()> close,
|
||||
Api::SendOptions options) {
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
_paymentCheckLifetime.destroy();
|
||||
|
||||
const auto withPaymentApproved = [=](int approved) {
|
||||
auto copy = options;
|
||||
copy.starsApproved = approved;
|
||||
send(list, show, close, copy);
|
||||
};
|
||||
const auto messagesCount = 1;
|
||||
const auto alreadyApproved = options.starsApproved;
|
||||
auto paid = std::vector<not_null<PeerData*>>();
|
||||
auto waiting = base::flat_set<not_null<PeerData*>>();
|
||||
auto totalStars = 0;
|
||||
for (const auto &peer : list) {
|
||||
const auto details = ComputePaymentDetails(peer, messagesCount);
|
||||
if (!details) {
|
||||
waiting.emplace(peer);
|
||||
} else if (details->stars > 0) {
|
||||
totalStars += details->stars;
|
||||
paid.push_back(peer);
|
||||
}
|
||||
}
|
||||
if (!waiting.empty()) {
|
||||
session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (waiting.contains(update.peer)) {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}
|
||||
}, _paymentCheckLifetime);
|
||||
|
||||
if (!session().credits().loaded()) {
|
||||
session().credits().loadedValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}, _paymentCheckLifetime);
|
||||
}
|
||||
return;
|
||||
} else if (totalStars > alreadyApproved) {
|
||||
const auto sessionShow = Main::MakeSessionShow(show, &session());
|
||||
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||
.messages = messagesCount,
|
||||
.stars = totalStars,
|
||||
}, [=] { withPaymentApproved(totalStars); });
|
||||
return;
|
||||
} else if (_sending) {
|
||||
return;
|
||||
}
|
||||
_sending = true;
|
||||
@@ -492,12 +695,18 @@ void InviteForbiddenController::send(
|
||||
if (link.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
auto full = options;
|
||||
auto &api = _peer->session().api();
|
||||
auto options = Api::SendOptions();
|
||||
for (const auto &to : list) {
|
||||
auto copy = full;
|
||||
copy.starsApproved = std::min(
|
||||
to->starsPerMessageChecked(),
|
||||
full.starsApproved);
|
||||
full.starsApproved -= copy.starsApproved;
|
||||
|
||||
const auto history = to->owner().history(to);
|
||||
auto message = Api::MessageToSend(
|
||||
Api::SendAction(history, options));
|
||||
Api::SendAction(history, copy));
|
||||
message.textWithTags = { link };
|
||||
message.action.clearDraft = false;
|
||||
api.sendMessage(std::move(message));
|
||||
@@ -542,10 +751,11 @@ void InviteForbiddenController::send(
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> InviteForbiddenController::createRow(
|
||||
std::unique_ptr<ForbiddenRow> InviteForbiddenController::createRow(
|
||||
not_null<UserData*> user) const {
|
||||
const auto locked = _can && !canInvite(user);
|
||||
return std::make_unique<ForbiddenRow>(user, locked);
|
||||
const auto lockSt = &computeListSt().item;
|
||||
return std::make_unique<ForbiddenRow>(user, lockSt, locked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -584,8 +794,8 @@ void AddParticipantsBoxController::subscribeToMigration() {
|
||||
}
|
||||
|
||||
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto premiumRequiredError = WritePremiumRequiredError;
|
||||
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
|
||||
const auto moneyRestrictionError = WriteMoneyRestrictionError;
|
||||
if (RecipientRow::ShowLockedError(this, row, moneyRestrictionError)) {
|
||||
return;
|
||||
}
|
||||
const auto &serverConfig = session().serverConfig();
|
||||
@@ -614,7 +824,7 @@ void AddParticipantsBoxController::itemDeselectedHook(
|
||||
void AddParticipantsBoxController::prepareViewHook() {
|
||||
updateTitle();
|
||||
|
||||
TrackPremiumRequiredChanges(this, lifetime());
|
||||
TrackMessageMoneyRestrictionsChanges(this, lifetime());
|
||||
}
|
||||
|
||||
int AddParticipantsBoxController::alreadyInCount() const {
|
||||
@@ -929,12 +1139,15 @@ bool ChatInviteForbidden(
|
||||
) | rpl::start_with_next([=](bool has) {
|
||||
box->clearButtons();
|
||||
if (has) {
|
||||
box->addButton(tr::lng_via_link_send(), [=] {
|
||||
const auto send = box->addButton(tr::lng_via_link_send(), [=] {
|
||||
weak->send(
|
||||
box->collectSelectedRows(),
|
||||
box->uiShow(),
|
||||
crl::guard(box, [=] { box->closeBox(); }));
|
||||
});
|
||||
send->setText(PaidSendButtonText(
|
||||
weak->starsToSend(),
|
||||
tr::lng_via_link_send()));
|
||||
}
|
||||
box->addButton(tr::lng_create_group_skip(), [=] {
|
||||
box->closeBox();
|
||||
|
||||
@@ -265,7 +265,7 @@ struct IconSelector {
|
||||
const auto manager = &controller->session().data().customEmojiManager();
|
||||
|
||||
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
|
||||
if (id == kDefaultIconId) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
@@ -288,7 +288,7 @@ struct IconSelector {
|
||||
.show = controller->uiShow(),
|
||||
.mode = EmojiListWidget::Mode::TopicIcon,
|
||||
.paused = Window::PausedIn(controller, PauseReason::Layer),
|
||||
.customRecentList = recent(),
|
||||
.customRecentList = DocumentListToRecent(recent()),
|
||||
.customRecentFactory = std::move(factory),
|
||||
.st = &st::reactPanelEmojiPan,
|
||||
}),
|
||||
@@ -297,7 +297,7 @@ struct IconSelector {
|
||||
icons->requestDefaultIfUnknown();
|
||||
icons->defaultUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
selector->provideRecent(recent());
|
||||
selector->provideRecent(DocumentListToRecent(recent()));
|
||||
}, selector->lifetime());
|
||||
|
||||
placeFooter(selector->createFooter());
|
||||
|
||||
@@ -2282,6 +2282,8 @@ void ParticipantsBoxSearchController::restoreState(
|
||||
_allLoaded = my->allLoaded;
|
||||
_offset = my->offset;
|
||||
_query = my->query;
|
||||
_timer.cancel();
|
||||
_requestId = 0;
|
||||
if (my->wasLoading) {
|
||||
searchOnServer();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/background_box.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_changes.h"
|
||||
@@ -165,7 +165,7 @@ private:
|
||||
|
||||
const uint32 _level;
|
||||
const TextWithEntities _icon;
|
||||
const Core::MarkedTextContext _context;
|
||||
const Ui::Text::MarkedContext _context;
|
||||
Ui::Text::String _text;
|
||||
bool _minimal = false;
|
||||
|
||||
@@ -313,9 +313,11 @@ PreviewWrap::PreviewWrap(
|
||||
WebPageCollage(),
|
||||
nullptr, // iv
|
||||
nullptr, // stickerSet
|
||||
nullptr, // uniqueGift
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
false, // photoIsVideoCover
|
||||
0)) // pendingTill
|
||||
, _theme(theme)
|
||||
, _style(style)
|
||||
@@ -464,7 +466,10 @@ LevelBadge::LevelBadge(
|
||||
st::settingsLevelBadgeLock,
|
||||
QMargins(0, st::settingsLevelBadgeLockSkip, 0, 0),
|
||||
false)))
|
||||
, _context({ .session = session }) {
|
||||
, _context(Core::TextContext({
|
||||
.session = session,
|
||||
.repaint = [this] { update(); },
|
||||
})) {
|
||||
updateText();
|
||||
}
|
||||
|
||||
@@ -525,7 +530,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) {
|
||||
struct SetValues {
|
||||
uint8 colorIndex = 0;
|
||||
DocumentId backgroundEmojiId = 0;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
};
|
||||
@@ -807,7 +812,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
emojiIdChosen(chosen.id);
|
||||
emojiIdChosen(chosen.id.documentId);
|
||||
}, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
@@ -871,13 +876,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
const auto customTextColor = [=] {
|
||||
return style->coloredValues(false, state->index).name;
|
||||
};
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->emojiId,
|
||||
.ensureAddedEmojiId = { state->emojiId },
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
@@ -901,8 +905,8 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> channel,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen,
|
||||
rpl::producer<EmojiStatusId> statusIdValue,
|
||||
Fn<void(EmojiStatusId,TimeId)> statusIdChosen,
|
||||
bool group) {
|
||||
const auto button = ButtonStyleWithRightEmoji(
|
||||
parent,
|
||||
@@ -924,20 +928,20 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
struct State {
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
statusIdChosen(chosen.id, chosen.until);
|
||||
statusIdChosen({ chosen.id }, chosen.until);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) {
|
||||
state->statusId = id;
|
||||
state->emoji = id
|
||||
? session->data().customEmojiManager().create(
|
||||
id,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
@@ -985,13 +989,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
||||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->statusId,
|
||||
.ensureAddedEmojiId = { state->statusId },
|
||||
.channelStatusMode = true,
|
||||
});
|
||||
}
|
||||
@@ -1183,7 +1186,7 @@ void EditPeerColorBox(
|
||||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
rpl::variable<DocumentId> statusId;
|
||||
rpl::variable<EmojiStatusId> statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
bool changing = false;
|
||||
@@ -1263,8 +1266,7 @@ void EditPeerColorBox(
|
||||
{ &st::menuBlueIconWallpaper }
|
||||
);
|
||||
button->setClickedCallback([=] {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto strong = show->resolveWindow(usage)) {
|
||||
if (const auto strong = show->resolveWindow()) {
|
||||
show->show(Box<BackgroundBox>(strong, channel));
|
||||
}
|
||||
});
|
||||
@@ -1321,7 +1323,7 @@ void EditPeerColorBox(
|
||||
show,
|
||||
channel,
|
||||
state->statusId.value(),
|
||||
[=](DocumentId id, TimeId until) {
|
||||
[=](EmojiStatusId id, TimeId until) {
|
||||
state->statusId = id;
|
||||
state->statusUntil = until;
|
||||
state->statusChanged = true;
|
||||
@@ -1471,8 +1473,7 @@ void CheckBoostLevel(
|
||||
return;
|
||||
}
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
if (const auto controller = show->resolveWindow()) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "boxes/peers/edit_peer_reactions.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/peers/verify_peers_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/stickers_box.h"
|
||||
#include "boxes/username_box.h"
|
||||
@@ -218,6 +219,33 @@ void SaveSlowmodeSeconds(
|
||||
api->registerModifyRequest(key, requestId);
|
||||
}
|
||||
|
||||
void SaveStarsPerMessage(
|
||||
not_null<ChannelData*> channel,
|
||||
int starsPerMessage,
|
||||
Fn<void()> done) {
|
||||
const auto api = &channel->session().api();
|
||||
const auto key = Api::RequestKey("stars_per_message", channel->id);
|
||||
|
||||
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
|
||||
channel->inputChannel,
|
||||
MTP_long(starsPerMessage)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
api->clearModifyRequest(key);
|
||||
api->applyUpdates(result);
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
api->clearModifyRequest(key);
|
||||
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
|
||||
return;
|
||||
}
|
||||
channel->setStarsPerMessage(starsPerMessage);
|
||||
done();
|
||||
}).send();
|
||||
|
||||
api->registerModifyRequest(key, requestId);
|
||||
}
|
||||
|
||||
void SaveBoostsUnrestrict(
|
||||
not_null<ChannelData*> channel,
|
||||
int boostsUnrestrict,
|
||||
@@ -270,6 +298,7 @@ void ShowEditPermissions(
|
||||
channel,
|
||||
result.boostsUnrestrict,
|
||||
close);
|
||||
SaveStarsPerMessage(channel, result.starsPerMessage, close);
|
||||
}
|
||||
};
|
||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||
@@ -281,7 +310,9 @@ void ShowEditPermissions(
|
||||
const auto saveFor = peer->migrateToOrMe();
|
||||
const auto chat = saveFor->asChat();
|
||||
if (!chat
|
||||
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|
||||
|| (!result.slowmodeSeconds
|
||||
&& !result.boostsUnrestrict
|
||||
&& !result.starsPerMessage)) {
|
||||
save(saveFor, result);
|
||||
return;
|
||||
}
|
||||
@@ -366,6 +397,7 @@ private:
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
void fillBotVerifyAccounts();
|
||||
|
||||
void submitTitle();
|
||||
void submitDescription();
|
||||
@@ -1206,6 +1238,7 @@ void Controller::fillManageSection() {
|
||||
Ui::Text::RichLangValue),
|
||||
st::boxDividerLabel),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
fillBotVerifyAccounts();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1241,7 +1274,9 @@ void Controller::fillManageSection() {
|
||||
? channel->canViewMembers()
|
||||
: chat->amIn();
|
||||
const auto canViewKicked = isChannel
|
||||
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||
&& (channel->isMegagroup()
|
||||
? (channel->isBroadcast() || channel->isGigagroup())
|
||||
: true);
|
||||
const auto hasRecentActions = isChannel
|
||||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
|
||||
@@ -1796,6 +1831,39 @@ void Controller::fillBotEditSettingsButton() {
|
||||
{ &st::menuIconSettings });
|
||||
}
|
||||
|
||||
void Controller::fillBotVerifyAccounts() {
|
||||
Expects(_isBot);
|
||||
|
||||
const auto user = _peer->asUser();
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
_controls.buttonsLayout,
|
||||
object_ptr<Ui::VerticalLayout>(
|
||||
_controls.buttonsLayout)));
|
||||
wrap->toggleOn(rpl::single(
|
||||
rpl::empty
|
||||
) | rpl::then(user->owner().botCommandsChanges(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == _peer
|
||||
) | rpl::to_empty) | rpl::map([=] {
|
||||
const auto info = user->botInfo.get();
|
||||
return info && info->verifierSettings;
|
||||
}));
|
||||
|
||||
const auto inner = wrap->entity();
|
||||
Ui::AddSkip(inner);
|
||||
AddButtonWithCount(
|
||||
inner,
|
||||
tr::lng_manage_peer_bot_verify(),
|
||||
rpl::never<QString>(),
|
||||
[controller = _navigation->parentController(), user] {
|
||||
controller->show(MakeVerifyPeersBox(controller, user));
|
||||
},
|
||||
{ &st::menuIconFactcheck });
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
}
|
||||
|
||||
void Controller::submitTitle() {
|
||||
Expects(_controls.title != nullptr);
|
||||
|
||||
@@ -2656,3 +2724,9 @@ bool EditPeerInfoBox::Available(not_null<PeerData*> peer) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ShowEditChatPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
ShowEditPermissions(navigation, peer);
|
||||
}
|
||||
|
||||
@@ -56,3 +56,7 @@ private:
|
||||
not_null<PeerData*> _peer;
|
||||
|
||||
};
|
||||
|
||||
void ShowEditChatPermissions(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
@@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -26,7 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_helpers.h" // GetErrorTextForSending.
|
||||
#include "history/history_item_helpers.h" // GetErrorForSending.
|
||||
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -740,10 +740,10 @@ void Controller::setupAboveJoinedWidget() {
|
||||
{ QString::number(current.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
Core::TextContext({
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
});
|
||||
.repaint = [=] { widget->update(); },
|
||||
}));
|
||||
auto &lifetime = widget->lifetime();
|
||||
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
|
||||
session().credits().rateValue(_peer));
|
||||
@@ -994,10 +994,7 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
lt_cost,
|
||||
{ QString::number(data.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { subtitle1->update(); },
|
||||
});
|
||||
Core::TextContext({ .session = session }));
|
||||
const auto subtitle2 = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
@@ -1019,7 +1016,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
|
||||
const auto show = controller->uiShow();
|
||||
AddSubscriberEntryTable(show, content, {}, row->peer(), data.date);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
@@ -1483,8 +1481,12 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
? tr::lng_group_invite_copied(tr::now)
|
||||
: copied);
|
||||
};
|
||||
auto countMessagesCallback = [=](const TextWithTags &comment) {
|
||||
return 1;
|
||||
};
|
||||
auto submitCallback = [=](
|
||||
std::vector<not_null<Data::Thread*>> &&result,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags &&comment,
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions) {
|
||||
@@ -1492,29 +1494,18 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto error = [&] {
|
||||
for (const auto thread : result) {
|
||||
const auto error = GetErrorTextForSending(
|
||||
thread,
|
||||
{ .text = &comment });
|
||||
if (!error.isEmpty()) {
|
||||
return std::make_pair(error, thread);
|
||||
}
|
||||
}
|
||||
return std::make_pair(QString(), result.front());
|
||||
}();
|
||||
if (!error.first.isEmpty()) {
|
||||
auto text = TextWithEntities();
|
||||
if (result.size() > 1) {
|
||||
text.append(
|
||||
Ui::Text::Bold(error.second->chatListName())
|
||||
).append("\n\n");
|
||||
}
|
||||
text.append(error.first);
|
||||
const auto errorWithThread = GetErrorForSending(
|
||||
result,
|
||||
{ .text = &comment });
|
||||
if (errorWithThread.error) {
|
||||
if (*box) {
|
||||
(*box)->uiShow()->showBox(Ui::MakeInformBox(text));
|
||||
(*box)->uiShow()->showBox(MakeSendErrorBox(
|
||||
errorWithThread,
|
||||
result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
@@ -1542,7 +1533,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
};
|
||||
auto filterCallback = [](not_null<Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1551,9 +1542,10 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
auto object = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.countMessagesCallback = std::move(countMessagesCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
});
|
||||
*box = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
|
||||
@@ -31,10 +31,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "boxes/peers/edit_participants_box.h"
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
#include "settings/settings_power_saving.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h" // megagroupSizeMax
|
||||
#include "apiwrap.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -49,7 +51,6 @@ namespace {
|
||||
|
||||
constexpr auto kSlowmodeValues = 7;
|
||||
constexpr auto kBoostsUnrestrictValues = 5;
|
||||
constexpr auto kSuggestGigagroupThreshold = 199000;
|
||||
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
|
||||
@@ -889,11 +890,10 @@ void AddBoostsUnrestrictLabels(
|
||||
manager->registerInternalEmoji(
|
||||
st::boostsMessageIcon,
|
||||
st::boostsMessageIconPadding));
|
||||
const auto context = Core::MarkedTextContext{
|
||||
const auto context = Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
.customEmojiLoopLimit = 1,
|
||||
};
|
||||
});
|
||||
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
labels,
|
||||
@@ -940,9 +940,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
|
||||
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
|
||||
channel ? channel->boostsUnrestrict() : 0);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
|
||||
Ui::AddSkip(container);
|
||||
|
||||
auto enabled = boostsUnrestrict->value(
|
||||
) | rpl::map(_1 > 0);
|
||||
@@ -1006,19 +1004,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
|
||||
wrap->toggleOn(std::move(shown), anim::type::normal);
|
||||
wrap->finishAnimating();
|
||||
|
||||
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
|
||||
const auto divider = container->add(
|
||||
const auto inner = wrap->entity();
|
||||
|
||||
auto result = AddBoostsUnrestrictSlider(inner, peer);
|
||||
|
||||
const auto skip = st::defaultVerticalListSkip;
|
||||
const auto divider = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
|
||||
container,
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
|
||||
divider->toggleOn(rpl::combine(
|
||||
std::move(shown),
|
||||
rpl::duplicate(result),
|
||||
!rpl::mappers::_1 || !rpl::mappers::_2));
|
||||
inner,
|
||||
object_ptr<Ui::BoxContentDivider>(inner),
|
||||
QMargins{ 0, skip, 0, skip }));
|
||||
divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
|
||||
divider->finishAnimating();
|
||||
|
||||
return result;
|
||||
@@ -1157,7 +1156,43 @@ void ShowEditPeerPermissionsBox(
|
||||
rpl::variable<int> slowmodeSeconds;
|
||||
rpl::variable<int> boostsUnrestrict;
|
||||
rpl::variable<bool> hasSendRestrictions;
|
||||
rpl::variable<int> starsPerMessage;
|
||||
};
|
||||
const auto state = inner->lifetime().make_state<State>();
|
||||
const auto channel = peer->asChannel();
|
||||
const auto available = channel && channel->paidMessagesAvailable();
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDivider(inner);
|
||||
auto charging = (Ui::SettingsButton*)nullptr;
|
||||
if (available) {
|
||||
Ui::AddSkip(inner);
|
||||
const auto starsPerMessage = peer->isChannel()
|
||||
? peer->asChannel()->starsPerMessage()
|
||||
: 0;
|
||||
charging = inner->add(object_ptr<Ui::SettingsButton>(
|
||||
inner,
|
||||
tr::lng_rights_charge_stars(),
|
||||
st::settingsButtonNoIcon));
|
||||
charging->toggleOn(rpl::single(starsPerMessage > 0));
|
||||
Ui::AddSkip(inner);
|
||||
Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
|
||||
|
||||
const auto chargeWrap = inner->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
inner,
|
||||
object_ptr<Ui::VerticalLayout>(inner)));
|
||||
chargeWrap->toggleOn(charging->toggledValue());
|
||||
chargeWrap->finishAnimating();
|
||||
const auto chargeInner = chargeWrap->entity();
|
||||
|
||||
Ui::AddSkip(chargeInner);
|
||||
state->starsPerMessage = SetupChargeSlider(
|
||||
chargeInner,
|
||||
peer,
|
||||
starsPerMessage);
|
||||
}
|
||||
|
||||
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
||||
| Flag::SendGames
|
||||
| Flag::SendGifs
|
||||
@@ -1171,7 +1206,6 @@ void ShowEditPeerPermissionsBox(
|
||||
| Flag::SendVoiceMessages
|
||||
| Flag::SendFiles
|
||||
| Flag::SendOther;
|
||||
const auto state = inner->lifetime().make_state<State>();
|
||||
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|
||||
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
|
||||
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
|
||||
@@ -1189,8 +1223,11 @@ void ShowEditPeerPermissionsBox(
|
||||
});
|
||||
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
constexpr auto kThresholdOffset = int(1000);
|
||||
const auto threshold = -kThresholdOffset
|
||||
+ channel->session().serverConfig().megagroupSizeMax;
|
||||
if (channel->amCreator()
|
||||
&& channel->membersCount() >= kSuggestGigagroupThreshold) {
|
||||
&& channel->membersCount() >= threshold) {
|
||||
AddSuggestGigagroup(
|
||||
inner,
|
||||
AboutGigagroupCallback(
|
||||
@@ -1209,10 +1246,14 @@ void ShowEditPeerPermissionsBox(
|
||||
const auto boostsUnrestrict = hasRestrictions
|
||||
? state->boostsUnrestrict.current()
|
||||
: 0;
|
||||
const auto starsPerMessage = (charging && charging->toggled())
|
||||
? state->starsPerMessage.current()
|
||||
: 0;
|
||||
done({
|
||||
restrictions,
|
||||
slowmodeSeconds,
|
||||
boostsUnrestrict,
|
||||
starsPerMessage,
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
||||
@@ -39,6 +39,7 @@ struct EditPeerPermissionsBoxResult final {
|
||||
ChatRestrictions rights;
|
||||
int slowmodeSeconds = 0;
|
||||
int boostsUnrestrict = 0;
|
||||
int starsPerMessage = 0;
|
||||
};
|
||||
|
||||
void ShowEditPeerPermissionsBox(
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
@@ -362,12 +363,15 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
const auto customEmojiPaused = [controller = args.controller] {
|
||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||
};
|
||||
auto factory = [=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
auto context = Core::TextContext({
|
||||
.session = session,
|
||||
});
|
||||
context.customEmojiFactory = [=](
|
||||
QStringView data,
|
||||
const Ui::Text::MarkedContext &context
|
||||
) -> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto id = Data::ParseCustomEmojiData(data);
|
||||
auto result = owner->customEmojiManager().create(
|
||||
data,
|
||||
std::move(update));
|
||||
auto result = Ui::Text::MakeCustomEmoji(data, context);
|
||||
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
|
||||
return std::make_unique<MaybeDisabledEmoji>(
|
||||
std::move(result),
|
||||
@@ -376,12 +380,10 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
using namespace Ui::Text;
|
||||
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
||||
};
|
||||
raw->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, customEmojiPaused, customEmojiPaused, std::move(factory));
|
||||
raw->setCustomTextContext(
|
||||
std::move(context),
|
||||
customEmojiPaused,
|
||||
customEmojiPaused);
|
||||
|
||||
const auto callback = args.callback;
|
||||
const auto isCustom = [=](DocumentId id) {
|
||||
@@ -491,7 +493,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
panelList.erase(
|
||||
ranges::remove(panelList, paid->selectAnimation->id),
|
||||
end(panelList));
|
||||
panel->selector()->provideRecentEmoji(panelList);
|
||||
panel->selector()->provideRecentEmoji(
|
||||
ChatHelpers::DocumentListToRecent(panelList));
|
||||
panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
|
||||
@@ -106,6 +106,8 @@ PeerShortInfoCover::PeerShortInfoCover(
|
||||
, _statusStyle(std::make_unique<CustomLabelStyle>(_st.status))
|
||||
, _status(_widget.get(), std::move(status), _statusStyle->st)
|
||||
, _roundMask(Images::CornersMask(_st.radius))
|
||||
, _roundMaskRetina(
|
||||
Images::CornersMask(_st.radius / style::DevicePixelRatio()))
|
||||
, _videoPaused(std::move(videoPaused)) {
|
||||
_widget->setCursor(_cursor);
|
||||
|
||||
@@ -190,7 +192,7 @@ void PeerShortInfoCover::paint(QPainter &p) {
|
||||
if (!frame.isNull()) {
|
||||
frame = Images::Round(
|
||||
std::move(frame),
|
||||
_roundMask,
|
||||
_roundMaskRetina,
|
||||
RectPart::TopLeft | RectPart::TopRight);
|
||||
} else if (_userpicImage.isNull()) {
|
||||
auto image = QImage(
|
||||
@@ -226,10 +228,11 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
const auto top = _widget->height() - fill;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
if (fill > 0) {
|
||||
const auto t = roundedHeight + _scrollTop;
|
||||
p.drawImage(
|
||||
QRect(0, top, roundedWidth, fill),
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor),
|
||||
image,
|
||||
QRect(0, top * factor, roundedWidth * factor, fill * factor));
|
||||
QRect(0, t, roundedWidth * factor, (roundedWidth - t) * factor));
|
||||
}
|
||||
if (covered <= 0) {
|
||||
return;
|
||||
@@ -238,9 +241,9 @@ void PeerShortInfoCover::paintCoverImage(QPainter &p, const QImage &image) {
|
||||
const auto from = top - rounded;
|
||||
auto q = QPainter(&_roundedTopImage);
|
||||
q.drawImage(
|
||||
QRect(0, 0, roundedWidth, rounded),
|
||||
QRect(0, 0, roundedWidth * factor, rounded * factor),
|
||||
image,
|
||||
QRect(0, from * factor, roundedWidth * factor, rounded * factor));
|
||||
QRect(0, _scrollTop, roundedWidth * factor, rounded * factor));
|
||||
q.end();
|
||||
_roundedTopImage = Images::Round(
|
||||
std::move(_roundedTopImage),
|
||||
|
||||
@@ -123,6 +123,7 @@ private:
|
||||
object_ptr<Ui::FlatLabel> _additionalStatus = { nullptr };
|
||||
|
||||
std::array<QImage, 4> _roundMask;
|
||||
std::array<QImage, 4> _roundMaskRetina;
|
||||
QImage _userpicImage;
|
||||
QImage _roundedTopImage;
|
||||
QImage _barSmall;
|
||||
|
||||
@@ -486,20 +486,23 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
const auto open = [=] { navigation->showPeerHistory(peer); };
|
||||
const auto open = [=] {
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
window->showPeerHistory(peer);
|
||||
}
|
||||
};
|
||||
const auto videoIsPaused = [=] {
|
||||
return navigation->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
return show->paused(Window::GifPauseReason::Layer);
|
||||
};
|
||||
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
|
||||
const auto controller = navigation->parentController();
|
||||
const auto peerSeparateId = Window::SeparateId(peer);
|
||||
if (controller->windowId() != peerSeparateId) {
|
||||
const auto window = show->resolveWindow();
|
||||
if (window && window->windowId() != peerSeparateId) {
|
||||
addAction(tr::lng_context_new_window(tr::now), [=] {
|
||||
Ui::PreventDelayedActivation();
|
||||
controller->showInNewWindow(peer);
|
||||
window->showInNewWindow(peer);
|
||||
}, &st::menuIconNewWindow);
|
||||
}
|
||||
};
|
||||
@@ -511,6 +514,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
stOverride);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
|
||||
}
|
||||
|
||||
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
|
||||
return StatusValue(peer);
|
||||
}
|
||||
|
||||