Compare commits
501 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0981ea8e3 | ||
|
|
a141d01a23 | ||
|
|
6a000207ee | ||
|
|
ee6edf9caa | ||
|
|
37907636e6 | ||
|
|
3f0f3a3c11 | ||
|
|
db0856f71c | ||
|
|
86cdda2277 | ||
|
|
3888e8084a | ||
|
|
517b456670 | ||
|
|
77d6e19214 | ||
|
|
fb64452495 | ||
|
|
24fabf2590 | ||
|
|
9b2847a11d | ||
|
|
c18e8fd777 | ||
|
|
f4afa762d8 | ||
|
|
5e1fb6ebbf | ||
|
|
2c7922ce7b | ||
|
|
ac78ae823c | ||
|
|
1ef6f462f6 | ||
|
|
c81f406759 | ||
|
|
889ec0c731 | ||
|
|
677fbdd84e | ||
|
|
1ebe3255e0 | ||
|
|
c70866a995 | ||
|
|
fd982b90db | ||
|
|
9461095c88 | ||
|
|
a2fa1a52e2 | ||
|
|
a847969e9c | ||
|
|
4d647e64b7 | ||
|
|
f8b756d447 | ||
|
|
484c647b5b | ||
|
|
730c968b1e | ||
|
|
8a6a749296 | ||
|
|
2f22a8f46b | ||
|
|
f123a9e16c | ||
|
|
11c45b0342 | ||
|
|
2cd6bfef06 | ||
|
|
61ca619db4 | ||
|
|
675ee9088f | ||
|
|
28a6aa45b9 | ||
|
|
08ec9e6bfd | ||
|
|
ee9f99a754 | ||
|
|
2412183b83 | ||
|
|
e83704982f | ||
|
|
6f86acf712 | ||
|
|
c22698084f | ||
|
|
8c55364afa | ||
|
|
2c3ef13b01 | ||
|
|
03454ca3b4 | ||
|
|
8a92c89f39 | ||
|
|
b83b403b75 | ||
|
|
8aac07b3c0 | ||
|
|
b4dfc25df5 | ||
|
|
917d1841c1 | ||
|
|
8ce10d5503 | ||
|
|
a130bb1be6 | ||
|
|
de52ac6b28 | ||
|
|
310837c9e1 | ||
|
|
8e6d7bb190 | ||
|
|
025ab40687 | ||
|
|
2a5071b66c | ||
|
|
2a63496054 | ||
|
|
a52d4eb4e8 | ||
|
|
4f7a124f3e | ||
|
|
4b9eb37bd5 | ||
|
|
1d5e4040f4 | ||
|
|
6818b8d8dc | ||
|
|
3f0b962ae5 | ||
|
|
8ac1ad3484 | ||
|
|
c6e1cf639e | ||
|
|
5b9278eced | ||
|
|
03d4dd00d4 | ||
|
|
f7d698b9ff | ||
|
|
46b69a938b | ||
|
|
ebba58217c | ||
|
|
94ad8f9bc3 | ||
|
|
6effac7915 | ||
|
|
78093173a9 | ||
|
|
a01d48f063 | ||
|
|
149c69c9f5 | ||
|
|
df277b366b | ||
|
|
f20475f07e | ||
|
|
b6664625ea | ||
|
|
1028219276 | ||
|
|
219671a3bc | ||
|
|
8c97e915ec | ||
|
|
b648548001 | ||
|
|
b377c02ad3 | ||
|
|
66d6b461f3 | ||
|
|
054a6db3ae | ||
|
|
bf7042df44 | ||
|
|
aa140b2919 | ||
|
|
8d0d9bb0bd | ||
|
|
9ca9904732 | ||
|
|
e6e1b9446d | ||
|
|
a507d28b49 | ||
|
|
ec2faca145 | ||
|
|
17bb430006 | ||
|
|
8cbeadc68a | ||
|
|
3947056654 | ||
|
|
ad7d1fddf0 | ||
|
|
ab20f8eb31 | ||
|
|
f75429cbaa | ||
|
|
dc8c4a8332 | ||
|
|
096530c96a | ||
|
|
ed0850d823 | ||
|
|
4a2173deaf | ||
|
|
f40a584905 | ||
|
|
e54204b136 | ||
|
|
715dff0a3e | ||
|
|
558daa3382 | ||
|
|
7265297b19 | ||
|
|
1fac6db8bd | ||
|
|
c9bd776d1e | ||
|
|
179e81478e | ||
|
|
9ef74c510c | ||
|
|
36f6917bd3 | ||
|
|
707951accb | ||
|
|
7eb98b50ec | ||
|
|
65f7bdb914 | ||
|
|
876c47c436 | ||
|
|
e8f16840de | ||
|
|
dd57ad567f | ||
|
|
54934fb835 | ||
|
|
bd49887607 | ||
|
|
aaa72b7c30 | ||
|
|
4cafacc8db | ||
|
|
374b3c68ac | ||
|
|
69d21f73ef | ||
|
|
6dc3bd65e8 | ||
|
|
deb9aa435b | ||
|
|
68cb568898 | ||
|
|
1ed81b1c9c | ||
|
|
a95fb5b28d | ||
|
|
4f7b5ca7da | ||
|
|
29b7673b88 | ||
|
|
66f375d2c6 | ||
|
|
77d214d2a5 | ||
|
|
09e6077e97 | ||
|
|
631d6abb06 | ||
|
|
c833b8a1b0 | ||
|
|
b58c03f0de | ||
|
|
1988435cdf | ||
|
|
7e704d9529 | ||
|
|
a39a8dbd2c | ||
|
|
90dfae52f5 | ||
|
|
dfc422b505 | ||
|
|
54cc12cf22 | ||
|
|
d81e832ae6 | ||
|
|
1399d2501d | ||
|
|
57254ca259 | ||
|
|
043e3ae97e | ||
|
|
68b9a8bc6a | ||
|
|
157d5c743b | ||
|
|
54d0290ba2 | ||
|
|
0fc2df8eec | ||
|
|
d9caf15d1d | ||
|
|
b674826392 | ||
|
|
37181f9d0a | ||
|
|
93aebc747d | ||
|
|
a84ac933dd | ||
|
|
b79c306bfe | ||
|
|
57d62423b3 | ||
|
|
f4674389d5 | ||
|
|
63c4c5064f | ||
|
|
ae1f364730 | ||
|
|
c6e322de86 | ||
|
|
68bf6f991c | ||
|
|
b15f5f8596 | ||
|
|
27a71a8dcd | ||
|
|
2044f8f9ad | ||
|
|
ffa4b1db87 | ||
|
|
cad25ae644 | ||
|
|
21094fe11b | ||
|
|
101dbdf243 | ||
|
|
0dc92762bc | ||
|
|
5fdaa6b91f | ||
|
|
968d036834 | ||
|
|
d47c5df73d | ||
|
|
6c1e7357c6 | ||
|
|
479b63c33a | ||
|
|
950a946a16 | ||
|
|
5f8da27c86 | ||
|
|
a9bd7803e6 | ||
|
|
3ece9b1566 | ||
|
|
e71a067f4b | ||
|
|
ebf456abe4 | ||
|
|
3552da5ce7 | ||
|
|
b5bd0f53ad | ||
|
|
7d115b3fab | ||
|
|
d0a030ab58 | ||
|
|
712c06756e | ||
|
|
301ffc15ef | ||
|
|
3c4a711b5d | ||
|
|
989145726d | ||
|
|
9eebd3b514 | ||
|
|
eb997ae9e3 | ||
|
|
db4c9b83f3 | ||
|
|
1196b6a3fb | ||
|
|
bef216bc93 | ||
|
|
811d75e383 | ||
|
|
049cde48ee | ||
|
|
cb65c50c19 | ||
|
|
f23c9a61bc | ||
|
|
b5d5ff3cbb | ||
|
|
c5ba0fa705 | ||
|
|
71893f4ef7 | ||
|
|
4a60c57661 | ||
|
|
fbbcc21198 | ||
|
|
d993386756 | ||
|
|
30819509d3 | ||
|
|
10c3fe0f63 | ||
|
|
3498a7f0ee | ||
|
|
648b23b548 | ||
|
|
ba89912834 | ||
|
|
c1bc7e6ab1 | ||
|
|
eea50ed6b0 | ||
|
|
006d6fe2c0 | ||
|
|
5180e0ec57 | ||
|
|
490ec7949f | ||
|
|
671a15d763 | ||
|
|
d2d5226dc7 | ||
|
|
b58ece3a38 | ||
|
|
2b4a2b5b97 | ||
|
|
382b175db2 | ||
|
|
2db81211c8 | ||
|
|
46157c99c4 | ||
|
|
c3ed5224c2 | ||
|
|
acee7c7cfc | ||
|
|
002fe9a72a | ||
|
|
93b7c47cda | ||
|
|
053f8ad1c0 | ||
|
|
60ca6895db | ||
|
|
fc5e9414b7 | ||
|
|
f768e405fa | ||
|
|
a22cf8e303 | ||
|
|
8a5797e1bd | ||
|
|
750ad600be | ||
|
|
eaf4575eb8 | ||
|
|
b9677fe1db | ||
|
|
4047f1733d | ||
|
|
3d114131e0 | ||
|
|
5810149a77 | ||
|
|
032fe3e0fc | ||
|
|
81d7fcba7e | ||
|
|
4e9b5b0d33 | ||
|
|
90068f6261 | ||
|
|
f37d056c14 | ||
|
|
f748988ae3 | ||
|
|
9a25d2c413 | ||
|
|
ec40292cbf | ||
|
|
5e8c3fb146 | ||
|
|
fa8ed186d8 | ||
|
|
c1f36d43d0 | ||
|
|
501cae2200 | ||
|
|
8ad5117495 | ||
|
|
d79da3d884 | ||
|
|
e4d88f829c | ||
|
|
2673e1df53 | ||
|
|
99e88d74bc | ||
|
|
43ffc9d67c | ||
|
|
053c462dc0 | ||
|
|
174b627a78 | ||
|
|
bb33d0b997 | ||
|
|
6f6fb3d1b6 | ||
|
|
3f216ad946 | ||
|
|
c49eb7041f | ||
|
|
a1d8202644 | ||
|
|
82428aef28 | ||
|
|
10f7b985c7 | ||
|
|
06075411a5 | ||
|
|
7e01b12825 | ||
|
|
273119fc55 | ||
|
|
e47e4ba338 | ||
|
|
cbcdeae200 | ||
|
|
c585112e37 | ||
|
|
392df8b56f | ||
|
|
0e6470a087 | ||
|
|
6c0ea0eb9f | ||
|
|
9229de2658 | ||
|
|
2935721cd0 | ||
|
|
a48cd5f15a | ||
|
|
9e77e80f92 | ||
|
|
deb50ee528 | ||
|
|
14909ae913 | ||
|
|
d5d9da7d0a | ||
|
|
86a048a021 | ||
|
|
aafa8631e0 | ||
|
|
9176bf2e47 | ||
|
|
72c667b153 | ||
|
|
fe6f65b3ab | ||
|
|
5db2821f8c | ||
|
|
9e3e7265d2 | ||
|
|
749b2e0e95 | ||
|
|
5b45397383 | ||
|
|
61c17c0a93 | ||
|
|
93e592472c | ||
|
|
379a94db42 | ||
|
|
4f4d216987 | ||
|
|
6400875d55 | ||
|
|
06d0e78b00 | ||
|
|
9043c18725 | ||
|
|
88e742927f | ||
|
|
a6fcc6d51d | ||
|
|
23a13ab54e | ||
|
|
e8a929bdbd | ||
|
|
eafc01e02b | ||
|
|
f817504d67 | ||
|
|
f91eb65239 | ||
|
|
e978770fbd | ||
|
|
9c83b8bac5 | ||
|
|
47ce34e987 | ||
|
|
1656a9c3e2 | ||
|
|
47e06cf385 | ||
|
|
f61f649a7e | ||
|
|
a7bffe7abd | ||
|
|
51b866293f | ||
|
|
bd20a3cfe4 | ||
|
|
86778aa4d9 | ||
|
|
12eecec501 | ||
|
|
26345208a9 | ||
|
|
ee680ac1f1 | ||
|
|
bb79a07262 | ||
|
|
70fe649743 | ||
|
|
400f0f8785 | ||
|
|
eac7bf1c48 | ||
|
|
f4abe37dff | ||
|
|
b7f165a259 | ||
|
|
c1b95afd88 | ||
|
|
36766e7546 | ||
|
|
59c016e4ce | ||
|
|
e9e347fa6c | ||
|
|
7aef0b0a83 | ||
|
|
d9572949f6 | ||
|
|
233e80d22d | ||
|
|
5c83858a50 | ||
|
|
c681569349 | ||
|
|
bb43afdd93 | ||
|
|
3ba1941808 | ||
|
|
72d1b43453 | ||
|
|
0c1b487956 | ||
|
|
ba611d0f2d | ||
|
|
50ce847b31 | ||
|
|
dd0d88ccd3 | ||
|
|
a1049ec7ce | ||
|
|
0fffeac8da | ||
|
|
1f0acae151 | ||
|
|
521c17b76c | ||
|
|
f9f51b4e41 | ||
|
|
4e8895ddd9 | ||
|
|
ad342a5324 | ||
|
|
5cfd86b829 | ||
|
|
27eb3e45be | ||
|
|
4953246c5d | ||
|
|
4df5372dab | ||
|
|
40fbd415ef | ||
|
|
974bf99921 | ||
|
|
8c0351be4e | ||
|
|
67f7816088 | ||
|
|
924d80ecba | ||
|
|
d219bccf2b | ||
|
|
02bd2bca64 | ||
|
|
57ecc2be1d | ||
|
|
d3a01b6235 | ||
|
|
58c060c59d | ||
|
|
cd7507fb23 | ||
|
|
9a5923676a | ||
|
|
c0f3d263a3 | ||
|
|
056ba644ed | ||
|
|
ebaffc333e | ||
|
|
be099880d8 | ||
|
|
885dcf0b28 | ||
|
|
a0d97f03cb | ||
|
|
c942034ca4 | ||
|
|
0bd780b20f | ||
|
|
7d75c25214 | ||
|
|
5defb9fb17 | ||
|
|
0549c8f037 | ||
|
|
3c246e1e92 | ||
|
|
58da617e3f | ||
|
|
1edf0ed70b | ||
|
|
c27c567225 | ||
|
|
93eff78cd6 | ||
|
|
a2a27e115c | ||
|
|
e9fb580ba4 | ||
|
|
d73313479b | ||
|
|
e4e343b871 | ||
|
|
dda6b92bec | ||
|
|
3dd894ad30 | ||
|
|
f08ff92470 | ||
|
|
923aaec085 | ||
|
|
1d3110228d | ||
|
|
7194781bb8 | ||
|
|
97a5e0c6ea | ||
|
|
a3ef36f9f7 | ||
|
|
d13bf19b79 | ||
|
|
493f0450b4 | ||
|
|
74861a334d | ||
|
|
a87a221f26 | ||
|
|
923a9ec6a8 | ||
|
|
b299881bf8 | ||
|
|
5ee2bca616 | ||
|
|
d1e914fb30 | ||
|
|
43cb315f47 | ||
|
|
dcc52a7333 | ||
|
|
84cde1354d | ||
|
|
3d81414c71 | ||
|
|
69c48e2b5b | ||
|
|
5ca9b74142 | ||
|
|
e11755af46 | ||
|
|
174fb62c32 | ||
|
|
0e30e306ff | ||
|
|
5e29f382cd | ||
|
|
8eb24f620d | ||
|
|
5adde6c93a | ||
|
|
42d6d0d58a | ||
|
|
6336df2bd6 | ||
|
|
030d35ea7e | ||
|
|
d81c3554cc | ||
|
|
ca37ffa086 | ||
|
|
154fe63b43 | ||
|
|
65384d54f1 | ||
|
|
2bf8cb84d0 | ||
|
|
f0a82de784 | ||
|
|
1a393ddebb | ||
|
|
9b11b95c5b | ||
|
|
d0bfee6963 | ||
|
|
f1636de572 | ||
|
|
b5eb195f43 | ||
|
|
4a0bffe618 | ||
|
|
53d97b4146 | ||
|
|
2a224c839e | ||
|
|
bc7aa91fbb | ||
|
|
ac2f35f12b | ||
|
|
39e03c3ca7 | ||
|
|
1ce49df123 | ||
|
|
1865fd382c | ||
|
|
e00c6ecfb8 | ||
|
|
279db771cf | ||
|
|
7b7438cd7b | ||
|
|
42d53e5543 | ||
|
|
fce520c9c0 | ||
|
|
b21bcb86cc | ||
|
|
7d61ab9412 | ||
|
|
701bf0d553 | ||
|
|
583bcca6a9 | ||
|
|
6a8edefc87 | ||
|
|
dd5643ac67 | ||
|
|
787cf7853e | ||
|
|
5bfbae3afc | ||
|
|
363b700f1f | ||
|
|
cde70b9807 | ||
|
|
f7ab8a2174 | ||
|
|
a011a7c316 | ||
|
|
487fa9728a | ||
|
|
8a58ded582 | ||
|
|
ec5d8b7373 | ||
|
|
732b67ca04 | ||
|
|
d102d256a9 | ||
|
|
bbb3a51b74 | ||
|
|
d0d1ef9e66 | ||
|
|
b01244fc42 | ||
|
|
b92a05011f | ||
|
|
5fb7992b04 | ||
|
|
f371cd1af2 | ||
|
|
144109db05 | ||
|
|
e120ae6ae6 | ||
|
|
d1106e5ae6 | ||
|
|
396ba9a984 | ||
|
|
92133e7f50 | ||
|
|
a19e71324b | ||
|
|
f762634036 | ||
|
|
ee4f83ffde | ||
|
|
f8188f360a | ||
|
|
2bbc7406da | ||
|
|
a9dd9aeb90 | ||
|
|
aa4156d1e7 | ||
|
|
4f5594c8cc | ||
|
|
0527e9a0f7 | ||
|
|
28cbb02b20 | ||
|
|
7d636820ac | ||
|
|
e2b78b673b | ||
|
|
a9a0fe7cf5 | ||
|
|
2b9e7a6b25 | ||
|
|
d6e827e982 | ||
|
|
d2e6003521 | ||
|
|
465fc42718 | ||
|
|
0dd6ff9d9b | ||
|
|
8a6b1677f4 | ||
|
|
470b3a2cbd | ||
|
|
a60783eae3 | ||
|
|
de73d8766c | ||
|
|
cd7cfcdf2f | ||
|
|
aee62c7591 | ||
|
|
0f524ac67d | ||
|
|
f223ae7eee | ||
|
|
68df8448a2 | ||
|
|
da31fef1ae | ||
|
|
4427ae4306 | ||
|
|
ef2aa05197 |
4
.github/workflows/mac_packaged.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
brew update
|
||||
brew upgrade || true
|
||||
brew install autoconf automake boost cmake ffmpeg@6 openal-soft openssl opus ninja pkg-config python qt yasm xz
|
||||
brew install autoconf automake boost cmake ffmpeg@6 openal-soft openh264 openssl opus ninja pkg-config python qt yasm xz
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
|
||||
xcodebuild -version > CACHE_KEY.txt
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
run: |
|
||||
cd $LibrariesPath
|
||||
|
||||
git clone --recursive --depth=1 $GIT/desktop-app/tg_owt.git
|
||||
git clone --depth=1 --recursive --shallow-submodules $GIT/desktop-app/tg_owt.git
|
||||
cd tg_owt
|
||||
|
||||
cmake -B build . -GNinja -DCMAKE_BUILD_TYPE=Debug
|
||||
|
||||
2
.github/workflows/win.yml
vendored
@@ -169,6 +169,8 @@ jobs:
|
||||
%TDESKTOP_BUILD_GENERATOR% ^
|
||||
%TDESKTOP_BUILD_ARCH% ^
|
||||
%TDESKTOP_BUILD_API% ^
|
||||
-D CMAKE_C_FLAGS="/WX" ^
|
||||
-D CMAKE_CXX_FLAGS="/WX" ^
|
||||
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
|
||||
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
|
||||
-D DESKTOP_APP_NO_PDB=ON ^
|
||||
|
||||
@@ -57,14 +57,6 @@ include(cmake/validate_d3d_compiler.cmake)
|
||||
include(cmake/target_prepare_qrc.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.13)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.8)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
set(desktop_app_skip_libs
|
||||
|
||||
@@ -120,10 +120,13 @@ PRIVATE
|
||||
api/api_common.h
|
||||
api/api_confirm_phone.cpp
|
||||
api/api_confirm_phone.h
|
||||
api/api_credits.cpp
|
||||
api/api_credits.h
|
||||
api/api_earn.cpp
|
||||
api/api_earn.h
|
||||
api/api_editing.cpp
|
||||
api/api_editing.h
|
||||
api/api_filter_updates.h
|
||||
api/api_global_privacy.cpp
|
||||
api/api_global_privacy.h
|
||||
api/api_hash.cpp
|
||||
@@ -162,6 +165,10 @@ PRIVATE
|
||||
api/api_single_message_search.h
|
||||
api/api_statistics.cpp
|
||||
api/api_statistics.h
|
||||
api/api_statistics_data_deserialize.cpp
|
||||
api/api_statistics_data_deserialize.h
|
||||
api/api_statistics_sender.cpp
|
||||
api/api_statistics_sender.h
|
||||
api/api_text_entities.cpp
|
||||
api/api_text_entities.h
|
||||
api/api_toggling_media.cpp
|
||||
@@ -299,6 +306,8 @@ PRIVATE
|
||||
boxes/ringtones_box.h
|
||||
boxes/self_destruction_box.cpp
|
||||
boxes/self_destruction_box.h
|
||||
boxes/send_credits_box.cpp
|
||||
boxes/send_credits_box.h
|
||||
boxes/send_files_box.cpp
|
||||
boxes/send_files_box.h
|
||||
boxes/sessions_box.cpp
|
||||
@@ -441,6 +450,8 @@ PRIVATE
|
||||
core/launcher.h
|
||||
core/local_url_handlers.cpp
|
||||
core/local_url_handlers.h
|
||||
core/phone_click_handler.cpp
|
||||
core/phone_click_handler.h
|
||||
core/sandbox.cpp
|
||||
core/sandbox.h
|
||||
core/shortcuts.cpp
|
||||
@@ -462,6 +473,10 @@ PRIVATE
|
||||
data/business/data_business_info.h
|
||||
data/business/data_shortcut_messages.cpp
|
||||
data/business/data_shortcut_messages.h
|
||||
data/components/factchecks.cpp
|
||||
data/components/factchecks.h
|
||||
data/components/location_pickers.cpp
|
||||
data/components/location_pickers.h
|
||||
data/components/recent_peers.cpp
|
||||
data/components/recent_peers.h
|
||||
data/components/scheduled_messages.cpp
|
||||
@@ -539,6 +554,8 @@ PRIVATE
|
||||
data/data_groups.h
|
||||
data/data_histories.cpp
|
||||
data/data_histories.h
|
||||
data/data_history_messages.cpp
|
||||
data/data_history_messages.h
|
||||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
@@ -712,8 +729,6 @@ PRIVATE
|
||||
history/view/media/history_view_dice.h
|
||||
history/view/media/history_view_document.cpp
|
||||
history/view/media/history_view_document.h
|
||||
history/view/media/history_view_extended_preview.cpp
|
||||
history/view/media/history_view_extended_preview.h
|
||||
history/view/media/history_view_file.cpp
|
||||
history/view/media/history_view_file.h
|
||||
history/view/media/history_view_game.cpp
|
||||
@@ -781,6 +796,8 @@ PRIVATE
|
||||
history/view/history_view_about_view.h
|
||||
history/view/history_view_bottom_info.cpp
|
||||
history/view/history_view_bottom_info.h
|
||||
history/view/history_view_chat_preview.cpp
|
||||
history/view/history_view_chat_preview.h
|
||||
history/view/history_view_contact_status.cpp
|
||||
history/view/history_view_contact_status.h
|
||||
history/view/history_view_context_menu.cpp
|
||||
@@ -795,6 +812,8 @@ PRIVATE
|
||||
history/view/history_view_emoji_interactions.h
|
||||
history/view/history_view_empty_list_bubble.cpp
|
||||
history/view/history_view_empty_list_bubble.h
|
||||
history/view/history_view_fake_items.cpp
|
||||
history/view/history_view_fake_items.h
|
||||
history/view/history_view_group_call_bar.cpp
|
||||
history/view/history_view_group_call_bar.h
|
||||
history/view/history_view_item_preview.h
|
||||
@@ -825,14 +844,14 @@ PRIVATE
|
||||
history/view/history_view_send_action.h
|
||||
history/view/history_view_service_message.cpp
|
||||
history/view/history_view_service_message.h
|
||||
history/view/history_view_spoiler_click_handler.cpp
|
||||
history/view/history_view_spoiler_click_handler.h
|
||||
history/view/history_view_sponsored_click_handler.cpp
|
||||
history/view/history_view_sponsored_click_handler.h
|
||||
history/view/history_view_sticker_toast.cpp
|
||||
history/view/history_view_sticker_toast.h
|
||||
history/view/history_view_sublist_section.cpp
|
||||
history/view/history_view_sublist_section.h
|
||||
history/view/history_view_text_helper.cpp
|
||||
history/view/history_view_text_helper.h
|
||||
history/view/history_view_transcribe_button.cpp
|
||||
history/view/history_view_transcribe_button.h
|
||||
history/view/history_view_translate_bar.cpp
|
||||
@@ -873,6 +892,10 @@ PRIVATE
|
||||
history/history_view_highlight_manager.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/bot/earn/info_bot_earn_list.cpp
|
||||
info/bot/earn/info_bot_earn_list.h
|
||||
info/bot/earn/info_bot_earn_widget.cpp
|
||||
info/bot/earn/info_bot_earn_widget.h
|
||||
info/channel_statistics/boosts/create_giveaway_box.cpp
|
||||
info/channel_statistics/boosts/create_giveaway_box.h
|
||||
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
|
||||
@@ -881,10 +904,10 @@ PRIVATE
|
||||
info/channel_statistics/boosts/info_boosts_inner_widget.h
|
||||
info/channel_statistics/boosts/info_boosts_widget.cpp
|
||||
info/channel_statistics/boosts/info_boosts_widget.h
|
||||
info/channel_statistics/earn/info_earn_inner_widget.cpp
|
||||
info/channel_statistics/earn/info_earn_inner_widget.h
|
||||
info/channel_statistics/earn/info_earn_widget.cpp
|
||||
info/channel_statistics/earn/info_earn_widget.h
|
||||
info/channel_statistics/earn/info_channel_earn_list.cpp
|
||||
info/channel_statistics/earn/info_channel_earn_list.h
|
||||
info/channel_statistics/earn/info_channel_earn_widget.cpp
|
||||
info/channel_statistics/earn/info_channel_earn_widget.h
|
||||
info/common_groups/info_common_groups_inner_widget.cpp
|
||||
info/common_groups/info_common_groups_inner_widget.h
|
||||
info/common_groups/info_common_groups_widget.cpp
|
||||
@@ -1209,6 +1232,8 @@ PRIVATE
|
||||
payments/payments_checkout_process.h
|
||||
payments/payments_form.cpp
|
||||
payments/payments_form.h
|
||||
payments/payments_non_panel_process.cpp
|
||||
payments/payments_non_panel_process.h
|
||||
platform/linux/file_utilities_linux.cpp
|
||||
platform/linux/file_utilities_linux.h
|
||||
platform/linux/launcher_linux.cpp
|
||||
@@ -1354,6 +1379,10 @@ PRIVATE
|
||||
settings/settings_codes.h
|
||||
settings/settings_common_session.cpp
|
||||
settings/settings_common_session.h
|
||||
settings/settings_credits.cpp
|
||||
settings/settings_credits.h
|
||||
settings/settings_credits_graphics.cpp
|
||||
settings/settings_credits_graphics.h
|
||||
settings/settings_experimental.cpp
|
||||
settings/settings_experimental.h
|
||||
settings/settings_folders.cpp
|
||||
@@ -1445,10 +1474,14 @@ PRIVATE
|
||||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/controls/location_picker.cpp
|
||||
ui/controls/location_picker.h
|
||||
ui/controls/silent_toggle.cpp
|
||||
ui/controls/silent_toggle.h
|
||||
ui/controls/userpic_button.cpp
|
||||
ui/controls/userpic_button.h
|
||||
ui/effects/credits_graphics.cpp
|
||||
ui/effects/credits_graphics.h
|
||||
ui/effects/emoji_fly_animation.cpp
|
||||
ui/effects/emoji_fly_animation.h
|
||||
ui/effects/message_sending_animation_common.h
|
||||
@@ -1464,6 +1497,10 @@ PRIVATE
|
||||
ui/image/image_location.h
|
||||
ui/image/image_location_factory.cpp
|
||||
ui/image/image_location_factory.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
@@ -1477,10 +1514,6 @@ PRIVATE
|
||||
ui/resize_area.h
|
||||
ui/search_field_controller.cpp
|
||||
ui/search_field_controller.h
|
||||
ui/text/format_song_document_name.cpp
|
||||
ui/text/format_song_document_name.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/unread_badge.cpp
|
||||
ui/unread_badge.h
|
||||
window/main_window.cpp
|
||||
@@ -1496,6 +1529,8 @@ PRIVATE
|
||||
window/section_widget.h
|
||||
window/window_adaptive.cpp
|
||||
window/window_adaptive.h
|
||||
window/window_chat_preview.cpp
|
||||
window/window_chat_preview.h
|
||||
window/window_connecting_widget.cpp
|
||||
window/window_connecting_widget.h
|
||||
window/window_controller.cpp
|
||||
@@ -1515,6 +1550,8 @@ PRIVATE
|
||||
window/window_peer_menu.cpp
|
||||
window/window_peer_menu.h
|
||||
window/window_section_common.h
|
||||
window/window_separate_id.cpp
|
||||
window/window_separate_id.h
|
||||
window/window_session_controller.cpp
|
||||
window/window_session_controller.h
|
||||
window/window_session_controller_link_info.h
|
||||
@@ -1581,6 +1618,7 @@ PRIVATE
|
||||
qrc/telegram/animations.qrc
|
||||
qrc/telegram/export.qrc
|
||||
qrc/telegram/iv.qrc
|
||||
qrc/telegram/picker.qrc
|
||||
qrc/telegram/telegram.qrc
|
||||
qrc/telegram/sounds.qrc
|
||||
winrc/Telegram.rc
|
||||
@@ -1804,12 +1842,49 @@ if (WIN32)
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
if (QT_VERSION GREATER 6)
|
||||
if (NOT build_winarm)
|
||||
target_link_options(Telegram PRIVATE
|
||||
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_options(Telegram
|
||||
PRIVATE
|
||||
/DELAYLOAD:API-MS-Win-Core-Console-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-1-1.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
|
||||
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
|
||||
# /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor
|
||||
/DELAYLOAD:authz.dll # Authz.lib
|
||||
/DELAYLOAD:comdlg32.dll
|
||||
/DELAYLOAD:dwrite.dll # DWrite.lib
|
||||
/DELAYLOAD:dxgi.dll # DXGI.lib
|
||||
/DELAYLOAD:d3d9.dll # D3D9.lib
|
||||
/DELAYLOAD:d3d11.dll # D3D11.lib
|
||||
/DELAYLOAD:d3d12.dll # D3D12.lib
|
||||
/DELAYLOAD:setupapi.dll # SetupAPI.lib
|
||||
/DELAYLOAD:winhttp.dll
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_prepare_qrc(Telegram)
|
||||
|
||||
BIN
Telegram/Resources/icons/chat/filled_location.png
Normal file
|
After Width: | Height: | Size: 536 B |
BIN
Telegram/Resources/icons/chat/filled_location@2x.png
Normal file
|
After Width: | Height: | Size: 987 B |
BIN
Telegram/Resources/icons/chat/filled_location@3x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/chats.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Telegram/Resources/icons/menu/chats@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/menu/chats@3x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Telegram/Resources/icons/menu/factcheck.png
Normal file
|
After Width: | Height: | Size: 588 B |
BIN
Telegram/Resources/icons/menu/factcheck@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/menu/factcheck@3x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/menu/passcode_finger.png
Normal file
|
After Width: | Height: | Size: 911 B |
BIN
Telegram/Resources/icons/menu/passcode_finger@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/menu/passcode_finger@3x.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Telegram/Resources/icons/menu/passcode_watch.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
Telegram/Resources/icons/menu/passcode_watch@2x.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
Telegram/Resources/icons/menu/passcode_watch@3x.png
Normal file
|
After Width: | Height: | Size: 886 B |
BIN
Telegram/Resources/icons/menu/passcode_winhello.png
Normal file
|
After Width: | Height: | Size: 454 B |
BIN
Telegram/Resources/icons/menu/passcode_winhello@2x.png
Normal file
|
After Width: | Height: | Size: 772 B |
BIN
Telegram/Resources/icons/menu/passcode_winhello@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/payments/small_star.png
Normal file
|
After Width: | Height: | Size: 484 B |
BIN
Telegram/Resources/icons/payments/small_star@2x.png
Normal file
|
After Width: | Height: | Size: 860 B |
BIN
Telegram/Resources/icons/payments/small_star@3x.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/premium/effects.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
Telegram/Resources/icons/settings/premium/effects@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/settings/premium/effects@3x.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -251,6 +251,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_caption_limit2#other" = "Make the caption shorter or subscribe to **Telegram Premium** to double the limit to **{count}** characters.";
|
||||
"lng_caption_limit_reached#one" = "You've reached the media caption limit. Please make the caption shorter by {count} character.";
|
||||
"lng_caption_limit_reached#other" = "You've reached the media caption limit. Please make the caption shorter by {count} characters.";
|
||||
"lng_caption_move_up" = "Move Caption Up";
|
||||
"lng_caption_move_down" = "Move Caption Down";
|
||||
|
||||
"lng_file_size_limit_title" = "File Too Large";
|
||||
"lng_file_size_limit#one" = "{count} Gb";
|
||||
@@ -302,6 +304,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
|
||||
"lng_sure_enable" = "Enable";
|
||||
"lng_proxy_box_title" = "Enable proxy";
|
||||
"lng_proxy_box_server" = "Server";
|
||||
"lng_proxy_box_port" = "Port";
|
||||
"lng_proxy_box_secret" = "Secret";
|
||||
"lng_proxy_box_status" = "Status";
|
||||
"lng_proxy_box_username" = "Username";
|
||||
"lng_proxy_box_password" = "Password";
|
||||
"lng_proxy_invalid" = "The proxy link is invalid.";
|
||||
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
|
||||
|
||||
@@ -561,6 +570,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_reaction_invoice" = "{reaction} to your invoice";
|
||||
"lng_reaction_gif" = "{reaction} to your GIF";
|
||||
|
||||
"lng_effect_add_title" = "Add an animated effect";
|
||||
"lng_effect_stickers_title" = "Effects from stickers";
|
||||
"lng_effect_send" = "Send with Effect";
|
||||
"lng_effect_none" = "No effects found.";
|
||||
"lng_effect_premium" = "Subscribe to {link} to add this animated effect.";
|
||||
"lng_effect_premium_link" = "Telegram Premium";
|
||||
|
||||
"lng_languages" = "Languages";
|
||||
"lng_languages_none" = "No languages found.";
|
||||
"lng_languages_count#one" = "{count} language";
|
||||
@@ -667,6 +683,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
"lng_settings_passcode_disable" = "Disable Passcode";
|
||||
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
|
||||
"lng_settings_use_winhello" = "Unlock with Windows Hello";
|
||||
"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello.";
|
||||
"lng_settings_use_touchid" = "Unlock with Touch ID";
|
||||
"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID.";
|
||||
"lng_settings_use_applewatch" = "Unlock with Apple Watch";
|
||||
"lng_settings_use_applewatch_about" = "You need to enter your passcode once before unlocking Telegram with Apple Watch.";
|
||||
"lng_settings_use_systempwd" = "Unlock with System Password";
|
||||
"lng_settings_use_systempwd_about" = "You need to enter your passcode once before unlocking Telegram with System Password.";
|
||||
"lng_settings_password_disable" = "Disable Cloud Password";
|
||||
"lng_settings_password_abort" = "Abort two-step verification setup";
|
||||
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
|
||||
@@ -769,6 +793,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_power_chat" = "Animations in Chats";
|
||||
"lng_settings_power_chat_background" = "Background rotation";
|
||||
"lng_settings_power_chat_spoiler" = "Animated spoiler effect";
|
||||
"lng_settings_power_chat_effects" = "Effects in messages";
|
||||
"lng_settings_power_calls" = "Animations in Calls";
|
||||
"lng_settings_power_ui" = "Interface animations";
|
||||
"lng_settings_power_auto" = "Save Power on Low Battery";
|
||||
@@ -880,6 +905,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_gift_premium_users_confirm" = "Proceed";
|
||||
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
|
||||
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
|
||||
"lng_settings_gift_premium_choose" = "Please choose at least one recipient.";
|
||||
|
||||
"lng_backgrounds_header" = "Choose Wallpaper";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
@@ -974,6 +1000,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passcode_ph" = "Your passcode";
|
||||
"lng_passcode_submit" = "Submit";
|
||||
"lng_passcode_logout" = "Log out";
|
||||
"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello.";
|
||||
"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID.";
|
||||
"lng_passcode_applewatch" = "You need to enter your passcode\nbefore you can use Watch to unlock.";
|
||||
"lng_passcode_systempwd" = "You need to enter your passcode\nbefore you can use system password.";
|
||||
"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello.";
|
||||
"lng_passcode_touchid_unlock" = "unlock";
|
||||
"lng_passcode_create_button" = "Save Passcode";
|
||||
"lng_passcode_check_button" = "Submit";
|
||||
"lng_passcode_change_button" = "Save Passcode";
|
||||
@@ -1083,6 +1115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_faq" = "Telegram FAQ";
|
||||
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
|
||||
"lng_settings_features" = "Telegram Features";
|
||||
"lng_settings_credits" = "Your Stars";
|
||||
"lng_settings_logout" = "Log Out";
|
||||
"lng_sure_logout" = "Are you sure you want to log out?";
|
||||
|
||||
@@ -1543,6 +1576,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_manage_peer_bot_public_link" = "Public Link";
|
||||
"lng_manage_peer_bot_public_links" = "Public Links";
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
@@ -1855,6 +1889,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_boost_apply#one" = "{from} boosted the group";
|
||||
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
|
||||
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
|
||||
"lng_action_payment_refunded" = "{peer} refunded back {amount}";
|
||||
|
||||
"lng_similar_channels_title" = "Similar channels";
|
||||
"lng_similar_channels_view_all" = "View all";
|
||||
@@ -2168,6 +2203,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages.";
|
||||
"lng_premium_summary_subtitle_business" = "Telegram Business";
|
||||
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
|
||||
"lng_premium_summary_subtitle_effects" = "Message Effects";
|
||||
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
|
||||
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
|
||||
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
@@ -2291,9 +2328,66 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_business_about_sponsored_link" = "Telegram Ad Platform {emoji}";
|
||||
"lng_business_about_sponsored_url" = "https://ads.telegram.org";
|
||||
|
||||
"lng_credits_summary_title" = "Telegram Stars";
|
||||
"lng_credits_summary_about" = "Buy Stars to unlock content and services in miniapps on Telegram.";
|
||||
"lng_credits_summary_options_subtitle" = "Choose package";
|
||||
"lng_credits_summary_options_credits#one" = "{count} Star";
|
||||
"lng_credits_summary_options_credits#other" = "{count} Stars";
|
||||
"lng_credits_summary_options_more" = "More Options";
|
||||
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
|
||||
"lng_credits_summary_options_about_link" = "Terms and Conditions";
|
||||
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_credits_summary_history_tab_full" = "All Transactions";
|
||||
"lng_credits_summary_history_tab_in" = "Incoming";
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
|
||||
"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
|
||||
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
|
||||
"lng_credits_box_out_photo" = "a photo";
|
||||
"lng_credits_box_out_photos#one" = "{count} photo";
|
||||
"lng_credits_box_out_photos#other" = "{count} photos";
|
||||
"lng_credits_box_out_video" = "a video";
|
||||
"lng_credits_box_out_videos#one" = "{count} video";
|
||||
"lng_credits_box_out_videos#other" = "{count} videos";
|
||||
"lng_credits_box_out_both" = "{photo} and {video}";
|
||||
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
|
||||
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
|
||||
"lng_credits_box_out_about" = "Review the {link} for Stars.";
|
||||
"lng_credits_media_done_title" = "Media Unlocked";
|
||||
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
|
||||
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
|
||||
"lng_credits_summary_in_toast_title" = "Stars Acquired";
|
||||
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_peer_in" = "From";
|
||||
"lng_credits_box_history_entry_via" = "Via";
|
||||
"lng_credits_box_history_entry_play_market" = "Play Market";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
"lng_credits_box_history_entry_fragment" = "Fragment";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
||||
"lng_credits_small_balance_title#other" = "{count} Stars Needed";
|
||||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
|
||||
"lng_location_title" = "Location";
|
||||
"lng_location_about" = "Display the location of your business on your account.";
|
||||
"lng_location_address" = "Enter Address";
|
||||
"lng_location_set_map" = "Set Location on Map";
|
||||
"lng_location_fallback" = "You can set your location on the map from your mobile device.";
|
||||
|
||||
"lng_hours_title" = "Business Hours";
|
||||
@@ -2722,12 +2816,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_prizes_badge" = "x{amount}";
|
||||
|
||||
"lng_prizes_results_title" = "Winners Selected!";
|
||||
"lng_prizes_results_title_one" = "Winner Selected!";
|
||||
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
|
||||
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
|
||||
"lng_prizes_results_link" = "Giveaway";
|
||||
"lng_prizes_results_winner" = "Winner";
|
||||
"lng_prizes_results_winners" = "Winners";
|
||||
"lng_prizes_results_more#one" = "and {count} more!";
|
||||
"lng_prizes_results_more#other" = "and {count} more!";
|
||||
"lng_prizes_results_one" = "The winner received their gift link in a private message.";
|
||||
"lng_prizes_results_all" = "All winners received gift links in private messages.";
|
||||
"lng_prizes_results_some" = "Some winners couldn't be selected.";
|
||||
|
||||
@@ -3063,6 +3160,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_bot_close_warning_sure" = "Close anyway";
|
||||
"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time.";
|
||||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_click_to_start" = "Click here to use this bot.";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
@@ -3098,6 +3197,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_maps_select_on_map" = "Select on the Map";
|
||||
"lng_maps_point_send" = "Send This Location";
|
||||
"lng_maps_point_set" = "Set This Location";
|
||||
"lng_maps_or_choose" = "Or choose a venue";
|
||||
"lng_maps_places_in_area" = "Places in this area";
|
||||
"lng_maps_no_places" = "No places found";
|
||||
"lng_maps_choose_to_search" = "Choose location to see places nearby.";
|
||||
"lng_maps_venues_source" = "Powered by Foursquare";
|
||||
"lng_live_location" = "Live Location";
|
||||
"lng_live_location_now" = "updated just now";
|
||||
"lng_live_location_minutes#one" = "updated {count} minute ago";
|
||||
@@ -3183,6 +3290,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_context_reply_msg" = "Reply";
|
||||
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||
"lng_context_edit_msg" = "Edit";
|
||||
"lng_context_add_factcheck" = "Add Fact Check";
|
||||
"lng_context_edit_factcheck" = "Edit Fact Check";
|
||||
"lng_context_forward_msg" = "Forward Message";
|
||||
"lng_context_send_now_msg" = "Send now";
|
||||
"lng_context_reschedule" = "Reschedule";
|
||||
@@ -3253,6 +3362,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
"lng_factcheck_whats_this" = "what's this?";
|
||||
"lng_factcheck_about" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_add_done" = "Fact check added.";
|
||||
"lng_factcheck_edit_done" = "Fact check edited.";
|
||||
"lng_factcheck_remove_done" = "Fact check removed.";
|
||||
"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_links" = "Only **t.me/** links are allowed.";
|
||||
|
||||
"lng_paid_title" = "Paid Content";
|
||||
"lng_paid_enter_cost" = "Enter Unlock Cost";
|
||||
"lng_paid_cost_placeholder" = "Stars to Unlock";
|
||||
"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
|
||||
"lng_paid_about_link" = "More about stars >";
|
||||
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"lng_paid_price" = "Unlock for {price}";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
@@ -3374,6 +3503,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_add_contact" = "Create";
|
||||
"lng_add_contact_button" = "New contact";
|
||||
"lng_contacts_header" = "Contacts";
|
||||
"lng_menu_not_contact" = "This number is not on Telegram";
|
||||
"lng_contacts_hidden_stories" = "Hidden Stories";
|
||||
"lng_contacts_stories_status#one" = "{count} story";
|
||||
"lng_contacts_stories_status#other" = "{count} stories";
|
||||
@@ -3519,6 +3649,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_formatting_link_text" = "Text";
|
||||
"lng_formatting_link_url" = "URL";
|
||||
"lng_formatting_link_create" = "Create";
|
||||
"lng_formatting_code_title" = "Code Language";
|
||||
"lng_formatting_code_language" = "Language for syntax highlighting.";
|
||||
"lng_formatting_code_auto" = "Auto-Detect";
|
||||
|
||||
"lng_text_copied" = "Text copied to clipboard.";
|
||||
"lng_code_copied" = "Block copied to clipboard.";
|
||||
@@ -3626,6 +3759,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_payments_card_declined" = "Your card was declined.";
|
||||
"lng_payments_payment_failed" = "Payment failed. Your card has not been billed.";
|
||||
"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed.";
|
||||
"lng_payments_precheckout_timeout" = "The bot didn't respond in time. Your card has not been billed.";
|
||||
"lng_payments_precheckout_stars_failed" = "The bot couldn't process your payment.";
|
||||
"lng_payments_precheckout_stars_timeout" = "The bot didn't respond in time.";
|
||||
"lng_payments_already_paid" = "You have already paid for this item.";
|
||||
|
||||
"lng_payments_terms_title" = "Terms of Service";
|
||||
@@ -4069,6 +4205,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
|
||||
|
||||
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
|
||||
"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
|
||||
|
||||
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
|
||||
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
|
||||
@@ -5096,6 +5233,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_history_return" = "Refund";
|
||||
"lng_channel_earn_history_return_about" = "Refunded back";
|
||||
"lng_channel_earn_history_pending" = "Pending";
|
||||
"lng_channel_earn_history_failed" = "Failed";
|
||||
"lng_channel_earn_history_show_more#one" = "Show {count} More Transaction";
|
||||
"lng_channel_earn_history_show_more#other" = "Show {count} More Transactions";
|
||||
"lng_channel_earn_off" = "Switch Off Ads";
|
||||
@@ -5118,6 +5256,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_chart_revenue" = "Ad revenue";
|
||||
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";
|
||||
"lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD";
|
||||
"lng_channel_earn_currency_history" = "TON Transactions";
|
||||
"lng_channel_earn_credits_history" = "Stars Transactions";
|
||||
"lng_channel_earn_out_check_password_about" = "You can withdraw only if you have:";
|
||||
|
||||
"lng_bot_earn_title" = "Stars Balance";
|
||||
"lng_bot_earn_chart_revenue" = "Revenue";
|
||||
"lng_bot_earn_overview_title" = "Proceeds overview";
|
||||
"lng_bot_earn_available" = "Available balance";
|
||||
"lng_bot_earn_total" = "Total lifetime proceeds";
|
||||
"lng_bot_earn_balance_title" = "Available balance";
|
||||
"lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned.";
|
||||
"lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count}";
|
||||
"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count}";
|
||||
"lng_bot_earn_balance_button_all" = "Withdraw all stars";
|
||||
"lng_bot_earn_balance_button_locked" = "Withdraw";
|
||||
"lng_bot_earn_balance_button_buy_ads" = "Buy Ads";
|
||||
"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}";
|
||||
"lng_bot_earn_out_ph" = "Enter amount to withdraw";
|
||||
"lng_bot_earn_balance_password_title" = "Two-step verification";
|
||||
"lng_bot_earn_balance_password_description" = "Please enter your password to collect.";
|
||||
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less then {link}.";
|
||||
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
|
||||
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
|
||||
|
||||
"lng_contact_add" = "Add";
|
||||
"lng_contact_send_message" = "message";
|
||||
@@ -5167,6 +5329,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_font_system" = "System font";
|
||||
"lng_font_not_found" = "Font not found.";
|
||||
|
||||
"lng_search_tab_my_messages" = "My Messages";
|
||||
"lng_search_tab_this_topic" = "This Topic";
|
||||
"lng_search_tab_this_chat" = "This Chat";
|
||||
"lng_search_tab_this_channel" = "This Channel";
|
||||
"lng_search_tab_this_group" = "This Group";
|
||||
"lng_search_tab_public_posts" = "Public Posts";
|
||||
"lng_search_tab_no_results" = "No Results";
|
||||
"lng_search_tab_no_results_text" = "There were no results for \"{query}\".";
|
||||
"lng_search_tab_no_results_retry" = "Try another hashtag.";
|
||||
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
|
||||
|
||||
"lng_contact_details_button" = "View Contact";
|
||||
"lng_contact_details_title" = "Contact details";
|
||||
"lng_contact_details_phone" = "Phone";
|
||||
"lng_contact_details_phone_main" = "Main Phone";
|
||||
"lng_contact_details_phone_home" = "Home Phone";
|
||||
"lng_contact_details_phone_mobile" = "Mobile Phone";
|
||||
"lng_contact_details_phone_work" = "Work Phone";
|
||||
"lng_contact_details_phone_other" = "Other Phone";
|
||||
"lng_contact_details_email" = "Email";
|
||||
"lng_contact_details_address" = "Address";
|
||||
"lng_contact_details_url" = "URL";
|
||||
"lng_contact_details_note" = "Note";
|
||||
"lng_contact_details_birthday" = "Birthday";
|
||||
"lng_contact_details_organization" = "Organization";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
"lng_wnd_choose_program_menu" = "Choose Default Program...";
|
||||
|
||||
120
Telegram/Resources/picker_html/picker.css
Normal file
@@ -0,0 +1,120 @@
|
||||
:root {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
border-radius: 5px !important;
|
||||
border: 3px solid transparent !important;
|
||||
background-color: var(--td-scroll-bg) !important;
|
||||
background-clip: content-box !important;
|
||||
width: 10px !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar:hover {
|
||||
background-color: var(--td-scroll-bg-over) !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px !important;
|
||||
border: 3px solid transparent !important;
|
||||
background-color: var(--td-scroll-bar-bg) !important;
|
||||
background-clip: content-box !important;
|
||||
}
|
||||
html.custom_scroll ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--td-scroll-bar-bg-over) !important;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#marker {
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#marker_drop {
|
||||
margin-bottom: 0px;
|
||||
transition: margin 160ms ease-in-out;
|
||||
}
|
||||
#marker_drop.moving {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
#marker_shadow {
|
||||
position: absolute;
|
||||
}
|
||||
#search_venues {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
top: -30px;
|
||||
transition: top 200ms ease-in-out;
|
||||
}
|
||||
#search_venues.shown {
|
||||
top: 6px;
|
||||
}
|
||||
#search_venues_inner {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: var(--td-window-bg);
|
||||
color: var(--td-window-active-text-fg);
|
||||
cursor: pointer;
|
||||
border-radius: 14px;
|
||||
padding: 5px 12px 6px;
|
||||
box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
|
||||
}
|
||||
#search_venues_inner:hover {
|
||||
background: var(--td-window-bg-over);
|
||||
}
|
||||
#search_venues_content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
#search_venues_content:before {
|
||||
content: var(--td-lng-maps-places-in-area);
|
||||
}
|
||||
#search_venues_inner .ripple .inner {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
background-color: var(--td-window-bg-ripple);
|
||||
}
|
||||
#search_venues_inner .ripple.hiding {
|
||||
animation: fadeOut 200ms linear forwards;
|
||||
}
|
||||
@keyframes ripple {
|
||||
to {
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
199
Telegram/Resources/picker_html/picker.js
Normal file
@@ -0,0 +1,199 @@
|
||||
var LocationPicker = {
|
||||
startZoom: 14,
|
||||
flySpeed: 2.4,
|
||||
notify: function(message) {
|
||||
if (window.external && window.external.invoke) {
|
||||
window.external.invoke(JSON.stringify(message));
|
||||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
const keyQ = (e.key === 'q')
|
||||
|| (e.code === 'KeyQ')
|
||||
|| (e.keyCode === 81);
|
||||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
e.preventDefault();
|
||||
LocationPicker.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
LocationPicker.notify({
|
||||
event: 'keydown',
|
||||
key: 'escape',
|
||||
});
|
||||
}
|
||||
},
|
||||
isNight: function() {
|
||||
var html = document.getElementsByTagName('html')[0];
|
||||
return html.style.getPropertyValue('--td-night') == '1';
|
||||
},
|
||||
lightPreset: function() {
|
||||
return LocationPicker.isNight() ? 'night' : 'day';
|
||||
},
|
||||
updateStyles: function (styles) {
|
||||
if (LocationPicker.styles !== styles) {
|
||||
LocationPicker.styles = styles;
|
||||
document.getElementsByTagName('html')[0].style = styles;
|
||||
|
||||
LocationPicker.map.setConfigProperty(
|
||||
'basemap',
|
||||
'lightPreset',
|
||||
LocationPicker.lightPreset());
|
||||
}
|
||||
},
|
||||
init: function (params) {
|
||||
mapboxgl.accessToken = params.token;
|
||||
if (params.protocol) {
|
||||
mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
|
||||
}
|
||||
|
||||
var options = { container: 'map', config: {
|
||||
basemap: { lightPreset: LocationPicker.lightPreset() }
|
||||
} };
|
||||
var center = params.center;
|
||||
if (center) {
|
||||
center = [center[1], center[0]];
|
||||
options.center = center;
|
||||
options.zoom = LocationPicker.startZoom;
|
||||
} else if (params.bounds) {
|
||||
options.bounds = params.bounds;
|
||||
center = new mapboxgl.LngLatBounds(params.bounds).getCenter();
|
||||
} else {
|
||||
center = [0, 0];
|
||||
}
|
||||
LocationPicker.map = new mapboxgl.Map(options);
|
||||
LocationPicker.createMarker(center);
|
||||
LocationPicker.trackMovement();
|
||||
LocationPicker.initSearchVenueRipple();
|
||||
},
|
||||
marker: function() {
|
||||
return document.getElementById('marker_drop');
|
||||
},
|
||||
createMarker: function(center) {
|
||||
document.getElementById('marker').style.display = 'flex';
|
||||
},
|
||||
clearMovingTimer: function() {
|
||||
if (LocationPicker.clearMovingTimeoutId) {
|
||||
clearTimeout(LocationPicker.clearMovingTimeoutId);
|
||||
LocationPicker.clearMovingTimeoutId = 0;
|
||||
}
|
||||
},
|
||||
startMovingTimer: function(done) {
|
||||
LocationPicker.clearMovingTimer();
|
||||
LocationPicker.clearMovingTimeoutId = setTimeout(done, 500);
|
||||
},
|
||||
trackMovement: function() {
|
||||
LocationPicker.map.on('movestart', function() {
|
||||
LocationPicker.marker().classList.add('moving');
|
||||
LocationPicker.clearMovingTimer();
|
||||
LocationPicker.toggleSearchVenues(false);
|
||||
LocationPicker.notify({ event: 'move_start' });
|
||||
});
|
||||
LocationPicker.map.on('moveend', function() {
|
||||
LocationPicker.startMovingTimer(function() {
|
||||
LocationPicker.marker().classList.remove('moving');
|
||||
LocationPicker.notify({
|
||||
event: 'move_end',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
narrowTo: function (point) {
|
||||
LocationPicker.map.flyTo({
|
||||
center: [point[1], point[0]],
|
||||
zoom: LocationPicker.startZoom,
|
||||
speed: LocationPicker.flySpeed,
|
||||
});
|
||||
},
|
||||
send: function () {
|
||||
LocationPicker.notify({
|
||||
event: 'send',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
},
|
||||
addRipple: function (button, x, y) {
|
||||
const ripple = document.createElement('span');
|
||||
ripple.classList.add('ripple');
|
||||
|
||||
const inner = document.createElement('span');
|
||||
inner.classList.add('inner');
|
||||
|
||||
var rect = button.getBoundingClientRect();
|
||||
x -= rect.x;
|
||||
y -= rect.y;
|
||||
|
||||
const mx = button.clientWidth - x;
|
||||
const my = button.clientHeight - y;
|
||||
const sq1 = x * x + y * y;
|
||||
const sq2 = mx * mx + y * y;
|
||||
const sq3 = x * x + my * my;
|
||||
const sq4 = mx * mx + my * my;
|
||||
const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
|
||||
|
||||
inner.style.width = inner.style.height = `${2 * radius}px`;
|
||||
inner.style.left = `${x - radius}px`;
|
||||
inner.style.top = `${y - radius}px`;
|
||||
inner.classList.add('inner');
|
||||
|
||||
ripple.addEventListener('animationend', function (e) {
|
||||
if (e.animationName === 'fadeOut') {
|
||||
ripple.remove();
|
||||
}
|
||||
});
|
||||
|
||||
ripple.appendChild(inner);
|
||||
button.appendChild(ripple);
|
||||
},
|
||||
stopRipples: function (button) {
|
||||
const id = button.id ? button.id : button;
|
||||
button = document.getElementById(id);
|
||||
const ripples = button.getElementsByClassName('ripple');
|
||||
for (var i = 0; i < ripples.length; ++i) {
|
||||
const ripple = ripples[i];
|
||||
if (!ripple.classList.contains('hiding')) {
|
||||
ripple.classList.add('hiding');
|
||||
}
|
||||
}
|
||||
},
|
||||
initSearchVenueRipple: function() {
|
||||
var button = document.getElementById('search_venues_inner');
|
||||
button.addEventListener('mousedown', function (e) {
|
||||
LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY);
|
||||
LocationPicker.searchVenuesPressed = true;
|
||||
});
|
||||
button.addEventListener('mouseup', function (e) {
|
||||
const id = e.currentTarget.id;
|
||||
setTimeout(function () {
|
||||
LocationPicker.stopRipples(id);
|
||||
}, 0);
|
||||
if (LocationPicker.searchVenuesPressed) {
|
||||
LocationPicker.searchVenuesPressed = false;
|
||||
LocationPicker.toggleSearchVenues(false);
|
||||
LocationPicker.notify({
|
||||
event: 'search_venues',
|
||||
latitude: LocationPicker.map.getCenter().lat,
|
||||
longitude: LocationPicker.map.getCenter().lng
|
||||
});
|
||||
}
|
||||
});
|
||||
button.addEventListener('mouseleave', function (e) {
|
||||
LocationPicker.stopRipples(e.currentTarget);
|
||||
LocationPicker.searchVenuesPressed = false;
|
||||
});
|
||||
},
|
||||
toggleSearchVenues: function(shown) {
|
||||
var button = document.getElementById('search_venues');
|
||||
button.classList.toggle('shown', shown);
|
||||
},
|
||||
};
|
||||
6
Telegram/Resources/qrc/telegram/picker.qrc
Normal file
@@ -0,0 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/picker">
|
||||
<file alias="picker.css">../../picker_html/picker.css</file>
|
||||
<file alias="picker.js">../../picker_html/picker.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.0.4.0" />
|
||||
Version="5.2.6.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,0,4,0
|
||||
PRODUCTVERSION 5,0,4,0
|
||||
FILEVERSION 5,2,6,0
|
||||
PRODUCTVERSION 5,2,6,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.0.4.0"
|
||||
VALUE "FileVersion", "5.2.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.0.4.0"
|
||||
VALUE "ProductVersion", "5.2.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,0,4,0
|
||||
PRODUCTVERSION 5,0,4,0
|
||||
FILEVERSION 5,2,6,0
|
||||
PRODUCTVERSION 5,2,6,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.0.4.0"
|
||||
VALUE "FileVersion", "5.2.6.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.0.4.0"
|
||||
VALUE "ProductVersion", "5.2.6.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -155,6 +155,7 @@ int main(int argc, char *argv[])
|
||||
QString remove;
|
||||
int version = 0;
|
||||
[[maybe_unused]] bool targetwin64 = false;
|
||||
[[maybe_unused]] bool targetwinarm = false;
|
||||
[[maybe_unused]] bool targetarmac = false;
|
||||
QFileInfoList files;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
@@ -165,6 +166,7 @@ int main(int argc, char *argv[])
|
||||
if (remove.isEmpty()) remove = info.canonicalPath() + "/";
|
||||
} else if (string("-target") == argv[i] && i + 1 < argc) {
|
||||
targetwin64 = (string("win64") == argv[i + 1]);
|
||||
targetwinarm = (string("winarm") == argv[i + 1]);
|
||||
} else if (string("-arch") == argv[i] && i + 1 < argc) {
|
||||
targetarmac = (string("arm64") == argv[i + 1]);
|
||||
if (!targetarmac && string("x86_64") != argv[i + 1]) {
|
||||
@@ -493,7 +495,7 @@ int main(int argc, char *argv[])
|
||||
cout << "Signature verified!\n";
|
||||
RSA_free(pbKey);
|
||||
#ifdef Q_OS_WIN
|
||||
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#elif defined Q_OS_MAC
|
||||
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
|
||||
#else
|
||||
|
||||
@@ -571,8 +571,8 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
|
||||
}
|
||||
if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) {
|
||||
WCHAR wstrPath[maxFileLen];
|
||||
DWORD wstrPathLen;
|
||||
if (wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) {
|
||||
DWORD wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen);
|
||||
if (wstrPathLen) {
|
||||
wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName);
|
||||
hDumpFile = _generateDumpFileAtPath(wstrPath);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/history_item_components.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_non_panel_process.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -126,11 +127,7 @@ void SendBotCallbackData(
|
||||
UrlClickHandler::Open(link);
|
||||
return;
|
||||
}
|
||||
const auto scoreLink = AppendShareGameScoreUrl(
|
||||
session,
|
||||
link,
|
||||
item->fullId());
|
||||
BotGameUrlClickHandler(bot, scoreLink).onClick({
|
||||
BotGameUrlClickHandler(bot, link).onClick({
|
||||
Qt::LeftButton,
|
||||
QVariant::fromValue(ClickHandlerContext{
|
||||
.itemId = item->fullId(),
|
||||
@@ -335,7 +332,8 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
Payments::Mode::Payment,
|
||||
crl::guard(controller, [=](auto) {
|
||||
controller->widget()->activate();
|
||||
}));
|
||||
}),
|
||||
Payments::ProcessNonPanelPaymentFormFactory(controller, item));
|
||||
} break;
|
||||
|
||||
case ButtonType::Url: {
|
||||
@@ -490,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||
|
||||
case ButtonType::WebView: {
|
||||
if (const auto bot = item->getMessageBot()) {
|
||||
bot->session().attachWebView().request(
|
||||
controller,
|
||||
Api::SendAction(bot->owner().history(bot)),
|
||||
bot,
|
||||
{ .text = button->text, .url = button->data });
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = false },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::SimpleWebView: {
|
||||
if (const auto bot = item->getMessageBot()) {
|
||||
bot->session().attachWebView().requestSimple(
|
||||
controller,
|
||||
bot,
|
||||
{ .text = button->text, .url = button->data });
|
||||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -20,13 +20,20 @@ namespace Api {
|
||||
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
|
||||
|
||||
struct SendOptions {
|
||||
uint64 price = 0;
|
||||
PeerData *sendAs = nullptr;
|
||||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
EffectId effectId = 0;
|
||||
bool silent = false;
|
||||
bool handleSupportSwitch = false;
|
||||
bool invertCaption = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendOptions &,
|
||||
const SendOptions &) = default;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
|
||||
@@ -49,6 +56,10 @@ struct SendAction {
|
||||
MsgId replaceMediaOf = 0;
|
||||
|
||||
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendAction &,
|
||||
const SendAction &) = default;
|
||||
};
|
||||
|
||||
struct MessageToSend {
|
||||
|
||||
@@ -97,8 +97,10 @@ void ConfirmPhone::resolve(
|
||||
box->resendRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
_api.request(MTPauth_ResendCode(
|
||||
MTP_flags(0),
|
||||
MTP_string(phone),
|
||||
MTP_string(phoneHash)
|
||||
MTP_string(phoneHash),
|
||||
MTPstring() // reason
|
||||
)).done([=] {
|
||||
if (boxWeak) {
|
||||
boxWeak->callDone();
|
||||
|
||||
306
Telegram/SourceFiles/api/api_credits.cpp
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_credits.h"
|
||||
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTransactionsLimit = 100;
|
||||
|
||||
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
|
||||
const MTPStarsTransaction &tl,
|
||||
not_null<PeerData*> peer) {
|
||||
using HistoryPeerTL = MTPDstarsTransactionPeer;
|
||||
using namespace Data;
|
||||
const auto owner = &peer->owner();
|
||||
const auto photo = tl.data().vphoto()
|
||||
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
|
||||
: nullptr;
|
||||
auto extended = std::vector<CreditsHistoryMedia>();
|
||||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
.type = CreditsHistoryMediaType::Photo,
|
||||
.id = photo->id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
.type = CreditsHistoryMediaType::Video,
|
||||
.id = document->id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const auto &) {});
|
||||
}
|
||||
}
|
||||
const auto barePeerId = tl.data().vpeer().match([](
|
||||
const HistoryPeerTL &p) {
|
||||
return peerFromMTP(p.vpeer());
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::PlayMarket;
|
||||
}, [](const MTPDstarsTransactionPeerFragment &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Fragment;
|
||||
}, [](const MTPDstarsTransactionPeerAppStore &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::AppStore;
|
||||
}, [](const MTPDstarsTransactionPeerUnsupported &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Unsupported;
|
||||
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.successDate = tl.data().vtransaction_date()
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
|
||||
const MTPpayments_StarsStatus &status,
|
||||
not_null<PeerData*> peer) {
|
||||
peer->owner().processUsers(status.data().vusers());
|
||||
peer->owner().processChats(status.data().vchats());
|
||||
return Data::CreditsStatusSlice{
|
||||
.list = ranges::views::all(
|
||||
status.data().vhistory().v
|
||||
) | ranges::views::transform([&](const MTPStarsTransaction &tl) {
|
||||
return HistoryFromTL(tl, peer);
|
||||
}) | ranges::to_vector,
|
||||
.balance = status.data().vbalance().v,
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CreditsTopupOptions::CreditsTopupOptions(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
using TLOption = MTPStarsTopupOption;
|
||||
_api.request(MTPpayments_GetStarsTopupOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = ranges::views::all(
|
||||
result.v
|
||||
) | ranges::views::transform([](const TLOption &option) {
|
||||
return Data::CreditTopupOption{
|
||||
.credits = option.data().vstars().v,
|
||||
.product = qs(
|
||||
option.data().vstore_product().value_or_empty()),
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.extended = option.data().is_extended(),
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
CreditsStatus::CreditsStatus(not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
void CreditsStatus::request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
using TLResult = MTPpayments_StarsStatus;
|
||||
|
||||
_requestId = _api.request(MTPpayments_GetStarsStatus(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
CreditsHistory::CreditsHistory(not_null<PeerData*> peer, bool in, bool out)
|
||||
: _peer(peer)
|
||||
, _flags((in == out)
|
||||
? HistoryTL::Flags(0)
|
||||
: HistoryTL::Flags(0)
|
||||
| (in ? HistoryTL::Flag::f_inbound : HistoryTL::Flags(0))
|
||||
| (out ? HistoryTL::Flag::f_outbound : HistoryTL::Flags(0)))
|
||||
, _api(&peer->session().api().instance()) {
|
||||
}
|
||||
|
||||
void CreditsHistory::request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPpayments_GetStarsTransactions(
|
||||
MTP_flags(_flags),
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
|
||||
MTP_string(token),
|
||||
MTP_int(kTransactionsLimit)
|
||||
)).done([=](const MTPpayments_StarsStatus &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session) {
|
||||
const auto username = session->appConfig().get<QString>(
|
||||
u"premium_bot_username"_q,
|
||||
QString());
|
||||
if (username.isEmpty()) {
|
||||
return rpl::never<not_null<PeerData*>>();
|
||||
}
|
||||
if (const auto p = session->data().peerByUsername(username)) {
|
||||
return rpl::single<not_null<PeerData*>>(p);
|
||||
}
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto api = lifetime.make_state<MTP::Sender>(&session->mtp());
|
||||
|
||||
api->request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
session->data().processUsers(result.data().vusers());
|
||||
session->data().processChats(result.data().vchats());
|
||||
const auto botPeer = session->data().peerLoaded(
|
||||
peerFromMTP(result.data().vpeer()));
|
||||
if (!botPeer) {
|
||||
return consumer.put_done();
|
||||
}
|
||||
consumer.put_next(not_null{ botPeer });
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
CreditsEarnStatistics::CreditsEarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto finish = [=](const QString &url) {
|
||||
makeRequest(MTPpayments_GetStarsRevenueStats(
|
||||
MTP_flags(0),
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPpayments_StarsRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &status = data.vstatus().data();
|
||||
_data = Data::CreditsEarnStatistics{
|
||||
.revenueGraph = StatisticalGraphFromTL(
|
||||
data.vrevenue_graph()),
|
||||
.currentBalance = status.vcurrent_balance().v,
|
||||
.availableBalance = status.vavailable_balance().v,
|
||||
.overallRevenue = status.voverall_revenue().v,
|
||||
.usdRate = data.vusd_rate().v,
|
||||
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
|
||||
.nextWithdrawalAt = status.vnext_withdrawal_at()
|
||||
? base::unixtime::parse(
|
||||
status.vnext_withdrawal_at()->v)
|
||||
: QDateTime(),
|
||||
.buyAdsUrl = url,
|
||||
};
|
||||
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
makeRequest(
|
||||
MTPpayments_GetStarsRevenueAdsAccountUrl(
|
||||
(_isUser ? user()->input : channel()->input))
|
||||
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
|
||||
finish(qs(result.data().vurl()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
finish({});
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
93
Telegram/SourceFiles/api/api_credits.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "api/api_statistics_sender.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_credits_earn.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class CreditsTopupOptions final {
|
||||
public:
|
||||
CreditsTopupOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditTopupOptions options() const;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
Data::CreditTopupOptions _options;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsStatus final {
|
||||
public:
|
||||
CreditsStatus(not_null<PeerData*> peer);
|
||||
|
||||
void request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsHistory final {
|
||||
public:
|
||||
CreditsHistory(not_null<PeerData*> peer, bool in, bool out);
|
||||
|
||||
void request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
|
||||
private:
|
||||
using HistoryTL = MTPpayments_GetStarsTransactions;
|
||||
const not_null<PeerData*> _peer;
|
||||
const HistoryTL::Flags _flags;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
||||
class CreditsEarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit CreditsEarnStatistics(not_null<PeerData*>);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Api
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -34,22 +35,33 @@ void RestrictSponsored(
|
||||
}
|
||||
|
||||
void HandleWithdrawalButton(
|
||||
not_null<ChannelData*> channel,
|
||||
RewardReceiver receiver,
|
||||
not_null<Ui::RippleButton*> button,
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
};
|
||||
|
||||
const auto channel = receiver.currencyReceiver;
|
||||
const auto peer = receiver.creditsReceiver;
|
||||
|
||||
const auto state = button->lifetime().make_state<State>();
|
||||
const auto session = &channel->session();
|
||||
const auto session = (channel ? &channel->session() : &peer->session());
|
||||
|
||||
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
|
||||
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
|
||||
|
||||
session->api().cloudPassword().reload();
|
||||
button->setClickedCallback([=] {
|
||||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
state->lifetime = session->api().cloudPassword().state(
|
||||
) | rpl::take(
|
||||
@@ -58,10 +70,12 @@ void HandleWithdrawalButton(
|
||||
state->loading = false;
|
||||
|
||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||
fields.customTitle
|
||||
= tr::lng_channel_earn_balance_password_title();
|
||||
fields.customDescription
|
||||
= tr::lng_channel_earn_balance_password_description(tr::now);
|
||||
fields.customTitle = channel
|
||||
? tr::lng_channel_earn_balance_password_title()
|
||||
: tr::lng_bot_earn_balance_password_title();
|
||||
fields.customDescription = channel
|
||||
? tr::lng_channel_earn_balance_password_description(tr::now)
|
||||
: tr::lng_bot_earn_balance_password_description(tr::now);
|
||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||
fields.customCheckCallback = crl::guard(button, [=](
|
||||
const Core::CloudPasswordResult &result,
|
||||
@@ -74,22 +88,63 @@ void HandleWithdrawalButton(
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const QString &error) {
|
||||
show->showToast(error);
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
};
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
result.result
|
||||
)).done([=](const MTPstats_BroadcastRevenueWithdrawalUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail(fail).send();
|
||||
} else if (peer) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
MTP_long(receiver.creditsAmount()),
|
||||
result.result
|
||||
)).done([=](const CreditsOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail(fail).send();
|
||||
}
|
||||
});
|
||||
show->show(Box<PasscodeBox>(session, fields));
|
||||
});
|
||||
|
||||
};
|
||||
button->setClickedCallback([=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
auto box = PrePasswordErrorBox(
|
||||
error.type(),
|
||||
session,
|
||||
TextWithEntities{
|
||||
tr::lng_channel_earn_out_check_password_about(tr::now),
|
||||
});
|
||||
if (box) {
|
||||
show->show(std::move(box));
|
||||
state->loading = false;
|
||||
} else {
|
||||
processOut();
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
MTP_long(std::numeric_limits<int64_t>::max()),
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,14 @@ void RestrictSponsored(
|
||||
bool restricted,
|
||||
Fn<void(QString)> failed);
|
||||
|
||||
struct RewardReceiver final {
|
||||
ChannelData *currencyReceiver = nullptr;
|
||||
PeerData *creditsReceiver = nullptr;
|
||||
Fn<uint64()> creditsAmount;
|
||||
};
|
||||
|
||||
void HandleWithdrawalButton(
|
||||
not_null<ChannelData*> channel,
|
||||
RewardReceiver receiver,
|
||||
not_null<Ui::RippleButton*> button,
|
||||
std::shared_ptr<Ui::Show> show);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_web_page.h"
|
||||
#include "history/view/controls/history_view_compose_media_edit_manager.h"
|
||||
#include "history/history.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -81,7 +82,8 @@ mtpRequestId EditMessage(
|
||||
| ((!webpage.removed && !webpage.url.isEmpty())
|
||||
? MTPmessages_EditMessage::Flag::f_media
|
||||
: emptyFlag)
|
||||
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||
|| options.invertCaption)
|
||||
? MTPmessages_EditMessage::Flag::f_invert_media
|
||||
: emptyFlag)
|
||||
| (!sentEntities.v.isEmpty()
|
||||
@@ -151,9 +153,7 @@ mtpRequestId EditMessage(
|
||||
const auto &text = item->originalText();
|
||||
const auto webpage = (!item->media() || !item->media()->webpage())
|
||||
? Data::WebPageDraft{ .removed = true }
|
||||
: Data::WebPageDraft{
|
||||
.id = item->media()->webpage()->id,
|
||||
};
|
||||
: Data::WebPageDraft::FromItem(item);
|
||||
return EditMessage(
|
||||
item,
|
||||
text,
|
||||
@@ -203,6 +203,7 @@ void RescheduleMessage(
|
||||
not_null<HistoryItem*> item,
|
||||
SendOptions options) {
|
||||
const auto empty = [] {};
|
||||
options.invertCaption = item->invertMedia();
|
||||
EditMessage(item, options, empty, empty);
|
||||
}
|
||||
|
||||
@@ -254,85 +255,85 @@ mtpRequestId EditTextMessage(
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail,
|
||||
std::optional<bool> spoilerMediaOverride) {
|
||||
if (spoilerMediaOverride) {
|
||||
const auto spoiler = *spoilerMediaOverride;
|
||||
if (const auto media = item->media()) {
|
||||
auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr);
|
||||
auto takeFileReference = Fn<QByteArray()>(nullptr);
|
||||
if (const auto photo = media->photo()) {
|
||||
using Flag = MTPDinputMediaPhoto::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
photo->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()));
|
||||
};
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoiler ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
}
|
||||
|
||||
const auto usedFileReference = takeFileReference
|
||||
? takeFileReference()
|
||||
: QByteArray();
|
||||
const auto origin = item->fullId();
|
||||
const auto api = &item->history()->session().api();
|
||||
const auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
mtpRequestId originalRequestId) -> mtpRequestId {
|
||||
const auto handleReference = [=](
|
||||
const QString &error,
|
||||
mtpRequestId requestId) {
|
||||
if (error.startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
api->refreshFileReference(origin, [=](const auto &) {
|
||||
if (takeFileReference &&
|
||||
(takeFileReference() != usedFileReference)) {
|
||||
repeatRequest(
|
||||
repeatRequest,
|
||||
originalRequestId
|
||||
? originalRequestId
|
||||
: requestId);
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
};
|
||||
const auto callback = [=](
|
||||
Fn<void()> applyUpdates,
|
||||
mtpRequestId requestId) {
|
||||
applyUpdates();
|
||||
done(originalRequestId ? originalRequestId : requestId);
|
||||
};
|
||||
const auto requestId = EditMessage(
|
||||
item,
|
||||
caption,
|
||||
webpage,
|
||||
options,
|
||||
callback,
|
||||
handleReference,
|
||||
takeInputMedia ? takeInputMedia() : std::nullopt);
|
||||
return originalRequestId ? originalRequestId : requestId;
|
||||
bool spoilered) {
|
||||
const auto media = item->media();
|
||||
if (media
|
||||
&& HistoryView::MediaEditManager::CanBeSpoilered(item)
|
||||
&& spoilered != media->hasSpoiler()) {
|
||||
auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr);
|
||||
auto takeFileReference = Fn<QByteArray()>(nullptr);
|
||||
if (const auto photo = media->photo()) {
|
||||
using Flag = MTPDinputMediaPhoto::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaPhoto(
|
||||
MTP_flags(flags),
|
||||
photo->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()));
|
||||
};
|
||||
return performRequest(performRequest, 0);
|
||||
takeFileReference = [=] { return photo->fileReference(); };
|
||||
} else if (const auto document = media->document()) {
|
||||
using Flag = MTPDinputMediaDocument::Flag;
|
||||
const auto flags = Flag()
|
||||
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
|
||||
| (spoilered ? Flag::f_spoiler : Flag());
|
||||
takeInputMedia = [=] {
|
||||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
}
|
||||
|
||||
const auto usedFileReference = takeFileReference
|
||||
? takeFileReference()
|
||||
: QByteArray();
|
||||
const auto origin = item->fullId();
|
||||
const auto api = &item->history()->session().api();
|
||||
const auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
mtpRequestId originalRequestId) -> mtpRequestId {
|
||||
const auto handleReference = [=](
|
||||
const QString &error,
|
||||
mtpRequestId requestId) {
|
||||
if (error.startsWith(u"FILE_REFERENCE_"_q)) {
|
||||
api->refreshFileReference(origin, [=](const auto &) {
|
||||
if (takeFileReference &&
|
||||
(takeFileReference() != usedFileReference)) {
|
||||
repeatRequest(
|
||||
repeatRequest,
|
||||
originalRequestId
|
||||
? originalRequestId
|
||||
: requestId);
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fail(error, requestId);
|
||||
}
|
||||
};
|
||||
const auto callback = [=](
|
||||
Fn<void()> applyUpdates,
|
||||
mtpRequestId requestId) {
|
||||
applyUpdates();
|
||||
done(originalRequestId ? originalRequestId : requestId);
|
||||
};
|
||||
const auto requestId = EditMessage(
|
||||
item,
|
||||
caption,
|
||||
webpage,
|
||||
options,
|
||||
callback,
|
||||
handleReference,
|
||||
takeInputMedia ? takeInputMedia() : std::nullopt);
|
||||
return originalRequestId ? originalRequestId : requestId;
|
||||
};
|
||||
return performRequest(performRequest, 0);
|
||||
}
|
||||
|
||||
const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {
|
||||
|
||||
@@ -56,6 +56,6 @@ mtpRequestId EditTextMessage(
|
||||
SendOptions options,
|
||||
Fn<void(mtpRequestId requestId)> done,
|
||||
Fn<void(const QString &error, mtpRequestId requestId)> fail,
|
||||
std::optional<bool> spoilerMediaOverride);
|
||||
bool spoilered);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
27
Telegram/SourceFiles/api/api_filter_updates.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Api {
|
||||
|
||||
template <typename Type>
|
||||
void PerformForUpdate(
|
||||
const MTPUpdates &updates,
|
||||
Fn<void(const Type &)> callback) {
|
||||
updates.match([&](const MTPDupdates &updates) {
|
||||
for (const auto &update : updates.vupdates().v) {
|
||||
update.match([&](const Type &d) {
|
||||
callback(d);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -68,6 +68,9 @@ void Polls::create(
|
||||
if (action.options.shortcutId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
@@ -89,7 +92,8 @@ void Polls::create(
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (clearCloudDraft) {
|
||||
history->finishSavingCloudDraft(
|
||||
|
||||
@@ -62,6 +62,79 @@ void InnerFillMessagePostFlags(
|
||||
}
|
||||
}
|
||||
|
||||
void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
|
||||
const auto history = action.history;
|
||||
const auto peer = history->peer;
|
||||
const auto session = &history->session();
|
||||
const auto api = &session->api();
|
||||
|
||||
action.clearDraft = false;
|
||||
action.generateLocal = false;
|
||||
api->sendAction(action);
|
||||
|
||||
const auto randomId = base::RandomValue<uint64>();
|
||||
|
||||
auto flags = NewMessageFlags(peer);
|
||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||
if (action.replyTo) {
|
||||
flags |= MessageFlag::HasReplyInfo;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||
}
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
InnerFillMessagePostFlags(action.options, peer, flags);
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
if (sendAs) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||
}
|
||||
const auto messagePostAuthor = peer->isBroadcast()
|
||||
? session->user()->name()
|
||||
: QString();
|
||||
|
||||
if (action.options.scheduled) {
|
||||
flags |= MessageFlag::IsOrWasScheduled;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||
}
|
||||
if (action.options.shortcutId) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
action.replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||
MTP_flags(sendFlags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
std::move(inputMedia),
|
||||
MTPstring(),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
MTPvector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId);
|
||||
});
|
||||
|
||||
api->finishForwarding(action);
|
||||
}
|
||||
|
||||
template <typename MediaData>
|
||||
void SendExistingMedia(
|
||||
MessageToSend &&message,
|
||||
@@ -133,6 +206,13 @@ void SendExistingMedia(
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -144,6 +224,7 @@ void SendExistingMedia(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, media, caption);
|
||||
|
||||
const auto performRequest = [=](const auto &repeatRequest) -> void {
|
||||
@@ -165,7 +246,8 @@ void SendExistingMedia(
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (error.code() == 400
|
||||
@@ -306,6 +388,13 @@ bool SendDice(MessageToSend &message) {
|
||||
flags |= MessageFlag::ShortcutMessage;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
if (action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
}
|
||||
|
||||
session->data().registerMessageRandomId(randomId, newId);
|
||||
|
||||
@@ -317,6 +406,7 @@ bool SendDice(MessageToSend &message) {
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaDice(
|
||||
MTP_int(0),
|
||||
MTP_string(emoji)));
|
||||
@@ -335,7 +425,8 @@ bool SendDice(MessageToSend &message) {
|
||||
MTP_vector<MTPMessageEntity>(),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, action.options.shortcutId),
|
||||
MTP_long(action.options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
api->sendMessageFail(error, peer, randomId, newId);
|
||||
@@ -344,6 +435,33 @@ bool SendDice(MessageToSend &message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendLocation(SendAction action, float64 lat, float64 lon) {
|
||||
SendSimpleMedia(
|
||||
action,
|
||||
MTP_inputMediaGeoPoint(
|
||||
MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(lat),
|
||||
MTP_double(lon),
|
||||
MTPint()))); // accuracy_radius
|
||||
}
|
||||
|
||||
void SendVenue(SendAction action, Data::InputVenue venue) {
|
||||
SendSimpleMedia(
|
||||
action,
|
||||
MTP_inputMediaVenue(
|
||||
MTP_inputGeoPoint(
|
||||
MTP_flags(0),
|
||||
MTP_double(venue.lat),
|
||||
MTP_double(venue.lon),
|
||||
MTPint()), // accuracy_radius
|
||||
MTP_string(venue.title),
|
||||
MTP_string(venue.address),
|
||||
MTP_string(venue.provider),
|
||||
MTP_string(venue.id),
|
||||
MTP_string(venue.venueType)));
|
||||
}
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
@@ -430,6 +548,9 @@ void SendConfirmedFile(
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
}
|
||||
if (file->to.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
}
|
||||
|
||||
const auto messageFromId = file->to.options.sendAs
|
||||
? file->to.options.sendAs->id
|
||||
@@ -493,6 +614,7 @@ void SendConfirmedFile(
|
||||
edition.ttl = 0;
|
||||
edition.mtpMedia = &media;
|
||||
edition.textWithEntities = caption;
|
||||
edition.invertMedia = file->to.options.invertCaption;
|
||||
edition.useSameViews = true;
|
||||
edition.useSameForwards = true;
|
||||
edition.useSameMarkup = true;
|
||||
@@ -510,6 +632,7 @@ void SendConfirmedFile(
|
||||
.shortcutId = file->to.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.groupedId = groupId,
|
||||
.effectId = file->to.options.effectId,
|
||||
}, caption, media);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class History;
|
||||
class PhotoData;
|
||||
class DocumentData;
|
||||
struct FilePrepareResult;
|
||||
|
||||
namespace Data {
|
||||
struct InputVenue;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct MessageToSend;
|
||||
@@ -33,6 +37,13 @@ void SendExistingPhoto(
|
||||
|
||||
bool SendDice(MessageToSend &message);
|
||||
|
||||
// We can't create Data::LocationPoint() and use it
|
||||
// for a local sending message, because we can't request
|
||||
// map thumbnail in messages history without access hash.
|
||||
void SendLocation(SendAction action, float64 lat, float64 lon);
|
||||
|
||||
void SendVenue(SendAction action, Data::InputVenue venue);
|
||||
|
||||
void FillMessagePostFlags(
|
||||
const SendAction &action,
|
||||
not_null<PeerData*> peer,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics.h"
|
||||
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -15,33 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_story.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
using namespace Statistic;
|
||||
const auto zoomToken = d.vzoom_token().has_value()
|
||||
? qs(*d.vzoom_token()).toUtf8()
|
||||
: QByteArray();
|
||||
return Data::StatisticalGraph{
|
||||
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||
zoomToken,
|
||||
};
|
||||
}, [&](const MTPDstatsGraphAsync &data) {
|
||||
return Data::StatisticalGraph{
|
||||
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||
};
|
||||
}, [&](const MTPDstatsGraphError &data) {
|
||||
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
|
||||
const MTPStatsAbsValueAndPrev &tl) {
|
||||
const auto current = tl.data().vcurrent().v;
|
||||
@@ -223,61 +201,6 @@ Statistics::Statistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
}
|
||||
|
||||
StatisticsRequestSender::StatisticsRequestSender(not_null<ChannelData *> channel)
|
||||
: _channel(channel)
|
||||
, _api(&_channel->session().api().instance())
|
||||
, _timer([=] { checkRequests(); }) {
|
||||
}
|
||||
|
||||
StatisticsRequestSender::~StatisticsRequestSender() {
|
||||
for (const auto &[dcId, ids] : _requests) {
|
||||
for (const auto id : ids) {
|
||||
_channel->session().api().unregisterStatsRequest(dcId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsRequestSender::checkRequests() {
|
||||
for (auto i = begin(_requests); i != end(_requests);) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (_api.pending(*j)) {
|
||||
++j;
|
||||
} else {
|
||||
_channel->session().api().unregisterStatsRequest(
|
||||
i->first,
|
||||
*j);
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
i = _requests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_requests.empty()) {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Request, typename, typename>
|
||||
auto StatisticsRequestSender::makeRequest(Request &&request) {
|
||||
const auto id = _api.allocateRequestId();
|
||||
const auto dcId = _channel->owner().statsDcId(_channel);
|
||||
if (dcId) {
|
||||
_channel->session().api().registerStatsRequest(dcId, id);
|
||||
_requests[dcId].emplace(id);
|
||||
if (!_timer.isActive()) {
|
||||
_timer.callEach(kCheckRequestsTimer);
|
||||
}
|
||||
}
|
||||
return std::move(_api.request(
|
||||
std::forward<Request>(request)
|
||||
).toDC(
|
||||
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
|
||||
).overrideId(id));
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Statistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
@@ -747,11 +670,11 @@ Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
EarnStatistics::EarnStatistics(not_null<ChannelData*> channel)
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
@@ -795,7 +718,7 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
};
|
||||
}
|
||||
|
||||
void EarnStatistics::requestHistory(
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
@@ -865,7 +788,7 @@ void EarnStatistics::requestHistory(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,45 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "api/api_statistics_sender.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_channel_earn.h"
|
||||
#include "data/data_statistics.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class StatisticsRequestSender {
|
||||
protected:
|
||||
explicit StatisticsRequestSender(not_null<ChannelData*> channel);
|
||||
~StatisticsRequestSender();
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] auto makeRequest(Request &&request);
|
||||
|
||||
[[nodiscard]] MTP::Sender &api() {
|
||||
return _api;
|
||||
}
|
||||
[[nodiscard]] not_null<ChannelData*> channel() {
|
||||
return _channel;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkRequests();
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
|
||||
|
||||
};
|
||||
|
||||
class Statistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit Statistics(not_null<ChannelData*> channel);
|
||||
@@ -108,9 +79,9 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit EarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
|
||||
35
Telegram/SourceFiles/api/api_statistics_data_deserialize.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
|
||||
#include "data/data_statistics_chart.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
using namespace Statistic;
|
||||
const auto zoomToken = d.vzoom_token().has_value()
|
||||
? qs(*d.vzoom_token()).toUtf8()
|
||||
: QByteArray();
|
||||
return Data::StatisticalGraph{
|
||||
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||
zoomToken,
|
||||
};
|
||||
}, [&](const MTPDstatsGraphAsync &data) {
|
||||
return Data::StatisticalGraph{
|
||||
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||
};
|
||||
}, [&](const MTPDstatsGraphError &data) {
|
||||
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
19
Telegram/SourceFiles/api/api_statistics_data_deserialize.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
struct StatisticalGraph;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl);
|
||||
|
||||
} // namespace Api
|
||||
86
Telegram/SourceFiles/api/api_statistics_sender.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics_sender.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
StatisticsRequestSender::StatisticsRequestSender(
|
||||
not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _channel(peer->asChannel())
|
||||
, _user(peer->asUser())
|
||||
, _api(&_peer->session().api().instance())
|
||||
, _timer([=] { checkRequests(); }) {
|
||||
}
|
||||
|
||||
MTP::Sender &StatisticsRequestSender::api() {
|
||||
return _api;
|
||||
}
|
||||
|
||||
not_null<ChannelData*> StatisticsRequestSender::channel() {
|
||||
Expects(_channel);
|
||||
return _channel;
|
||||
}
|
||||
|
||||
not_null<UserData*> StatisticsRequestSender::user() {
|
||||
Expects(_user);
|
||||
return _user;
|
||||
}
|
||||
|
||||
void StatisticsRequestSender::checkRequests() {
|
||||
for (auto i = begin(_requests); i != end(_requests);) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (_api.pending(*j)) {
|
||||
++j;
|
||||
} else {
|
||||
_peer->session().api().unregisterStatsRequest(
|
||||
i->first,
|
||||
*j);
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
i = _requests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_requests.empty()) {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
auto StatisticsRequestSender::ensureRequestIsRegistered()
|
||||
-> StatisticsRequestSender::Registered {
|
||||
const auto id = _api.allocateRequestId();
|
||||
const auto dcId = _peer->owner().statsDcId(_peer);
|
||||
if (dcId) {
|
||||
_peer->session().api().registerStatsRequest(dcId, id);
|
||||
_requests[dcId].emplace(id);
|
||||
if (!_timer.isActive()) {
|
||||
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
|
||||
_timer.callEach(kCheckRequestsTimer);
|
||||
}
|
||||
}
|
||||
return StatisticsRequestSender::Registered{ id, dcId };
|
||||
}
|
||||
|
||||
StatisticsRequestSender::~StatisticsRequestSender() {
|
||||
for (const auto &[dcId, ids] : _requests) {
|
||||
for (const auto id : ids) {
|
||||
_peer->session().api().unregisterStatsRequest(dcId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
58
Telegram/SourceFiles/api/api_statistics_sender.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class StatisticsRequestSender {
|
||||
protected:
|
||||
explicit StatisticsRequestSender(not_null<PeerData*> peer);
|
||||
~StatisticsRequestSender();
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] auto makeRequest(Request &&request) {
|
||||
const auto [id, dcId] = ensureRequestIsRegistered();
|
||||
return std::move(_api.request(
|
||||
std::forward<Request>(request)
|
||||
).toDC(
|
||||
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
|
||||
).overrideId(id));
|
||||
}
|
||||
|
||||
[[nodiscard]] MTP::Sender &api();
|
||||
[[nodiscard]] not_null<ChannelData*> channel();
|
||||
[[nodiscard]] not_null<UserData*> user();
|
||||
|
||||
private:
|
||||
struct Registered final {
|
||||
mtpRequestId id;
|
||||
MTP::DcId dcId;
|
||||
};
|
||||
[[nodiscard]] Registered ensureRequestIsRegistered();
|
||||
void checkRequests();
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
ChannelData * const _channel;
|
||||
UserData * const _user;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -178,7 +178,11 @@ EntitiesInText EntitiesFromMTP(
|
||||
});
|
||||
}
|
||||
}, [&](const MTPDmessageEntityPhone &d) {
|
||||
// Skipping phones.
|
||||
result.push_back({
|
||||
EntityType::Phone,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
});
|
||||
}, [&](const MTPDmessageEntityCashtag &d) {
|
||||
result.push_back({
|
||||
EntityType::Cashtag,
|
||||
@@ -217,6 +221,7 @@ EntitiesInText EntitiesFromMTP(
|
||||
EntityType::Blockquote,
|
||||
d.voffset().v,
|
||||
d.vlength().v,
|
||||
d.is_collapsed() ? u"1"_q : QString(),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -265,6 +270,9 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
case EntityType::Email: {
|
||||
v.push_back(MTP_messageEntityEmail(offset, length));
|
||||
} break;
|
||||
case EntityType::Phone: {
|
||||
v.push_back(MTP_messageEntityPhone(offset, length));
|
||||
} break;
|
||||
case EntityType::Hashtag: {
|
||||
v.push_back(MTP_messageEntityHashtag(offset, length));
|
||||
} break;
|
||||
@@ -311,7 +319,13 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||
MTP_string(entity.data())));
|
||||
} break;
|
||||
case EntityType::Blockquote: {
|
||||
v.push_back(MTP_messageEntityBlockquote(offset, length));
|
||||
using Flag = MTPDmessageEntityBlockquote::Flag;
|
||||
const auto collapsed = !entity.data().isEmpty();
|
||||
v.push_back(
|
||||
MTP_messageEntityBlockquote(
|
||||
MTP_flags(collapsed ? Flag::f_collapsed : Flag()),
|
||||
offset,
|
||||
length));
|
||||
} break;
|
||||
case EntityType::Spoiler: {
|
||||
v.push_back(MTP_messageEntitySpoiler(offset, length));
|
||||
|
||||
@@ -1137,7 +1137,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint()), // quick_reply_shortcut_id
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1172,7 +1174,9 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
||||
MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTP_int(d.vttl_period().value_or_empty()),
|
||||
MTPint()), // quick_reply_shortcut_id
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPlong(), // effect
|
||||
MTPFactCheck()),
|
||||
MessageFlags(),
|
||||
NewMessageType::Unread);
|
||||
} break;
|
||||
@@ -1692,7 +1696,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto peerId = peerFromMTP(d.vpeer());
|
||||
const auto msgId = d.vmsg_id().v;
|
||||
if (const auto item = session().data().message(peerId, msgId)) {
|
||||
item->applyEdition(d.vextended_media());
|
||||
item->applyEdition(d.vextended_media().v);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -2117,6 +2121,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
};
|
||||
if (IsForceLogoutNotification(d)) {
|
||||
Core::App().forceLogOut(&session().account(), text);
|
||||
} else if (IsWithdrawalNotification(d)) {
|
||||
return;
|
||||
} else if (d.is_popup()) {
|
||||
const auto &windows = session().windows();
|
||||
if (!windows.empty()) {
|
||||
@@ -2610,7 +2616,16 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
_session->data().stories().apply(data.vstealth_mode());
|
||||
} break;
|
||||
|
||||
case mtpc_updateStarsBalance: {
|
||||
const auto &data = update.c_updateStarsBalance();
|
||||
_session->setCredits(data.vbalance().v);
|
||||
} break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) {
|
||||
return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -211,4 +211,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool IsWithdrawalNotification(
|
||||
const MTPDupdateServiceNotification &);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
void ViewsManager::pollExtendedMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
bool force) {
|
||||
if (!item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
@@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
const auto peer = item->history()->peer;
|
||||
auto &request = _pollRequests[peer];
|
||||
if (request.ids.contains(id) || request.sent.contains(id)) {
|
||||
return;
|
||||
if (!force || request.forced) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
request.ids.emplace(id);
|
||||
if (!request.id && !request.when) {
|
||||
request.when = crl::now() + kPollExtendedMediaPeriod;
|
||||
if (force) {
|
||||
request.forced = true;
|
||||
}
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = force ? 1 : kPollExtendedMediaPeriod;
|
||||
if (!request.id && (!request.when || force)) {
|
||||
request.when = crl::now() + delay;
|
||||
}
|
||||
if (!_pollTimer.isActive() || force) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
|
||||
if (i->second.ids.empty()) {
|
||||
i = _pollRequests.erase(i);
|
||||
} else {
|
||||
i->second.when = now + kPollExtendedMediaPeriod;
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = i->second.forced
|
||||
? 1
|
||||
: kPollExtendedMediaPeriod;
|
||||
i->second.when = now + delay;
|
||||
if (!_pollTimer.isActive() || i->second.forced) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item);
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item, bool force = false);
|
||||
|
||||
private:
|
||||
struct PollExtendedMediaRequest {
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
mtpRequestId id = 0;
|
||||
base::flat_set<MsgId> ids;
|
||||
base::flat_set<MsgId> sent;
|
||||
bool forced = false;
|
||||
};
|
||||
|
||||
void viewsIncrement();
|
||||
|
||||
@@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_history_messages.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "core/application.h"
|
||||
#include "base/unixtime.h"
|
||||
@@ -2164,7 +2165,8 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
entities,
|
||||
Data::WebPageForMTP(
|
||||
cloudDraft->webpage,
|
||||
textWithTags.text.isEmpty())
|
||||
textWithTags.text.isEmpty()),
|
||||
MTP_long(0) // effect
|
||||
)).done([=](const MTPBool &result, const MTP::Response &response) {
|
||||
const auto requestId = response.requestId;
|
||||
history->finishSavingCloudDraft(
|
||||
@@ -3078,6 +3080,46 @@ void ApiWrap::resolveJumpToHistoryDate(
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestHistory(
|
||||
not_null<History*> history,
|
||||
MsgId messageId,
|
||||
SliceType slice) {
|
||||
const auto peer = history->peer;
|
||||
const auto key = HistoryRequest{
|
||||
peer,
|
||||
messageId,
|
||||
slice,
|
||||
};
|
||||
if (_historyRequests.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto prepared = Api::PrepareHistoryRequest(peer, messageId, slice);
|
||||
auto &histories = history->owner().histories();
|
||||
const auto requestType = Data::Histories::RequestType::History;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
return request(
|
||||
std::move(prepared)
|
||||
).done([=](const Api::HistoryRequestResult &result) {
|
||||
_historyRequests.remove(key);
|
||||
auto parsed = Api::ParseHistoryResult(
|
||||
peer,
|
||||
messageId,
|
||||
slice,
|
||||
result);
|
||||
history->messages().addSlice(
|
||||
std::move(parsed.messageIds),
|
||||
parsed.noSkipRange,
|
||||
parsed.fullCount);
|
||||
finish();
|
||||
}).fail([=] {
|
||||
_historyRequests.remove(key);
|
||||
finish();
|
||||
}).send();
|
||||
});
|
||||
_historyRequests.emplace(key);
|
||||
}
|
||||
|
||||
void ApiWrap::requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
@@ -3342,6 +3384,9 @@ void ApiWrap::forwardMessages(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
|
||||
// forwarded messages don't have effects
|
||||
//.effectId = action.options.effectId,
|
||||
}, item);
|
||||
_session->data().registerMessageRandomId(randomId, newId);
|
||||
if (!localIds) {
|
||||
@@ -3442,6 +3487,7 @@ void ApiWrap::sendSharedContact(
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, TextWithEntities(), MTP_messageMediaContact(
|
||||
MTP_string(phone),
|
||||
MTP_string(firstName),
|
||||
@@ -3729,7 +3775,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
FillMessagePostFlags(action, peer, flags);
|
||||
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
|
||||
if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
|
||||
|| action.options.invertCaption) {
|
||||
flags |= MessageFlag::InvertMedia;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||
@@ -3775,6 +3822,10 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_quick_reply_shortcut;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
|
||||
}
|
||||
if (action.options.effectId) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_effect;
|
||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_effect;
|
||||
}
|
||||
lastMessage = history->addNewLocalMessage({
|
||||
.id = newId.msg,
|
||||
.flags = flags,
|
||||
@@ -3783,6 +3834,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
.date = HistoryItem::NewMessageDate(action.options),
|
||||
.shortcutId = action.options.shortcutId,
|
||||
.postAuthor = messagePostAuthor,
|
||||
.effectId = action.options.effectId,
|
||||
}, sending, media);
|
||||
const auto done = [=](
|
||||
const MTPUpdates &result,
|
||||
@@ -3828,7 +3880,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
), done, fail);
|
||||
} else {
|
||||
histories.sendPreparedMessage(
|
||||
@@ -3845,7 +3898,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||
sentEntities,
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
mtpShortcut
|
||||
mtpShortcut,
|
||||
MTP_long(action.options.effectId)
|
||||
), done, fail);
|
||||
}
|
||||
isFirst = false;
|
||||
@@ -4119,7 +4173,9 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : 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));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
@@ -4132,14 +4188,19 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
media,
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
@@ -4151,6 +4212,82 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendMultiPaidMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<SendingAlbum*> album,
|
||||
Fn<void(bool)> done) {
|
||||
Expects(album->options.price > 0);
|
||||
|
||||
const auto groupId = album->groupId;
|
||||
const auto &options = album->options;
|
||||
const auto randomId = album->items.front().randomId;
|
||||
auto medias = album->items | ranges::view::transform([](
|
||||
const SendingAlbum::Item &part) {
|
||||
Assert(part.media.has_value());
|
||||
return MTPInputMedia(part.media->data().vmedia());
|
||||
}) | ranges::to<QVector<MTPInputMedia>>();
|
||||
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
caption.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (replyTo ? Flag::f_reply_to : Flag(0))
|
||||
| (ShouldSendSilent(history->peer, options)
|
||||
? Flag::f_silent
|
||||
: Flag(0))
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| (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));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
const auto copy = (*album)->items;
|
||||
for (const auto &part : copy) {
|
||||
if (const auto item = history->owner().message(part.msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (done) done(true);
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (done) done(false);
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
@@ -4204,8 +4341,11 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (!sample) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
} else if (album->options.price > 0) {
|
||||
sendMultiPaidMedia(sample, album);
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().c_inputSingleMedia();
|
||||
const auto &single = medias.front().data();
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
@@ -4227,7 +4367,9 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
| (sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (album->options.shortcutId
|
||||
? Flag::f_quick_reply_shortcut
|
||||
: Flag(0));
|
||||
: Flag(0))
|
||||
| (album->options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
histories.sendPreparedMessage(
|
||||
@@ -4241,7 +4383,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
MTP_vector<MTPInputSingleMedia>(medias),
|
||||
MTP_int(album->options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, album->options.shortcutId),
|
||||
MTP_long(album->options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
|
||||
@@ -274,6 +274,10 @@ public:
|
||||
Fn<void(not_null<PeerData*>, MsgId)> callback);
|
||||
|
||||
using SliceType = Data::LoadDirection;
|
||||
void requestHistory(
|
||||
not_null<History*> history,
|
||||
MsgId messageId,
|
||||
SliceType slice);
|
||||
void requestSharedMedia(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId topicRootId,
|
||||
@@ -511,7 +515,8 @@ private:
|
||||
not_null<PeerData*> peer,
|
||||
bool justClear,
|
||||
bool revoke);
|
||||
void applyAffectedMessages(const MTPmessages_AffectedMessages &result) const;
|
||||
void applyAffectedMessages(
|
||||
const MTPmessages_AffectedMessages &result) const;
|
||||
|
||||
void deleteAllFromParticipantSend(
|
||||
not_null<ChannelData*> channel,
|
||||
@@ -540,6 +545,10 @@ private:
|
||||
Api::SendOptions options,
|
||||
uint64 randomId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMultiPaidMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<SendingAlbum*> album,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
|
||||
void getTopPromotionDelayed(TimeId now, TimeId next);
|
||||
void topPromotionDone(const MTPhelp_PromoData &proxy);
|
||||
@@ -645,6 +654,17 @@ private:
|
||||
};
|
||||
base::flat_set<SharedMediaRequest> _sharedMediaRequests;
|
||||
|
||||
struct HistoryRequest {
|
||||
not_null<PeerData*> peer;
|
||||
MsgId aroundId = 0;
|
||||
SliceType sliceType = {};
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const HistoryRequest&,
|
||||
const HistoryRequest&) = default;
|
||||
};
|
||||
base::flat_set<HistoryRequest> _historyRequests;
|
||||
|
||||
std::unique_ptr<DialogsLoadState> _dialogsLoadState;
|
||||
TimeId _dialogsLoadTill = 0;
|
||||
rpl::variable<bool> _dialogsLoadMayBlockByDate = false;
|
||||
|
||||
@@ -100,6 +100,8 @@ void AboutBox::showVersionHistory() {
|
||||
url += u"win/%1.zip"_q;
|
||||
} else if (Platform::IsWindows64Bit()) {
|
||||
url += u"win64/%1.zip"_q;
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
url += u"winarm/%1.zip"_q;
|
||||
} else if (Platform::IsMac()) {
|
||||
url += u"mac/%1.zip"_q;
|
||||
} else if (Platform::IsLinux()) {
|
||||
@@ -155,6 +157,8 @@ QString currentVersionText() {
|
||||
}
|
||||
if (Platform::IsWindows64Bit()) {
|
||||
result += " x64";
|
||||
} else if (Platform::IsWindowsARM64()) {
|
||||
result += " arm64";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -192,20 +192,27 @@ void ShowAddParticipantsError(
|
||||
&& channel
|
||||
&& !channel->isMegagroup()
|
||||
&& channel->canAddAdmins()) {
|
||||
const auto makeAdmin = [=] {
|
||||
const auto makeAdmin = [=](Fn<void()> close) {
|
||||
const auto user = forbidden.users.front();
|
||||
const auto weak = std::make_shared<QPointer<EditAdminBox>>();
|
||||
const auto close = [=](auto&&...) {
|
||||
if (*weak) {
|
||||
(*weak)->closeBox();
|
||||
const auto done = [=](auto&&...) {
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->uiShow()->showToast(
|
||||
tr::lng_box_done(tr::now));
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto fail = [=] {
|
||||
if (const auto strong = weak->data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
const auto saveCallback = SaveAdminCallback(
|
||||
show,
|
||||
channel,
|
||||
user,
|
||||
close,
|
||||
close);
|
||||
done,
|
||||
fail);
|
||||
auto box = Box<EditAdminBox>(
|
||||
channel,
|
||||
user,
|
||||
@@ -214,6 +221,7 @@ void ShowAddParticipantsError(
|
||||
box->setSaveCallback(saveCallback);
|
||||
*weak = box.data();
|
||||
show->showBox(std::move(box));
|
||||
close();
|
||||
};
|
||||
show->showBox(
|
||||
Ui::MakeConfirmBox({
|
||||
|
||||
@@ -237,7 +237,7 @@ shareColumnSkip: 6px;
|
||||
shareActivateDuration: 150;
|
||||
shareScrollDuration: 300;
|
||||
shareComment: InputField(defaultInputField) {
|
||||
font: normalFont;
|
||||
style: defaultTextStyle;
|
||||
textMargins: margins(8px, 8px, 8px, 6px);
|
||||
heightMin: 36px;
|
||||
heightMax: 72px;
|
||||
@@ -290,6 +290,26 @@ passcodeTextLine: 28px;
|
||||
passcodeLittleSkip: 5px;
|
||||
passcodeAboutSkip: 7px;
|
||||
passcodeSkip: 23px;
|
||||
passcodeSystemUnlock: IconButton(defaultIconButton) {
|
||||
width: 32px;
|
||||
height: 36px;
|
||||
icon: icon{{ "menu/passcode_winhello", lightButtonFg }};
|
||||
iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }};
|
||||
iconPosition: point(4px, 4px);
|
||||
rippleAreaSize: 32px;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
passcodeSystemTouchID: icon{{ "menu/passcode_finger", lightButtonFg }};
|
||||
passcodeSystemAppleWatch: icon{{ "menu/passcode_watch", lightButtonFg }};
|
||||
passcodeSystemSystemPwd: icon{{ "menu/permissions", lightButtonFg }};
|
||||
passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) {
|
||||
align: align(top);
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
passcodeSystemUnlockSkip: 12px;
|
||||
|
||||
newGroupAboutFg: windowSubTextFg;
|
||||
newGroupPadding: margins(4px, 6px, 4px, 3px);
|
||||
@@ -585,7 +605,7 @@ groupStickersRemovePosition: point(6px, 6px);
|
||||
groupStickersFieldPadding: margins(8px, 6px, 8px, 6px);
|
||||
groupStickersField: InputField(defaultMultiSelectSearchField) {
|
||||
placeholderFont: boxTextFont;
|
||||
font: boxTextFont;
|
||||
style: boxTextStyle;
|
||||
placeholderMargins: margins(0px, 0px, 0px, 0px);
|
||||
textMargins: margins(0px, 7px, 0px, 0px);
|
||||
textBg: boxBg;
|
||||
@@ -642,6 +662,10 @@ proxyDropdownUpPosition: point(-2px, 20px);
|
||||
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
|
||||
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
|
||||
|
||||
proxyApplyBoxLabel : FlatLabel(defaultFlatLabel) {
|
||||
maxHeight: 30px;
|
||||
}
|
||||
|
||||
markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px);
|
||||
|
||||
termsContent: FlatLabel(defaultFlatLabel) {
|
||||
@@ -668,7 +692,6 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
createPollField: InputField(defaultInputField) {
|
||||
font: boxTextFont;
|
||||
textMargins: margins(0px, 4px, 0px, 4px);
|
||||
textAlign: align(left);
|
||||
heightMin: 36px;
|
||||
@@ -873,7 +896,6 @@ scheduleDateField: InputField(defaultInputField) {
|
||||
placeholderScale: 0.;
|
||||
heightMin: 30px;
|
||||
textAlign: align(top);
|
||||
font: font(14px);
|
||||
}
|
||||
scheduleTimeField: InputField(scheduleDateField) {
|
||||
border: 0px;
|
||||
@@ -901,7 +923,6 @@ muteBoxTimeField: InputField(scheduleDateField) {
|
||||
placeholderScale: 0.;
|
||||
heightMin: 30px;
|
||||
textAlign: align(left);
|
||||
font: font(14px);
|
||||
}
|
||||
muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ Data::ChatFilter ChangedFilter(
|
||||
filter.id(),
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
filter.colorIndex(),
|
||||
filter.flags(),
|
||||
std::move(always),
|
||||
filter.pinned(),
|
||||
@@ -58,6 +59,7 @@ Data::ChatFilter ChangedFilter(
|
||||
filter.id(),
|
||||
filter.title(),
|
||||
filter.iconEmoji(),
|
||||
filter.colorIndex(),
|
||||
filter.flags(),
|
||||
std::move(always),
|
||||
filter.pinned(),
|
||||
@@ -81,7 +83,7 @@ void ChangeFilterById(
|
||||
MTP_int(filter.id()),
|
||||
filter.tl()
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()] {
|
||||
const auto account = &history->session().account();
|
||||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
controller->showToast((add
|
||||
? tr::lng_filters_toast_add
|
||||
|
||||
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/local_url_handlers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -36,7 +35,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "boxes/abstract_box.h" // Ui::show().
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
@@ -1186,59 +1187,84 @@ ProxiesBoxController::ProxiesBoxController(not_null<Main::Account*> account)
|
||||
}
|
||||
|
||||
void ProxiesBoxController::ShowApplyConfirmation(
|
||||
Window::SessionController *controller,
|
||||
Type type,
|
||||
const QMap<QString, QString> &fields) {
|
||||
const auto proxy = ProxyDataFromFields(type, fields);
|
||||
if (proxy) {
|
||||
static const auto UrlStartRegExp = QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto UrlEndRegExp = QRegularExpression("/$");
|
||||
const auto displayed = "https://" + proxy.host + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
? displayed
|
||||
: parsed.isValid()
|
||||
? QString::fromUtf8(parsed.toEncoded())
|
||||
: UrlClickHandler::ShowEncoded(displayed);
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto text = tr::lng_sure_enable_socks(
|
||||
tr::now,
|
||||
lt_server,
|
||||
displayServer,
|
||||
lt_port,
|
||||
QString::number(proxy.port))
|
||||
+ (proxy.type == Type::Mtproto
|
||||
? "\n\n" + tr::lng_proxy_sponsor_warning(tr::now)
|
||||
: QString());
|
||||
auto callback = [=](Fn<void()> &&close) {
|
||||
if (!proxy) {
|
||||
auto box = Ui::MakeInformBox(
|
||||
(proxy.status() == ProxyData::Status::Unsupported
|
||||
? tr::lng_proxy_unsupported(tr::now)
|
||||
: tr::lng_proxy_invalid(tr::now)));
|
||||
if (controller) {
|
||||
controller->uiShow()->showBox(std::move(box));
|
||||
} else {
|
||||
Ui::show(std::move(box));
|
||||
}
|
||||
return;
|
||||
}
|
||||
static const auto UrlStartRegExp = QRegularExpression(
|
||||
"^https://",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const auto UrlEndRegExp = QRegularExpression("/$");
|
||||
const auto displayed = "https://" + proxy.host + "/";
|
||||
const auto parsed = QUrl::fromUserInput(displayed);
|
||||
const auto displayUrl = !UrlClickHandler::IsSuspicious(displayed)
|
||||
? displayed
|
||||
: parsed.isValid()
|
||||
? QString::fromUtf8(parsed.toEncoded())
|
||||
: UrlClickHandler::ShowEncoded(displayed);
|
||||
const auto displayServer = QString(
|
||||
displayUrl
|
||||
).replace(
|
||||
UrlStartRegExp,
|
||||
QString()
|
||||
).replace(UrlEndRegExp, QString());
|
||||
const auto box = [=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_proxy_box_title());
|
||||
if (type == Type::Mtproto) {
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_proxy_sponsor_warning(),
|
||||
st::boxDividerLabel));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
const auto &stL = st::proxyApplyBoxLabel;
|
||||
const auto &stSubL = st::boxDividerLabel;
|
||||
const auto add = [&](const QString &s, tr::phrase<> phrase) {
|
||||
if (!s.isEmpty()) {
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(box, s, stL));
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(box, phrase(), stSubL));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
}
|
||||
};
|
||||
if (!displayServer.isEmpty()) {
|
||||
add(displayServer, tr::lng_proxy_box_server);
|
||||
}
|
||||
add(QString::number(proxy.port), tr::lng_proxy_box_port);
|
||||
if (type == Type::Socks5) {
|
||||
add(proxy.user, tr::lng_proxy_box_username);
|
||||
add(proxy.password, tr::lng_proxy_box_password);
|
||||
} else if (type == Type::Mtproto) {
|
||||
add(proxy.password, tr::lng_proxy_box_secret);
|
||||
}
|
||||
box->addButton(tr::lng_sure_enable(), [=] {
|
||||
auto &proxies = Core::App().settings().proxy().list();
|
||||
if (!ranges::contains(proxies, proxy)) {
|
||||
proxies.push_back(proxy);
|
||||
}
|
||||
Core::App().setCurrentProxy(
|
||||
proxy,
|
||||
ProxyData::Settings::Enabled);
|
||||
Core::App().setCurrentProxy(proxy, ProxyData::Settings::Enabled);
|
||||
Local::writeSettings();
|
||||
close();
|
||||
};
|
||||
Ui::show(
|
||||
Ui::MakeConfirmBox({
|
||||
.text = text,
|
||||
.confirmed = std::move(callback),
|
||||
.confirmText = tr::lng_sure_enable(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther);
|
||||
box->closeBox();
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
};
|
||||
if (controller) {
|
||||
controller->uiShow()->showBox(Box(box));
|
||||
} else {
|
||||
Ui::show(Ui::MakeInformBox(
|
||||
(proxy.status() == ProxyData::Status::Unsupported
|
||||
? tr::lng_proxy_unsupported(tr::now)
|
||||
: tr::lng_proxy_invalid(tr::now))));
|
||||
Ui::show(Box(box));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ namespace Main {
|
||||
class Account;
|
||||
} // namespace Main
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
class ProxiesBoxController {
|
||||
public:
|
||||
using ProxyData = MTP::ProxyData;
|
||||
@@ -38,6 +42,7 @@ public:
|
||||
explicit ProxiesBoxController(not_null<Main::Account*> account);
|
||||
|
||||
static void ShowApplyConfirmation(
|
||||
Window::SessionController *controller,
|
||||
Type type,
|
||||
const QMap<QString, QString> &fields);
|
||||
|
||||
|
||||
@@ -910,12 +910,12 @@ CreatePollBox::CreatePollBox(
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType)
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: _controller(controller)
|
||||
, _chosen(chosen)
|
||||
, _disabled(disabled)
|
||||
, _sendType(sendType)
|
||||
, _sendMenuType(sendMenuType) {
|
||||
, _sendMenuDetails([result = sendMenuDetails] { return result; }) {
|
||||
}
|
||||
|
||||
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
|
||||
@@ -1044,7 +1044,16 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
|
||||
solution->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
solution->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
solution->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
solution->setMarkdownReplacesEnabled(rpl::single(
|
||||
Ui::MarkdownEnabledState{ Ui::MarkdownEnabled{ {
|
||||
Ui::InputField::kTagBold,
|
||||
Ui::InputField::kTagItalic,
|
||||
Ui::InputField::kTagUnderline,
|
||||
Ui::InputField::kTagStrikeOut,
|
||||
Ui::InputField::kTagCode,
|
||||
Ui::InputField::kTagSpoiler,
|
||||
} } }
|
||||
));
|
||||
solution->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(_controller->uiShow(), solution));
|
||||
solution->customTab(true);
|
||||
@@ -1288,19 +1297,9 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
_submitRequests.fire({ collectResult(), sendOptions });
|
||||
}
|
||||
};
|
||||
const auto sendSilent = [=] {
|
||||
send({ .silent = true });
|
||||
};
|
||||
const auto sendScheduled = [=] {
|
||||
_controller->show(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
SendMenu::Type::Scheduled,
|
||||
send));
|
||||
};
|
||||
const auto sendWhenOnline = [=] {
|
||||
send(Api::DefaultSendWhenOnlineOptions());
|
||||
};
|
||||
const auto sendAction = SendMenu::DefaultCallback(
|
||||
_controller->uiShow(),
|
||||
crl::guard(this, send));
|
||||
|
||||
options->scrollToWidget(
|
||||
) | rpl::start_with_next([=](not_null<QWidget*> widget) {
|
||||
@@ -1313,24 +1312,25 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
|
||||
}, lifetime());
|
||||
|
||||
const auto isNormal = (_sendType == Api::SendType::Normal);
|
||||
|
||||
const auto schedule = [=] {
|
||||
sendAction(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
_sendMenuDetails());
|
||||
};
|
||||
const auto submit = addButton(
|
||||
isNormal
|
||||
(isNormal
|
||||
? tr::lng_polls_create_button()
|
||||
: tr::lng_schedule_button(),
|
||||
[=] { isNormal ? send({}) : sendScheduled(); });
|
||||
const auto sendMenuType = [=] {
|
||||
: tr::lng_schedule_button()),
|
||||
[=] { isNormal ? send({}) : schedule(); });
|
||||
const auto sendMenuDetails = [=] {
|
||||
collectError();
|
||||
return (*error)
|
||||
? SendMenu::Type::Disabled
|
||||
: _sendMenuType;
|
||||
return (*error) ? SendMenu::Details() : _sendMenuDetails();
|
||||
};
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
submit.data(),
|
||||
sendMenuType,
|
||||
sendSilent,
|
||||
sendScheduled,
|
||||
sendWhenOnline);
|
||||
_controller->uiShow(),
|
||||
sendMenuDetails,
|
||||
sendAction);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
return result;
|
||||
|
||||
@@ -27,7 +27,7 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
class CreatePollBox : public Ui::BoxContent {
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
PollData::Flags chosen,
|
||||
PollData::Flags disabled,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType);
|
||||
SendMenu::Details sendMenuDetails);
|
||||
|
||||
[[nodiscard]] rpl::producer<Result> submitRequests() const;
|
||||
void submitFailed(const QString &error);
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
const PollData::Flags _chosen = PollData::Flags();
|
||||
const PollData::Flags _disabled = PollData::Flags();
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
const SendMenu::Type _sendMenuType;
|
||||
const Fn<SendMenu::Details()> _sendMenuDetails;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
Fn<void()> _setInnerFocus;
|
||||
Fn<rpl::producer<bool>()> _dataIsValidValue;
|
||||
|
||||
@@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mainwidget.h" // controller->content() -> QWidget*
|
||||
#include "menu/menu_send.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "storage/localimageloader.h" // SendMediaType
|
||||
@@ -175,7 +176,7 @@ void ChooseReplacement(
|
||||
void EditPhotoImage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
bool wasSpoiler,
|
||||
bool spoilered,
|
||||
Fn<void(Ui::PreparedList)> done) {
|
||||
const auto large = media
|
||||
? media->image(Data::PhotoSize::Large)
|
||||
@@ -198,7 +199,7 @@ void EditPhotoImage(
|
||||
|
||||
using ImageInfo = Ui::PreparedFileInformation::Image;
|
||||
auto &file = list.files.front();
|
||||
file.spoiler = wasSpoiler;
|
||||
file.spoiler = spoilered;
|
||||
const auto image = std::get_if<ImageInfo>(&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
@@ -225,25 +226,18 @@ void EditPhotoImage(
|
||||
|
||||
} // namespace
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item)
|
||||
: EditCaptionBox({}, controller, item, PrepareEditText(item), {}, {}) {
|
||||
}
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media()
|
||||
? item->media()->allowsEditMedia()
|
||||
: false)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
@@ -261,6 +255,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
_controller->session().data().itemRemoved(
|
||||
_historyItem->fullId()
|
||||
) | rpl::start_with_next([=] {
|
||||
@@ -274,6 +270,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
@@ -285,6 +283,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
};
|
||||
@@ -299,6 +299,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
@@ -332,6 +334,8 @@ void EditCaptionBox::StartMediaReplace(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
}
|
||||
@@ -342,14 +346,15 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved) {
|
||||
const auto session = &controller->session();
|
||||
const auto item = session->data().message(itemId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto hasSpoiler = item->media() && item->media()->hasSpoiler();
|
||||
EditPhotoImage(controller, media, hasSpoiler, [=](
|
||||
EditPhotoImage(controller, media, spoilered, [=](
|
||||
Ui::PreparedList &&list) mutable {
|
||||
const auto item = session->data().message(itemId);
|
||||
if (!item) {
|
||||
@@ -359,15 +364,48 @@ void EditCaptionBox::StartPhotoEdit(
|
||||
controller,
|
||||
item,
|
||||
std::move(text),
|
||||
spoilered,
|
||||
invertCaption,
|
||||
std::move(list),
|
||||
std::move(saved)));
|
||||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
||||
const auto details = crl::guard(this, [=] {
|
||||
auto result = SendMenu::Details();
|
||||
const auto allWithSpoilers = ranges::all_of(
|
||||
_preparedList.files,
|
||||
&Ui::PreparedFile::spoiler);
|
||||
result.spoiler = !_preparedList.hasSpoilerMenu(!_asFile)
|
||||
? SendMenu::SpoilerState::None
|
||||
: allWithSpoilers
|
||||
? SendMenu::SpoilerState::Enabled
|
||||
: SendMenu::SpoilerState::Possible;
|
||||
const auto canMoveCaption = _preparedList.canMoveCaption(
|
||||
false,
|
||||
!_asFile
|
||||
) && _field && HasSendText(_field);
|
||||
result.caption = !canMoveCaption
|
||||
? SendMenu::CaptionState::None
|
||||
: _mediaEditManager.invertCaption()
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
return result;
|
||||
});
|
||||
const auto callback = [=](SendMenu::Action action, const auto &) {
|
||||
_mediaEditManager.apply(action);
|
||||
rebuildPreview();
|
||||
};
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
button,
|
||||
nullptr,
|
||||
details,
|
||||
crl::guard(this, callback));
|
||||
|
||||
updateBoxSize();
|
||||
|
||||
setupField();
|
||||
@@ -396,7 +434,6 @@ void EditCaptionBox::rebuildPreview() {
|
||||
|
||||
applyChanges();
|
||||
|
||||
_previewHasSpoiler = nullptr;
|
||||
if (_preparedList.files.empty()) {
|
||||
const auto media = _historyItem->media();
|
||||
const auto photo = media->photo();
|
||||
@@ -426,11 +463,18 @@ void EditCaptionBox::rebuildPreview() {
|
||||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
if (media && (!withCheckbox || !_asFile)) {
|
||||
_previewHasSpoiler = [media] { return media->hasSpoiler(); };
|
||||
media->spoileredChanges(
|
||||
) | rpl::start_with_next([=](bool spoilered) {
|
||||
_mediaEditManager.apply({ .type = spoilered
|
||||
? SendMenu::ActionType::SpoilerOn
|
||||
: SendMenu::ActionType::SpoilerOff
|
||||
});
|
||||
}, media->lifetime());
|
||||
_content.reset(media);
|
||||
} else {
|
||||
_content.reset(Ui::CreateChild<Ui::SingleFilePreview>(
|
||||
@@ -757,10 +801,7 @@ bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) {
|
||||
}
|
||||
|
||||
bool EditCaptionBox::hasSpoiler() const {
|
||||
return _preparedList.files.empty()
|
||||
? (_historyItem->media()
|
||||
&& _historyItem->media()->hasSpoiler())
|
||||
: _preparedList.files.front().spoiler;
|
||||
return _mediaEditManager.spoilered();
|
||||
}
|
||||
|
||||
void EditCaptionBox::captionResized() {
|
||||
@@ -869,8 +910,8 @@ bool EditCaptionBox::validateLength(const QString &text) const {
|
||||
}
|
||||
|
||||
void EditCaptionBox::applyChanges() {
|
||||
if (!_preparedList.files.empty() && _previewHasSpoiler) {
|
||||
_preparedList.files.front().spoiler = _previewHasSpoiler();
|
||||
if (!_preparedList.files.empty()) {
|
||||
_preparedList.files.front().spoiler = _mediaEditManager.spoilered();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,6 +940,7 @@ void EditCaptionBox::save() {
|
||||
auto options = Api::SendOptions();
|
||||
options.scheduled = item->isScheduled() ? item->date() : 0;
|
||||
options.shortcutId = item->shortcutId();
|
||||
options.invertCaption = _mediaEditManager.invertCaption();
|
||||
|
||||
if (!_preparedList.files.empty()) {
|
||||
if ((_albumType != Ui::AlbumType::None)
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "history/view/controls/history_view_compose_media_edit_manager.h"
|
||||
#include "ui/layers/box_content.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
|
||||
@@ -32,15 +33,13 @@ enum class AlbumType;
|
||||
|
||||
class EditCaptionBox final : public Ui::BoxContent {
|
||||
public:
|
||||
EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
TextWithTags &&text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Ui::PreparedList &&list,
|
||||
Fn<void()> saved);
|
||||
~EditCaptionBox();
|
||||
@@ -49,18 +48,24 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
static void StartMediaReplace(
|
||||
not_null<Window::SessionController*> controller,
|
||||
FullMsgId itemId,
|
||||
Ui::PreparedList &&list,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
static void StartPhotoEdit(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Data::PhotoMedia> media,
|
||||
FullMsgId itemId,
|
||||
TextWithTags text,
|
||||
bool spoilered,
|
||||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
|
||||
protected:
|
||||
@@ -111,7 +116,6 @@ private:
|
||||
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
|
||||
|
||||
base::unique_qptr<Ui::AbstractSinglePreview> _content;
|
||||
Fn<bool()> _previewHasSpoiler;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
||||
@@ -122,6 +126,7 @@ private:
|
||||
std::shared_ptr<Data::PhotoMedia> _photoMedia;
|
||||
|
||||
Ui::PreparedList _preparedList;
|
||||
HistoryView::MediaEditManager _mediaEditManager;
|
||||
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
(rules.flags() & ~flag),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
@@ -104,6 +105,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
rules.flags(),
|
||||
std::move(always),
|
||||
std::move(pinned),
|
||||
@@ -170,6 +172,7 @@ void EditExceptions(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
rules.iconEmoji(),
|
||||
rules.colorIndex(),
|
||||
((rules.flags() & ~options)
|
||||
| rawController->chosenOptions()),
|
||||
include ? std::move(changed) : std::move(removeFrom),
|
||||
@@ -240,6 +243,7 @@ void CreateIconSelector(
|
||||
rules.id(),
|
||||
rules.title(),
|
||||
Ui::LookupFilterIcon(icon).emoji,
|
||||
rules.colorIndex(),
|
||||
rules.flags(),
|
||||
rules.always(),
|
||||
rules.pinned(),
|
||||
|
||||
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_media_types.h" // Data::GiveawayStart.
|
||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
@@ -1010,14 +1011,16 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
}) | ranges::views::filter([](UserData *u) -> bool {
|
||||
return u;
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
if (!users.empty()) {
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
if (users.empty()) {
|
||||
show->showToast(
|
||||
tr::lng_settings_gift_premium_choose(tr::now));
|
||||
}
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
(*ignoreClose) = true;
|
||||
peersBox->closeBox();
|
||||
};
|
||||
@@ -1147,16 +1150,18 @@ void GiftCodeBox(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st::giveawayGiftCodeCover,
|
||||
nullptr,
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_title(),
|
||||
tr::lng_gift_link_title()),
|
||||
rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||
true));
|
||||
Ui::Premium::TopBarDescriptor{
|
||||
.clickContextOther = nullptr,
|
||||
.title = rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_title(),
|
||||
tr::lng_gift_link_title()),
|
||||
.about = rpl::conditional(
|
||||
state->used.value(),
|
||||
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||
.light = true,
|
||||
}));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
@@ -1283,13 +1288,15 @@ void GiftCodePendingBox(
|
||||
object_ptr<Ui::Premium::TopBar>(
|
||||
box,
|
||||
st,
|
||||
clickContext,
|
||||
tr::lng_gift_link_title(),
|
||||
tr::lng_gift_link_pending_about(
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Link(resultToName)),
|
||||
Ui::Text::RichLangValue),
|
||||
true));
|
||||
Ui::Premium::TopBarDescriptor{
|
||||
.clickContextOther = clickContext,
|
||||
.title = tr::lng_gift_link_title(),
|
||||
.about = tr::lng_gift_link_pending_about(
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Link(resultToName)),
|
||||
Ui::Text::RichLangValue),
|
||||
.light = true,
|
||||
}));
|
||||
|
||||
const auto max = st::giveawayGiftCodeTopHeight;
|
||||
bar->setMaximumHeight(max);
|
||||
@@ -1629,3 +1636,119 @@ void ResolveGiveawayInfo(
|
||||
messageId,
|
||||
crl::guard(controller, show));
|
||||
}
|
||||
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
if (peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto session = &controller->session();
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
const auto link = session->createInternalLink(query);
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(Ui::Text::Link(link)),
|
||||
st::giveawayGiftCodeValue);
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
controller->showPeerHistory(channel, {}, msgId);
|
||||
return false;
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_media(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
}
|
||||
using Type = Data::CreditsHistoryEntry::PeerType;
|
||||
if (entry.peerType == Type::AppStore) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_app_store(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::PlayMarket) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_play_market(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::Fragment) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_fragment(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::Ads) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::PremiumBot) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_via_premium_bot(
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(
|
||||
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
|
||||
oneLine
|
||||
? st::giveawayGiftCodeValue
|
||||
: st::giveawayGiftCodeValueMultiline);
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
TextUtilities::SetClipboardText(
|
||||
TextForMimeData::Simple(entry.id));
|
||||
controller->showToast(
|
||||
tr::lng_credits_box_history_entry_id_copied(tr::now));
|
||||
return false;
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_id(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
if (!entry.successDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_success_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
if (!entry.successLink.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_success_url(),
|
||||
rpl::single(
|
||||
Ui::Text::Link(entry.successLink, entry.successLink)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
@@ -71,3 +73,8 @@ void ResolveGiveawayInfo(
|
||||
MsgId messageId,
|
||||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
@@ -133,8 +133,8 @@ void MaxInviteBox::paintEvent(QPaintEvent *e) {
|
||||
auto option = QTextOption(style::al_left);
|
||||
option.setWrapMode(QTextOption::WrapAnywhere);
|
||||
p.setFont(_linkOver
|
||||
? st::defaultInputField.font->underline()
|
||||
: st::defaultInputField.font);
|
||||
? st::defaultInputField.style.font->underline()
|
||||
: st::defaultInputField.style.font);
|
||||
p.setPen(st::defaultLinkButton.color);
|
||||
const auto inviteLinkText = _channel->inviteLink().isEmpty()
|
||||
? tr::lng_group_invite_create(tr::now)
|
||||
|
||||
@@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <xxhash.h> // XXH64.
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
[[nodiscard]] PeerListRowId UniqueRowIdFromString(const QString &d) {
|
||||
return XXH64(d.data(), d.size() * sizeof(ushort), 0);
|
||||
@@ -1552,15 +1553,44 @@ void PeerListContent::handleMouseMove(QPoint globalPosition) {
|
||||
&& *_lastMousePosition == globalPosition) {
|
||||
return;
|
||||
}
|
||||
if (_trackPressStart
|
||||
&& ((*_trackPressStart - globalPosition).manhattanLength()
|
||||
> QApplication::startDragDistance())) {
|
||||
_trackPressStart = {};
|
||||
_controller->rowTrackPressCancel();
|
||||
}
|
||||
if (!_controller->rowTrackPressSkipMouseSelection()) {
|
||||
selectByMouse(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListContent::pressLeftToContextMenu(bool shown) {
|
||||
if (shown) {
|
||||
setContexted(_pressed);
|
||||
setPressed(Selected());
|
||||
} else {
|
||||
setContexted(Selected());
|
||||
}
|
||||
}
|
||||
|
||||
bool PeerListContent::trackRowPressFromGlobal(QPoint globalPosition) {
|
||||
selectByMouse(globalPosition);
|
||||
if (const auto row = getRow(_selected.index)) {
|
||||
if (_controller->rowTrackPress(row)) {
|
||||
_trackPressStart = globalPosition;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
_pressButton = e->button();
|
||||
selectByMouse(e->globalPos());
|
||||
setPressed(_selected);
|
||||
if (auto row = getRow(_selected.index)) {
|
||||
auto updateCallback = [this, row, hint = _selected.index] {
|
||||
_trackPressStart = {};
|
||||
if (const auto row = getRow(_selected.index)) {
|
||||
const auto updateCallback = [this, row, hint = _selected.index] {
|
||||
updateRow(row, hint);
|
||||
};
|
||||
if (_selected.element) {
|
||||
@@ -1586,8 +1616,11 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
|
||||
row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback));
|
||||
}
|
||||
}
|
||||
if (_pressButton == Qt::LeftButton && _controller->rowTrackPress(row)) {
|
||||
_trackPressStart = e->globalPos();
|
||||
}
|
||||
}
|
||||
if (anim::Disabled() && !_selected.element) {
|
||||
if (anim::Disabled() && !_trackPressStart && !_selected.element) {
|
||||
mousePressReleased(e->button());
|
||||
}
|
||||
}
|
||||
@@ -1597,6 +1630,9 @@ void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void PeerListContent::mousePressReleased(Qt::MouseButton button) {
|
||||
_trackPressStart = {};
|
||||
_controller->rowTrackPressCancel();
|
||||
|
||||
updateRow(_pressed.index);
|
||||
updateRow(_selected.index);
|
||||
|
||||
|
||||
@@ -348,6 +348,9 @@ public:
|
||||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
template <typename PeerDataRange>
|
||||
void peerListAddSelectedPeers(PeerDataRange &&range) {
|
||||
for (const auto peer : range) {
|
||||
@@ -478,6 +481,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool rowTrackPress(not_null<PeerListRow*> row) {
|
||||
return false;
|
||||
}
|
||||
virtual void rowTrackPressCancel() {
|
||||
}
|
||||
virtual bool rowTrackPressSkipMouseSelection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void loadMoreRows() {
|
||||
}
|
||||
virtual void itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
@@ -655,6 +667,8 @@ public:
|
||||
void refreshRows();
|
||||
|
||||
void mouseLeftGeometry();
|
||||
void pressLeftToContextMenu(bool shown);
|
||||
bool trackRowPressFromGlobal(QPoint globalPosition);
|
||||
|
||||
void setSearchMode(PeerListSearchMode mode);
|
||||
void changeCheckState(
|
||||
@@ -829,6 +843,7 @@ private:
|
||||
bool _mouseSelection = false;
|
||||
std::optional<QPoint> _lastMousePosition;
|
||||
Qt::MouseButton _pressButton = Qt::LeftButton;
|
||||
std::optional<QPoint> _trackPressStart;
|
||||
|
||||
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
|
||||
|
||||
@@ -992,6 +1007,13 @@ public:
|
||||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
bool peerListTrackRowPressFromGlobal(QPoint globalPosition) override {
|
||||
return _content->trackRowPressFromGlobal(globalPosition);
|
||||
}
|
||||
|
||||
protected:
|
||||
not_null<PeerListContent*> content() const {
|
||||
return _content;
|
||||
|
||||
@@ -42,16 +42,14 @@ namespace {
|
||||
|
||||
constexpr auto kDefaultIconId = DocumentId(0x7FFF'FFFF'FFFF'FFFFULL);
|
||||
|
||||
struct DefaultIcon {
|
||||
QString title;
|
||||
int32 colorId = 0;
|
||||
};
|
||||
using DefaultIcon = Data::TopicIconDescriptor;
|
||||
|
||||
class DefaultIconEmoji final : public Ui::Text::CustomEmoji {
|
||||
public:
|
||||
DefaultIconEmoji(
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint);
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag);
|
||||
|
||||
int width() override;
|
||||
QString entityData() override;
|
||||
@@ -64,14 +62,17 @@ public:
|
||||
private:
|
||||
DefaultIcon _icon = {};
|
||||
QImage _image;
|
||||
Data::CustomEmojiSizeTag _tag = {};
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
DefaultIconEmoji::DefaultIconEmoji(
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint) {
|
||||
rpl::producer<DefaultIcon> value,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag)
|
||||
: _tag(tag) {
|
||||
std::move(value) | rpl::start_with_next([=](DefaultIcon value) {
|
||||
_icon = value;
|
||||
_image = QImage();
|
||||
@@ -88,15 +89,22 @@ QString DefaultIconEmoji::entityData() {
|
||||
}
|
||||
|
||||
void DefaultIconEmoji::paint(QPainter &p, const Context &context) {
|
||||
const auto &st = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
? st::normalForumTopicIcon
|
||||
: st::defaultForumTopicIcon;
|
||||
if (_image.isNull()) {
|
||||
_image = Data::ForumTopicIconFrame(
|
||||
_icon.colorId,
|
||||
_icon.title,
|
||||
st::defaultForumTopicIcon);
|
||||
_image = Data::IsForumGeneralIconTitle(_icon.title)
|
||||
? Data::ForumTopicGeneralIconFrame(
|
||||
st.size,
|
||||
Data::ParseForumGeneralIconColor(_icon.colorId))
|
||||
: Data::ForumTopicIconFrame(_icon.colorId, _icon.title, st);
|
||||
}
|
||||
const auto esize = Ui::Emoji::GetSizeLarge() / style::DevicePixelRatio();
|
||||
const auto full = (_tag == Data::CustomEmojiSizeTag::Normal)
|
||||
? Ui::Emoji::GetSizeNormal()
|
||||
: Ui::Emoji::GetSizeLarge();
|
||||
const auto esize = full / style::DevicePixelRatio();
|
||||
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
|
||||
const auto skip = (customSize - st::defaultForumTopicIcon.size) / 2;
|
||||
const auto skip = (customSize - st.size) / 2;
|
||||
p.drawImage(context.position + QPoint(skip, skip), _image);
|
||||
}
|
||||
|
||||
@@ -212,7 +220,7 @@ bool DefaultIconEmoji::readyInDefaultState() {
|
||||
) | rpl::start_with_next([=] {
|
||||
state->frame = Data::ForumTopicGeneralIconFrame(
|
||||
st::largeForumTopicIcon.size,
|
||||
st::windowSubTextFg);
|
||||
st::windowSubTextFg->c);
|
||||
result->update();
|
||||
}, result->lifetime());
|
||||
|
||||
@@ -261,7 +269,8 @@ struct IconSelector {
|
||||
if (id == kDefaultIconId) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
rpl::duplicate(defaultIcon),
|
||||
repaint);
|
||||
std::move(repaint),
|
||||
tag);
|
||||
}
|
||||
return manager->create(id, std::move(repaint), tag);
|
||||
};
|
||||
@@ -572,3 +581,13 @@ void EditForumTopicBox(
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
|
||||
Data::TopicIconDescriptor descriptor,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
rpl::single(descriptor),
|
||||
std::move(repaint),
|
||||
tag);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
struct TopicIconDescriptor;
|
||||
enum class CustomEmojiSizeTag : uchar;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
@@ -25,3 +30,8 @@ void EditForumTopicBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<History*> forum,
|
||||
MsgId rootId);
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> MakeTopicIconEmoji(
|
||||
Data::TopicIconDescriptor descriptor,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag);
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/toast/toast.h"
|
||||
@@ -71,6 +75,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kBotManagerUsername = "BotFather"_cs;
|
||||
@@ -343,6 +349,7 @@ private:
|
||||
void fillPendingRequestsButton();
|
||||
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotBalanceButton();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
@@ -1126,6 +1133,7 @@ void Controller::fillManageSection() {
|
||||
|
||||
::AddSkip(container, 0);
|
||||
fillBotUsernamesButton();
|
||||
fillBotBalanceButton();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
@@ -1536,6 +1544,84 @@ void Controller::fillBotUsernamesButton() {
|
||||
{ &st::menuIconLinks });
|
||||
}
|
||||
|
||||
void Controller::fillBotBalanceButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
rpl::variable<QString> balance;
|
||||
};
|
||||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::BotEarn::Make(peer));
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
const auto api = button->lifetime().make_state<Api::CreditsStatus>(
|
||||
_peer);
|
||||
api->request({}, [=](Data::CreditsStatusSlice data) {
|
||||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = QString::number(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
constexpr auto kSizeShift = 3;
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(
|
||||
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
|
||||
colorized.toUtf8());
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(icon->height());
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
svg->render(&p, Rect(starSize));
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft + kSizeShift / 2.,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Controller::fillBotEditIntroButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
@@ -2114,8 +2200,11 @@ void Controller::saveForum() {
|
||||
channel->inputChannel,
|
||||
MTP_bool(*_savingData.forum)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
const auto weak = base::make_weak(this);
|
||||
channel->session().api().applyUpdates(result);
|
||||
continueSave();
|
||||
if (weak) { // todo better to be able to save in closed already box.
|
||||
continueSave();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -351,8 +352,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
const auto customEmojiPaused = [controller = args.controller] {
|
||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||
};
|
||||
raw->setCustomEmojiFactory([=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
auto factory = [=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto id = Data::ParseCustomEmojiData(data);
|
||||
auto result = owner->customEmojiManager().create(
|
||||
data,
|
||||
@@ -364,7 +365,13 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
}
|
||||
using namespace Ui::Text;
|
||||
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
||||
}, std::move(customEmojiPaused));
|
||||
};
|
||||
raw->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, customEmojiPaused, customEmojiPaused, std::move(factory));
|
||||
|
||||
const auto callback = args.callback;
|
||||
const auto isCustom = [=](DocumentId id) {
|
||||
|
||||
@@ -398,7 +398,7 @@ void PeerShortInfoCover::paintRadial(QPainter &p) {
|
||||
QImage PeerShortInfoCover::currentVideoFrame() const {
|
||||
const auto size = QSize(_st.size, _st.size);
|
||||
const auto request = Media::Streaming::FrameRequest{
|
||||
.resize = size * style::DevicePixelRatio(),
|
||||
.resize = size,
|
||||
.outer = size,
|
||||
};
|
||||
return (_videoInstance
|
||||
|
||||
@@ -459,6 +459,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
|
||||
.customWallpaperLevel = group
|
||||
? levelLimits.groupCustomWallpaperLevelMin()
|
||||
: levelLimits.channelCustomWallpaperLevelMin(),
|
||||
.sponsoredLevel = levelLimits.channelRestrictSponsoredLevelMin(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -418,7 +418,9 @@ void SimpleLimitBox(
|
||||
BoxShowFinishes(box),
|
||||
0,
|
||||
descriptor.current,
|
||||
descriptor.premiumLimit,
|
||||
(descriptor.complexRatio
|
||||
? descriptor.premiumLimit
|
||||
: 2 * descriptor.current),
|
||||
premiumPossible,
|
||||
descriptor.phrase,
|
||||
descriptor.icon);
|
||||
@@ -769,7 +771,7 @@ void FilterLinksLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconChats,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true */}); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
|
||||
@@ -856,7 +858,7 @@ void ShareableFiltersLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconFolders,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true*/ }); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
void FilterPinsLimitBox(
|
||||
|
||||
@@ -131,6 +131,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_translation();
|
||||
case PremiumFeature::Business:
|
||||
return tr::lng_premium_summary_subtitle_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_subtitle_effects();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_subtitle_location();
|
||||
@@ -192,6 +194,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_translation();
|
||||
case PremiumFeature::Business:
|
||||
return tr::lng_premium_summary_about_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_about_effects();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_about_location();
|
||||
@@ -286,7 +290,7 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
document,
|
||||
media->videoThumbnailContent(),
|
||||
QString(),
|
||||
true);
|
||||
Stickers::EffectType::PremiumSticker);
|
||||
|
||||
const auto update = [=] {
|
||||
if (!state->readyInvoked
|
||||
@@ -529,6 +533,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::Wallpapers: return "wallpapers";
|
||||
case PremiumFeature::LastSeen: return "last_seen";
|
||||
case PremiumFeature::MessagePrivacy: return "message_privacy";
|
||||
case PremiumFeature::Effects: return "effects";
|
||||
|
||||
case PremiumFeature::BusinessLocation: return "business_location";
|
||||
case PremiumFeature::BusinessHours: return "business_hours";
|
||||
|
||||
@@ -70,6 +70,7 @@ enum class PremiumFeature {
|
||||
LastSeen,
|
||||
MessagePrivacy,
|
||||
Business,
|
||||
Effects,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/admin_log/history_admin_log_item.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/reactions/history_view_reactions_strip.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
#include "history/view/history_view_fake_items.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -43,53 +44,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
const auto peerId = Data::FakePeerIdForJustName(name);
|
||||
history->owner().processUser(MTP_user(
|
||||
MTP_flags(MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_min),
|
||||
peerToBareMTPInt(peerId),
|
||||
MTP_long(0),
|
||||
MTP_string(tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||
MTPstring(), // last name
|
||||
MTPstring(), // username
|
||||
MTPstring(), // phone
|
||||
MTPUserProfilePhoto(), // profile photo
|
||||
MTPUserStatus(), // status
|
||||
MTP_int(0), // bot info version
|
||||
MTPVector<MTPRestrictionReason>(), // restrictions
|
||||
MTPstring(), // bot placeholder
|
||||
MTPstring(), // lang code
|
||||
MTPEmojiStatus(),
|
||||
MTPVector<MTPUsername>(),
|
||||
MTPint(), // stories_max_id
|
||||
MTPPeerColor(), // color
|
||||
MTPPeerColor())); // profile_color
|
||||
return peerId;
|
||||
}
|
||||
|
||||
AdminLog::OwnedItem GenerateItem(
|
||||
not_null<HistoryView::ElementDelegate*> delegate,
|
||||
not_null<History*> history,
|
||||
PeerId from,
|
||||
FullMsgId replyTo,
|
||||
const QString &text) {
|
||||
Expects(history->peer->isUser());
|
||||
|
||||
const auto item = history->addNewLocalMessage({
|
||||
.id = history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::HasFromId
|
||||
| MessageFlag::HasReplyInfo),
|
||||
.from = from,
|
||||
.replyTo = FullReplyTo{ .messageId = replyTo },
|
||||
.date = base::unixtime::now(),
|
||||
}, TextWithEntities{ .text = text }, MTP_messageMediaEmpty());
|
||||
|
||||
return AdminLog::OwnedItem(delegate, item);
|
||||
}
|
||||
|
||||
void AddMessage(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -135,15 +89,15 @@ void AddMessage(
|
||||
|
||||
const auto history = controller->session().data().history(
|
||||
PeerData::kServiceNotificationsId);
|
||||
state->reply = GenerateItem(
|
||||
state->reply = HistoryView::GenerateItem(
|
||||
state->delegate.get(),
|
||||
history,
|
||||
GenerateUser(
|
||||
HistoryView::GenerateUser(
|
||||
history,
|
||||
tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||
FullMsgId(),
|
||||
tr::lng_settings_chat_message_reply(tr::now));
|
||||
auto message = GenerateItem(
|
||||
auto message = HistoryView::GenerateItem(
|
||||
state->delegate.get(),
|
||||
history,
|
||||
history->peer->id,
|
||||
|
||||
403
Telegram/SourceFiles/boxes/send_credits_box.cpp
Normal file
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/send_credits_box.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "payments/payments_checkout_process.h"
|
||||
#include "payments/payments_form.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
struct PaidMediaData {
|
||||
const Data::Invoice *invoice = nullptr;
|
||||
HistoryItem *item = nullptr;
|
||||
PeerData *peer = nullptr;
|
||||
int photos = 0;
|
||||
int videos = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return invoice && item && peer && (photos || videos);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] PaidMediaData LookupPaidMediaData(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form) {
|
||||
using namespace Payments;
|
||||
const auto message = std::get_if<InvoiceMessage>(&form->id.value);
|
||||
const auto item = message
|
||||
? session->data().message(message->peer, message->itemId)
|
||||
: nullptr;
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto invoice = media ? media->invoice() : nullptr;
|
||||
if (!invoice || !invoice->isPaidMedia) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto photos = 0;
|
||||
auto videos = 0;
|
||||
for (const auto &media : invoice->extendedMedia) {
|
||||
const auto photo = media->photo();
|
||||
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
|
||||
++photos;
|
||||
} else {
|
||||
++videos;
|
||||
}
|
||||
}
|
||||
|
||||
const auto sender = item->originalSender();
|
||||
const auto broadcast = (sender && sender->isBroadcast())
|
||||
? sender
|
||||
: message->peer.get();
|
||||
return {
|
||||
.invoice = invoice,
|
||||
.item = item,
|
||||
.peer = broadcast,
|
||||
.photos = photos,
|
||||
.videos = videos,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form) {
|
||||
if (const auto data = LookupPaidMediaData(session, form)) {
|
||||
auto photos = 0;
|
||||
auto videos = 0;
|
||||
for (const auto &media : data.invoice->extendedMedia) {
|
||||
const auto photo = media->photo();
|
||||
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
|
||||
++photos;
|
||||
} else {
|
||||
++videos;
|
||||
}
|
||||
}
|
||||
|
||||
auto photosBold = tr::lng_credits_box_out_photos(
|
||||
lt_count,
|
||||
rpl::single(photos) | tr::to_count(),
|
||||
Ui::Text::Bold);
|
||||
auto videosBold = tr::lng_credits_box_out_videos(
|
||||
lt_count,
|
||||
rpl::single(videos) | tr::to_count(),
|
||||
Ui::Text::Bold);
|
||||
auto media = (!videos)
|
||||
? ((photos > 1)
|
||||
? std::move(photosBold)
|
||||
: tr::lng_credits_box_out_photo(Ui::Text::WithEntities))
|
||||
: (!photos)
|
||||
? ((videos > 1)
|
||||
? std::move(videosBold)
|
||||
: tr::lng_credits_box_out_video(Ui::Text::WithEntities))
|
||||
: tr::lng_credits_box_out_both(
|
||||
lt_photo,
|
||||
std::move(photosBold),
|
||||
lt_video,
|
||||
std::move(videosBold),
|
||||
Ui::Text::WithEntities);
|
||||
return tr::lng_credits_box_out_media(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_media,
|
||||
std::move(media),
|
||||
lt_chat,
|
||||
rpl::single(Ui::Text::Bold(data.peer->name())),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
|
||||
const auto bot = session->data().user(form->botId);
|
||||
return tr::lng_credits_box_out_sure(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_text,
|
||||
rpl::single(TextWithEntities{ form->title }),
|
||||
lt_bot,
|
||||
rpl::single(TextWithEntities{ bot->name() }),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> SendCreditsThumbnail(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form,
|
||||
int photoSize) {
|
||||
if (const auto data = LookupPaidMediaData(session, form)) {
|
||||
const auto first = data.invoice->extendedMedia[0]->photo();
|
||||
const auto second = (data.photos > 1)
|
||||
? data.invoice->extendedMedia[1]->photo()
|
||||
: nullptr;
|
||||
const auto totalCount = int(data.invoice->extendedMedia.size());
|
||||
if (first && first->extendedMediaPreview()) {
|
||||
return Settings::PaidMediaThumbnail(
|
||||
parent,
|
||||
first,
|
||||
second,
|
||||
totalCount,
|
||||
photoSize);
|
||||
}
|
||||
}
|
||||
if (form->photo) {
|
||||
return Settings::HistoryEntryPhoto(parent, form->photo, photoSize);
|
||||
}
|
||||
const auto bot = session->data().user(form->botId);
|
||||
return object_ptr<Ui::UserpicButton>(
|
||||
parent,
|
||||
bot,
|
||||
st::defaultUserpicButton);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Payments::CreditsFormData> form,
|
||||
Fn<void()> sent) {
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
struct State {
|
||||
rpl::variable<bool> confirmButtonBusy = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto session = form->invoice.session;
|
||||
|
||||
const auto photoSize = st::defaultUserpicButton.photoSize;
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
Ui::AddSkip(content, photoSize / 2);
|
||||
|
||||
{
|
||||
const auto ministarsContainer = Ui::CreateChild<Ui::RpWidget>(box);
|
||||
const auto fullHeight = photoSize * 2;
|
||||
using MiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto ministars = box->lifetime().make_state<MiniStars>(
|
||||
ministarsContainer,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
ministars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
|
||||
ministarsContainer->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(ministarsContainer);
|
||||
ministars->paint(p);
|
||||
}, ministarsContainer->lifetime());
|
||||
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
ministarsContainer->resize(width, fullHeight);
|
||||
const auto w = fullHeight / 3 * 2;
|
||||
ministars->setCenter(QRect(
|
||||
(width - w) / 2,
|
||||
(fullHeight - w) / 2,
|
||||
w,
|
||||
w));
|
||||
}, ministarsContainer->lifetime());
|
||||
}
|
||||
|
||||
const auto thumb = box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
SendCreditsThumbnail(content, session, form.get(), photoSize)));
|
||||
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_title(),
|
||||
st::settingsPremiumUserTitle)));
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
SendCreditsConfirmText(session, form.get()),
|
||||
st::creditsBoxAbout)));
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
const auto show = box->uiShow();
|
||||
const auto weak = MakeWeak(box.get());
|
||||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
}
|
||||
sent();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
}
|
||||
const auto id = error.type();
|
||||
if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
|
||||
auto error = ::Ui::MakeInformBox(
|
||||
tr::lng_payments_precheckout_stars_failed(tr::now));
|
||||
error->boxClosing() | rpl::start_with_next([=] {
|
||||
if (const auto paybox = weak.data()) {
|
||||
paybox->closeBox();
|
||||
}
|
||||
}, error->lifetime());
|
||||
show->showBox(std::move(error));
|
||||
} else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
|
||||
show->showToast(
|
||||
tr::lng_payments_precheckout_stars_timeout(tr::now));
|
||||
} else {
|
||||
show->showToast(id);
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
{
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
button,
|
||||
st::giveawayGiftCodeStartButton.height / 2);
|
||||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
{
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st::creditsBoxButtonLabel);
|
||||
std::move(
|
||||
buttonText
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { buttonLabel->update(); },
|
||||
});
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setTextColorOverride(
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->confirmButtonBusy.value(
|
||||
) | rpl::start_with_next([=](bool busy) {
|
||||
buttonLabel->setVisible(!busy);
|
||||
}, buttonLabel->lifetime());
|
||||
}
|
||||
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
|
||||
button->widthValue() | rpl::filter([=] {
|
||||
return (button->widthNoMargins() != buttonWidth);
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->resizeToWidth(buttonWidth);
|
||||
}, button->lifetime());
|
||||
|
||||
{
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
box.get(),
|
||||
st::boxTitleClose);
|
||||
close->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0);
|
||||
close->raise();
|
||||
}, close->lifetime());
|
||||
}
|
||||
|
||||
{
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
content,
|
||||
session->creditsValue(),
|
||||
false);
|
||||
const auto api = balance->lifetime().make_state<Api::CreditsStatus>(
|
||||
session->user());
|
||||
api->request({}, [=](Data::CreditsStatusSlice slice) {
|
||||
session->setCredits(slice.balance);
|
||||
});
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
content->sizeValue()
|
||||
) | rpl::start_with_next([=](const QSize &, const QSize &) {
|
||||
balance->moveToLeft(
|
||||
st::creditsHistoryRightSkip * 2,
|
||||
st::creditsHistoryRightSkip);
|
||||
balance->update();
|
||||
}, balance->lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::settingsPremiumIconStar,
|
||||
QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 },
|
||||
true),
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::starIconSmall,
|
||||
st::starIconSmallPadding,
|
||||
true),
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
35
Telegram/SourceFiles/boxes/send_credits_box.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Payments {
|
||||
struct CreditsFormData;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void()> sent);
|
||||
|
||||
[[nodiscard]] TextWithEntities CreditsEmoji(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
@@ -24,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/controls/history_view_characters_limit.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "ui/effects/scroll_content_shadow.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -36,10 +41,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/attach/attach_single_file_preview.h"
|
||||
#include "ui/chat/attach/attach_single_media_preview.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||
@@ -103,6 +111,84 @@ rpl::producer<QString> FieldPlaceholder(
|
||||
: tr::lng_photos_comment();
|
||||
}
|
||||
|
||||
void EditPriceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
uint64 price,
|
||||
Fn<void(uint64)> apply) {
|
||||
box->setTitle(tr::lng_paid_title());
|
||||
AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_paid_enter_cost(),
|
||||
(st::boxRowPadding - QMargins(
|
||||
st::defaultSubsectionTitlePadding.left(),
|
||||
0,
|
||||
st::defaultSubsectionTitlePadding.right(),
|
||||
0)));
|
||||
const auto limit = session->appConfig().get<int>(
|
||||
u"stars_paid_post_amount_max"_q,
|
||||
10'000);
|
||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::editTagField.heightMin));
|
||||
auto owned = object_ptr<Ui::NumberInput>(
|
||||
wrap,
|
||||
st::editTagField,
|
||||
tr::lng_paid_cost_placeholder(),
|
||||
price ? QString::number(price) : QString(),
|
||||
limit);
|
||||
const auto field = owned.data();
|
||||
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
field->move(0, 0);
|
||||
field->resize(width, field->height());
|
||||
wrap->resize(width, field->height());
|
||||
}, wrap->lifetime());
|
||||
field->selectAll();
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
});
|
||||
const auto about = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_paid_about(
|
||||
lt_link,
|
||||
tr::lng_paid_about_link() | Ui::Text::ToLink(),
|
||||
Ui::Text::WithEntities),
|
||||
st::paidAmountAbout),
|
||||
st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
Core::App().iv().openWithIvPreferred(
|
||||
session,
|
||||
tr::lng_paid_about_link_url(tr::now));
|
||||
return false;
|
||||
});
|
||||
|
||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(field);
|
||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||
}, field->lifetime());
|
||||
|
||||
const auto save = [=] {
|
||||
const auto now = field->getLastText().toULongLong();
|
||||
if (now > limit) {
|
||||
field->showError();
|
||||
return;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
apply(now);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
|
||||
QObject::connect(field, &Ui::NumberInput::submitted, box, save);
|
||||
|
||||
box->addButton(tr::lng_settings_save(), save);
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
|
||||
@@ -153,7 +239,8 @@ SendFilesBox::Block::Block(
|
||||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way)
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
@@ -170,14 +257,16 @@ SendFilesBox::Block::Block(
|
||||
parent.get(),
|
||||
st,
|
||||
my,
|
||||
way);
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
parent,
|
||||
st,
|
||||
gifPaused,
|
||||
first);
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
@@ -253,6 +342,14 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
return album->orderUpdated();
|
||||
}
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
|
||||
if (!_isAlbum) {
|
||||
if (_isSingleMedia) {
|
||||
@@ -321,6 +418,18 @@ void SendFilesBox::Block::applyChanges() {
|
||||
}
|
||||
}
|
||||
|
||||
QImage SendFilesBox::Block::generatePriceTagBackground() const {
|
||||
const auto preview = _preview.get();
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->generatePriceTagBackground();
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->generatePriceTagBackground();
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
SendFilesBox::SendFilesBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -328,7 +437,7 @@ SendFilesBox::SendFilesBox(
|
||||
const TextWithTags &caption,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType)
|
||||
SendMenu::Details sendMenuDetails)
|
||||
: SendFilesBox(nullptr, {
|
||||
.show = controller->uiShow(),
|
||||
.list = std::move(list),
|
||||
@@ -337,7 +446,7 @@ SendFilesBox::SendFilesBox(
|
||||
.limits = DefaultLimitsForPeer(toPeer),
|
||||
.check = DefaultCheckForPeer(controller, toPeer),
|
||||
.sendType = sendType,
|
||||
.sendMenuType = sendMenuType,
|
||||
.sendMenuDetails = [=] { return sendMenuDetails; },
|
||||
}) {
|
||||
}
|
||||
|
||||
@@ -350,7 +459,8 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
|
||||
, _titleHeight(st::boxTitleHeight)
|
||||
, _list(std::move(descriptor.list))
|
||||
, _limits(descriptor.limits)
|
||||
, _sendMenuType(descriptor.sendMenuType)
|
||||
, _sendMenuDetails(prepareSendMenuDetails(descriptor))
|
||||
, _sendMenuCallback(prepareSendMenuCallback())
|
||||
, _captionToPeer(descriptor.captionToPeer)
|
||||
, _check(std::move(descriptor.check))
|
||||
, _confirmedCallback(std::move(descriptor.confirmed))
|
||||
@@ -364,6 +474,54 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
|
||||
enqueueNextPrepare();
|
||||
}
|
||||
|
||||
Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor) {
|
||||
auto initial = descriptor.sendMenuDetails;
|
||||
return crl::guard(this, [=] {
|
||||
auto result = initial ? initial() : SendMenu::Details();
|
||||
result.spoiler = !hasSpoilerMenu()
|
||||
? SendMenu::SpoilerState::None
|
||||
: allWithSpoilers()
|
||||
? SendMenu::SpoilerState::Enabled
|
||||
: SendMenu::SpoilerState::Possible;
|
||||
const auto way = _sendWay.current();
|
||||
const auto canMoveCaption = _list.canMoveCaption(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos()
|
||||
) && _caption && HasSendText(_caption);
|
||||
result.caption = !canMoveCaption
|
||||
? SendMenu::CaptionState::None
|
||||
: _invertCaption
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
result.price = canChangePrice()
|
||||
? _price.current()
|
||||
: std::optional<uint64>();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
auto SendFilesBox::prepareSendMenuCallback()
|
||||
-> Fn<void(MenuAction, MenuDetails)> {
|
||||
return crl::guard(this, [=](MenuAction action, MenuDetails details) {
|
||||
using Type = SendMenu::ActionType;
|
||||
switch (action.type) {
|
||||
case Type::CaptionDown: _invertCaption = false; break;
|
||||
case Type::CaptionUp: _invertCaption = true; break;
|
||||
case Type::SpoilerOn: toggleSpoilers(true); break;
|
||||
case Type::SpoilerOff: toggleSpoilers(false); break;
|
||||
case Type::ChangePrice: changePrice(); break;
|
||||
default:
|
||||
SendMenu::DefaultCallback(
|
||||
_show,
|
||||
sendCallback())(
|
||||
action,
|
||||
details);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::initPreview() {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
@@ -529,10 +687,9 @@ void SendFilesBox::refreshButtons() {
|
||||
if (_sendType == Api::SendType::Normal) {
|
||||
SendMenu::SetupMenuAndShortcuts(
|
||||
_send,
|
||||
[=] { return _sendMenuType; },
|
||||
[=] { sendSilent(); },
|
||||
[=] { sendScheduled(); },
|
||||
[=] { sendWhenOnline(); });
|
||||
_show,
|
||||
_sendMenuDetails,
|
||||
_sendMenuCallback);
|
||||
}
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
_addFile = addLeftButton(
|
||||
@@ -544,21 +701,27 @@ void SendFilesBox::refreshButtons() {
|
||||
addMenuButton();
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSendMenu() const {
|
||||
return (_sendMenuType != SendMenu::Type::Disabled);
|
||||
bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
|
||||
return (details.type != SendMenu::Type::Disabled)
|
||||
|| (details.spoiler != SendMenu::SpoilerState::None)
|
||||
|| (details.caption != SendMenu::CaptionState::None);
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSpoilerMenu() const {
|
||||
const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) {
|
||||
using Type = Ui::PreparedFile::Type;
|
||||
return (f.type != Type::Video);
|
||||
});
|
||||
const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) {
|
||||
using Type = Ui::PreparedFile::Type;
|
||||
return (f.type != Type::Photo) && (f.type != Type::Video);
|
||||
});
|
||||
return allAreVideo
|
||||
|| (allAreMedia && _sendWay.current().sendImagesAsPhotos());
|
||||
return !hasPrice()
|
||||
&& _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
bool SendFilesBox::canChangePrice() const {
|
||||
const auto way = _sendWay.current();
|
||||
const auto broadcast = _captionToPeer
|
||||
? _captionToPeer->asBroadcast()
|
||||
: nullptr;
|
||||
return broadcast
|
||||
&& broadcast->canPostPaidMedia()
|
||||
&& _list.canChangePrice(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
void SendFilesBox::applyBlockChanges() {
|
||||
@@ -581,41 +744,139 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::changePrice() {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto session = &_show->session();
|
||||
const auto now = _price.current();
|
||||
_show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
|
||||
if (weak && price != now) {
|
||||
_price = price;
|
||||
refreshPriceTag();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasPrice() const {
|
||||
return canChangePrice() && _price.current() > 0;
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshPriceTag() {
|
||||
const auto resetSpoilers = hasPrice() || _priceTag;
|
||||
if (resetSpoilers) {
|
||||
for (auto &file : _list.files) {
|
||||
file.spoiler = false;
|
||||
}
|
||||
for (auto &block : _blocks) {
|
||||
block.toggleSpoilers(hasPrice());
|
||||
}
|
||||
}
|
||||
if (!hasPrice()) {
|
||||
_priceTag = nullptr;
|
||||
_priceTagBg = QImage();
|
||||
} else if (!_priceTag) {
|
||||
_priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
|
||||
const auto raw = _priceTag.get();
|
||||
|
||||
raw->show();
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
if (_priceTagBg.isNull()) {
|
||||
_priceTagBg = preparePriceTagBg(raw->size());
|
||||
}
|
||||
QPainter(raw).drawImage(0, 0, _priceTagBg);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &_show->session();
|
||||
auto price = _price.value() | rpl::map([=](uint64 amount) {
|
||||
auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session));
|
||||
result.append(Lang::FormatCountDecimal(amount));
|
||||
return result;
|
||||
});
|
||||
auto text = tr::lng_paid_price(
|
||||
lt_price,
|
||||
std::move(price),
|
||||
Ui::Text::WithEntities);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString(),
|
||||
st::paidTagLabel);
|
||||
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
||||
label->setMarkedText(text, Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
}, label->lifetime());
|
||||
label->show();
|
||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
const auto inner = QRect(QPoint(), size);
|
||||
const auto rect = inner.marginsAdded(st::paidTagPadding);
|
||||
raw->resize(rect.size());
|
||||
label->move(-rect.topLeft());
|
||||
}, label->lifetime());
|
||||
_inner->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
raw->move(
|
||||
(size.width() - raw->width()) / 2,
|
||||
(size.height() - raw->height()) / 2);
|
||||
}, raw->lifetime());
|
||||
} else {
|
||||
_priceTag->raise();
|
||||
_priceTag->update();
|
||||
_priceTagBg = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
QImage SendFilesBox::preparePriceTagBg(QSize size) const {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto outer = _blocks.empty()
|
||||
? size
|
||||
: _inner->widgetAt(0)->geometry().size();
|
||||
auto bg = _blocks.empty()
|
||||
? QImage()
|
||||
: _blocks.front().generatePriceTagBackground();
|
||||
if (bg.isNull()) {
|
||||
bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
bg.fill(Qt::black);
|
||||
}
|
||||
|
||||
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::black);
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawImage(
|
||||
QRect(
|
||||
(size.width() - outer.width()) / 2,
|
||||
(size.height() - outer.height()) / 2,
|
||||
outer.width(),
|
||||
outer.height()),
|
||||
bg);
|
||||
p.fillRect(QRect(QPoint(), size), st::msgDateImgBg);
|
||||
p.end();
|
||||
|
||||
const auto radius = std::min(size.width(), size.height()) / 2;
|
||||
return Images::Round(std::move(result), Images::CornersMask(radius));
|
||||
}
|
||||
|
||||
void SendFilesBox::addMenuButton() {
|
||||
if (!hasSendMenu() && !hasSpoilerMenu()) {
|
||||
const auto details = _sendMenuDetails();
|
||||
if (!hasSendMenu(details)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto top = addTopButton(_st.files.menu);
|
||||
top->setClickedCallback([=] {
|
||||
const auto &tabbed = _st.tabbed;
|
||||
const auto &icons = tabbed.icons;
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(top, tabbed.menu);
|
||||
if (hasSpoilerMenu()) {
|
||||
const auto spoilered = allWithSpoilers();
|
||||
_menu->addAction(
|
||||
(spoilered
|
||||
? tr::lng_context_disable_spoiler(tr::now)
|
||||
: tr::lng_context_spoiler_effect(tr::now)),
|
||||
[=] { toggleSpoilers(!spoilered); },
|
||||
spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
|
||||
if (hasSendMenu()) {
|
||||
_menu->addSeparator(&tabbed.expandedSeparator);
|
||||
}
|
||||
}
|
||||
if (hasSendMenu()) {
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_sendMenuType,
|
||||
[=] { sendSilent(); },
|
||||
[=] { sendScheduled(); },
|
||||
[=] { sendWhenOnline(); },
|
||||
&_st.tabbed.icons);
|
||||
}
|
||||
_menu->popup(QCursor::pos());
|
||||
const auto position = QCursor::pos();
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_show,
|
||||
_sendMenuDetails(),
|
||||
_sendMenuCallback,
|
||||
&_st.tabbed.icons,
|
||||
position);
|
||||
_menu->popup(position);
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SendFilesBox::initSendWay() {
|
||||
@@ -657,9 +918,8 @@ void SendFilesBox::initSendWay() {
|
||||
for (auto &block : _blocks) {
|
||||
block.setSendWay(value);
|
||||
}
|
||||
if (!hasSendMenu()) {
|
||||
refreshButtons();
|
||||
}
|
||||
refreshButtons();
|
||||
refreshPriceTag();
|
||||
if (was != hidden()) {
|
||||
updateBoxSize();
|
||||
updateControlsGeometry();
|
||||
@@ -745,7 +1005,8 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
from,
|
||||
till,
|
||||
gifPaused,
|
||||
_sendWay.current());
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
@@ -868,12 +1129,18 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
if (_priceTag) {
|
||||
_priceTagBg = QImage();
|
||||
_priceTag->update();
|
||||
}
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshControls(bool initial) {
|
||||
if (initial || !hasSendMenu()) {
|
||||
refreshButtons();
|
||||
}
|
||||
refreshButtons();
|
||||
refreshPriceTag();
|
||||
refreshTitleText();
|
||||
updateSendWayControls();
|
||||
updateCaptionPlaceholder();
|
||||
@@ -1425,7 +1692,13 @@ void SendFilesBox::send(
|
||||
if ((_sendType == Api::SendType::Scheduled
|
||||
|| _sendType == Api::SendType::ScheduledToUser)
|
||||
&& !options.scheduled) {
|
||||
return sendScheduled();
|
||||
auto child = _sendMenuDetails();
|
||||
child.spoiler = SendMenu::SpoilerState::None;
|
||||
child.caption = SendMenu::CaptionState::None;
|
||||
child.price = std::nullopt;
|
||||
return SendMenu::DefaultCallback(_show, sendCallback())(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
child);
|
||||
}
|
||||
if (_preparing) {
|
||||
_whenReadySend = [=] {
|
||||
@@ -1453,6 +1726,13 @@ void SendFilesBox::send(
|
||||
if (!validateLength(caption.text)) {
|
||||
return;
|
||||
}
|
||||
options.invertCaption = _invertCaption;
|
||||
options.price = hasPrice() ? _price.current() : 0;
|
||||
if (options.price > 0) {
|
||||
for (auto &file : _list.files) {
|
||||
file.spoiler = false;
|
||||
}
|
||||
}
|
||||
_confirmedCallback(
|
||||
std::move(_list),
|
||||
_sendWay.current(),
|
||||
@@ -1463,25 +1743,10 @@ void SendFilesBox::send(
|
||||
closeBox();
|
||||
}
|
||||
|
||||
void SendFilesBox::sendSilent() {
|
||||
send({ .silent = true });
|
||||
}
|
||||
|
||||
void SendFilesBox::sendScheduled() {
|
||||
const auto type = (_sendType == Api::SendType::ScheduledToUser)
|
||||
? SendMenu::Type::ScheduledToUser
|
||||
: _sendMenuType;
|
||||
const auto callback = [=](Api::SendOptions options) { send(options); };
|
||||
auto box = HistoryView::PrepareScheduleBox(this, type, callback);
|
||||
const auto weak = Ui::MakeWeak(box.data());
|
||||
_show->showBox(std::move(box));
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->setCloseByOutsideClick(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::sendWhenOnline() {
|
||||
send(Api::DefaultSendWhenOnlineOptions());
|
||||
Fn<void(Api::SendOptions)> SendFilesBox::sendCallback() {
|
||||
return crl::guard(this, [=](Api::SendOptions options) {
|
||||
send(options, false);
|
||||
});
|
||||
}
|
||||
|
||||
SendFilesBox::~SendFilesBox() = default;
|
||||
|
||||
@@ -47,7 +47,8 @@ class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
struct Action;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace HistoryView::Controls {
|
||||
@@ -96,7 +97,7 @@ struct SendFilesBoxDescriptor {
|
||||
SendFilesLimits limits = {};
|
||||
SendFilesCheck check;
|
||||
Api::SendType sendType = {};
|
||||
SendMenu::Type sendMenuType = {};
|
||||
Fn<SendMenu::Details()> sendMenuDetails = nullptr;
|
||||
const style::ComposeControls *stOverride = nullptr;
|
||||
SendFilesConfirmed confirmed;
|
||||
Fn<void()> cancelled;
|
||||
@@ -115,7 +116,7 @@ public:
|
||||
const TextWithTags &caption,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType);
|
||||
SendMenu::Details sendMenuDetails);
|
||||
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
|
||||
|
||||
void setConfirmedCallback(SendFilesConfirmed callback) {
|
||||
@@ -136,6 +137,9 @@ protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
using MenuAction = SendMenu::Action;
|
||||
using MenuDetails = SendMenu::Details;
|
||||
|
||||
class Block final {
|
||||
public:
|
||||
Block(
|
||||
@@ -145,7 +149,8 @@ private:
|
||||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way);
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
@@ -156,11 +161,14 @@ private:
|
||||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
void toggleSpoilers(bool enabled);
|
||||
void applyChanges();
|
||||
|
||||
[[nodiscard]] QImage generatePriceTagBackground() const;
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::RpWidget> _preview;
|
||||
not_null<std::vector<Ui::PreparedFile>*> _items;
|
||||
@@ -173,7 +181,7 @@ private:
|
||||
|
||||
void initSendWay();
|
||||
void initPreview();
|
||||
[[nodiscard]] bool hasSendMenu() const;
|
||||
[[nodiscard]] bool hasSendMenu(const MenuDetails &details) const;
|
||||
[[nodiscard]] bool hasSpoilerMenu() const;
|
||||
[[nodiscard]] bool allWithSpoilers();
|
||||
[[nodiscard]] bool checkWithWay(
|
||||
@@ -186,6 +194,12 @@ private:
|
||||
void addMenuButton();
|
||||
void applyBlockChanges();
|
||||
void toggleSpoilers(bool enabled);
|
||||
void changePrice();
|
||||
|
||||
[[nodiscard]] bool canChangePrice() const;
|
||||
[[nodiscard]] bool hasPrice() const;
|
||||
void refreshPriceTag();
|
||||
[[nodiscard]] QImage preparePriceTagBg(QSize size) const;
|
||||
|
||||
bool validateLength(const QString &text) const;
|
||||
void refreshButtons();
|
||||
@@ -202,9 +216,7 @@ private:
|
||||
void generatePreviewFrom(int fromBlock);
|
||||
|
||||
void send(Api::SendOptions options, bool ctrlShiftEnter = false);
|
||||
void sendSilent();
|
||||
void sendScheduled();
|
||||
void sendWhenOnline();
|
||||
[[nodiscard]] Fn<void(Api::SendOptions)> sendCallback();
|
||||
void captionResized();
|
||||
void saveSendWaySettings();
|
||||
|
||||
@@ -227,6 +239,11 @@ private:
|
||||
|
||||
void checkCharsLimitation();
|
||||
|
||||
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
|
||||
const SendFilesBoxDescriptor &descriptor);
|
||||
[[nodiscard]] auto prepareSendMenuCallback()
|
||||
-> Fn<void(MenuAction, MenuDetails)>;
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const style::ComposeControls &_st;
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
@@ -238,12 +255,17 @@ private:
|
||||
std::optional<int> _removingIndex;
|
||||
|
||||
SendFilesLimits _limits = {};
|
||||
SendMenu::Type _sendMenuType = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
|
||||
PeerData *_captionToPeer = nullptr;
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
Fn<void()> _cancelledCallback;
|
||||
rpl::variable<uint64> _price = 0;
|
||||
std::unique_ptr<Ui::RpWidget> _priceTag;
|
||||
QImage _priceTagBg;
|
||||
bool _confirmed = false;
|
||||
bool _invertCaption = false;
|
||||
|
||||
object_ptr<Ui::InputField> _caption = { nullptr };
|
||||
TextWithTags _prefilledCaptionText;
|
||||
|
||||
@@ -473,15 +473,18 @@ void ShareBox::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
SendMenu::Type ShareBox::sendMenuType() const {
|
||||
SendMenu::Details ShareBox::sendMenuDetails() const {
|
||||
const auto selected = _inner->selected();
|
||||
return ranges::all_of(
|
||||
const auto type = 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
|
||||
: SendMenu::Type::Scheduled;
|
||||
|
||||
// We can't support effect here because we don't have ChatHelpers::Show.
|
||||
return { .type = type, .effectAllowed = false };
|
||||
}
|
||||
|
||||
void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
@@ -518,15 +521,32 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
||||
_menu->addSeparator();
|
||||
}
|
||||
|
||||
const auto result = SendMenu::FillSendMenu(
|
||||
using namespace SendMenu;
|
||||
const auto sendAction = crl::guard(this, [=](Action action, Details) {
|
||||
if (action.type == ActionType::Send) {
|
||||
submit(action.options);
|
||||
return;
|
||||
}
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
nullptr, // ChatHelpers::Show for effect attachment.
|
||||
sendMenuDetails(),
|
||||
[=](Api::SendOptions options) { submit(options); },
|
||||
action.options,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
});
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
const auto result = FillSendMenu(
|
||||
_menu.get(),
|
||||
sendMenuType(),
|
||||
[=] { submitSilent(); },
|
||||
[=] { submitScheduled(); },
|
||||
[=] { submitWhenOnline(); });
|
||||
const auto success = (result == SendMenu::FillMenuResult::Success);
|
||||
if (_descriptor.forwardOptions.show || success) {
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
nullptr, // showForEffect.
|
||||
sendMenuDetails(),
|
||||
sendAction);
|
||||
if (result == SendMenu::FillMenuResult::Prepared) {
|
||||
_menu->popupPrepared();
|
||||
} else if (_descriptor.forwardOptions.show
|
||||
&& result != SendMenu::FillMenuResult::Failed) {
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
}
|
||||
@@ -607,25 +627,6 @@ void ShareBox::submit(Api::SendOptions options) {
|
||||
}
|
||||
}
|
||||
|
||||
void ShareBox::submitSilent() {
|
||||
submit({ .silent = true });
|
||||
}
|
||||
|
||||
void ShareBox::submitScheduled() {
|
||||
const auto callback = [=](Api::SendOptions options) { submit(options); };
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
sendMenuType(),
|
||||
callback,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
}
|
||||
|
||||
void ShareBox::submitWhenOnline() {
|
||||
submit(Api::DefaultSendWhenOnlineOptions());
|
||||
}
|
||||
|
||||
void ShareBox::copyLink() const {
|
||||
if (const auto onstack = _descriptor.copyCallback) {
|
||||
onstack();
|
||||
@@ -1408,55 +1409,6 @@ std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
QString AppendShareGameScoreUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const FullMsgId &fullId) {
|
||||
auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
|
||||
auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data());
|
||||
const auto peer = fullId.peer
|
||||
? session->data().peerLoaded(fullId.peer)
|
||||
: static_cast<PeerData*>(nullptr);
|
||||
const auto channelAccessHash = uint64((peer && peer->isChannel())
|
||||
? peer->asChannel()->access
|
||||
: 0);
|
||||
shareHashDataInts[0] = session->userId().bare;
|
||||
shareHashDataInts[1] = fullId.peer.value;
|
||||
shareHashDataInts[2] = uint64(fullId.msg.bare);
|
||||
shareHashDataInts[3] = channelAccessHash;
|
||||
|
||||
// Count SHA1() of data.
|
||||
auto key128Size = 0x10;
|
||||
auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
|
||||
hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
|
||||
|
||||
//// Mix in channel access hash to the first 64 bits of SHA1 of data.
|
||||
//*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash;
|
||||
|
||||
// Encrypt data.
|
||||
if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
|
||||
return url;
|
||||
}
|
||||
|
||||
auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash);
|
||||
|
||||
auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl);
|
||||
|
||||
auto hashPosition = url.indexOf('#');
|
||||
if (hashPosition < 0) {
|
||||
return url + '#' + shareComponent;
|
||||
}
|
||||
auto hash = url.mid(hashPosition + 1);
|
||||
if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) {
|
||||
return url + '&' + shareComponent;
|
||||
}
|
||||
if (!hash.isEmpty()) {
|
||||
return url + '?' + shareComponent;
|
||||
}
|
||||
return url + shareComponent;
|
||||
}
|
||||
|
||||
ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
|
||||
const std::vector<not_null<Data::Thread*>> &result,
|
||||
const MessageIdsList &msgIds) {
|
||||
@@ -1611,9 +1563,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto show = controller->uiShow();
|
||||
const auto history = item->history();
|
||||
const auto owner = &history->owner();
|
||||
const auto session = &history->session();
|
||||
@@ -1642,7 +1593,7 @@ void FastShareMessage(
|
||||
}
|
||||
if (item->hasDirectLink()) {
|
||||
using namespace HistoryView;
|
||||
CopyPostLink(controller, item->fullId(), Context::History);
|
||||
CopyPostLink(show, item->fullId(), Context::History);
|
||||
} else if (const auto bot = item->getMessageBot()) {
|
||||
if (const auto media = item->media()) {
|
||||
if (const auto game = media->game()) {
|
||||
@@ -1674,23 +1625,27 @@ void FastShareMessage(
|
||||
auto copyLinkCallback = canCopyLink
|
||||
? Fn<void()>(std::move(copyCallback))
|
||||
: Fn<void()>();
|
||||
controller->show(
|
||||
Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}),
|
||||
Ui::LayerOption::CloseOther);
|
||||
show->show(Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = session,
|
||||
.copyCallback = std::move(copyLinkCallback),
|
||||
.submitCallback = ShareBox::DefaultForwardCallback(
|
||||
show,
|
||||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
.show = !hasOnlyForcedForwardedInfo,
|
||||
},
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}), Ui::LayerOption::CloseOther);
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
FastShareMessage(controller->uiShow(), item);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
@@ -1792,111 +1747,3 @@ auto SharePremiumRequiredError()
|
||||
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
|
||||
return WritePremiumRequiredError;
|
||||
}
|
||||
|
||||
void ShareGameScoreByHash(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash) {
|
||||
auto &session = controller->session();
|
||||
auto key128Size = 0x10;
|
||||
|
||||
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt data.
|
||||
auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized);
|
||||
if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Count SHA1() of data.
|
||||
char dataSha1[20] = { 0 };
|
||||
hashSha1(hashData.constData(), hashData.size(), dataSha1);
|
||||
|
||||
//// Mix out channel access hash from the first 64 bits of SHA1 of data.
|
||||
//auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
|
||||
|
||||
//// Check next 64 bits of SHA1() of data.
|
||||
//auto skipSha1Part = sizeof(channelAccessHash);
|
||||
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
|
||||
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
|
||||
// return;
|
||||
//}
|
||||
|
||||
// Check 128 bits of SHA1() of data.
|
||||
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
|
||||
if (hashDataInts[0] != session.userId().bare) {
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto peerId = PeerId(hashDataInts[1]);
|
||||
const auto channelAccessHash = hashDataInts[3];
|
||||
if (!peerIsChannel(peerId) && channelAccessHash) {
|
||||
// If there is no channel id, there should be no channel access_hash.
|
||||
controller->show(
|
||||
Ui::MakeInformBox(tr::lng_share_wrong_user()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto msgId = MsgId(int64(hashDataInts[2]));
|
||||
if (const auto item = session.data().message(peerId, msgId)) {
|
||||
FastShareMessage(controller, item);
|
||||
} else {
|
||||
const auto weak = base::make_weak(controller);
|
||||
const auto resolveMessageAndShareScore = crl::guard(weak, [=](
|
||||
PeerData *peer) {
|
||||
auto done = crl::guard(weak, [=] {
|
||||
const auto item = weak->session().data().message(
|
||||
peerId,
|
||||
msgId);
|
||||
if (item) {
|
||||
FastShareMessage(weak.get(), item);
|
||||
} else {
|
||||
weak->show(
|
||||
Ui::MakeInformBox(tr::lng_edit_deleted()),
|
||||
Ui::LayerOption::CloseOther);
|
||||
}
|
||||
});
|
||||
auto &api = weak->session().api();
|
||||
api.requestMessageData(peer, msgId, std::move(done));
|
||||
});
|
||||
|
||||
const auto peer = peerIsChannel(peerId)
|
||||
? controller->session().data().peerLoaded(peerId)
|
||||
: nullptr;
|
||||
if (peer || !peerIsChannel(peerId)) {
|
||||
resolveMessageAndShareScore(peer);
|
||||
} else {
|
||||
const auto owner = &controller->session().data();
|
||||
controller->session().api().request(MTPchannels_GetChannels(
|
||||
MTP_vector<MTPInputChannel>(
|
||||
1,
|
||||
MTP_inputChannel(
|
||||
MTP_long(peerToChannel(peerId).bare),
|
||||
MTP_long(channelAccessHash)))
|
||||
)).done([=](const MTPmessages_Chats &result) {
|
||||
result.match([&](const auto &data) {
|
||||
owner->processChats(data.vchats());
|
||||
});
|
||||
if (const auto peer = owner->peerLoaded(peerId)) {
|
||||
resolveMessageAndShareScore(peer);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ struct PeerList;
|
||||
} // namespace style
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
struct Details;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace Window {
|
||||
@@ -59,13 +59,11 @@ class SlideWrap;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
QString AppendShareGameScoreUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &url,
|
||||
const FullMsgId &fullId);
|
||||
void ShareGameScoreByHash(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash);
|
||||
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);
|
||||
@@ -130,13 +128,10 @@ private:
|
||||
void scrollAnimationCallback();
|
||||
|
||||
void submit(Api::SendOptions options);
|
||||
void submitSilent();
|
||||
void submitScheduled();
|
||||
void submitWhenOnline();
|
||||
void copyLink() const;
|
||||
bool searchByUsername(bool useCache = false);
|
||||
|
||||
SendMenu::Type sendMenuType() const;
|
||||
[[nodiscard]] SendMenu::Details sendMenuDetails() const;
|
||||
|
||||
void scrollTo(Ui::ScrollToRequest request);
|
||||
void needSearchByUsername();
|
||||
|
||||
@@ -73,7 +73,9 @@ using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
using SetFlag = Data::StickersSetFlag;
|
||||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(const QImage &frame) {
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
if (frame.isNull()
|
||||
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
return {};
|
||||
@@ -83,7 +85,7 @@ using SetFlag = Data::StickersSetFlag;
|
||||
auto sb = int64();
|
||||
auto sa = int64();
|
||||
const auto factor = frame.devicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size() * factor;
|
||||
const auto size = lockIcon.size() * factor;
|
||||
const auto width = std::min(frame.width(), size.width());
|
||||
const auto height = std::min(frame.height(), size.height());
|
||||
const auto skipx = (frame.width() - width) / 2;
|
||||
@@ -110,22 +112,30 @@ using SetFlag = Data::StickersSetFlag;
|
||||
|
||||
}
|
||||
|
||||
[[nodiscard]] QColor ComputeLockColor(const QImage &frame) {
|
||||
return ComputeImageColor(frame).value_or(st::windowSubTextFg->c);
|
||||
[[nodiscard]] QColor ComputeLockColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
return ComputeImageColor(
|
||||
lockIcon,
|
||||
frame
|
||||
).value_or(st::windowSubTextFg->c);
|
||||
}
|
||||
|
||||
void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
||||
void ValidatePremiumLockBg(
|
||||
const style::icon &lockIcon,
|
||||
QImage &image,
|
||||
const QImage &frame) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size();
|
||||
const auto size = lockIcon.size();
|
||||
image = QImage(
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
auto p = QPainter(&image);
|
||||
const auto color = ComputeLockColor(frame);
|
||||
const auto color = ComputeLockColor(lockIcon, frame);
|
||||
p.fillRect(
|
||||
QRect(QPoint(), size),
|
||||
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
||||
@@ -134,12 +144,12 @@ void ValidatePremiumLockBg(QImage &image, const QImage &frame) {
|
||||
image = Images::Circle(std::move(image));
|
||||
}
|
||||
|
||||
void ValidatePremiumStarFg(QImage &image) {
|
||||
void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = st::stickersPremiumLock.size();
|
||||
const auto size = lockIcon.size();
|
||||
image = QImage(
|
||||
size * factor,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
@@ -176,7 +186,10 @@ void ValidatePremiumStarFg(QImage &image) {
|
||||
|
||||
} // namespace
|
||||
|
||||
StickerPremiumMark::StickerPremiumMark(not_null<Main::Session*> session) {
|
||||
StickerPremiumMark::StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon)
|
||||
: _lockIcon(lockIcon) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_lockGray = QImage();
|
||||
@@ -202,16 +215,14 @@ void StickerPremiumMark::paint(
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto point = position + QPoint(
|
||||
(_premium
|
||||
? (singleSize.width() - (bg.width() / factor) - radius)
|
||||
: (singleSize.width() - (bg.width() / factor)) / 2),
|
||||
(singleSize.width() - (bg.width() / factor) - radius),
|
||||
singleSize.height() - (bg.height() / factor) - radius);
|
||||
p.drawImage(point, bg);
|
||||
if (_premium) {
|
||||
validateStar();
|
||||
p.drawImage(point, _star);
|
||||
} else {
|
||||
st::stickersPremiumLock.paint(p, point, outerWidth);
|
||||
_lockIcon.paint(p, point, outerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,11 +230,11 @@ void StickerPremiumMark::validateLock(
|
||||
const QImage &frame,
|
||||
QImage &backCache) {
|
||||
auto &image = frame.isNull() ? _lockGray : backCache;
|
||||
ValidatePremiumLockBg(image, frame);
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame);
|
||||
}
|
||||
|
||||
void StickerPremiumMark::validateStar() {
|
||||
ValidatePremiumStarFg(_star);
|
||||
ValidatePremiumStarFg(_lockIcon, _star);
|
||||
}
|
||||
|
||||
class StickerSetBox::Inner final : public Ui::RpWidget {
|
||||
@@ -664,7 +675,7 @@ StickerSetBox::Inner::Inner(
|
||||
st::windowBgRipple,
|
||||
st::windowBgOver,
|
||||
[=] { repaintItems(); }))
|
||||
, _premiumMark(_session)
|
||||
, _premiumMark(_session, st::stickersPremiumLock)
|
||||
, _updateItemsTimer([=] { updateItems(); })
|
||||
, _input(set)
|
||||
, _padding((type == Data::StickersType::Emoji)
|
||||
@@ -1014,7 +1025,7 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
this,
|
||||
st::popupMenuWithIcons);
|
||||
const auto type = _show->sendMenuType();
|
||||
const auto details = _show->sendMenuDetails();
|
||||
if (setType() == Data::StickersType::Emoji) {
|
||||
if (const auto t = PrepareTextFromEmoji(_pack[index]); !t.empty()) {
|
||||
_menu->addAction(tr::lng_mediaview_copy(tr::now), [=] {
|
||||
@@ -1023,17 +1034,16 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}, &st::menuIconCopy);
|
||||
}
|
||||
} else if (type != SendMenu::Type::Disabled) {
|
||||
} else if (details.type != SendMenu::Type::Disabled) {
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
const auto send = crl::guard(this, [=](Api::SendOptions options) {
|
||||
chosen(index, document, options);
|
||||
};
|
||||
});
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(_show, type, sendSelected),
|
||||
SendMenu::DefaultWhenOnlineCallback(sendSelected));
|
||||
_show,
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
const auto show = _show;
|
||||
const auto toggleFavedSticker = [=] {
|
||||
|
||||
@@ -23,10 +23,6 @@ namespace Data {
|
||||
class StickersSet;
|
||||
} // namespace Data
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace ChatHelpers {
|
||||
struct FileChosen;
|
||||
class Show;
|
||||
@@ -34,7 +30,9 @@ class Show;
|
||||
|
||||
class StickerPremiumMark final {
|
||||
public:
|
||||
explicit StickerPremiumMark(not_null<Main::Session*> session);
|
||||
StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon);
|
||||
|
||||
void paint(
|
||||
QPainter &p,
|
||||
@@ -48,6 +46,7 @@ private:
|
||||
void validateLock(const QImage &frame, QImage &backCache);
|
||||
void validateStar();
|
||||
|
||||
const style::icon &_lockIcon;
|
||||
QImage _lockGray;
|
||||
QImage _star;
|
||||
bool _premium = false;
|
||||
|
||||