Compare commits
377 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af35beefc2 | ||
|
|
532258bea8 | ||
|
|
5b257293eb | ||
|
|
8e6d23ddd6 | ||
|
|
6d5d61c842 | ||
|
|
9d2e2a1739 | ||
|
|
19e2642ec1 | ||
|
|
f9df522b41 | ||
|
|
b3e5c4a4aa | ||
|
|
06ed6c81a7 | ||
|
|
d1e4dbb603 | ||
|
|
abe8079296 | ||
|
|
033cb2790c | ||
|
|
188edce258 | ||
|
|
34858b36c1 | ||
|
|
044ef3447c | ||
|
|
2660439160 | ||
|
|
3f6f96cfb4 | ||
|
|
99a1c98ae0 | ||
|
|
fd718dfd5c | ||
|
|
31cb2f1999 | ||
|
|
a6a8e32be7 | ||
|
|
594bb8a76b | ||
|
|
228bbc1e8e | ||
|
|
b14c2878b3 | ||
|
|
57f10040e1 | ||
|
|
0fb67c78a9 | ||
|
|
ff0f7f49da | ||
|
|
9682e37547 | ||
|
|
7197d9480b | ||
|
|
3315c9c7f4 | ||
|
|
f57eff4195 | ||
|
|
f0c8e48403 | ||
|
|
de87bed375 | ||
|
|
5cdfaab2db | ||
|
|
acb0b029b9 | ||
|
|
4c74cbbbe9 | ||
|
|
ad64e068db | ||
|
|
6bed3f3f09 | ||
|
|
d02e55da06 | ||
|
|
d662a8f2b9 | ||
|
|
13a93102a5 | ||
|
|
0d6a1e6610 | ||
|
|
05cd9eebb8 | ||
|
|
d93d8ab1cc | ||
|
|
540fa0e669 | ||
|
|
17a10cf6bb | ||
|
|
11d0f9db03 | ||
|
|
f024ceecdd | ||
|
|
08c07a0785 | ||
|
|
b843f91b3c | ||
|
|
4b2c5b3321 | ||
|
|
2b43f2682a | ||
|
|
7da0124286 | ||
|
|
feaeef6482 | ||
|
|
15bcfeec1d | ||
|
|
5fb002ab4c | ||
|
|
f56ddbb1e0 | ||
|
|
6bd2a7c962 | ||
|
|
9d591ae806 | ||
|
|
7d30e3913c | ||
|
|
19d7dd7aa3 | ||
|
|
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 |
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
|
||||
|
||||
@@ -919,6 +919,8 @@ PRIVATE
|
||||
history/history_unread_things.h
|
||||
history/history_view_highlight_manager.cpp
|
||||
history/history_view_highlight_manager.h
|
||||
history/history_view_swipe_back_session.cpp
|
||||
history/history_view_swipe_back_session.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/bot/earn/info_bot_earn_list.cpp
|
||||
@@ -959,6 +961,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
|
||||
@@ -1014,8 +1017,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
|
||||
@@ -1135,6 +1138,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
|
||||
@@ -1203,6 +1208,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
|
||||
@@ -1221,7 +1228,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
|
||||
@@ -1471,6 +1477,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
|
||||
@@ -2055,14 +2063,16 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
|
||||
configure_file("../lib/xdg/org.telegram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" @ONLY)
|
||||
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml")
|
||||
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "telegram.png")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg")
|
||||
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "org.telegram.desktop.png")
|
||||
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-symbolic.svg")
|
||||
install(FILES "Resources/icons/tray_monochrome_attention.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-attention-symbolic.svg")
|
||||
install(FILES "Resources/icons/tray_monochrome_mute.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "org.telegram.desktop-mute-symbolic.svg")
|
||||
install(FILES "../lib/xdg/org.telegram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/org.telegram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
|
||||
|
||||
@@ -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/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 |
7
Telegram/Resources/icons/tray_monochrome_attention.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<circle class="error" fill="#f23c34" cx="3.9" cy="12.7" r="2.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
7
Telegram/Resources/icons/tray_monochrome_mute.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<circle fill="#888888" cx="3.9" cy="12.7" r="2.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -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,47 @@ 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_shortcuts_show_chat_preview" = "Show chat preview";
|
||||
|
||||
"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 +1219,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 +1358,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 +1398,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";
|
||||
@@ -2020,16 +2074,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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}";
|
||||
@@ -2038,6 +2104,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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.";
|
||||
@@ -2099,6 +2167,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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";
|
||||
@@ -2108,9 +2185,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";
|
||||
@@ -2373,6 +2464,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";
|
||||
@@ -2517,6 +2609,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";
|
||||
@@ -2586,6 +2682,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";
|
||||
@@ -2699,6 +2801,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}";
|
||||
@@ -2960,11 +3064,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";
|
||||
@@ -3226,6 +3336,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 >";
|
||||
@@ -3237,12 +3348,22 @@ 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!";
|
||||
@@ -3254,20 +3375,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_owner_change" = "change";
|
||||
"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";
|
||||
@@ -3276,6 +3403,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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}.";
|
||||
@@ -3291,14 +3419,21 @@ 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.";
|
||||
@@ -3309,6 +3444,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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";
|
||||
@@ -3328,6 +3464,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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";
|
||||
@@ -3344,6 +3486,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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.";
|
||||
@@ -3485,6 +3643,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}.";
|
||||
@@ -3513,6 +3687,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}";
|
||||
@@ -3759,6 +3934,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";
|
||||
@@ -3875,6 +4052,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}";
|
||||
@@ -3903,6 +4081,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";
|
||||
@@ -4032,6 +4212,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.";
|
||||
@@ -4155,7 +4336,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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";
|
||||
@@ -4666,6 +4849,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.";
|
||||
@@ -4673,6 +4860,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.";
|
||||
@@ -4804,6 +5013,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";
|
||||
|
||||
@@ -5520,6 +5732,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?";
|
||||
@@ -5836,6 +6049,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";
|
||||
@@ -5895,6 +6109,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";
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
<file alias="icons/settings/star.svg">../../icons/settings/star.svg</file>
|
||||
<file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file>
|
||||
<file alias="icons/tray/monochrome.svg">../../icons/tray_monochrome.svg</file>
|
||||
<file alias="icons/tray/monochrome_attention.svg">../../icons/tray_monochrome_attention.svg</file>
|
||||
<file alias="icons/tray/monochrome_mute.svg">../../icons/tray_monochrome_mute.svg</file>
|
||||
<file alias="topic_icons/blue.svg">../../art/topic_icons/blue.svg</file>
|
||||
<file alias="topic_icons/yellow.svg">../../art/topic_icons/yellow.svg</file>
|
||||
<file alias="topic_icons/violet.svg">../../art/topic_icons/violet.svg</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.10.3.0" />
|
||||
Version="5.12.5.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,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,12,5,0
|
||||
PRODUCTVERSION 5,12,5,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.10.3.0"
|
||||
VALUE "FileVersion", "5.12.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.12.5.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,12,5,0
|
||||
PRODUCTVERSION 5,12,5,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.10.3.0"
|
||||
VALUE "FileVersion", "5.12.5.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.12.5.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);
|
||||
|
||||
@@ -149,18 +149,14 @@ void InitFilterLinkHeader(
|
||||
iconEmoji
|
||||
).value_or(Ui::FilterIcon::Custom)).active;
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> repaint) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||
.type = type,
|
||||
.title = TitleText(type)(tr::now),
|
||||
.about = AboutText(type, title.text),
|
||||
.makeAboutContext = makeContext,
|
||||
.aboutContext = Core::TextContext({
|
||||
.session = &box->peerListUiShow()->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
.folderTitle = title.text,
|
||||
.folderIcon = icon,
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
@@ -560,16 +556,12 @@ void ShowImportToast(
|
||||
text.append('\n').append(phrase(tr::now, lt_count, added));
|
||||
}
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
strong->showToast({
|
||||
.text = std::move(text),
|
||||
.textContext = makeContext,
|
||||
.textContext = Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -640,18 +632,14 @@ void ProcessFilterInvite(
|
||||
raw->setRealContentHeight(box->heightValue());
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title.text,
|
||||
makeContext,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
@@ -873,18 +861,14 @@ void ProcessFilterRemove(
|
||||
}, type, title, iconEmoji, rpl::single(0), horizontalFilters);
|
||||
|
||||
const auto isStatic = title.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &strong->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title.text,
|
||||
makeContext,
|
||||
Core::TextContext({
|
||||
.session = &strong->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -90,7 +90,13 @@ 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>();
|
||||
@@ -110,9 +116,9 @@ constexpr auto kTransactionsLimit = 100;
|
||||
.bareGiftStickerId = giftStickerId,
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.uniqueGift = parsedGift ? parsedGift->unique : nullptr,
|
||||
.starrefAmount = starrefAmount,
|
||||
.starrefCommission = starrefCommission,
|
||||
.starrefRecipientId = starrefBarePeerId,
|
||||
.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 &) {
|
||||
@@ -138,9 +144,15 @@ constexpr auto kTransactionsLimit = 100;
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.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(),
|
||||
@@ -513,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);
|
||||
@@ -694,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()));
|
||||
@@ -727,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(
|
||||
@@ -805,8 +839,14 @@ std::optional<Data::StarGift> FromTL(
|
||||
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()),
|
||||
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
|
||||
.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,
|
||||
@@ -829,9 +869,9 @@ std::optional<Data::StarGift> FromTL(
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<Data::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());
|
||||
@@ -841,8 +881,12 @@ std::optional<Data::UserStarGift> FromTL(
|
||||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
}
|
||||
return Data::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()),
|
||||
@@ -855,12 +899,12 @@ std::optional<Data::UserStarGift> FromTL(
|
||||
.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(),
|
||||
};
|
||||
@@ -910,11 +954,9 @@ Data::UniqueGiftOriginalDetails FromTL(
|
||||
auto result = Data::UniqueGiftOriginalDetails();
|
||||
result.date = data.vdate().v;
|
||||
result.senderId = data.vsender_id()
|
||||
? peerFromUser(
|
||||
UserId(data.vsender_id().value_or_empty()))
|
||||
? peerFromMTP(*data.vsender_id())
|
||||
: PeerId();
|
||||
result.recipientId = peerFromUser(
|
||||
UserId(data.vrecipient_id().v));
|
||||
result.recipientId = peerFromMTP(data.vrecipient_id());
|
||||
result.message = data.vmessage()
|
||||
? ParseTextWithEntities(session, *data.vmessage())
|
||||
: TextWithEntities();
|
||||
|
||||
@@ -116,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();
|
||||
@@ -166,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;
|
||||
|
||||
};
|
||||
|
||||
@@ -208,6 +209,7 @@ private:
|
||||
};
|
||||
struct Store final {
|
||||
uint64 amount = 0;
|
||||
QString currency;
|
||||
QString product;
|
||||
int quantity = 0;
|
||||
};
|
||||
@@ -218,7 +220,7 @@ private:
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
std::vector<QString> currencies;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
std::vector<int> _availablePresets;
|
||||
@@ -244,12 +246,20 @@ 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);
|
||||
|
||||
@@ -259,9 +269,9 @@ enum class RequirePremiumState {
|
||||
[[nodiscard]] std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1219,7 +1219,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1257,7 +1258,8 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck(),
|
||||
MTPint()), // report_delivery_until_date
|
||||
MTPint(), // report_delivery_until_date
|
||||
MTPlong()), // paid_message_stars
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -2709,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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +172,6 @@ void ChangeFilterById(
|
||||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
const auto isStatic = name.isStatic;
|
||||
const auto textContext = [=](not_null<QWidget*> widget) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
controller->showToast({
|
||||
.text = (add
|
||||
? tr::lng_filters_toast_add
|
||||
@@ -189,7 +182,10 @@ void ChangeFilterById(
|
||||
lt_folder,
|
||||
Ui::Text::Wrapped(name.text, EntityType::Bold),
|
||||
Ui::Text::WithEntities),
|
||||
.textContext = textContext,
|
||||
.textContext = Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
@@ -290,19 +286,18 @@ void FillChooseFilterMenu(
|
||||
const auto title = filter.title();
|
||||
auto item = base::make_unique_q<FilterAction>(
|
||||
menu.get(),
|
||||
st::foldersMenu,
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu.get(),
|
||||
Ui::Text::FixAmpersandInAction(title.text.text),
|
||||
std::move(callback)),
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr,
|
||||
contains ? &st::mediaPlayerMenuCheck : nullptr);
|
||||
const auto context = Core::MarkedTextContext{
|
||||
item->setMarkedText(title.text, QString(), Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = [raw = item.get()] { raw->update(); },
|
||||
.repaint = [raw = item.get()] { raw->update(); },
|
||||
.customEmojiLoopLimit = title.isStatic ? -1 : 0,
|
||||
};
|
||||
item->setMarkedText(title.text, QString(), context);
|
||||
}));
|
||||
|
||||
item->setIcon(Icon(showColors ? filter : filter.withColorIndex({})));
|
||||
const auto action = menu->addAction(std::move(item));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -271,7 +271,8 @@ void DeleteMessagesBox::prepare() {
|
||||
appendDetails({
|
||||
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
|
||||
});
|
||||
} else if (!peer->isSelf()) {
|
||||
} else if (!peer->isSelf()
|
||||
&& (!peer->isUser() || !peer->asUser()->isInaccessible())) {
|
||||
if (const auto user = peer->asUser(); user && user->isBot()) {
|
||||
_revokeForBot = true;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
: tr::lng_edit_privacy_exceptions_add(tr::now);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -441,13 +441,10 @@ void EditFilterBox(
|
||||
using namespace Window;
|
||||
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
|
||||
};
|
||||
name->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
});
|
||||
}, [paused] {
|
||||
name->setCustomTextContext(Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiLoopLimit = value ? -1 : 0,
|
||||
}), [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
@@ -609,10 +606,7 @@ void EditFilterBox(
|
||||
float64 alpha = 1.;
|
||||
};
|
||||
const auto tag = preview->lifetime().make_state<TagState>();
|
||||
tag->context.textContext = Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [] {},
|
||||
};
|
||||
tag->context.textContext = Core::TextContext({ .session = session });
|
||||
preview->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(preview);
|
||||
p.setOpacity(tag->alpha);
|
||||
|
||||
@@ -163,10 +163,10 @@ ExceptionRow::ExceptionRow(
|
||||
st::defaultTextStyle,
|
||||
filters,
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
Core::TextContext({
|
||||
.session = &history->session(),
|
||||
.customEmojiRepaint = repaint,
|
||||
});
|
||||
.repaint = repaint,
|
||||
}));
|
||||
} else if (peer()->isSelf()) {
|
||||
setCustomStatus(tr::lng_saved_forward_here(tr::now));
|
||||
}
|
||||
|
||||
@@ -537,13 +537,6 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
verticalLayout->add(std::move(icon.widget));
|
||||
|
||||
const auto isStatic = _filterTitle.isStatic;
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = &_window->session(),
|
||||
.customEmojiRepaint = update,
|
||||
.customEmojiLoopLimit = isStatic ? -1 : 0,
|
||||
};
|
||||
};
|
||||
verticalLayout->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
verticalLayout,
|
||||
@@ -559,7 +552,10 @@ void LinkController::addHeader(not_null<Ui::VerticalLayout*> container) {
|
||||
Ui::Text::WithEntities)),
|
||||
st::settingsFilterDividerLabel,
|
||||
st::defaultPopupMenu,
|
||||
makeContext)),
|
||||
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,29 +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()> 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);
|
||||
|
||||
@@ -1154,8 +1154,7 @@ RecoverBox::RecoverBox(
|
||||
rpl::single(Ui::Text::WrapEmailPattern(pattern)),
|
||||
Ui::Text::WithEntities),
|
||||
st::termsContent,
|
||||
st::defaultPopupMenu,
|
||||
[=](Fn<void()> update) { return CommonTextContext{ std::move(update) }; })
|
||||
st::defaultPopupMenu)
|
||||
, _closeParent(std::move(closeParent)) {
|
||||
_patternLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (_cloudFields.pendingResetDate != 0 || !session) {
|
||||
|
||||
@@ -873,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.
|
||||
@@ -912,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);
|
||||
|
||||
@@ -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> &;
|
||||
|
||||
@@ -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,6 +41,7 @@ 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"
|
||||
@@ -275,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;
|
||||
}
|
||||
}
|
||||
@@ -318,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();
|
||||
@@ -726,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(
|
||||
@@ -759,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 {
|
||||
@@ -769,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) {
|
||||
@@ -836,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(
|
||||
@@ -860,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;
|
||||
}
|
||||
|
||||
@@ -1093,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());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -219,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,
|
||||
@@ -271,6 +298,7 @@ void ShowEditPermissions(
|
||||
channel,
|
||||
result.boostsUnrestrict,
|
||||
close);
|
||||
SaveStarsPerMessage(channel, result.starsPerMessage, close);
|
||||
}
|
||||
};
|
||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||
@@ -282,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;
|
||||
}
|
||||
@@ -1244,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)
|
||||
@@ -2692,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"
|
||||
@@ -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) {
|
||||
@@ -1502,6 +1504,8 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
|
||||
result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
@@ -1529,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;
|
||||
}
|
||||
}
|
||||
@@ -1538,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,17 @@ 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 simpleContext = Core::TextContext({
|
||||
.session = session,
|
||||
.repaint = [=] { raw->update(); },
|
||||
});
|
||||
auto context = simpleContext;
|
||||
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, simpleContext);
|
||||
if (state->unifiedFactoryOwner->lookupReactionId(id).custom()) {
|
||||
return std::make_unique<MaybeDisabledEmoji>(
|
||||
std::move(result),
|
||||
@@ -376,12 +382,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 +495,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);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ struct ShortInfoCover;
|
||||
struct ShortInfoBox;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui::Menu {
|
||||
struct MenuCallback;
|
||||
} // namespace Ui::Menu
|
||||
@@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic {
|
||||
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
|
||||
@@ -1139,8 +1139,7 @@ void PreviewBox(
|
||||
button->resizeToWidth(width);
|
||||
if (!descriptor.fromSettings) {
|
||||
button->setClickedCallback([=] {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto window = show->resolveWindow();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
@@ -1527,6 +1526,18 @@ void DoubledLimitsPreviewBox(
|
||||
Main::Domain::kPremiumMaxAccounts,
|
||||
till,
|
||||
});
|
||||
{
|
||||
const auto premium = limits.similarChannelsPremium();
|
||||
entries.push_back({
|
||||
tr::lng_premium_double_limits_subtitle_similar_channels(),
|
||||
tr::lng_premium_double_limits_about_similar_channels(
|
||||
lt_count,
|
||||
rpl::single(float64(premium)),
|
||||
Ui::Text::RichLangValue),
|
||||
limits.similarChannelsDefault(),
|
||||
premium,
|
||||
});
|
||||
}
|
||||
Ui::Premium::ShowListBox(
|
||||
box,
|
||||
st::defaultPremiumLimits,
|
||||
|
||||
@@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "core/ui_integration.h" // TextContext
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_photo.h"
|
||||
@@ -511,32 +511,28 @@ TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
|
||||
}
|
||||
|
||||
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::starIconSmall,
|
||||
st::starIconSmallPadding,
|
||||
true),
|
||||
return Ui::Text::IconEmoji(
|
||||
&st::starIconEmoji,
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
Text::MarkedContext context,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg) {
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st);
|
||||
context.repaint = [=] { buttonLabel->update(); };
|
||||
rpl::duplicate(
|
||||
text
|
||||
) | rpl::filter([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
context([=] { buttonLabel->update(); }));
|
||||
buttonLabel->setMarkedText(text, context);
|
||||
}, buttonLabel->lifetime());
|
||||
if (textFg) {
|
||||
buttonLabel->setTextColorOverride((*textFg)->c);
|
||||
@@ -565,15 +561,12 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg) {
|
||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = update,
|
||||
};
|
||||
}, st, textFg);
|
||||
return SetButtonMarkedLabel(button, text, Core::TextContext({
|
||||
.session = session,
|
||||
}), st, textFg);
|
||||
}
|
||||
|
||||
void SendStarGift(
|
||||
void SendStarsForm(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done) {
|
||||
|
||||
@@ -41,7 +41,7 @@ void SendCreditsBox(
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
Text::MarkedContext context,
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
@@ -52,7 +52,7 @@ not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
const style::FlatLabel &st,
|
||||
const style::color *textFg = nullptr);
|
||||
|
||||
void SendStarGift(
|
||||
void SendStarsForm(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done);
|
||||
|
||||
@@ -59,9 +59,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
@@ -242,7 +242,7 @@ SendFilesBox::Block::Block(
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
Fn<bool(const Ui::PreparedFile &, Ui::AttachActionType)> actionAllowed)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
@@ -260,7 +260,9 @@ SendFilesBox::Block::Block(
|
||||
st,
|
||||
my,
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](int index, Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from + index], type);
|
||||
});
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
@@ -268,7 +270,9 @@ SendFilesBox::Block::Block(
|
||||
st,
|
||||
gifPaused,
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
[=](Ui::AttachActionType type) {
|
||||
return actionAllowed((*_items)[from], type);
|
||||
});
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
@@ -344,6 +348,38 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemEditCoverRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbEditCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->editCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemClearCoverRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbClearCoverRequested() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->clearCoverRequests() | rpl::map_to(from);
|
||||
} else {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
@@ -678,6 +714,18 @@ void SendFilesBox::openDialogToAddFileToAlbum() {
|
||||
crl::guard(this, callback));
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshMessagesCount() {
|
||||
const auto way = _sendWay.current();
|
||||
const auto withCaption = _list.canAddCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos());
|
||||
const auto withComment = !withCaption
|
||||
&& _caption
|
||||
&& !_caption->isHidden()
|
||||
&& !_caption->getTextWithTags().text.isEmpty();
|
||||
_messagesCount = _list.files.size() + (withComment ? 1 : 0);
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshButtons() {
|
||||
clearButtons();
|
||||
|
||||
@@ -686,6 +734,15 @@ void SendFilesBox::refreshButtons() {
|
||||
? tr::lng_send_button()
|
||||
: tr::lng_create_group_next()),
|
||||
[=] { send({}); });
|
||||
refreshMessagesCount();
|
||||
|
||||
const auto perMessage = _captionToPeer
|
||||
? _captionToPeer->starsPerMessageChecked()
|
||||
: 0;
|
||||
if (perMessage > 0) {
|
||||
_send->setText(PaidSendButtonText(_messagesCount.value(
|
||||
) | rpl::map(rpl::mappers::_1 * perMessage)));
|
||||
}
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
_send,
|
||||
@@ -802,10 +859,9 @@ void SendFilesBox::refreshPriceTag() {
|
||||
QString(),
|
||||
st::paidTagLabel);
|
||||
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
||||
label->setMarkedText(text, Core::MarkedTextContext{
|
||||
label->setMarkedText(text, Core::TextContext({
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
}));
|
||||
}, label->lifetime());
|
||||
label->show();
|
||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
@@ -1008,7 +1064,16 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
till,
|
||||
gifPaused,
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
[=](const Ui::PreparedFile &file, Ui::AttachActionType type) {
|
||||
return (type == Ui::AttachActionType::ToggleSpoiler)
|
||||
? !hasPrice()
|
||||
: (type == Ui::AttachActionType::EditCover)
|
||||
? (file.isVideoFile()
|
||||
&& _captionToPeer
|
||||
&& (_captionToPeer->isBroadcast()
|
||||
|| _captionToPeer->isSelf()))
|
||||
: (file.videoCover != nullptr);
|
||||
});
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
@@ -1129,7 +1194,79 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
show,
|
||||
&_list.files[index],
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
[=](bool ok) { if (ok) refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemEditCoverRequest(
|
||||
) | rpl::start_with_next([=, show = _show](int index) {
|
||||
applyBlockChanges();
|
||||
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
return;
|
||||
}
|
||||
auto &entry = _list.files[index];
|
||||
const auto video = entry.information
|
||||
? std::get_if<Ui::PreparedFileInformation::Video>(
|
||||
&entry.information->media)
|
||||
: nullptr;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
auto old = std::shared_ptr<Ui::PreparedFile>(
|
||||
std::move(entry.videoCover));
|
||||
entry.videoCover = std::make_unique<Ui::PreparedFile>(
|
||||
std::move(list.files.front()));
|
||||
Editor::OpenWithPreparedFile(
|
||||
this,
|
||||
show,
|
||||
entry.videoCover.get(),
|
||||
st::sendMediaPreviewSize,
|
||||
crl::guard(this, [=](bool ok) {
|
||||
if (!ok) {
|
||||
_list.files[index].videoCover = old
|
||||
? std::make_unique<Ui::PreparedFile>(
|
||||
std::move(*old))
|
||||
: nullptr;
|
||||
}
|
||||
refreshAllAfterChanges(from);
|
||||
}),
|
||||
video->thumbnail.size());
|
||||
};
|
||||
const auto checkResult = [=](const Ui::PreparedList &list) {
|
||||
if (list.files.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (list.files.front().type != Ui::PreparedFile::Type::Photo) {
|
||||
show->showToast(tr::lng_choose_cover_bad(tr::now));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto callback = [=](FileDialog::OpenResult &&result) {
|
||||
const auto premium = _show->session().premium();
|
||||
FileDialogCallback(
|
||||
std::move(result),
|
||||
checkResult,
|
||||
replace,
|
||||
premium,
|
||||
show);
|
||||
};
|
||||
|
||||
FileDialog::GetOpenPath(
|
||||
this,
|
||||
tr::lng_choose_cover(tr::now),
|
||||
FileDialog::ImagesFilter(),
|
||||
crl::guard(this, callback));
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemClearCoverRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
applyBlockChanges();
|
||||
refreshAllAfterChanges(from, [&] {
|
||||
auto &entry = _list.files[index];
|
||||
entry.videoCover = nullptr;
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
@@ -1334,6 +1471,7 @@ void SendFilesBox::setupCaption() {
|
||||
_caption->changes()
|
||||
) | rpl::start_with_next([=] {
|
||||
checkCharsLimitation();
|
||||
refreshMessagesCount();
|
||||
}, _caption->lifetime());
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,9 @@ private:
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Fn<bool(
|
||||
const Ui::PreparedFile &,
|
||||
Ui::AttachActionType)> actionAllowed);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
@@ -164,6 +166,8 @@ private:
|
||||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemEditCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemClearCoverRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
@@ -242,6 +246,7 @@ private:
|
||||
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
||||
|
||||
void checkCharsLimitation();
|
||||
void refreshMessagesCount();
|
||||
|
||||
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor);
|
||||
@@ -257,6 +262,7 @@ private:
|
||||
|
||||
Ui::PreparedList _list;
|
||||
std::optional<int> _removingIndex;
|
||||
rpl::variable<int> _messagesCount;
|
||||
|
||||
SendFilesLimits _limits = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
|
||||
@@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
@@ -117,12 +118,13 @@ private:
|
||||
Ui::RoundImageCheckbox checkbox;
|
||||
Ui::Text::String name;
|
||||
Ui::Animations::Simple nameActive;
|
||||
bool locked = false;
|
||||
Api::MessageMoneyRestriction restriction;
|
||||
RestrictionBadgeCache badgeCache;
|
||||
};
|
||||
|
||||
void invalidateCache();
|
||||
bool showLockedError(not_null<Chat*> chat);
|
||||
void refreshLockedRows();
|
||||
void refreshRestrictedRows();
|
||||
|
||||
[[nodiscard]] int displayedChatsCount() const;
|
||||
[[nodiscard]] not_null<Data::Thread*> chatThread(
|
||||
@@ -131,7 +133,7 @@ private:
|
||||
void paintChat(Painter &p, not_null<Chat*> chat, int index);
|
||||
void updateChat(not_null<PeerData*> peer);
|
||||
void updateChatName(not_null<Chat*> chat);
|
||||
void initChatLocked(not_null<Chat*> chat);
|
||||
void initChatRestriction(not_null<Chat*> chat);
|
||||
void repaintChat(not_null<PeerData*> peer);
|
||||
int chatIndex(not_null<PeerData*> peer) const;
|
||||
void repaintChatAtIndex(int index);
|
||||
@@ -197,16 +199,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
|
||||
, _api(&_descriptor.session->mtp())
|
||||
, _select(
|
||||
this,
|
||||
(_descriptor.stMultiSelect
|
||||
? *_descriptor.stMultiSelect
|
||||
(_descriptor.st.multiSelect
|
||||
? *_descriptor.st.multiSelect
|
||||
: st::defaultMultiSelect),
|
||||
tr::lng_participant_filter())
|
||||
, _comment(
|
||||
this,
|
||||
object_ptr<Ui::InputField>(
|
||||
this,
|
||||
(_descriptor.stComment
|
||||
? *_descriptor.stComment
|
||||
(_descriptor.st.comment
|
||||
? *_descriptor.st.comment
|
||||
: st::shareComment),
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_photos_comment()),
|
||||
@@ -251,7 +253,7 @@ void ShareBox::prepareCommentField() {
|
||||
.session = _descriptor.session,
|
||||
.show = Main::MakeSessionShow(show, _descriptor.session),
|
||||
.field = field,
|
||||
.fieldStyle = _descriptor.stLabel,
|
||||
.fieldStyle = _descriptor.st.label,
|
||||
});
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
@@ -276,7 +278,9 @@ void ShareBox::prepare() {
|
||||
_select->resizeToWidth(st::boxWideWidth);
|
||||
Ui::SendPendingMoveResizeEvents(_select);
|
||||
|
||||
setTitle(tr::lng_share_title());
|
||||
setTitle(_descriptor.titleOverride
|
||||
? std::move(_descriptor.titleOverride)
|
||||
: tr::lng_share_title());
|
||||
|
||||
_inner = setInnerWidget(
|
||||
object_ptr<Inner>(this, _descriptor, uiShow()),
|
||||
@@ -509,9 +513,19 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
SendMenu::Details ShareBox::sendMenuDetails() const {
|
||||
const auto selected = _inner->selected();
|
||||
const auto type = ranges::all_of(
|
||||
selected | ranges::views::transform(&Data::Thread::peer),
|
||||
HistoryView::CanScheduleUntilOnline)
|
||||
const auto hasPaid = [&] {
|
||||
for (const auto &thread : selected) {
|
||||
if (thread->peer()->starsPerMessageChecked()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
const auto type = hasPaid
|
||||
? SendMenu::Type::SilentOnly
|
||||
: ranges::all_of(
|
||||
selected | ranges::views::transform(&Data::Thread::peer),
|
||||
HistoryView::CanScheduleUntilOnline)
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: (selected.size() == 1 && selected.front()->peer()->isSelf())
|
||||
? SendMenu::Type::Reminder
|
||||
@@ -561,6 +575,9 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
submit(action.options);
|
||||
return;
|
||||
}
|
||||
const auto st = _descriptor.st.scheduleBox
|
||||
? *_descriptor.st.scheduleBox
|
||||
: HistoryView::ScheduleBoxStyleArgs();
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
@@ -569,7 +586,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
[=](Api::SendOptions options) { submit(options); },
|
||||
action.options,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
st));
|
||||
});
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
const auto result = FillSendMenu(
|
||||
@@ -603,6 +620,9 @@ void ShareBox::createButtons() {
|
||||
showMenu(send);
|
||||
}
|
||||
}, send->lifetime());
|
||||
send->setText(PaidSendButtonText(
|
||||
_starsToSend.value(),
|
||||
tr::lng_share_confirm()));
|
||||
} else if (_descriptor.copyCallback) {
|
||||
addButton(_copyLinkText.value(), [=] { copyLink(); });
|
||||
}
|
||||
@@ -646,6 +666,73 @@ void ShareBox::innerSelectedChanged(
|
||||
}
|
||||
|
||||
void ShareBox::submit(Api::SendOptions options) {
|
||||
_submitLifetime.destroy();
|
||||
|
||||
auto threads = _inner->selected();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto field = _comment->entity();
|
||||
auto comment = field->getTextWithAppliedMarkdown();
|
||||
const auto checkPaid = [=] {
|
||||
if (!_descriptor.countMessagesCallback) {
|
||||
return true;
|
||||
}
|
||||
const auto withPaymentApproved = crl::guard(weak, [=](int approved) {
|
||||
auto copy = options;
|
||||
copy.starsApproved = approved;
|
||||
submit(copy);
|
||||
});
|
||||
const auto messagesCount = _descriptor.countMessagesCallback(
|
||||
comment);
|
||||
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 &thread : threads) {
|
||||
const auto peer = thread->peer();
|
||||
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()) {
|
||||
_descriptor.session->changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::FullInfo
|
||||
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||
if (waiting.contains(update.peer)) {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}
|
||||
}, _submitLifetime);
|
||||
|
||||
if (!_descriptor.session->credits().loaded()) {
|
||||
_descriptor.session->credits().loadedValue(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
withPaymentApproved(alreadyApproved);
|
||||
}, _submitLifetime);
|
||||
}
|
||||
return false;
|
||||
} else if (totalStars > alreadyApproved) {
|
||||
const auto show = uiShow();
|
||||
const auto session = _descriptor.session;
|
||||
const auto sessionShow = Main::MakeSessionShow(show, session);
|
||||
const auto scheduleBoxSt = _descriptor.st.scheduleBox.get();
|
||||
ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
|
||||
.messages = messagesCount,
|
||||
.stars = totalStars,
|
||||
}, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{
|
||||
.label = (scheduleBoxSt
|
||||
? scheduleBoxSt->chooseDateTimeArgs.labelStyle
|
||||
: nullptr),
|
||||
.checkbox = _descriptor.st.checkbox,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (const auto onstack = _descriptor.submitCallback) {
|
||||
const auto forwardOptions = (_forwardOptions.captionsCount
|
||||
&& _forwardOptions.dropCaptions)
|
||||
@@ -654,8 +741,9 @@ void ShareBox::submit(Api::SendOptions options) {
|
||||
? Data::ForwardOptions::NoSenderNames
|
||||
: Data::ForwardOptions::PreserveInfo;
|
||||
onstack(
|
||||
_inner->selected(),
|
||||
_comment->entity()->getTextWithAppliedMarkdown(),
|
||||
std::move(threads),
|
||||
checkPaid,
|
||||
std::move(comment),
|
||||
options,
|
||||
forwardOptions);
|
||||
}
|
||||
@@ -675,9 +763,23 @@ void ShareBox::selectedChanged() {
|
||||
_comment->toggle(_hasSelected, anim::type::normal);
|
||||
_comment->resizeToWidth(st::boxWideWidth);
|
||||
}
|
||||
computeStarsCount();
|
||||
update();
|
||||
}
|
||||
|
||||
void ShareBox::computeStarsCount() {
|
||||
auto perMessage = 0;
|
||||
for (const auto &thread : _inner->selected()) {
|
||||
perMessage += thread->peer()->starsPerMessageChecked();
|
||||
}
|
||||
const auto messagesCount = _descriptor.countMessagesCallback
|
||||
? _descriptor.countMessagesCallback(_comment
|
||||
? _comment->entity()->getTextWithTags()
|
||||
: TextWithTags())
|
||||
: 0;
|
||||
_starsToSend = perMessage * messagesCount;
|
||||
}
|
||||
|
||||
void ShareBox::scrollTo(Ui::ScrollToRequest request) {
|
||||
scrollToY(request.ymin, request.ymax);
|
||||
//auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
|
||||
@@ -704,7 +806,9 @@ ShareBox::Inner::Inner(
|
||||
: RpWidget(parent)
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _st(_descriptor.st.peerList
|
||||
? *_descriptor.st.peerList
|
||||
: st::shareBoxList)
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add))
|
||||
@@ -713,13 +817,13 @@ ShareBox::Inner::Inner(
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
if (_descriptor.premiumRequiredError) {
|
||||
if (_descriptor.moneyRestrictionError) {
|
||||
const auto session = _descriptor.session;
|
||||
rpl::merge(
|
||||
Data::AmPremiumValue(session) | rpl::to_empty,
|
||||
session->api().premium().somePremiumRequiredResolved()
|
||||
session->api().premium().someMessageMoneyRestrictionsResolved()
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshLockedRows();
|
||||
refreshRestrictedRows();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
@@ -780,38 +884,36 @@ void ShareBox::Inner::invalidateCache() {
|
||||
}
|
||||
|
||||
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
|
||||
if (!chat->locked) {
|
||||
if (!chat->restriction.premiumRequired) {
|
||||
return false;
|
||||
}
|
||||
::Settings::ShowPremiumPromoToast(
|
||||
Main::MakeSessionShow(_show, _descriptor.session),
|
||||
ChatHelpers::ResolveWindowDefault(),
|
||||
_descriptor.premiumRequiredError(chat->peer->asUser()).text,
|
||||
_descriptor.moneyRestrictionError(chat->peer->asUser()).text,
|
||||
u"require_premium"_q);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShareBox::Inner::refreshLockedRows() {
|
||||
void ShareBox::Inner::refreshRestrictedRows() {
|
||||
auto changed = false;
|
||||
for (const auto &[peer, data] : _dataMap) {
|
||||
const auto history = data->history;
|
||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes);
|
||||
if (data->locked != locked) {
|
||||
data->locked = locked;
|
||||
history);
|
||||
if (data->restriction != restriction) {
|
||||
data->restriction = restriction;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (const auto &data : d_byUsernameFiltered) {
|
||||
const auto history = data->history;
|
||||
const auto locked = (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes);
|
||||
if (data->locked != locked) {
|
||||
data->locked = locked;
|
||||
history);
|
||||
if (data->restriction != restriction) {
|
||||
data->restriction = restriction;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -878,14 +980,14 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
||||
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
|
||||
if (_descriptor.premiumRequiredError) {
|
||||
void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
|
||||
if (_descriptor.moneyRestrictionError) {
|
||||
const auto history = chat->history;
|
||||
if (Api::ResolveRequiresPremiumToWrite(
|
||||
const auto restriction = Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Yes) {
|
||||
chat->locked = true;
|
||||
history);
|
||||
if (restriction || restriction.known) {
|
||||
chat->restriction = restriction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1011,14 +1113,15 @@ void ShareBox::Inner::loadProfilePhotos() {
|
||||
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
|
||||
entry->chatListPreloadData();
|
||||
const auto history = entry->asHistory();
|
||||
if (!_descriptor.premiumRequiredError || !history) {
|
||||
if (!_descriptor.moneyRestrictionError || !history) {
|
||||
return;
|
||||
} else if (Api::ResolveRequiresPremiumToWrite(
|
||||
history->peer,
|
||||
history
|
||||
) == Api::RequirePremiumState::Unknown) {
|
||||
const auto user = history->peer->asUser();
|
||||
_descriptor.session->api().premium().resolvePremiumRequired(user);
|
||||
} else if (!Api::ResolveMessageMoneyRestrictions(
|
||||
history->peer,
|
||||
history).known) {
|
||||
if (const auto user = history->peer->asUser()) {
|
||||
const auto api = &_descriptor.session->api();
|
||||
api->premium().resolveMessageMoneyRestrictions(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1041,7 +1144,7 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
|
||||
repaintChat(peer);
|
||||
}));
|
||||
updateChatName(i->second.get());
|
||||
initChatLocked(i->second.get());
|
||||
initChatRestriction(i->second.get());
|
||||
row->attached = i->second.get();
|
||||
return i->second.get();
|
||||
}
|
||||
@@ -1075,10 +1178,12 @@ void ShareBox::Inner::paintChat(
|
||||
auto photoTop = st::sharePhotoTop;
|
||||
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
|
||||
|
||||
if (chat->locked) {
|
||||
PaintPremiumRequiredLock(
|
||||
if (chat->restriction) {
|
||||
PaintRestrictionBadge(
|
||||
p,
|
||||
&_st.item,
|
||||
chat->restriction.starsPerMessage,
|
||||
chat->badgeCache,
|
||||
x + photoLeft,
|
||||
y + photoTop,
|
||||
outerWidth,
|
||||
@@ -1433,7 +1538,7 @@ void ShareBox::Inner::peopleReceived(
|
||||
_st.item,
|
||||
[=] { repaintChat(peer); }));
|
||||
updateChatName(d_byUsernameFiltered.back().get());
|
||||
initChatLocked(d_byUsernameFiltered.back().get());
|
||||
initChatRestriction(d_byUsernameFiltered.back().get());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1486,22 +1591,34 @@ ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
||||
};
|
||||
}
|
||||
|
||||
ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages(
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) {
|
||||
return [=](const TextWithTags &comment) {
|
||||
const auto items = history->owner().idsToItems(msgIds);
|
||||
return int(items.size()) + (comment.empty() ? 0 : 1);
|
||||
};
|
||||
}
|
||||
|
||||
ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) {
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp) {
|
||||
struct State final {
|
||||
base::flat_set<mtpRequestId> requests;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
return [=](
|
||||
std::vector<not_null<Data::Thread*>> &&result,
|
||||
TextWithTags &&comment,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags comment,
|
||||
Api::SendOptions options,
|
||||
Data::ForwardOptions forwardOptions) {
|
||||
if (!state->requests.empty()) {
|
||||
return; // Share clicked already.
|
||||
}
|
||||
|
||||
const auto items = history->owner().idsToItems(msgIds);
|
||||
const auto existingIds = history->owner().itemsToIds(items);
|
||||
if (existingIds.empty() || result.empty()) {
|
||||
@@ -1514,6 +1631,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
if (error.error) {
|
||||
show->showBox(MakeSendErrorBox(error, result.size() > 1));
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPmessages_ForwardMessages::Flag;
|
||||
@@ -1525,6 +1644,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
: Flag(0))
|
||||
| ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
|
||||
? Flag::f_drop_media_captions
|
||||
: Flag(0))
|
||||
| (videoTimestamp.has_value()
|
||||
? Flag::f_video_timestamp
|
||||
: Flag(0));
|
||||
auto mtpMsgIds = QVector<MTPint>();
|
||||
mtpMsgIds.reserve(existingIds.size());
|
||||
@@ -1559,6 +1681,12 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
: topicRootId;
|
||||
const auto peer = thread->peer();
|
||||
const auto threadHistory = thread->owningHistory();
|
||||
const auto starsPaid = std::min(
|
||||
peer->starsPerMessageChecked(),
|
||||
options.starsApproved);
|
||||
if (starsPaid) {
|
||||
options.starsApproved -= starsPaid;
|
||||
}
|
||||
histories.sendRequest(threadHistory, requestType, [=](
|
||||
Fn<void()> finish) {
|
||||
const auto session = &threadHistory->session();
|
||||
@@ -1570,7 +1698,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
: Flag(0))
|
||||
| (options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0));
|
||||
: Flag(0))
|
||||
| (starsPaid ? Flag::f_allow_paid_stars : Flag());
|
||||
threadHistory->sendRequestId = api.request(
|
||||
MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
@@ -1581,7 +1710,9 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
MTP_int(topMsgId),
|
||||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTP_int(videoTimestamp.value_or(0)),
|
||||
MTP_long(starsPaid)
|
||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||
threadHistory->session().api().applyUpdates(updates);
|
||||
state->requests.remove(reqId);
|
||||
@@ -1596,7 +1727,11 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
}
|
||||
finish();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
||||
const auto type = error.type();
|
||||
if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
|
||||
show->showToast(u"Payment requirements changed. "
|
||||
"Please, try again."_q);
|
||||
} else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) {
|
||||
show->showToast(
|
||||
tr::lng_restricted_send_voice_messages(
|
||||
tr::now,
|
||||
@@ -1612,9 +1747,38 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
};
|
||||
}
|
||||
|
||||
ShareBoxStyleOverrides DarkShareBoxStyle() {
|
||||
using namespace HistoryView;
|
||||
|
||||
const auto schedule = [&] {
|
||||
auto date = Ui::ChooseDateTimeStyleArgs();
|
||||
date.labelStyle = &st::groupCallBoxLabel;
|
||||
date.dateFieldStyle = &st::groupCallScheduleDateField;
|
||||
date.timeFieldStyle = &st::groupCallScheduleTimeField;
|
||||
date.separatorStyle = &st::callMuteButtonLabel;
|
||||
date.atStyle = &st::callMuteButtonLabel;
|
||||
date.calendarStyle = &st::groupCallCalendarColors;
|
||||
|
||||
auto st = ScheduleBoxStyleArgs();
|
||||
st.topButtonStyle = &st::groupCallMenuToggle;
|
||||
st.popupMenuStyle = &st::groupCallPopupMenu;
|
||||
st.chooseDateTimeArgs = std::move(date);
|
||||
return st;
|
||||
};
|
||||
return {
|
||||
.multiSelect = &st::groupCallMultiSelect,
|
||||
.comment = &st::groupCallShareBoxComment,
|
||||
.peerList = &st::groupCallShareBoxList,
|
||||
.label = &st::groupCallField,
|
||||
.checkbox = &st::groupCallCheckbox,
|
||||
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
||||
};
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto history = item->history();
|
||||
const auto owner = &history->owner();
|
||||
const auto session = &history->session();
|
||||
@@ -1663,7 +1827,7 @@ void FastShareMessage(
|
||||
const auto requiresInline = item->requiresSendInlineRight();
|
||||
auto filterCallback = [=](not_null<Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1678,43 +1842,54 @@ void FastShareMessage(
|
||||
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.countMessagesCallback = ShareBox::DefaultForwardCountMessages(
|
||||
history,
|
||||
msgIds),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
}), Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
FastShareMessage(controller->uiShow(), item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareMessage(controller->uiShow(), item, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url) {
|
||||
FastShareLink(controller->uiShow(), url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareLink(controller->uiShow(), url, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url) {
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto sending = std::make_shared<bool>();
|
||||
auto copyCallback = [=] {
|
||||
QGuiApplication::clipboard()->setText(url);
|
||||
show->showToast(tr::lng_background_link_copied(tr::now));
|
||||
};
|
||||
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) {
|
||||
@@ -1731,6 +1906,8 @@ void FastShareLink(
|
||||
MakeSendErrorBox(error, result.size() > 1));
|
||||
}
|
||||
return;
|
||||
} else if (!checkPaid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
*sending = true;
|
||||
@@ -1758,7 +1935,7 @@ void FastShareLink(
|
||||
};
|
||||
auto filterCallback = [](not_null<::Data::Thread*> thread) {
|
||||
if (const auto user = thread->peer()->asUser()) {
|
||||
if (user->canSendIgnoreRequirePremium()) {
|
||||
if (user->canSendIgnoreMoneyRestrictions()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1768,15 +1945,17 @@ void FastShareLink(
|
||||
Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &show->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
.countMessagesCallback = std::move(countMessagesCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
.st = st,
|
||||
.moneyRestrictionError = ShareMessageMoneyRestrictionError(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther,
|
||||
anim::type::normal);
|
||||
}
|
||||
|
||||
auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
||||
return WritePremiumRequiredError;
|
||||
auto ShareMessageMoneyRestrictionError()
|
||||
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
|
||||
return WriteMoneyRestrictionError;
|
||||
}
|
||||
|
||||
@@ -61,58 +61,78 @@ class PopupMenu;
|
||||
|
||||
class ShareBox;
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item);
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url);
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url);
|
||||
struct ShareBoxStyleOverrides {
|
||||
const style::MultiSelect *multiSelect = nullptr;
|
||||
const style::InputField *comment = nullptr;
|
||||
const style::PeerList *peerList = nullptr;
|
||||
const style::InputField *label = nullptr;
|
||||
const style::Checkbox *checkbox = nullptr;
|
||||
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
||||
};
|
||||
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
||||
|
||||
struct RecipientPremiumRequiredError;
|
||||
[[nodiscard]] auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
|
||||
struct RecipientMoneyRestrictionError;
|
||||
[[nodiscard]] auto ShareMessageMoneyRestrictionError()
|
||||
-> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)>;
|
||||
|
||||
class ShareBox final : public Ui::BoxContent {
|
||||
public:
|
||||
using CopyCallback = Fn<void()>;
|
||||
using CountMessagesCallback = Fn<int(const TextWithTags&)>;
|
||||
using SubmitCallback = Fn<void(
|
||||
std::vector<not_null<Data::Thread*>>&&,
|
||||
Fn<bool()> checkPaid,
|
||||
TextWithTags&&,
|
||||
Api::SendOptions,
|
||||
Data::ForwardOptions)>;
|
||||
using FilterCallback = Fn<bool(not_null<Data::Thread*>)>;
|
||||
|
||||
[[nodiscard]] static auto DefaultForwardCountMessages(
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds) -> CountMessagesCallback;
|
||||
[[nodiscard]] static SubmitCallback DefaultForwardCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<History*> history,
|
||||
MessageIdsList msgIds);
|
||||
MessageIdsList msgIds,
|
||||
std::optional<TimeId> videoTimestamp = {});
|
||||
|
||||
struct Descriptor {
|
||||
not_null<Main::Session*> session;
|
||||
CopyCallback copyCallback;
|
||||
CountMessagesCallback countMessagesCallback;
|
||||
SubmitCallback submitCallback;
|
||||
FilterCallback filterCallback;
|
||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||
rpl::producer<QString> copyLinkText;
|
||||
const style::MultiSelect *stMultiSelect = nullptr;
|
||||
const style::InputField *stComment = nullptr;
|
||||
const style::PeerList *st = nullptr;
|
||||
const style::InputField *stLabel = nullptr;
|
||||
rpl::producer<QString> titleOverride;
|
||||
ShareBoxStyleOverrides st;
|
||||
std::optional<TimeId> videoTimestamp;
|
||||
struct {
|
||||
int sendersCount = 0;
|
||||
int captionsCount = 0;
|
||||
bool show = false;
|
||||
} forwardOptions;
|
||||
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
using MoneyRestrictionError = RecipientMoneyRestrictionError;
|
||||
Fn<MoneyRestrictionError(
|
||||
not_null<UserData*>)> moneyRestrictionError;
|
||||
};
|
||||
ShareBox(QWidget*, Descriptor &&descriptor);
|
||||
|
||||
@@ -137,6 +157,7 @@ private:
|
||||
void needSearchByUsername();
|
||||
void applyFilterUpdate(const QString &query);
|
||||
void selectedChanged();
|
||||
void computeStarsCount();
|
||||
void createButtons();
|
||||
int getTopScrollSkip() const;
|
||||
int getBottomScrollSkip() const;
|
||||
@@ -168,6 +189,7 @@ private:
|
||||
|
||||
bool _hasSelected = false;
|
||||
rpl::variable<QString> _copyLinkText;
|
||||
rpl::variable<int> _starsToSend;
|
||||
|
||||
base::Timer _searchTimer;
|
||||
QString _peopleQuery;
|
||||
@@ -183,5 +205,6 @@ private:
|
||||
PeopleQueries _peopleQueries;
|
||||
|
||||
Ui::Animations::Simple _scrollAnimation;
|
||||
rpl::lifetime _submitLifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
struct GiftCode;
|
||||
@@ -17,6 +23,11 @@ namespace Payments {
|
||||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Settings {
|
||||
struct GiftWearBoxStyleOverride;
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -27,6 +38,7 @@ class CustomEmoji;
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class PopupMenu;
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
|
||||
@@ -41,6 +53,16 @@ void AddUniqueGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr);
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void ShowUniqueGiftWearBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::UniqueGift &gift,
|
||||
Settings::GiftWearBoxStyleOverride st);
|
||||
|
||||
struct PatternPoint {
|
||||
QPointF position;
|
||||
@@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs {
|
||||
not_null<Window::SessionController*> controller;
|
||||
base::required<uint64> stargiftId;
|
||||
Fn<void(bool)> ready;
|
||||
not_null<UserData*> user;
|
||||
MsgId itemId = 0;
|
||||
not_null<PeerData*> peer;
|
||||
Data::SavedStarGiftId savedId;
|
||||
int cost = 0;
|
||||
bool canAddSender = false;
|
||||
bool canAddComment = false;
|
||||
@@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs {
|
||||
};
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box);
|
||||
void AddUniqueCloseButton(
|
||||
not_null<GenericBox*> box,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
not_null<Window::SessionController*> window,
|
||||
@@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit(
|
||||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result);
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||