Compare commits
250 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79c8c5ec5d | ||
|
|
3548aba652 | ||
|
|
5d1812efc8 | ||
|
|
0477d43fbb | ||
|
|
73609fe5b2 | ||
|
|
e027196c8a | ||
|
|
6d00165e5a | ||
|
|
693eaf9262 | ||
|
|
3549349ffb | ||
|
|
fac20e436d | ||
|
|
63f0feaf04 | ||
|
|
3001464f6b | ||
|
|
eec9c8a46b | ||
|
|
80b3754be1 | ||
|
|
6d61caea4e | ||
|
|
b9de12fedb | ||
|
|
074dbf41e0 | ||
|
|
e51b2c0c91 | ||
|
|
e4538947c3 | ||
|
|
624e068f2f | ||
|
|
30077133d4 | ||
|
|
b0fece2fd0 | ||
|
|
5f8c007a0c | ||
|
|
a2785867b2 | ||
|
|
9ea495f59d | ||
|
|
24a7e48b75 | ||
|
|
7ef44fb621 | ||
|
|
0a1ddddd81 | ||
|
|
284f1a5210 | ||
|
|
37283a7a35 | ||
|
|
7f4b540aad | ||
|
|
83f4c53766 | ||
|
|
155f4ea252 | ||
|
|
f6c071bd18 | ||
|
|
56959398e2 | ||
|
|
d34d3a796d | ||
|
|
dca61541d6 | ||
|
|
81a79e2895 | ||
|
|
d6541d777f | ||
|
|
457301493f | ||
|
|
4f7364b798 | ||
|
|
32e8ed93e2 | ||
|
|
4760337958 | ||
|
|
f2f85a9083 | ||
|
|
70d5dd8b71 | ||
|
|
ec93a91db2 | ||
|
|
cc3baad377 | ||
|
|
d1313f38eb | ||
|
|
49e6c4f552 | ||
|
|
fb252bb644 | ||
|
|
78e09f2605 | ||
|
|
ba4c521d7a | ||
|
|
92f70a0ebb | ||
|
|
a13f0cb11e | ||
|
|
617ad38a68 | ||
|
|
fb66c85567 | ||
|
|
05fa2c381a | ||
|
|
ae18b4c851 | ||
|
|
7ed715b01c | ||
|
|
8158c52e82 | ||
|
|
44c1109798 | ||
|
|
ba6bbf54e6 | ||
|
|
75b26b1a85 | ||
|
|
c8a3b0ab80 | ||
|
|
52a199a362 | ||
|
|
e89f2b55e8 | ||
|
|
ac92e1c99e | ||
|
|
02610de010 | ||
|
|
273e041935 | ||
|
|
474c0838d1 | ||
|
|
afe30da9f4 | ||
|
|
9bb1fa8782 | ||
|
|
bb3fc17489 | ||
|
|
126fd89bb7 | ||
|
|
fb8f3ad26c | ||
|
|
cedf161e44 | ||
|
|
89dc18aaea | ||
|
|
ba99706e75 | ||
|
|
efc8417ab1 | ||
|
|
c4f45c4b7c | ||
|
|
c985b77a48 | ||
|
|
6148fb9474 | ||
|
|
fe86f5d050 | ||
|
|
916926bfa6 | ||
|
|
9eb15f7b17 | ||
|
|
2d1b1fbd44 | ||
|
|
1301c42afa | ||
|
|
0de5080874 | ||
|
|
a982560a62 | ||
|
|
4a5d8046d5 | ||
|
|
65a14bcab4 | ||
|
|
e9bb6f65e3 | ||
|
|
74f7fa80b7 | ||
|
|
2ff0ed50be | ||
|
|
281ad01b85 | ||
|
|
4864a6996f | ||
|
|
0af3028cd6 | ||
|
|
11c91c1a42 | ||
|
|
7f3dc27aa9 | ||
|
|
51fc104c60 | ||
|
|
6b96466c5e | ||
|
|
7c1510b611 | ||
|
|
993c0ee648 | ||
|
|
503c3c7b00 | ||
|
|
b7c14f17a7 | ||
|
|
0b6bd7075a | ||
|
|
148690d8b1 | ||
|
|
a422aec99a | ||
|
|
813d0501da | ||
|
|
db80096e6b | ||
|
|
cf896aeb13 | ||
|
|
76314e3c03 | ||
|
|
8959679b3c | ||
|
|
bb6c94ef4f | ||
|
|
fb9ce6d3a8 | ||
|
|
dac4389e37 | ||
|
|
a25b2e9700 | ||
|
|
699a7bdc58 | ||
|
|
4108debca0 | ||
|
|
8b2bbfba6a | ||
|
|
4f37343e8b | ||
|
|
2dcf40817e | ||
|
|
3eeb01be61 | ||
|
|
6a8a85e395 | ||
|
|
031233ea98 | ||
|
|
4b09050061 | ||
|
|
992c876930 | ||
|
|
a5ffd8b7cf | ||
|
|
5fdd4eba80 | ||
|
|
54ce85f8e6 | ||
|
|
0bfb0fd045 | ||
|
|
8ad2d3d39a | ||
|
|
b8a19b56b6 | ||
|
|
24b93a5eff | ||
|
|
127f651d5e | ||
|
|
e760a0983f | ||
|
|
3f2cb8f8c9 | ||
|
|
bcb6e9e1af | ||
|
|
847d66c973 | ||
|
|
5c797d1f31 | ||
|
|
f749616dd8 | ||
|
|
3cc92e01fe | ||
|
|
06f2b23687 | ||
|
|
6a167b33f5 | ||
|
|
850155b3be | ||
|
|
358e586801 | ||
|
|
54214ff2ad | ||
|
|
06fc813e95 | ||
|
|
0046bae53f | ||
|
|
aeb5e57061 | ||
|
|
a32b781e49 | ||
|
|
caef698e54 | ||
|
|
e9650385ad | ||
|
|
f55584b160 | ||
|
|
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 |
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 ada-url 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 ^
|
||||
|
||||
@@ -272,6 +272,8 @@ PRIVATE
|
||||
boxes/edit_caption_box.h
|
||||
boxes/edit_privacy_box.cpp
|
||||
boxes/edit_privacy_box.h
|
||||
boxes/gift_credits_box.cpp
|
||||
boxes/gift_credits_box.h
|
||||
boxes/gift_premium_box.cpp
|
||||
boxes/gift_premium_box.h
|
||||
boxes/language_box.cpp
|
||||
@@ -473,8 +475,12 @@ PRIVATE
|
||||
data/business/data_business_info.h
|
||||
data/business/data_shortcut_messages.cpp
|
||||
data/business/data_shortcut_messages.h
|
||||
data/components/credits.cpp
|
||||
data/components/credits.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
|
||||
@@ -820,6 +826,8 @@ PRIVATE
|
||||
history/view/history_view_message.cpp
|
||||
history/view/history_view_message.h
|
||||
history/view/history_view_object.h
|
||||
history/view/history_view_paid_reaction_toast.cpp
|
||||
history/view/history_view_paid_reaction_toast.h
|
||||
history/view/history_view_pinned_bar.cpp
|
||||
history/view/history_view_pinned_bar.h
|
||||
history/view/history_view_pinned_section.cpp
|
||||
@@ -1232,6 +1240,8 @@ PRIVATE
|
||||
payments/payments_form.h
|
||||
payments/payments_non_panel_process.cpp
|
||||
payments/payments_non_panel_process.h
|
||||
payments/payments_reaction_process.cpp
|
||||
payments/payments_reaction_process.h
|
||||
platform/linux/file_utilities_linux.cpp
|
||||
platform/linux/file_utilities_linux.h
|
||||
platform/linux/launcher_linux.cpp
|
||||
@@ -1464,6 +1474,8 @@ PRIVATE
|
||||
support/support_preload.h
|
||||
support/support_templates.cpp
|
||||
support/support_templates.h
|
||||
ui/boxes/edit_invite_link_session.cpp
|
||||
ui/boxes/edit_invite_link_session.h
|
||||
ui/chat/attach/attach_item_single_file_preview.cpp
|
||||
ui/chat/attach/attach_item_single_file_preview.h
|
||||
ui/chat/attach/attach_item_single_media_preview.cpp
|
||||
@@ -1472,6 +1484,8 @@ 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
|
||||
@@ -1493,6 +1507,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
|
||||
@@ -1506,10 +1524,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
|
||||
@@ -1614,6 +1628,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
|
||||
@@ -1813,7 +1828,7 @@ endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32)
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
delayimp
|
||||
@@ -1844,9 +1859,14 @@ if (WIN32)
|
||||
/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-EventLog-Legacy-l1-1-0.dll
|
||||
/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
|
||||
@@ -1863,7 +1883,7 @@ if (WIN32)
|
||||
/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
|
||||
# /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
|
||||
@@ -1905,7 +1925,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
||||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
target_link_libraries(Updater
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
||||
BIN
Telegram/Resources/animations/star_reaction/appear.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/center.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect1.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect2.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/effect3.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/select.tgs
Normal file
BIN
Telegram/Resources/animations/star_reaction/toast.tgs
Normal file
BIN
Telegram/Resources/animations/stats_boosts.tgs
Normal file
BIN
Telegram/Resources/animations/stats_earn.tgs
Normal file
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/chat/mini_stars.png
Normal file
|
After Width: | Height: | Size: 349 B |
BIN
Telegram/Resources/icons/chat/mini_stars@2x.png
Normal file
|
After Width: | Height: | Size: 565 B |
BIN
Telegram/Resources/icons/chat/mini_stars@3x.png
Normal file
|
After Width: | Height: | Size: 740 B |
36
Telegram/Resources/icons/info/edit/links_subscription.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>General / menu_incomes</title>
|
||||
<defs>
|
||||
<rect id="path-1" x="0" y="0" width="72" height="37"></rect>
|
||||
<rect id="path-3" x="0" y="0" width="72" height="42"></rect>
|
||||
</defs>
|
||||
<g id="General-/-menu_incomes" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-2">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Rectangle"></g>
|
||||
<g id="Group" mask="url(#mask-2)" stroke="#FFFFFF" stroke-linecap="round" stroke-width="5.4">
|
||||
<g transform="translate(35.056529, 35.872413) rotate(66.000000) translate(-35.056529, -35.872413) translate(5.056529, 5.872413)" id="Path">
|
||||
<path d="M7.74068421e-14,27 C7.74068421e-14,27.6514434 0.0207638485,31.2980371 0.0616658298,31.9391554 C1.06104802,47.603977 14.0829009,60 30,60 C46.5685425,60 60,46.5685425 60,30 C60,13.4314575 46.5685425,-1.34265911e-13 30,-1.34265911e-13 C20.9122281,-1.34265911e-13 12.7682399,4.04081874 7.26674545,10.4237463" transform="translate(30.000000, 30.000000) rotate(90.000000) translate(-30.000000, -30.000000) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group-2-Copy" transform="translate(0.000000, 28.000000)">
|
||||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<g id="Rectangle"></g>
|
||||
<g id="Group" mask="url(#mask-4)" stroke="#FFFFFF" stroke-dasharray="5,14" stroke-linecap="round" stroke-width="5.4">
|
||||
<g transform="translate(35.056529, 7.872413) rotate(66.000000) translate(-35.056529, -7.872413) translate(-2.745677, -29.929792)" id="Path">
|
||||
<path d="M7.80220532,34.8022053 C7.80220532,35.4536487 7.82296917,39.1002424 7.86387115,39.7413607 C8.86325334,55.4061823 21.8851062,67.8022053 37.8022053,67.8022053 C54.3707478,67.8022053 67.8022053,54.3707478 67.8022053,37.8022053 C67.8022053,21.2336628 54.3707478,7.80220532 37.8022053,7.80220532 C28.7144334,7.80220532 20.5704452,11.8430241 15.0689508,18.2259516" transform="translate(37.802205, 37.802205) rotate(72.000000) translate(-37.802205, -37.802205) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="$" transform="translate(23.000000, 16.000000)" fill="#FFFFFF" fill-rule="nonzero">
|
||||
<path d="M12.034384,39.8 C13.0659026,39.8 14.182808,39.2321492 14.182808,37.869307 L14.182808,35.752184 C20.3489971,35.093477 24,31.5046593 24,26.280431 C24,21.7830518 21.2722063,19.0573675 15.6103152,17.8308096 L10.9570201,16.7859639 C8.04584527,16.1499709 6.62464183,14.8325568 6.62464183,12.9018637 C6.62464183,10.585032 8.64183381,8.90419336 11.8051576,8.90419336 C14.3724928,8.90419336 16.1604585,9.76732673 18.1547278,11.9705882 C19.1633238,13.0154339 19.9426934,13.4015725 20.9971347,13.4015725 C22.2808023,13.4015725 23.2664756,12.5157251 23.2664756,11.198311 C23.2664756,9.92632499 22.5100287,8.54076878 21.226361,7.2914968 C19.530086,5.70151427 17.1653295,4.65666861 14.3,4.29324403 L14.3,1.907979 C14.3,0.56785087 13.2034384,0 12.1489971,0 C11.1174785,0 10,0.545136833 10,1.907979 L10,4.22510192 C4.06303725,4.77023879 0.481375358,8.29091439 0.481375358,13.3334304 C0.481375358,17.7399534 3.20916905,20.6700641 8.43553009,21.8284799 L13.0888252,22.8960396 C16.4813754,23.6910309 17.8796562,24.8721607 17.8796562,26.8709959 C17.8796562,29.460396 15.8395415,31.0958066 12.1948424,31.0958066 C9.46704871,31.0958066 7.19770774,30.073675 5.13467049,27.8704135 C3.96561605,26.7119977 3.32378223,26.4621433 2.45272206,26.4621433 C1.05444126,26.4621433 0,27.3479907 0,28.8698311 C0,30.2099592 0.779369628,31.5955154 2.17765043,32.7993593 C4.01146132,34.457484 6.67679058,35.4796156 9.863037,35.7748981 L9.863037,37.869307 C9.863037,39.2321492 10.9799427,39.8 12.034384,39.8 Z" id="Path"></path>
|
||||
</g>
|
||||
<path d="M66.2217021,28.4347599 L72.4528242,38.8199633 C72.7823745,39.3692138 72.6042723,40.0816225 72.0550218,40.4111728 C71.8747737,40.5193217 71.668522,40.5764501 71.4583183,40.5764501 L57.5551143,40.5764501 C56.9145837,40.5764501 56.3953311,40.0571975 56.3953311,39.4166669 C56.3953311,39.2064632 56.4524595,39.0002115 56.5606084,38.8199633 L62.7917304,28.4347599 C63.3600268,27.4875994 64.5885473,27.1804692 65.5357078,27.7487655 C65.817207,27.9176651 66.0528026,28.1532607 66.2217021,28.4347599 Z" id="Triangle" fill="#FFFFFF" transform="translate(64.506716, 33.076450) rotate(168.000000) translate(-64.506716, -33.076450) "></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 771 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -618,9 +618,6 @@ var IV = {
|
||||
element.getAnimations().forEach(
|
||||
(animation) => animation.finish());
|
||||
},
|
||||
back: function () {
|
||||
window.history.back();
|
||||
},
|
||||
menuShown: function (shown) {
|
||||
var already = document.getElementById('menu_page_blocker');
|
||||
if (already && shown) {
|
||||
|
||||
@@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"cloud_lng_forwarded_psa_covid" = "COVID-19 Notification from {channel}";
|
||||
"cloud_lng_tooltip_psa_covid" = "This message provides you with a public service announcement in relation to the ongoing COVID-19 pandemic. Learn more about this initiative at https://telegram.org/blog/coronavirus";
|
||||
|
||||
"cloud_lng_topup_purpose_subs" = "Buy **Stars** to keep your channel subscriptions.";
|
||||
|
||||
"cloud_lng_passport_in_ar" = "Arabic";
|
||||
"cloud_lng_passport_in_az" = "Azerbaijani";
|
||||
"cloud_lng_passport_in_bg" = "Bulgarian";
|
||||
|
||||
@@ -1115,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?";
|
||||
|
||||
@@ -1314,6 +1315,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_profile_bot_settings" = "Bot Settings";
|
||||
"lng_profile_bot_help" = "Bot Help";
|
||||
"lng_profile_bot_privacy" = "Bot Privacy Policy";
|
||||
"lng_profile_bot_privacy_url" = "https://telegram.org/privacy-tpa";
|
||||
"lng_profile_common_groups#one" = "{count} group in common";
|
||||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
@@ -1447,6 +1449,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_info_topic_title" = "Topic Info";
|
||||
"lng_profile_enable_notifications" = "Notifications";
|
||||
"lng_profile_send_message" = "Send Message";
|
||||
"lng_profile_open_app" = "Open App";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_suggest_photo" = "Suggest Profile Photo";
|
||||
@@ -1546,6 +1551,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_manage_peer_reactions_max_slider#other" = "{count} reactions per post";
|
||||
"lng_manage_peer_reactions_max_about" = "Limit the number of different reactions that can be added to a post, including already published ones.";
|
||||
|
||||
"lng_manage_peer_reactions_paid" = "Enable Paid Reactions";
|
||||
"lng_manage_peer_reactions_paid_about" = "Switch this on to let your subscribers set paid reactions with Telegram Stars, which you will be able to withdraw later as TON. {link}";
|
||||
"lng_manage_peer_reactions_paid_link" = "Learn more >";
|
||||
|
||||
"lng_manage_peer_antispam" = "Aggressive Anti-Spam";
|
||||
"lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions.";
|
||||
"lng_manage_peer_antispam_not_enough#one" = "Aggressive filtering can be enabled only in groups with more than **{count} member**.";
|
||||
@@ -1844,6 +1853,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_suggested_photo_me" = "You suggested {user} to use this profile photo.";
|
||||
"lng_action_suggested_photo" = "{user} suggests you to use this profile photo.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
@@ -1888,6 +1898,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";
|
||||
@@ -1937,6 +1948,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_members#other" = "{count} members, among them:";
|
||||
"lng_channel_invite_private" = "This channel is private.\nPlease join it to continue viewing its content.";
|
||||
|
||||
"lng_channel_invite_subscription_button" = "Subscribe";
|
||||
"lng_channel_invite_subscription_title" = "Subscribe to the Channel";
|
||||
"lng_channel_invite_subscription_about" = "Do you want to subscribe for {channel} for {price} per month?";
|
||||
"lng_channel_invite_subscription_terms" = "By subscribing you agree to the {link}.";
|
||||
|
||||
"lng_group_invite_create" = "Create an invite link";
|
||||
"lng_group_invite_about_new" = "Your previous link will be deactivated and we'll generate a new invite link for you.";
|
||||
"lng_group_invite_copied" = "Invite link copied to clipboard.";
|
||||
@@ -2011,6 +2027,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
|
||||
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
|
||||
"lng_group_invite_about_no_approve_channel" = "New users will be able to join the channel without being approved by the admins.";
|
||||
"lng_group_invite_subscription" = "Require Monthly Fee";
|
||||
"lng_group_invite_subscription_ph" = "Stars Amount per month";
|
||||
"lng_group_invite_subscription_price" = "~{cost} / month";
|
||||
"lng_group_invite_subscription_toast" = "Sorry, you cannot change the number of Stars for an already created invite link.";
|
||||
"lng_group_invite_subscription_about" = "Charge a subscription fee from people joining your channel via this link. {link}";
|
||||
"lng_group_invite_subscription_about_link" = "Learn more {emoji}";
|
||||
"lng_group_invite_subscription_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_group_invite_subscription_info_subtitle" = "Subscription fee";
|
||||
"lng_group_invite_subscription_info_title" = "{emoji} {price} / month {multiplier} {total}";
|
||||
"lng_group_invite_subscription_info_title_none" = "{emoji} {price} / month";
|
||||
"lng_group_invite_subscription_info_about" = "you get approximately {total} montly";
|
||||
"lng_group_invite_joined_right" = "per month";
|
||||
"lng_group_invite_joined_status" = "joined {date}";
|
||||
"lng_group_invite_joined_row_subscriber" = "Subscriber";
|
||||
"lng_group_invite_joined_row_date" = "Subscribed";
|
||||
|
||||
"lng_group_request_to_join" = "Request to Join";
|
||||
"lng_group_request_about" = "This group accepts new members only after they are approved by its admins.";
|
||||
@@ -2340,11 +2371,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_gift_button" = "Gift Stars to Friends";
|
||||
"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_media_user#one" = "Do you want to unlock {media} from {user} for **{count} Star**?";
|
||||
"lng_credits_box_out_media_user#other" = "Do you want to unlock {media} from {user} 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";
|
||||
@@ -2355,18 +2389,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_box_out_about_link" = "https://telegram.org/tos/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_media_done_text_user#one" = "**{count} Star** transferred to {user}.";
|
||||
"lng_credits_media_done_text_user#other" = "**{count} Stars** transferred to {user}.";
|
||||
"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_anonymous" = "Unknown User";
|
||||
"lng_credits_box_history_entry_gift_name" = "Received Gift";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
"lng_credits_box_history_entry_gift_about_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"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";
|
||||
@@ -2374,14 +2421,49 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"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_box_history_entry_reaction_name" = "Star Reaction";
|
||||
"lng_credits_box_history_entry_subscription" = "Monthly subscription fee";
|
||||
|
||||
"lng_credits_subscription_section" = "My subscriptions";
|
||||
"lng_credits_box_subscription_title" = "Subscription";
|
||||
"lng_credits_subscription_subtitle" = "{emoji} {cost} / month";
|
||||
"lng_credits_subscriber_subtitle" = "appx. {total} per month";
|
||||
|
||||
"lng_credits_subscription_row_to" = "Subscription";
|
||||
"lng_credits_subscription_row_from" = "Subscribed";
|
||||
|
||||
"lng_credits_subscription_row_next_on" = "Renews";
|
||||
"lng_credits_subscription_row_next_off" = "Expires";
|
||||
"lng_credits_subscription_row_next_none" = "Expired";
|
||||
|
||||
"lng_credits_subscription_on_button" = "Cancel Subscription";
|
||||
"lng_credits_subscription_on_about" = "If you cancel now, you will still be able to access your subscription until {date}.";
|
||||
|
||||
"lng_credits_subscription_off_button" = "Renew Subscription";
|
||||
"lng_credits_subscription_off_about" = "You have cancelled your subscription.";
|
||||
|
||||
"lng_credits_subscription_status_on" = "renews on {date}";
|
||||
"lng_credits_subscription_status_off" = "expires on {date}";
|
||||
"lng_credits_subscription_status_none" = "expired on {date}";
|
||||
"lng_credits_subscription_status_off_right" = "cancelled";
|
||||
"lng_credits_subscription_status_none_right" = "expired";
|
||||
|
||||
"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_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||
"lng_credits_enough_link" = "Buy anyway";
|
||||
|
||||
"lng_credits_gift_title" = "Gift 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";
|
||||
@@ -2810,12 +2892,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.";
|
||||
|
||||
@@ -2845,6 +2930,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_gift_link_pending_toast" = "Only the recipient can see the link.";
|
||||
"lng_gift_link_pending_footer" = "This link hasn't been activated yet.";
|
||||
|
||||
"lng_gift_stars_title#one" = "{count} Star";
|
||||
"lng_gift_stars_title#other" = "{count} Stars";
|
||||
"lng_gift_stars_outgoing" = "With Stars, {user} will be able to unlock content and services on Telegram.";
|
||||
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
@@ -2920,6 +3010,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_masks_has_been_archived" = "Mask pack has been archived.";
|
||||
"lng_masks_installed" = "Mask pack has been installed.";
|
||||
"lng_emoji_nothing_found" = "No emoji found";
|
||||
"lng_stickers_context_reorder" = "Reorder";
|
||||
"lng_stickers_context_edit_name" = "Edit name";
|
||||
"lng_stickers_context_delete" = "Delete sticker";
|
||||
"lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?";
|
||||
"lng_stickers_box_edit_name_title" = "Edit Sticker Set Name";
|
||||
"lng_stickers_box_edit_name_about" = "Choose a name for your set.";
|
||||
"lng_stickers_creator_badge" = "edit";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_album" = "Album";
|
||||
@@ -3151,6 +3248,11 @@ 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_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_click_to_start" = "Click here to use this bot.";
|
||||
"lng_bot_status_users#one" = "{count} user";
|
||||
"lng_bot_status_users#other" = "{count} users";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
@@ -3186,6 +3288,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";
|
||||
@@ -3364,6 +3474,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"lng_paid_price" = "Unlock for {price}";
|
||||
|
||||
"lng_paid_react_title" = "Star Reaction";
|
||||
"lng_paid_react_about" = "Choose how many **Stars** you want to send to {channel} to support this post.";
|
||||
"lng_paid_react_already#one" = "You sent **{count} Star** to support this post.";
|
||||
"lng_paid_react_already#other" = "You sent **{count} Stars** to support this post.";
|
||||
"lng_paid_react_top_title" = "Top Senders";
|
||||
"lng_paid_react_send" = "Send {price}";
|
||||
"lng_paid_react_agree" = "By sending stars, you agree to the {link}.";
|
||||
"lng_paid_react_agree_link" = "Terms of Service";
|
||||
"lng_paid_react_toast#one" = "Star Sent!";
|
||||
"lng_paid_react_toast#other" = "Stars Sent!";
|
||||
"lng_paid_react_toast_text#one" = "You reacted with **{count} Star**.";
|
||||
"lng_paid_react_toast_text#other" = "You reacted with **{count} Stars**.";
|
||||
"lng_paid_react_undo" = "Undo";
|
||||
"lng_paid_react_show_in_top" = "Show me in Top Senders";
|
||||
"lng_paid_react_anonymous" = "Anonymous";
|
||||
|
||||
"lng_sensitive_tag" = "18+";
|
||||
"lng_sensitive_title" = "18+";
|
||||
"lng_sensitive_text" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
|
||||
"lng_sensitive_always" = "Always show 18+ media";
|
||||
"lng_sensitive_view" = "View Anyway";
|
||||
"lng_sensitive_toast" = "You can update the visibility of sensitive media in **Settings > Chat Settings > Sensitive content**";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
@@ -3468,6 +3601,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_edit_channel_title" = "Edit channel";
|
||||
"lng_edit_bot_title" = "Edit bot";
|
||||
"lng_edit_sign_messages" = "Sign messages";
|
||||
"lng_edit_sign_messages_about" = "Add names of admins to the messages they post.";
|
||||
"lng_edit_sign_profiles" = "Show authors' profiles";
|
||||
"lng_edit_sign_profiles_about" = "Add names and photos of admins to the messages they post, linking to their profiles.";
|
||||
"lng_edit_group" = "Edit group";
|
||||
"lng_edit_channel_color" = "Change name color";
|
||||
"lng_edit_channel_level_min" = "Level 1+";
|
||||
@@ -3740,6 +3876,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";
|
||||
@@ -4254,6 +4393,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_admin_log_invites_disabled" = "{from} disabled group invites";
|
||||
"lng_admin_log_signatures_enabled" = "{from} enabled signatures";
|
||||
"lng_admin_log_signatures_disabled" = "{from} disabled signatures";
|
||||
"lng_admin_log_signature_profiles_enabled" = "{from} enabled showing authors' profiles";
|
||||
"lng_admin_log_signature_profiles_disabled" = "{from} disabled showing authors' profiles";
|
||||
"lng_admin_log_forwards_enabled" = "{from} allowed content copying";
|
||||
"lng_admin_log_forwards_disabled" = "{from} restricted content copying";
|
||||
"lng_admin_log_history_made_hidden" = "{from} made group history hidden for new members";
|
||||
@@ -5128,6 +5269,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_stats_loading" = "Loading stats...";
|
||||
"lng_stats_loading_subtext" = "Please wait a few moments while we generate your stats.";
|
||||
"lng_stats_boosts_loading" = "Loading boosts list...";
|
||||
"lng_stats_boosts_loading_subtext" = "Please wait a few moments while we generate your stats.";
|
||||
"lng_stats_earn_loading" = "Loading rewards info...";
|
||||
"lng_stats_earn_loading_subtext" = "Please wait a few moments while we generate your stats.";
|
||||
|
||||
"lng_chart_title_member_count" = "Growth";
|
||||
"lng_chart_title_join" = "Followers";
|
||||
@@ -5267,6 +5412,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_iv_join_channel" = "Join";
|
||||
"lng_iv_window_title" = "Instant View";
|
||||
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||
"lng_iv_not_supported" = "This link appears to be invalid.";
|
||||
|
||||
"lng_limit_download_title" = "Download speed limited";
|
||||
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
|
||||
@@ -5295,12 +5441,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_recent_none" = "Recent search results\nwill appear here.";
|
||||
"lng_recent_chats" = "Chats";
|
||||
"lng_recent_channels" = "Channels";
|
||||
"lng_recent_apps" = "Apps";
|
||||
"lng_channels_none_title" = "No channels yet...";
|
||||
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||
"lng_channels_your_title" = "Channels you joined";
|
||||
"lng_channels_your_more" = "Show more";
|
||||
"lng_channels_your_less" = "Show less";
|
||||
"lng_channels_recommended" = "Recommended channels";
|
||||
"lng_bot_apps_your" = "Apps you use";
|
||||
"lng_bot_apps_popular" = "Popular apps";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
||||
@@ -11,20 +11,41 @@
|
||||
<file alias="ttl.tgs">../../animations/ttl.tgs</file>
|
||||
<file alias="discussion.tgs">../../animations/discussion.tgs</file>
|
||||
<file alias="stats.tgs">../../animations/stats.tgs</file>
|
||||
<file alias="stats_boosts.tgs">../../animations/stats_boosts.tgs</file>
|
||||
<file alias="stats_earn.tgs">../../animations/stats_earn.tgs</file>
|
||||
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
|
||||
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
|
||||
<file alias="palette.tgs">../../animations/palette.tgs</file>
|
||||
<file alias="sleep.tgs">../../animations/sleep.tgs</file>
|
||||
<file alias="greeting.tgs">../../animations/greeting.tgs</file>
|
||||
<file alias="location.tgs">../../animations/location.tgs</file>
|
||||
<file alias="robot.tgs">../../animations/robot.tgs</file>
|
||||
<file alias="writing.tgs">../../animations/writing.tgs</file>
|
||||
<file alias="hours.tgs">../../animations/hours.tgs</file>
|
||||
<file alias="phone.tgs">../../animations/phone.tgs</file>
|
||||
<file alias="greeting.tgs">../../animations/greeting.tgs</file>
|
||||
<file alias="location.tgs">../../animations/location.tgs</file>
|
||||
<file alias="robot.tgs">../../animations/robot.tgs</file>
|
||||
<file alias="writing.tgs">../../animations/writing.tgs</file>
|
||||
<file alias="hours.tgs">../../animations/hours.tgs</file>
|
||||
<file alias="phone.tgs">../../animations/phone.tgs</file>
|
||||
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
|
||||
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||
|
||||
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
|
||||
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
<file alias="bball_idle.tgs">../../animations/dice/bball_idle.tgs</file>
|
||||
<file alias="fball_idle.tgs">../../animations/dice/fball_idle.tgs</file>
|
||||
<file alias="slot_0_idle.tgs">../../animations/dice/slot_0_idle.tgs</file>
|
||||
<file alias="slot_1_idle.tgs">../../animations/dice/slot_1_idle.tgs</file>
|
||||
<file alias="slot_2_idle.tgs">../../animations/dice/slot_2_idle.tgs</file>
|
||||
<file alias="slot_back.tgs">../../animations/dice/slot_back.tgs</file>
|
||||
<file alias="slot_pull.tgs">../../animations/dice/slot_pull.tgs</file>
|
||||
<file alias="winners.tgs">../../animations/dice/winners.tgs</file>
|
||||
|
||||
<file alias="star_reaction_appear.tgs">../../animations/star_reaction/appear.tgs</file>
|
||||
<file alias="star_reaction_center.tgs">../../animations/star_reaction/center.tgs</file>
|
||||
<file alias="star_reaction_select.tgs">../../animations/star_reaction/select.tgs</file>
|
||||
<file alias="star_reaction_toast.tgs">../../animations/star_reaction/toast.tgs</file>
|
||||
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
|
||||
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
|
||||
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
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>
|
||||
@@ -7,16 +7,6 @@
|
||||
<file alias="art/logo_256.png">../../art/logo_256.png</file>
|
||||
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
|
||||
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
|
||||
<file alias="art/dice_idle.tgs">../../art/dice_idle.tgs</file>
|
||||
<file alias="art/dart_idle.tgs">../../art/dart_idle.tgs</file>
|
||||
<file alias="art/bball_idle.tgs">../../art/bball_idle.tgs</file>
|
||||
<file alias="art/fball_idle.tgs">../../art/fball_idle.tgs</file>
|
||||
<file alias="art/slot_0_idle.tgs">../../art/slot_0_idle.tgs</file>
|
||||
<file alias="art/slot_1_idle.tgs">../../art/slot_1_idle.tgs</file>
|
||||
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
|
||||
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
|
||||
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
|
||||
<file alias="art/winners.tgs">../../art/winners.tgs</file>
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
@@ -40,6 +30,7 @@
|
||||
<file alias="topic_icons/red.svg">../../art/topic_icons/red.svg</file>
|
||||
<file alias="topic_icons/gray.svg">../../art/topic_icons/gray.svg</file>
|
||||
<file alias="topic_icons/general.svg">../../art/topic_icons/general.svg</file>
|
||||
<file alias="links_subscription.svg">../../icons/info/edit/links_subscription.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icons">
|
||||
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.2.2.0" />
|
||||
Version="5.4.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
@@ -37,6 +37,9 @@
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
<uap3:Protocol Name="tg" Parameters="-- "%1"" />
|
||||
<uap3:Extension Category="windows.protocol">
|
||||
</uap3:Extension>
|
||||
<uap3:Protocol Name="tonsite" Parameters="-- "%1"" />
|
||||
</uap3:Extension>
|
||||
<desktop:Extension
|
||||
Category="windows.startupTask"
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,2,0
|
||||
PRODUCTVERSION 5,2,2,0
|
||||
FILEVERSION 5,4,0,0
|
||||
PRODUCTVERSION 5,4,0,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.2.2.0"
|
||||
VALUE "FileVersion", "5.4.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.2.0"
|
||||
VALUE "ProductVersion", "5.4.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,2,2,0
|
||||
PRODUCTVERSION 5,2,2,0
|
||||
FILEVERSION 5,4,0,0
|
||||
PRODUCTVERSION 5,4,0,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.2.2.0"
|
||||
VALUE "FileVersion", "5.4.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "5.2.2.0"
|
||||
VALUE "ProductVersion", "5.4.0.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);
|
||||
}
|
||||
|
||||
@@ -127,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(),
|
||||
@@ -492,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;
|
||||
}
|
||||
|
||||
@@ -8,26 +8,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_chat_invite.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "core/application.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_file_origin.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_stars_colored.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
@@ -101,12 +114,237 @@ void SubmitChatInvite(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ConfirmSubscriptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
const QString &hash,
|
||||
const MTPDchatInvite *data) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
const auto amount = data->vsubscription_pricing()->data().vamount().v;
|
||||
const auto formId = data->vsubscription_form_id()->v;
|
||||
const auto name = qs(data->vtitle());
|
||||
const auto maybePhoto = session->data().processPhoto(data->vphoto());
|
||||
const auto photo = maybePhoto->isNull() ? nullptr : maybePhoto.get();
|
||||
|
||||
struct State final {
|
||||
std::shared_ptr<Data::PhotoMedia> photoMedia;
|
||||
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
|
||||
|
||||
std::optional<MTP::Sender> api;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
rpl::variable<bool> loading;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
|
||||
Ui::AddSkip(content, st::confirmInvitePhotoTop);
|
||||
const auto userpicWrap = content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::RpWidget>(content)));
|
||||
const auto userpic = userpicWrap->entity();
|
||||
const auto photoSize = st::confirmInvitePhotoSize;
|
||||
userpic->resize(Size(photoSize));
|
||||
const auto options = Images::Option::RoundCircle;
|
||||
userpic->paintRequest(
|
||||
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
|
||||
auto p = QPainter(userpic);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
}, userpicWrap->lifetime());
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (photo) {
|
||||
state->photoMedia = photo->createMediaView();
|
||||
state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin());
|
||||
if (!state->photoMedia->image(Data::PhotoSize::Small)) {
|
||||
session->downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
userpic->update();
|
||||
}, userpicWrap->entity()->lifetime());
|
||||
}
|
||||
} else {
|
||||
state->photoEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||
Ui::EmptyUserpic::UserpicColor(0),
|
||||
name);
|
||||
}
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWideWidth - photoSize,
|
||||
photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_channel_invite_subscription_title(),
|
||||
st::inviteLinkSubscribeBoxTitle)));
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_channel_invite_subscription_about(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(name)),
|
||||
lt_price,
|
||||
tr::lng_credits_summary_options_credits(
|
||||
lt_count,
|
||||
rpl::single(amount) | tr::to_count(),
|
||||
Ui::Text::Bold),
|
||||
Ui::Text::WithEntities),
|
||||
st::inviteLinkSubscribeBoxAbout)));
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_channel_invite_subscription_terms(
|
||||
lt_link,
|
||||
rpl::combine(
|
||||
tr::lng_paid_react_agree_link(),
|
||||
tr::lng_group_invite_subscription_about_url()
|
||||
) | rpl::map([](const QString &text, const QString &url) {
|
||||
return Ui::Text::Link(text, url);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::inviteLinkSubscribeBoxTerms)));
|
||||
|
||||
{
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
content,
|
||||
session->credits().balanceValue(),
|
||||
true);
|
||||
session->credits().load(true);
|
||||
|
||||
rpl::combine(
|
||||
balance->sizeValue(),
|
||||
content->sizeValue()
|
||||
) | rpl::start_with_next([=](const QSize &, const QSize &) {
|
||||
balance->moveToRight(
|
||||
st::creditsHistoryRightSkip * 2,
|
||||
st::creditsHistoryRightSkip);
|
||||
balance->update();
|
||||
}, balance->lifetime());
|
||||
}
|
||||
|
||||
const auto sendCredits = [=, weak = Ui::MakeWeak(box)] {
|
||||
const auto show = box->uiShow();
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->api->request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(formId),
|
||||
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
state->api = std::nullopt;
|
||||
state->loading.force_assign(false);
|
||||
result.match([&](const MTPDpayments_paymentResult &data) {
|
||||
session->api().applyUpdates(data.vupdates());
|
||||
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
|
||||
});
|
||||
if (weak) {
|
||||
box->closeBox();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto id = error.type();
|
||||
if (weak) {
|
||||
state->api = std::nullopt;
|
||||
}
|
||||
show->showToast(id);
|
||||
state->loading.force_assign(false);
|
||||
}).send();
|
||||
if (state->saveButton) {
|
||||
state->saveButton->resizeToWidth(buttonWidth);
|
||||
}
|
||||
};
|
||||
|
||||
auto confirmText = tr::lng_channel_invite_subscription_button();
|
||||
state->saveButton = box->addButton(std::move(confirmText), [=] {
|
||||
if (state->api) {
|
||||
return;
|
||||
}
|
||||
state->api.emplace(&session->mtp());
|
||||
state->loading.force_assign(true);
|
||||
|
||||
const auto done = [=](Settings::SmallBalanceResult result) {
|
||||
if (result == Settings::SmallBalanceResult::Success
|
||||
|| result == Settings::SmallBalanceResult::Already) {
|
||||
sendCredits();
|
||||
} else {
|
||||
state->api = std::nullopt;
|
||||
state->loading.force_assign(false);
|
||||
}
|
||||
};
|
||||
Settings::MaybeRequestBalanceIncrease(
|
||||
Main::MakeSessionShow(box->uiShow(), session),
|
||||
amount,
|
||||
Settings::SmallBalanceSubscription{ .name = name },
|
||||
done);
|
||||
});
|
||||
|
||||
if (const auto saveButton = state->saveButton) {
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
saveButton,
|
||||
saveButton->height() / 2,
|
||||
&st::editStickerSetNameLoading);
|
||||
AddChildToWidgetCenter(saveButton, loadingAnimation);
|
||||
loadingAnimation->showOn(
|
||||
state->loading.value() | rpl::map(rpl::mappers::_1));
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CheckChatInvite(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash,
|
||||
ChannelData *invitePeekChannel) {
|
||||
ChannelData *invitePeekChannel,
|
||||
Fn<void()> loaded) {
|
||||
const auto session = &controller->session();
|
||||
const auto weak = base::make_weak(controller);
|
||||
session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) {
|
||||
@@ -114,6 +352,9 @@ void CheckChatInvite(
|
||||
if (!strong) {
|
||||
return;
|
||||
}
|
||||
if (loaded) {
|
||||
loaded();
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
const auto show = [&](not_null<PeerData*> chat) {
|
||||
const auto way = Window::SectionShow::Way::Forward;
|
||||
@@ -125,11 +366,23 @@ void CheckChatInvite(
|
||||
};
|
||||
result.match([=](const MTPDchatInvite &data) {
|
||||
const auto isGroup = !data.is_broadcast();
|
||||
const auto box = strong->show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
[=] { SubmitChatInvite(weak, session, hash, isGroup); }));
|
||||
const auto hasPricing = !!data.vsubscription_pricing();
|
||||
if (hasPricing && !data.vsubscription_form_id()) {
|
||||
strong->uiShow()->showToast(
|
||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||
return;
|
||||
}
|
||||
const auto box = hasPricing
|
||||
? strong->show(Box(
|
||||
ConfirmSubscriptionBox,
|
||||
session,
|
||||
hash,
|
||||
&data))
|
||||
: strong->show(Box<ConfirmInviteBox>(
|
||||
session,
|
||||
data,
|
||||
invitePeekChannel,
|
||||
[=] { SubmitChatInvite(weak, session, hash, isGroup); }));
|
||||
if (invitePeekChannel) {
|
||||
box->boxClosing(
|
||||
) | rpl::filter([=] {
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace Api {
|
||||
void CheckChatInvite(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &hash,
|
||||
ChannelData *invitePeekChannel = nullptr);
|
||||
ChannelData *invitePeekChannel = nullptr,
|
||||
Fn<void()> loaded = nullptr);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ struct SendOptions {
|
||||
bool invertCaption = false;
|
||||
bool hideViaBot = false;
|
||||
crl::time ttlSeconds = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendOptions &,
|
||||
const SendOptions &) = default;
|
||||
};
|
||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||
|
||||
@@ -52,6 +56,10 @@ struct SendAction {
|
||||
MsgId replaceMediaOf = 0;
|
||||
|
||||
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
|
||||
|
||||
friend inline bool operator==(
|
||||
const SendAction &,
|
||||
const SendAction &) = default;
|
||||
};
|
||||
|
||||
struct MessageToSend {
|
||||
|
||||
@@ -94,31 +94,70 @@ constexpr auto kTransactionsLimit = 100;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.subscriptionUntil = tl.data().vsubscription_period()
|
||||
? base::unixtime::parse(base::unixtime::now()
|
||||
+ tl.data().vsubscription_period()->v)
|
||||
: QDateTime(),
|
||||
.successDate = tl.data().vtransaction_date()
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::SubscriptionEntry SubscriptionFromTL(
|
||||
const MTPStarsSubscription &tl) {
|
||||
return Data::SubscriptionEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.inviteHash = qs(tl.data().vchat_invite_hash().value_or_empty()),
|
||||
.until = base::unixtime::parse(tl.data().vuntil_date().v),
|
||||
.subscription = Data::PeerSubscription{
|
||||
.credits = tl.data().vpricing().data().vamount().v,
|
||||
.period = tl.data().vpricing().data().vperiod().v,
|
||||
},
|
||||
.barePeerId = peerFromMTP(tl.data().vpeer()).value,
|
||||
.cancelled = tl.data().is_canceled(),
|
||||
.expired = (base::unixtime::now() > tl.data().vuntil_date().v),
|
||||
.canRefulfill = tl.data().is_can_refulfill(),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::CreditsStatusSlice StatusFromTL(
|
||||
const MTPpayments_StarsStatus &status,
|
||||
not_null<PeerData*> peer) {
|
||||
peer->owner().processUsers(status.data().vusers());
|
||||
peer->owner().processChats(status.data().vchats());
|
||||
const auto &data = status.data();
|
||||
peer->owner().processUsers(data.vusers());
|
||||
peer->owner().processChats(data.vchats());
|
||||
auto entries = std::vector<Data::CreditsHistoryEntry>();
|
||||
if (const auto history = data.vhistory()) {
|
||||
entries.reserve(history->v.size());
|
||||
for (const auto &tl : history->v) {
|
||||
entries.push_back(HistoryFromTL(tl, peer));
|
||||
}
|
||||
}
|
||||
auto subscriptions = std::vector<Data::SubscriptionEntry>();
|
||||
if (const auto history = data.vsubscriptions()) {
|
||||
subscriptions.reserve(history->v.size());
|
||||
for (const auto &tl : history->v) {
|
||||
subscriptions.push_back(SubscriptionFromTL(tl));
|
||||
}
|
||||
}
|
||||
return Data::CreditsStatusSlice{
|
||||
.list = ranges::views::all(
|
||||
status.data().vhistory().v
|
||||
) | ranges::views::transform([&](const MTPStarsTransaction &tl) {
|
||||
return HistoryFromTL(tl, peer);
|
||||
}) | ranges::to_vector,
|
||||
.list = std::move(entries),
|
||||
.subscriptions = std::move(subscriptions),
|
||||
.balance = status.data().vbalance().v,
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
.tokenSubscriptions = qs(
|
||||
status.data().vsubscriptions_next_offset().value_or_empty()),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,12 +172,12 @@ 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) {
|
||||
const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0;
|
||||
|
||||
const auto optionsFromTL = [giftBarePeerId](const auto &options) {
|
||||
return ranges::views::all(
|
||||
options
|
||||
) | ranges::views::transform([=](const auto &option) {
|
||||
return Data::CreditTopupOption{
|
||||
.credits = option.data().vstars().v,
|
||||
.product = qs(
|
||||
@@ -146,12 +185,31 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
|
||||
.currency = qs(option.data().vcurrency()),
|
||||
.amount = option.data().vamount().v,
|
||||
.extended = option.data().is_extended(),
|
||||
.giftBarePeerId = giftBarePeerId,
|
||||
};
|
||||
}) | ranges::to_vector;
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
if (_peer->isSelf()) {
|
||||
using TLOption = MTPStarsTopupOption;
|
||||
_api.request(MTPpayments_GetStarsTopupOptions(
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
} else if (const auto user = _peer->asUser()) {
|
||||
using TLOption = MTPStarsGiftOption;
|
||||
_api.request(MTPpayments_GetStarsGiftOptions(
|
||||
MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id),
|
||||
user->inputUser
|
||||
)).done([=](const MTPVector<TLOption> &result) {
|
||||
_options = optionsFromTL(result.v);
|
||||
consumer.put_done();
|
||||
}).fail(fail).send();
|
||||
}
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
@@ -175,10 +233,14 @@ void CreditsStatus::request(
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
if (const auto onstack = done) {
|
||||
onstack({});
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -200,6 +262,7 @@ void CreditsHistory::request(
|
||||
}
|
||||
_requestId = _api.request(MTPpayments_GetStarsTransactions(
|
||||
MTP_flags(_flags),
|
||||
MTPstring(), // subscription_id
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
|
||||
MTP_string(token),
|
||||
MTP_int(kTransactionsLimit)
|
||||
@@ -212,6 +275,25 @@ void CreditsHistory::request(
|
||||
}).send();
|
||||
}
|
||||
|
||||
void CreditsHistory::requestSubscriptions(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPpayments_GetStarsSubscriptions(
|
||||
MTP_flags(0),
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
|
||||
MTP_string(token)
|
||||
)).done([=](const MTPpayments_StarsStatus &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::CreditTopupOptions CreditsTopupOptions::options() const {
|
||||
return _options;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ public:
|
||||
void request(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
void requestSubscriptions(
|
||||
const Data::CreditsStatusSlice::OffsetToken &token,
|
||||
Fn<void(Data::CreditsStatusSlice)> done);
|
||||
|
||||
private:
|
||||
using HistoryTL = MTPpayments_GetStarsTransactions;
|
||||
|
||||
@@ -128,7 +128,7 @@ mtpRequestId EditMessage(
|
||||
}
|
||||
|
||||
if (updateRecentStickers) {
|
||||
api->requestRecentStickersForce(true);
|
||||
api->requestSpecialStickersForce(false, false, true);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
|
||||
if constexpr (ErrorWithId<FailCallback>) {
|
||||
@@ -153,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,
|
||||
|
||||
@@ -8,12 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_invite_links.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -69,59 +69,46 @@ JoinedByLinkSlice ParseJoinedByLinkSlice(
|
||||
InviteLinks::InviteLinks(not_null<ApiWrap*> api) : _api(api) {
|
||||
}
|
||||
|
||||
void InviteLinks::create(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
performCreate(
|
||||
peer,
|
||||
std::move(done),
|
||||
false,
|
||||
label,
|
||||
expireDate,
|
||||
usageLimit,
|
||||
requestApproval);
|
||||
void InviteLinks::create(const CreateInviteLinkArgs &args) {
|
||||
performCreate(args, false);
|
||||
}
|
||||
|
||||
void InviteLinks::performCreate(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
bool revokeLegacyPermanent,
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
if (const auto i = _createCallbacks.find(peer)
|
||||
const CreateInviteLinkArgs &args,
|
||||
bool revokeLegacyPermanent) {
|
||||
if (const auto i = _createCallbacks.find(args.peer)
|
||||
; i != end(_createCallbacks)) {
|
||||
if (done) {
|
||||
i->second.push_back(std::move(done));
|
||||
if (args.done) {
|
||||
i->second.push_back(std::move(args.done));
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto &callbacks = _createCallbacks[peer];
|
||||
if (done) {
|
||||
callbacks.push_back(std::move(done));
|
||||
auto &callbacks = _createCallbacks[args.peer];
|
||||
if (args.done) {
|
||||
callbacks.push_back(std::move(args.done));
|
||||
}
|
||||
|
||||
const auto requestApproval = !args.subscription && args.requestApproval;
|
||||
using Flag = MTPmessages_ExportChatInvite::Flag;
|
||||
_api->request(MTPmessages_ExportChatInvite(
|
||||
MTP_flags((revokeLegacyPermanent
|
||||
? Flag::f_legacy_revoke_permanent
|
||||
: Flag(0))
|
||||
| (!label.isEmpty() ? Flag::f_title : Flag(0))
|
||||
| (expireDate ? Flag::f_expire_date : Flag(0))
|
||||
| ((!requestApproval && usageLimit)
|
||||
| (!args.label.isEmpty() ? Flag::f_title : Flag(0))
|
||||
| (args.expireDate ? Flag::f_expire_date : Flag(0))
|
||||
| ((!requestApproval && args.usageLimit)
|
||||
? Flag::f_usage_limit
|
||||
: Flag(0))
|
||||
| (requestApproval ? Flag::f_request_needed : Flag(0))),
|
||||
peer->input,
|
||||
MTP_int(expireDate),
|
||||
MTP_int(usageLimit),
|
||||
MTP_string(label)
|
||||
)).done([=](const MTPExportedChatInvite &result) {
|
||||
| (requestApproval ? Flag::f_request_needed : Flag(0))
|
||||
| (args.subscription ? Flag::f_subscription_pricing : Flag(0))),
|
||||
args.peer->input,
|
||||
MTP_int(args.expireDate),
|
||||
MTP_int(args.usageLimit),
|
||||
MTP_string(args.label),
|
||||
MTP_starsSubscriptionPricing(
|
||||
MTP_int(args.subscription.period),
|
||||
MTP_long(args.subscription.credits))
|
||||
)).done([=, peer = args.peer](const MTPExportedChatInvite &result) {
|
||||
const auto callbacks = _createCallbacks.take(peer);
|
||||
const auto link = prepend(peer, peer->session().user(), result);
|
||||
if (link && callbacks) {
|
||||
@@ -129,7 +116,7 @@ void InviteLinks::performCreate(
|
||||
callback(*link);
|
||||
}
|
||||
}
|
||||
}).fail([=] {
|
||||
}).fail([=, peer = args.peer] {
|
||||
_createCallbacks.erase(peer);
|
||||
}).send();
|
||||
}
|
||||
@@ -238,6 +225,15 @@ void InviteLinks::edit(
|
||||
requestApproval);
|
||||
}
|
||||
|
||||
void InviteLinks::editTitle(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link,
|
||||
const QString &label,
|
||||
Fn<void(Link)> done) {
|
||||
performEdit(peer, admin, link, done, false, label, 0, 0, false, true);
|
||||
}
|
||||
|
||||
void InviteLinks::performEdit(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
@@ -247,7 +243,8 @@ void InviteLinks::performEdit(
|
||||
const QString &label,
|
||||
TimeId expireDate,
|
||||
int usageLimit,
|
||||
bool requestApproval) {
|
||||
bool requestApproval,
|
||||
bool editOnlyTitle) {
|
||||
const auto key = LinkKey{ peer, link };
|
||||
if (_deleteCallbacks.contains(key)) {
|
||||
return;
|
||||
@@ -272,7 +269,7 @@ void InviteLinks::performEdit(
|
||||
? Flag::f_request_needed
|
||||
: Flag(0));
|
||||
_api->request(MTPmessages_EditExportedChatInvite(
|
||||
MTP_flags(flags),
|
||||
MTP_flags(editOnlyTitle ? Flag::f_title : flags),
|
||||
peer->input,
|
||||
MTP_string(link),
|
||||
MTP_int(expireDate),
|
||||
@@ -344,7 +341,7 @@ void InviteLinks::revokePermanent(
|
||||
} else if (!admin->isSelf()) {
|
||||
crl::on_main(&peer->session(), done);
|
||||
} else {
|
||||
performCreate(peer, callback, true);
|
||||
performCreate({ peer, callback }, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +747,12 @@ auto InviteLinks::parse(
|
||||
return std::optional<Link>(Link{
|
||||
.link = qs(data.vlink()),
|
||||
.label = qs(data.vtitle().value_or_empty()),
|
||||
.subscription = data.vsubscription_pricing()
|
||||
? Data::PeerSubscription{
|
||||
data.vsubscription_pricing()->data().vamount().v,
|
||||
data.vsubscription_pricing()->data().vperiod().v,
|
||||
}
|
||||
: Data::PeerSubscription(),
|
||||
.admin = peer->session().data().user(data.vadmin_id()),
|
||||
.date = data.vdate().v,
|
||||
.startDate = data.vstart_date().value_or_empty(),
|
||||
|
||||
@@ -9,11 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class ApiWrap;
|
||||
|
||||
#include "data/data_subscriptions.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct InviteLink {
|
||||
QString link;
|
||||
QString label;
|
||||
Data::PeerSubscription subscription;
|
||||
not_null<UserData*> admin;
|
||||
TimeId date = 0;
|
||||
TimeId startDate = 0;
|
||||
@@ -53,6 +56,16 @@ struct InviteLinkUpdate {
|
||||
not_null<PeerData*> peer,
|
||||
const MTPmessages_ChatInviteImporters &slice);
|
||||
|
||||
struct CreateInviteLinkArgs {
|
||||
not_null<PeerData*> peer;
|
||||
Fn<void(InviteLink)> done = nullptr;
|
||||
QString label;
|
||||
TimeId expireDate = 0;
|
||||
int usageLimit = 0;
|
||||
bool requestApproval = false;
|
||||
Data::PeerSubscription subscription;
|
||||
};
|
||||
|
||||
class InviteLinks final {
|
||||
public:
|
||||
explicit InviteLinks(not_null<ApiWrap*> api);
|
||||
@@ -61,13 +74,7 @@ public:
|
||||
using Links = PeerInviteLinks;
|
||||
using Update = InviteLinkUpdate;
|
||||
|
||||
void create(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done = nullptr,
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
void create(const CreateInviteLinkArgs &args);
|
||||
void edit(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
@@ -77,6 +84,12 @@ public:
|
||||
int usageLimit,
|
||||
bool requestApproval,
|
||||
Fn<void(Link)> done = nullptr);
|
||||
void editTitle(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
const QString &link,
|
||||
const QString &label,
|
||||
Fn<void(Link)> done = nullptr);
|
||||
void revoke(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<UserData*> admin,
|
||||
@@ -187,15 +200,11 @@ private:
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
bool requestApproval = false,
|
||||
bool editOnlyTitle = false);
|
||||
void performCreate(
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(Link)> done,
|
||||
bool revokeLegacyPermanent,
|
||||
const QString &label = QString(),
|
||||
TimeId expireDate = 0,
|
||||
int usageLimit = 0,
|
||||
bool requestApproval = false);
|
||||
const CreateInviteLinkArgs &args,
|
||||
bool revokeLegacyPermanent);
|
||||
|
||||
void requestJoinedFirstSlice(LinkKey key);
|
||||
[[nodiscard]] std::optional<JoinedByLinkSlice> lookupJoinedFirstSlice(
|
||||
|
||||
@@ -36,7 +36,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
||||
MTP_double(document->duration() / 1000.),
|
||||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint())); // preload_prefix_size
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
||||
@@ -39,9 +39,9 @@ namespace {
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::SubscriptionOptions GiftCodesFromTL(
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions GiftCodesFromTL(
|
||||
const QVector<MTPPremiumGiftCodeOption> &tlOptions) {
|
||||
auto options = SubscriptionOptionsFromTL(tlOptions);
|
||||
auto options = PremiumSubscriptionOptionsFromTL(tlOptions);
|
||||
for (auto i = 0; i < options.size(); i++) {
|
||||
const auto &tlOption = tlOptions[i].data();
|
||||
const auto perUserText = Ui::FillAmountAndCurrency(
|
||||
@@ -143,7 +143,7 @@ void Premium::reloadPromo() {
|
||||
const auto &data = result.data();
|
||||
_session->data().processUsers(data.vusers());
|
||||
|
||||
_subscriptionOptions = SubscriptionOptionsFromTL(
|
||||
_subscriptionOptions = PremiumSubscriptionOptionsFromTL(
|
||||
data.vperiod_options().v);
|
||||
for (const auto &option : data.vperiod_options().v) {
|
||||
if (option.data().vmonths().v == 1) {
|
||||
@@ -372,7 +372,7 @@ void Premium::resolveGiveawayInfo(
|
||||
}).send();
|
||||
}
|
||||
|
||||
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
const Data::PremiumSubscriptionOptions &Premium::subscriptionOptions() const {
|
||||
return _subscriptionOptions;
|
||||
}
|
||||
|
||||
@@ -547,7 +547,7 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
||||
};
|
||||
}
|
||||
|
||||
Data::SubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
return it->second;
|
||||
|
||||
@@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class History;
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
Fn<void(GiveawayInfo)> done);
|
||||
|
||||
[[nodiscard]] auto subscriptionOptions() const
|
||||
-> const Data::SubscriptionOptions &;
|
||||
-> const Data::PremiumSubscriptionOptions &;
|
||||
|
||||
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
|
||||
void resolvePremiumRequired(not_null<UserData*> user);
|
||||
@@ -156,7 +156,7 @@ private:
|
||||
MsgId _giveawayInfoMessageId = 0;
|
||||
Fn<void(GiveawayInfo)> _giveawayInfoDone;
|
||||
|
||||
Data::SubscriptionOptions _subscriptionOptions;
|
||||
Data::PremiumSubscriptionOptions _subscriptionOptions;
|
||||
|
||||
rpl::event_stream<> _somePremiumRequiredResolved;
|
||||
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
|
||||
@@ -170,7 +170,7 @@ public:
|
||||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::SubscriptionOptions options(int amount);
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
|
||||
@@ -200,8 +200,9 @@ private:
|
||||
int quantity = 0;
|
||||
};
|
||||
using Amount = int;
|
||||
using PremiumSubscriptionOptions = Data::PremiumSubscriptionOptions;
|
||||
const not_null<PeerData*> _peer;
|
||||
base::flat_map<Amount, Data::SubscriptionOptions> _subscriptionOptions;
|
||||
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Api {
|
||||
|
||||
constexpr auto kDiscountDivider = 1.;
|
||||
|
||||
Data::SubscriptionOption CreateSubscriptionOption(
|
||||
Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
int64 amount,
|
||||
|
||||
@@ -7,11 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
[[nodiscard]] Data::SubscriptionOption CreateSubscriptionOption(
|
||||
[[nodiscard]] Data::PremiumSubscriptionOption CreateSubscriptionOption(
|
||||
int months,
|
||||
int monthlyAmount,
|
||||
int64 amount,
|
||||
@@ -19,22 +19,22 @@ namespace Api {
|
||||
const QString &botUrl);
|
||||
|
||||
template<typename Option>
|
||||
[[nodiscard]] Data::SubscriptionOptions SubscriptionOptionsFromTL(
|
||||
const QVector<Option> &tlOptions) {
|
||||
if (tlOptions.isEmpty()) {
|
||||
[[nodiscard]] auto PremiumSubscriptionOptionsFromTL(
|
||||
const QVector<Option> &tlOpts) -> Data::PremiumSubscriptionOptions {
|
||||
if (tlOpts.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto result = Data::SubscriptionOptions();
|
||||
auto result = Data::PremiumSubscriptionOptions();
|
||||
const auto monthlyAmount = [&] {
|
||||
const auto &min = ranges::min_element(
|
||||
tlOptions,
|
||||
tlOpts,
|
||||
ranges::less(),
|
||||
[](const Option &o) { return o.data().vamount().v; }
|
||||
)->data();
|
||||
return min.vamount().v / float64(min.vmonths().v);
|
||||
}();
|
||||
result.reserve(tlOptions.size());
|
||||
for (const auto &tlOption : tlOptions) {
|
||||
result.reserve(tlOpts.size());
|
||||
for (const auto &tlOption : tlOpts) {
|
||||
const auto &option = tlOption.data();
|
||||
auto botUrl = QString();
|
||||
if constexpr (!std::is_same_v<Option, MTPPremiumGiftCodeOption>) {
|
||||
|
||||
@@ -41,14 +41,14 @@ void InnerFillMessagePostFlags(
|
||||
const SendOptions &options,
|
||||
not_null<PeerData*> peer,
|
||||
MessageFlags &flags) {
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
if (ShouldSendSilent(peer, options)) {
|
||||
flags |= MessageFlag::Silent;
|
||||
}
|
||||
if (!anonymousPost || options.sendAs) {
|
||||
if (!peer->amAnonymous()) {
|
||||
flags |= MessageFlag::HasFromId;
|
||||
return;
|
||||
} else if (peer->asMegagroup()) {
|
||||
}
|
||||
const auto channel = peer->asBroadcast();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
flags |= MessageFlag::Post;
|
||||
@@ -57,11 +57,84 @@ void InnerFillMessagePostFlags(
|
||||
return;
|
||||
}
|
||||
flags |= MessageFlag::HasViews;
|
||||
if (peer->asChannel()->addsSignature()) {
|
||||
if (channel->addsSignature()) {
|
||||
flags |= MessageFlag::HasPostAuthor;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -362,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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRefreshAppConfigTimeout = 3 * crl::time(1000);
|
||||
constexpr auto kRefreshAppConfigTimeout = crl::time(1);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -24,19 +24,40 @@ SensitiveContent::SensitiveContent(not_null<ApiWrap*> api)
|
||||
, _appConfigReloadTimer([=] { _session->appConfig().refresh(); }) {
|
||||
}
|
||||
|
||||
void SensitiveContent::reload() {
|
||||
if (_requestId) {
|
||||
void SensitiveContent::preload() {
|
||||
if (!_loaded) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
void SensitiveContent::reload(bool force) {
|
||||
if (_loadRequestId) {
|
||||
if (force) {
|
||||
_loadPending = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPaccount_GetContentSettings(
|
||||
_loaded = true;
|
||||
_loadRequestId = _api.request(MTPaccount_GetContentSettings(
|
||||
)).done([=](const MTPaccount_ContentSettings &result) {
|
||||
_requestId = 0;
|
||||
result.match([&](const MTPDaccount_contentSettings &data) {
|
||||
_enabled = data.is_sensitive_enabled();
|
||||
_canChange = data.is_sensitive_can_change();
|
||||
});
|
||||
_loadRequestId = 0;
|
||||
const auto &data = result.data();
|
||||
const auto enabled = data.is_sensitive_enabled();
|
||||
const auto canChange = data.is_sensitive_can_change();
|
||||
const auto changed = (_enabled.current() != enabled)
|
||||
|| (_canChange.current() != canChange);
|
||||
if (changed) {
|
||||
_enabled = enabled;
|
||||
_canChange = canChange;
|
||||
}
|
||||
if (base::take(_appConfigReloadForce) || changed) {
|
||||
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
|
||||
}
|
||||
if (base::take(_loadPending)) {
|
||||
reload();
|
||||
}
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
_loadRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
@@ -57,17 +78,24 @@ void SensitiveContent::update(bool enabled) {
|
||||
return;
|
||||
}
|
||||
using Flag = MTPaccount_SetContentSettings::Flag;
|
||||
_api.request(_requestId).cancel();
|
||||
_requestId = _api.request(MTPaccount_SetContentSettings(
|
||||
_api.request(_saveRequestId).cancel();
|
||||
if (const auto load = base::take(_loadRequestId)) {
|
||||
_api.request(load).cancel();
|
||||
_loadPending = true;
|
||||
}
|
||||
const auto finish = [=] {
|
||||
_saveRequestId = 0;
|
||||
if (base::take(_loadPending)) {
|
||||
_appConfigReloadForce = true;
|
||||
reload(true);
|
||||
} else {
|
||||
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
|
||||
}
|
||||
};
|
||||
_saveRequestId = _api.request(MTPaccount_SetContentSettings(
|
||||
MTP_flags(enabled ? Flag::f_sensitive_enabled : Flag(0))
|
||||
)).done([=] {
|
||||
_requestId = 0;
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
)).done(finish).fail(finish).send();
|
||||
_enabled = enabled;
|
||||
|
||||
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -22,7 +22,8 @@ class SensitiveContent final {
|
||||
public:
|
||||
explicit SensitiveContent(not_null<ApiWrap*> api);
|
||||
|
||||
void reload();
|
||||
void preload();
|
||||
void reload(bool force = false);
|
||||
void update(bool enabled);
|
||||
|
||||
[[nodiscard]] bool enabledCurrent() const;
|
||||
@@ -32,10 +33,14 @@ public:
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
MTP::Sender _api;
|
||||
mtpRequestId _requestId = 0;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
mtpRequestId _saveRequestId = 0;
|
||||
rpl::variable<bool> _enabled = false;
|
||||
rpl::variable<bool> _canChange = false;
|
||||
base::Timer _appConfigReloadTimer;
|
||||
bool _appConfigReloadForce = false;
|
||||
bool _loadPending = false;
|
||||
bool _loaded = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/top_peers.h"
|
||||
#include "data/notify/data_notify_settings.h"
|
||||
@@ -2618,7 +2619,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
|
||||
case mtpc_updateStarsBalance: {
|
||||
const auto &data = update.c_updateStarsBalance();
|
||||
_session->setCredits(data.vbalance().v);
|
||||
_session->credits().apply(data);
|
||||
} break;
|
||||
|
||||
}
|
||||
|
||||
@@ -2585,7 +2585,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
|
||||
void ApiWrap::updateStickers() {
|
||||
const auto now = crl::now();
|
||||
requestStickers(now);
|
||||
requestRecentStickers(now);
|
||||
requestRecentStickers(now, false);
|
||||
requestFavedStickers(now);
|
||||
requestFeaturedStickers(now);
|
||||
}
|
||||
@@ -2607,8 +2607,15 @@ void ApiWrap::updateCustomEmoji() {
|
||||
requestFeaturedEmoji(now);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersForce(bool attached) {
|
||||
requestRecentStickersWithHash(0, attached);
|
||||
void ApiWrap::requestSpecialStickersForce(
|
||||
bool faved,
|
||||
bool recent,
|
||||
bool attached) {
|
||||
if (faved) {
|
||||
requestFavedStickers(std::nullopt);
|
||||
} else if (recent || attached) {
|
||||
requestRecentStickers(std::nullopt, attached);
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::setGroupStickerSet(
|
||||
@@ -2761,18 +2768,17 @@ void ApiWrap::requestCustomEmoji(TimeId now) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickers(TimeId now, bool attached) {
|
||||
const auto needed = attached
|
||||
? _session->data().stickers().recentAttachedUpdateNeeded(now)
|
||||
: _session->data().stickers().recentUpdateNeeded(now);
|
||||
void ApiWrap::requestRecentStickers(
|
||||
std::optional<TimeId> now,
|
||||
bool attached) {
|
||||
const auto needed = !now
|
||||
? true
|
||||
: attached
|
||||
? _session->data().stickers().recentAttachedUpdateNeeded(*now)
|
||||
: _session->data().stickers().recentUpdateNeeded(*now);
|
||||
if (!needed) {
|
||||
return;
|
||||
}
|
||||
requestRecentStickersWithHash(
|
||||
Api::CountRecentStickersHash(_session, attached), attached);
|
||||
}
|
||||
|
||||
void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
const auto requestId = [=]() -> mtpRequestId & {
|
||||
return attached
|
||||
? _recentAttachedStickersUpdateRequest
|
||||
@@ -2795,7 +2801,7 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
: MTPmessages_getRecentStickers::Flags(0);
|
||||
requestId() = request(MTPmessages_GetRecentStickers(
|
||||
MTP_flags(flags),
|
||||
MTP_long(hash)
|
||||
MTP_long(now ? Api::CountRecentStickersHash(_session, attached) : 0)
|
||||
)).done([=](const MTPmessages_RecentStickers &result) {
|
||||
finish();
|
||||
|
||||
@@ -2822,13 +2828,15 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ApiWrap::requestFavedStickers(TimeId now) {
|
||||
if (!_session->data().stickers().favedUpdateNeeded(now)
|
||||
|| _favedStickersUpdateRequest) {
|
||||
return;
|
||||
void ApiWrap::requestFavedStickers(std::optional<TimeId> now) {
|
||||
if (now) {
|
||||
if (!_session->data().stickers().favedUpdateNeeded(*now)
|
||||
|| _favedStickersUpdateRequest) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers(
|
||||
MTP_long(Api::CountFavedStickersHash(_session))
|
||||
MTP_long(now ? Api::CountFavedStickersHash(_session) : 0)
|
||||
)).done([=](const MTPmessages_FavedStickers &result) {
|
||||
_session->data().stickers().setLastFavedUpdate(crl::now());
|
||||
_favedStickersUpdateRequest = 0;
|
||||
@@ -3274,9 +3282,9 @@ void ApiWrap::forwardMessages(
|
||||
if (!action.options.scheduled && !action.options.shortcutId) {
|
||||
histories.readInbox(history);
|
||||
}
|
||||
const auto sendAs = action.options.sendAs;
|
||||
const auto anonymousPost = peer->amAnonymous();
|
||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||
const auto sendAs = action.options.sendAs;
|
||||
|
||||
using SendFlag = MTPmessages_ForwardMessages::Flag;
|
||||
auto flags = MessageFlags();
|
||||
@@ -4204,7 +4212,7 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (done) done(true);
|
||||
if (updateRecentStickers) {
|
||||
requestRecentStickersForce(true);
|
||||
requestRecentStickers(std::nullopt, true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (done) done(false);
|
||||
|
||||
@@ -244,7 +244,10 @@ public:
|
||||
void updateSavedGifs();
|
||||
void updateMasks();
|
||||
void updateCustomEmoji();
|
||||
void requestRecentStickersForce(bool attached = false);
|
||||
void requestSpecialStickersForce(
|
||||
bool faved,
|
||||
bool recent,
|
||||
bool attached);
|
||||
void setGroupStickerSet(
|
||||
not_null<ChannelData*> megagroup,
|
||||
const StickerSetIdentifier &set);
|
||||
@@ -477,9 +480,10 @@ private:
|
||||
void requestStickers(TimeId now);
|
||||
void requestMasks(TimeId now);
|
||||
void requestCustomEmoji(TimeId now);
|
||||
void requestRecentStickers(TimeId now, bool attached = false);
|
||||
void requestRecentStickersWithHash(uint64 hash, bool attached = false);
|
||||
void requestFavedStickers(TimeId now);
|
||||
void requestRecentStickers(
|
||||
std::optional<TimeId> now,
|
||||
bool attached);
|
||||
void requestFavedStickers(std::optional<TimeId> now);
|
||||
void requestFeaturedStickers(TimeId now);
|
||||
void requestFeaturedEmoji(TimeId now);
|
||||
void requestSavedGifs(TimeId now);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -898,9 +898,10 @@ void GroupInfoBox::checkInviteLink() {
|
||||
channelReady();
|
||||
} else if (_createdChannel->isFullLoaded() && !_creatingInviteLink) {
|
||||
_creatingInviteLink = true;
|
||||
_createdChannel->session().api().inviteLinks().create(
|
||||
_createdChannel->session().api().inviteLinks().create({
|
||||
_createdChannel,
|
||||
crl::guard(this, [=](auto&&) { channelReady(); }));
|
||||
crl::guard(this, [=](auto&&) { channelReady(); }),
|
||||
});
|
||||
} else {
|
||||
_createdChannel->session().changes().peerUpdates(
|
||||
_createdChannel,
|
||||
@@ -1243,7 +1244,7 @@ void SetupChannelBox::mousePressEvent(QMouseEvent *e) {
|
||||
showToast(tr::lng_create_channel_link_copied(tr::now));
|
||||
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
|
||||
_creatingInviteLink = true;
|
||||
_channel->session().api().inviteLinks().create(_channel);
|
||||
_channel->session().api().inviteLinks().create({ _channel });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -25,7 +25,7 @@ DownloadPathBox::DownloadPathBox(
|
||||
, _path(Core::App().settings().downloadPath())
|
||||
, _pathBookmark(Core::App().settings().downloadPathBookmark())
|
||||
, _group(std::make_shared<Ui::RadioenumGroup<Directory>>(typeFromPath(_path)))
|
||||
, _default(Core::App().canReadDefaultDownloadPath(true)
|
||||
, _default(Core::App().canReadDefaultDownloadPath()
|
||||
? object_ptr<Ui::Radioenum<Directory>>(
|
||||
this,
|
||||
_group,
|
||||
@@ -149,7 +149,7 @@ void DownloadPathBox::setPathText(const QString &text) {
|
||||
DownloadPathBox::Directory DownloadPathBox::typeFromPath(
|
||||
const QString &path) {
|
||||
if (path.isEmpty()) {
|
||||
return Core::App().canReadDefaultDownloadPath(true)
|
||||
return Core::App().canReadDefaultDownloadPath()
|
||||
? Directory::Downloads
|
||||
: Directory::Temp;
|
||||
} else if (path == FileDialog::Tmp()) {
|
||||
|
||||
@@ -763,9 +763,6 @@ void EditMessagesPrivacyBox(
|
||||
lt_link,
|
||||
link,
|
||||
Ui::Text::WithEntities),
|
||||
.st = &st::defaultMultilineToast,
|
||||
.duration = Ui::Toast::kDefaultDuration * 2,
|
||||
.multiline = true,
|
||||
.filter = crl::guard(&controller->session(), [=](
|
||||
const ClickHandlerPtr &,
|
||||
Qt::MouseButton button) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
181
Telegram/SourceFiles/boxes/gift_credits_box.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
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/gift_credits_box.h"
|
||||
|
||||
#include "api/api_credits.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/session/session_show.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.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/label_with_custom_emoji.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_channel_earn.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void GiftCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void()> gifted) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::creditsGiftBox);
|
||||
box->setNoContentMargin(true);
|
||||
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
|
||||
|
||||
const auto content = box->setPinnedToTopContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
const auto &stUser = st::premiumGiftsUserpicButton;
|
||||
const auto userpicWrap = content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
{
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
|
||||
widget,
|
||||
false,
|
||||
Ui::Premium::MiniStars::Type::BiStars);
|
||||
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
|
||||
widget->resize(
|
||||
st::boxWidth - stUser.photoSize,
|
||||
stUser.photoSize * 2);
|
||||
content->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
widget->moveToLeft(stUser.photoSize / 2, 0);
|
||||
const auto starsRect = Rect(widget->size());
|
||||
stars->setPosition(starsRect.topLeft());
|
||||
stars->setSize(starsRect.size());
|
||||
widget->lower();
|
||||
}, widget->lifetime());
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(widget);
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
{
|
||||
Ui::AddSkip(content);
|
||||
const auto arrow = Ui::Text::SingleCustomEmoji(
|
||||
peer->owner().customEmojiManager().registerInternalEmoji(
|
||||
st::topicButtonArrow,
|
||||
st::channelEarnLearnArrowMargins,
|
||||
false));
|
||||
auto link = tr::lng_credits_box_history_entry_gift_about_link(
|
||||
lt_emoji,
|
||||
rpl::single(arrow),
|
||||
Ui::Text::RichLangValue
|
||||
) | rpl::map([](TextWithEntities text) {
|
||||
return Ui::Text::Link(
|
||||
std::move(text),
|
||||
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
|
||||
});
|
||||
content->add(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
Ui::CreateLabelWithCustomEmoji(
|
||||
content,
|
||||
tr::lng_credits_box_history_entry_gift_out_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ peer->shortName() }),
|
||||
lt_link,
|
||||
std::move(link),
|
||||
Ui::Text::RichLangValue),
|
||||
{ .session = &peer->session() },
|
||||
st::creditsBoxAbout)),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
|
||||
Settings::FillCreditOptions(
|
||||
Main::MakeSessionShow(box->uiShow(), &peer->session()),
|
||||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
}
|
||||
|
||||
void ShowGiftCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> gifted) {
|
||||
|
||||
class Controller final : public ContactsBoxController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Main::Session*> session,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
: ContactsBoxController(session)
|
||||
, _choose(std::move(choose)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
_choose(row->peer());
|
||||
}
|
||||
|
||||
private:
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
peersBox->setTitle(tr::lng_credits_gift_title());
|
||||
peersBox->addButton(tr::lng_cancel(), [=] { peersBox->closeBox(); });
|
||||
};
|
||||
|
||||
const auto show = controller->uiShow();
|
||||
auto listController = std::make_unique<Controller>(
|
||||
&controller->session(),
|
||||
[=](not_null<PeerData*> peer) {
|
||||
show->showBox(Box(GiftCreditsBox, peer, gifted));
|
||||
});
|
||||
show->showBox(
|
||||
Box<PeerListBox>(std::move(listController), std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
20
Telegram/SourceFiles/boxes/gift_credits_box.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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 Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ShowGiftCreditsBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
Fn<void()> gifted);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_media_types.h" // Data::GiveawayStart.
|
||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_subscription_option.h"
|
||||
#include "data/data_premium_subscription_option.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
@@ -66,8 +66,8 @@ namespace {
|
||||
|
||||
constexpr auto kUserpicsMax = size_t(3);
|
||||
|
||||
using GiftOption = Data::SubscriptionOption;
|
||||
using GiftOptions = Data::SubscriptionOptions;
|
||||
using GiftOption = Data::PremiumSubscriptionOption;
|
||||
using GiftOptions = Data::PremiumSubscriptionOptions;
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
@@ -75,7 +75,7 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
if (!gifts) {
|
||||
return result;
|
||||
}
|
||||
result = Api::SubscriptionOptionsFromTL(gifts->v);
|
||||
result = Api::PremiumSubscriptionOptionsFromTL(gifts->v);
|
||||
for (auto &option : result) {
|
||||
option.costPerMonth = tr::lng_premium_gift_per(
|
||||
tr::now,
|
||||
@@ -932,8 +932,8 @@ void ShowAlreadyPremiumToast(
|
||||
Ui::Text::Link(
|
||||
Ui::Text::Bold(tr::lng_gift_link_already_link(tr::now))),
|
||||
Ui::Text::WithEntities),
|
||||
.duration = 6 * crl::time(1000),
|
||||
.filter = crl::guard(navigation, shareLink),
|
||||
.duration = 6 * crl::time(1000),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1014,6 +1014,7 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
if (users.empty()) {
|
||||
show->showToast(
|
||||
tr::lng_settings_gift_premium_choose(tr::now));
|
||||
return;
|
||||
}
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
@@ -1641,6 +1642,9 @@ void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
@@ -1648,7 +1652,9 @@ void AddCreditsHistoryEntryTable(
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
if (peerId) {
|
||||
auto text = tr::lng_credits_box_history_entry_peer();
|
||||
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)) {
|
||||
@@ -1692,14 +1698,24 @@ void AddCreditsHistoryEntryTable(
|
||||
} 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));
|
||||
(entry.gift
|
||||
? tr::lng_credits_box_history_entry_peer_in
|
||||
: tr::lng_credits_box_history_entry_via)(),
|
||||
(entry.gift
|
||||
? tr::lng_credits_box_history_entry_anonymous
|
||||
: 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;
|
||||
@@ -1744,3 +1760,56 @@ void AddCreditsHistoryEntryTable(
|
||||
Ui::Text::Link(entry.successLink, entry.successLink)));
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::SubscriptionEntry &s) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(s.barePeerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_subscription_row_to(),
|
||||
controller,
|
||||
peerId);
|
||||
if (!s.until.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
s.expired
|
||||
? tr::lng_credits_subscription_row_next_none()
|
||||
: s.cancelled
|
||||
? tr::lng_credits_subscription_row_next_off()
|
||||
: tr::lng_credits_subscription_row_next_on(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(s.until))));
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubscriberEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_group_invite_joined_row_subscriber(),
|
||||
controller,
|
||||
peer->id);
|
||||
if (const auto d = base::unixtime::parse(date); !d.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_group_invite_joined_row_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(d))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Data {
|
||||
struct CreditsHistoryEntry;
|
||||
struct GiveawayStart;
|
||||
struct GiveawayResults;
|
||||
struct SubscriptionEntry;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
@@ -78,3 +79,13 @@ void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::SubscriptionEntry &s);
|
||||
void AddSubscriberEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
@@ -94,7 +94,7 @@ void MaxInviteBox::mousePressEvent(QMouseEvent *e) {
|
||||
showToast(tr::lng_create_channel_link_copied(tr::now));
|
||||
} else if (_channel->isFullLoaded() && !_creatingInviteLink) {
|
||||
_creatingInviteLink = true;
|
||||
_channel->session().api().inviteLinks().create(_channel);
|
||||
_channel->session().api().inviteLinks().create({ _channel });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,10 +517,13 @@ void InviteForbiddenController::send(
|
||||
};
|
||||
const auto sendForFull = [=] {
|
||||
if (!sendLink()) {
|
||||
_peer->session().api().inviteLinks().create(_peer, [=](auto) {
|
||||
if (!sendLink()) {
|
||||
close();
|
||||
}
|
||||
_peer->session().api().inviteLinks().create({
|
||||
_peer,
|
||||
[=](auto) {
|
||||
if (!sendLink()) {
|
||||
close();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -318,6 +318,7 @@ private:
|
||||
std::optional<bool> hiddenPreHistory;
|
||||
std::optional<bool> forum;
|
||||
std::optional<bool> signatures;
|
||||
std::optional<bool> signatureProfiles;
|
||||
std::optional<bool> noForwards;
|
||||
std::optional<bool> joinToWrite;
|
||||
std::optional<bool> requestToJoin;
|
||||
@@ -408,6 +409,7 @@ private:
|
||||
std::optional<EditPeerTypeData> _typeDataSavedValue;
|
||||
std::optional<bool> _forumSavedValue;
|
||||
std::optional<bool> _signaturesSavedValue;
|
||||
std::optional<bool> _signatureProfilesSavedValue;
|
||||
|
||||
const not_null<Window::SessionNavigation*> _navigation;
|
||||
const not_null<Ui::BoxContent*> _box;
|
||||
@@ -620,13 +622,17 @@ object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
|
||||
local.x() + emojiToggle->width() * 3);
|
||||
};
|
||||
|
||||
base::install_event_filter(container, [=](not_null<QEvent*> event) {
|
||||
const auto type = event->type();
|
||||
if (type == QEvent::Move || type == QEvent::Resize) {
|
||||
crl::on_main(field, [=] { updateEmojiPanelGeometry(); });
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
field->lifetime().make_state<base::unique_qptr<QObject>>([&] {
|
||||
return base::install_event_filter(container, [=](
|
||||
not_null<QEvent*> event) {
|
||||
const auto type = event->type();
|
||||
if (type == QEvent::Move || type == QEvent::Resize) {
|
||||
crl::on_main(field, [=] { updateEmojiPanelGeometry(); });
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}());
|
||||
|
||||
field->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto &p = st::editPeerTitleEmojiPosition;
|
||||
@@ -1051,17 +1057,50 @@ void Controller::fillSignaturesButton() {
|
||||
return;
|
||||
}
|
||||
|
||||
AddButtonWithText(
|
||||
const auto signs = AddButtonWithText(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_edit_sign_messages(),
|
||||
rpl::single(QString()),
|
||||
[] {},
|
||||
{ &st::menuIconSigned }
|
||||
)->toggleOn(rpl::single(channel->addsSignature())
|
||||
)->toggledValue(
|
||||
)->toggleOn(rpl::single(channel->addsSignature()));
|
||||
|
||||
const auto profiles = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_edit_sign_profiles(),
|
||||
rpl::single(QString()),
|
||||
[] {},
|
||||
st::manageGroupTopButtonWithText,
|
||||
{ &st::menuIconProfile })));
|
||||
profiles->toggleOn(signs->toggledValue());
|
||||
profiles->finishAnimating();
|
||||
|
||||
profiles->entity()->toggleOn(rpl::single(
|
||||
channel->addsSignature() && channel->signatureProfiles()
|
||||
))->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
_signatureProfilesSavedValue = toggled;
|
||||
}, profiles->entity()->lifetime());
|
||||
|
||||
signs->toggledValue(
|
||||
) | rpl::start_with_next([=](bool toggled) {
|
||||
_signaturesSavedValue = toggled;
|
||||
if (!toggled) {
|
||||
_signatureProfilesSavedValue = false;
|
||||
}
|
||||
}, _controls.buttonsLayout->lifetime());
|
||||
|
||||
Ui::AddSkip(_controls.buttonsLayout);
|
||||
Ui::AddDividerText(
|
||||
_controls.buttonsLayout,
|
||||
rpl::conditional(
|
||||
signs->toggledValue(),
|
||||
tr::lng_edit_sign_profiles_about(Ui::Text::WithEntities),
|
||||
tr::lng_edit_sign_messages_about(Ui::Text::WithEntities)));
|
||||
Ui::AddSkip(_controls.buttonsLayout);
|
||||
}
|
||||
|
||||
void Controller::fillHistoryVisibilityButton() {
|
||||
@@ -1219,11 +1258,9 @@ void Controller::fillManageSection() {
|
||||
}
|
||||
if (canEditSignatures) {
|
||||
fillSignaturesButton();
|
||||
}
|
||||
if (canEditPreHistoryHidden
|
||||
} else if (canEditPreHistoryHidden
|
||||
|| canEditForum
|
||||
|| canEditColorIndex
|
||||
|| canEditSignatures
|
||||
//|| canEditInviteLinks
|
||||
|| canViewOrEditLinkedChat
|
||||
|| canEditType) {
|
||||
@@ -1779,10 +1816,14 @@ bool Controller::validateForum(Saving &to) const {
|
||||
}
|
||||
|
||||
bool Controller::validateSignatures(Saving &to) const {
|
||||
Expects(_signaturesSavedValue.has_value()
|
||||
== _signatureProfilesSavedValue.has_value());
|
||||
|
||||
if (!_signaturesSavedValue.has_value()) {
|
||||
return true;
|
||||
}
|
||||
to.signatures = _signaturesSavedValue;
|
||||
to.signatureProfiles = _signatureProfilesSavedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2215,15 +2256,27 @@ void Controller::saveForum() {
|
||||
}
|
||||
|
||||
void Controller::saveSignatures() {
|
||||
Expects(_savingData.signatures.has_value()
|
||||
== _savingData.signatureProfiles.has_value());
|
||||
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!_savingData.signatures
|
||||
|| !channel
|
||||
|| *_savingData.signatures == channel->addsSignature()) {
|
||||
|| ((*_savingData.signatures == channel->addsSignature())
|
||||
&& (*_savingData.signatureProfiles
|
||||
== channel->signatureProfiles()))) {
|
||||
return continueSave();
|
||||
}
|
||||
using Flag = MTPchannels_ToggleSignatures::Flag;
|
||||
_api.request(MTPchannels_ToggleSignatures(
|
||||
channel->inputChannel,
|
||||
MTP_bool(*_savingData.signatures)
|
||||
MTP_flags(Flag()
|
||||
| (*_savingData.signatures
|
||||
? Flag::f_signatures_enabled
|
||||
: Flag())
|
||||
| (*_savingData.signatureProfiles
|
||||
? Flag::f_profiles_enabled
|
||||
: Flag())),
|
||||
channel->inputChannel
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
channel->session().api().applyUpdates(result);
|
||||
continueSave();
|
||||
|
||||
@@ -7,49 +7,58 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/peers/edit_peer_invite_link.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "main/main_session.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.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_helpers.h" // GetErrorTextForSending.
|
||||
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "qr/qr_generate.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/edit_invite_link.h"
|
||||
#include "ui/boxes/edit_invite_link_session.h"
|
||||
#include "ui/controls/invite_link_buttons.h"
|
||||
#include "ui/controls/invite_link_label.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/abstract_button.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/boxes/edit_invite_link.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "history/view/history_view_group_call_bar.h" // GenerateUserpics...
|
||||
#include "history/history_item_helpers.h" // GetErrorTextForSending.
|
||||
#include "history/history.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "mainwindow.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "qr/qr_generate.h"
|
||||
#include "intro/intro_qr.h" // TelegramLogoImage
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtCore/QMimeData>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -72,6 +81,66 @@ void ShowPeerInfoSync(not_null<PeerData*> peer) {
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptionRow final : public PeerListRow {
|
||||
public:
|
||||
SubscriptionRow(
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date,
|
||||
Data::PeerSubscription subscription);
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) override;
|
||||
|
||||
private:
|
||||
std::optional<Settings::SubscriptionRightLabel> _rightLabel;
|
||||
|
||||
};
|
||||
|
||||
SubscriptionRow::SubscriptionRow(
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date,
|
||||
Data::PeerSubscription subscription)
|
||||
: PeerListRow(peer) {
|
||||
if (subscription) {
|
||||
_rightLabel = Settings::PaintSubscriptionRightLabelCallback(
|
||||
&peer->session(),
|
||||
st::peerListBoxItem,
|
||||
subscription.credits);
|
||||
}
|
||||
setCustomStatus(
|
||||
tr::lng_group_invite_joined_status(
|
||||
tr::now,
|
||||
lt_date,
|
||||
langDayOfMonthFull(base::unixtime::parse(date).date())));
|
||||
}
|
||||
|
||||
QSize SubscriptionRow::rightActionSize() const {
|
||||
return _rightLabel ? _rightLabel->size : QSize();
|
||||
}
|
||||
|
||||
QMargins SubscriptionRow::rightActionMargins() const {
|
||||
return QMargins(0, 0, st::boxRowPadding.right(), 0);
|
||||
}
|
||||
|
||||
void SubscriptionRow::rightActionPaint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (_rightLabel) {
|
||||
return _rightLabel->draw(p, x, y, st::peerListBoxItem.height);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestedRow final : public PeerListRow {
|
||||
public:
|
||||
RequestedRow(not_null<PeerData*> peer, TimeId date);
|
||||
@@ -604,6 +673,101 @@ void Controller::setupAboveJoinedWidget() {
|
||||
if (revoked || !current.permanent) {
|
||||
addHeaderBlock(container);
|
||||
}
|
||||
if (current.subscription) {
|
||||
const auto &st = st::peerListSingleRow.item;
|
||||
Ui::AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_group_invite_subscription_info_subtitle());
|
||||
const auto widget = container->add(
|
||||
CreateSkipWidget(container, st.height));
|
||||
const auto name = widget->lifetime().make_state<Ui::Text::String>();
|
||||
auto userpic = QImage(
|
||||
Size(st.photoSize) * style::DevicePixelRatio(),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
{
|
||||
constexpr auto kGreenIndex = 3;
|
||||
const auto colors = Ui::EmptyUserpic::UserpicColor(kGreenIndex);
|
||||
auto emptyUserpic = Ui::EmptyUserpic(colors, {});
|
||||
|
||||
userpic.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
userpic.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&userpic);
|
||||
emptyUserpic.paintCircle(p, 0, 0, st.photoSize, st.photoSize);
|
||||
|
||||
auto svg = QSvgRenderer(u":/gui/links_subscription.svg"_q);
|
||||
const auto size = st.photoSize / 4. * 3.;
|
||||
const auto r = QRectF(
|
||||
(st.photoSize - size) / 2.,
|
||||
(st.photoSize - size) / 2.,
|
||||
size,
|
||||
size);
|
||||
p.setPen(st::historyPeerUserpicFg);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
svg.render(&p, r);
|
||||
}
|
||||
name->setMarkedText(
|
||||
st.nameStyle,
|
||||
current.usage
|
||||
? tr::lng_group_invite_subscription_info_title(
|
||||
tr::now,
|
||||
lt_emoji,
|
||||
session().data().customEmojiManager().creditsEmoji(),
|
||||
lt_price,
|
||||
{ QString::number(current.subscription.credits) },
|
||||
lt_multiplier,
|
||||
TextWithEntities{ .text = QString(QChar(0x00D7)) },
|
||||
lt_total,
|
||||
{ QString::number(current.usage) },
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_group_invite_subscription_info_title_none(
|
||||
tr::now,
|
||||
lt_emoji,
|
||||
session().data().customEmojiManager().creditsEmoji(),
|
||||
lt_price,
|
||||
{ QString::number(current.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
kMarkupTextOptions,
|
||||
Core::MarkedTextContext{
|
||||
.session = &session(),
|
||||
.customEmojiRepaint = [=] { widget->update(); },
|
||||
});
|
||||
auto &lifetime = widget->lifetime();
|
||||
const auto rateValue = lifetime.make_state<rpl::variable<float64>>(
|
||||
session().credits().rateValue(_peer));
|
||||
const auto currency = u"USD"_q;
|
||||
const auto allCredits = current.subscription.credits * current.usage;
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(widget);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setPen(st.nameFg);
|
||||
name->draw(p, {
|
||||
.position = st.namePosition,
|
||||
.outerWidth = widget->width() - name->maxWidth(),
|
||||
.availableWidth = widget->width() - name->maxWidth(),
|
||||
});
|
||||
|
||||
p.drawImage(st.photoPosition, userpic);
|
||||
|
||||
const auto rate = rateValue->current();
|
||||
const auto status = (allCredits <= 0)
|
||||
? tr::lng_group_invite_no_joined(tr::now)
|
||||
: (rate > 0)
|
||||
? tr::lng_group_invite_subscription_info_about(
|
||||
tr::now,
|
||||
lt_total,
|
||||
Ui::FillAmountAndCurrency(allCredits * rate, currency))
|
||||
: QString();
|
||||
p.setPen(st.statusFg);
|
||||
p.setFont(st::contactsStatusFont);
|
||||
p.drawTextLeft(
|
||||
st.statusPosition.x(),
|
||||
st.statusPosition.y(),
|
||||
widget->width() - st.statusPosition.x(),
|
||||
status);
|
||||
}, widget->lifetime());
|
||||
}
|
||||
Ui::AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_group_invite_created_by());
|
||||
@@ -743,6 +907,11 @@ void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
|
||||
_lastUser = user;
|
||||
auto row = (_role == Role::Requested)
|
||||
? std::make_unique<RequestedRow>(user.user, user.date)
|
||||
: (_data.current().subscription)
|
||||
? std::make_unique<SubscriptionRow>(
|
||||
user.user,
|
||||
user.date,
|
||||
_data.current().subscription)
|
||||
: std::make_unique<PeerListRow>(user.user);
|
||||
if (_role != Role::Requested && user.viaFilterLink) {
|
||||
row->setCustomStatus(
|
||||
@@ -760,11 +929,117 @@ void Controller::appendSlice(const Api::JoinedByLinkSlice &slice) {
|
||||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
if (!_data.current().subscription) {
|
||||
return ShowPeerInfoSync(row->peer());
|
||||
}
|
||||
const auto channel = _peer;
|
||||
const auto data = _data.current();
|
||||
const auto show = delegate()->peerListUiShow();
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto w = Core::App().findWindow(box);
|
||||
const auto controller = w ? w->sessionController() : nullptr;
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
box->setStyle(st::giveawayGiftCodeBox);
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
const auto content = box->verticalLayout();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto &stUser = st::boostReplaceUserpic;
|
||||
const auto session = &row->peer()->session();
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, channel, stUser))
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_subscription_title(),
|
||||
st::creditsBoxAboutTitle)));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto subtitle1 = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
st::creditsTopupPrice)))->entity();
|
||||
subtitle1->setMarkedText(
|
||||
tr::lng_credits_subscription_subtitle(
|
||||
tr::now,
|
||||
lt_emoji,
|
||||
session->data().customEmojiManager().creditsEmoji(),
|
||||
lt_cost,
|
||||
{ QString::number(data.subscription.credits) },
|
||||
Ui::Text::WithEntities),
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { subtitle1->update(); },
|
||||
});
|
||||
const auto subtitle2 = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
st::creditsTopupPrice)))->entity();
|
||||
session->credits().rateValue(
|
||||
channel
|
||||
) | rpl::start_with_next([=, currency = u"USD"_q](float64 rate) {
|
||||
subtitle2->setText(
|
||||
tr::lng_credits_subscriber_subtitle(
|
||||
tr::now,
|
||||
lt_total,
|
||||
Ui::FillAmountAndCurrency(
|
||||
data.subscription.credits * rate,
|
||||
currency)));
|
||||
}, subtitle2->lifetime());
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_about(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | Ui::Text::ToLink(
|
||||
tr::lng_credits_box_out_about_link(tr::now)),
|
||||
Ui::Text::WithEntities),
|
||||
st::creditsBoxAboutDivider)));
|
||||
|
||||
const auto button = box->addButton(tr::lng_box_ok(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
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());
|
||||
}));
|
||||
}
|
||||
|
||||
void Controller::rowRightActionClicked(not_null<PeerListRow*> row) {
|
||||
if (_role != Role::Requested) {
|
||||
if (_role != Role::Requested || _data.current().subscription) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListShowRowMenu(row, true);
|
||||
@@ -1251,6 +1526,8 @@ object_ptr<Ui::BoxContent> InviteLinkQrBox(
|
||||
object_ptr<Ui::BoxContent> EditLinkBox(
|
||||
not_null<PeerData*> peer,
|
||||
const Api::InviteLink &data) {
|
||||
constexpr auto kPeriod = 3600 * 24 * 30;
|
||||
constexpr auto kTestModePeriod = 300;
|
||||
const auto creating = data.link.isEmpty();
|
||||
const auto box = std::make_shared<QPointer<Ui::GenericBox>>();
|
||||
using Fields = Ui::InviteLinkFields;
|
||||
@@ -1266,13 +1543,25 @@ object_ptr<Ui::BoxContent> EditLinkBox(
|
||||
};
|
||||
if (creating) {
|
||||
Assert(data.admin->isSelf());
|
||||
peer->session().api().inviteLinks().create(
|
||||
const auto period = peer->session().isTestMode()
|
||||
? kTestModePeriod
|
||||
: kPeriod;
|
||||
peer->session().api().inviteLinks().create({
|
||||
peer,
|
||||
finish,
|
||||
result.label,
|
||||
result.expireDate,
|
||||
result.usageLimit,
|
||||
result.requestApproval);
|
||||
result.requestApproval,
|
||||
{ uint64(result.subscriptionCredits), period },
|
||||
});
|
||||
} else if (result.subscriptionCredits) {
|
||||
peer->session().api().inviteLinks().editTitle(
|
||||
peer,
|
||||
data.admin,
|
||||
result.link,
|
||||
result.label,
|
||||
finish);
|
||||
} else {
|
||||
peer->session().api().inviteLinks().edit(
|
||||
peer,
|
||||
@@ -1287,26 +1576,31 @@ object_ptr<Ui::BoxContent> EditLinkBox(
|
||||
};
|
||||
const auto isGroup = !peer->isBroadcast();
|
||||
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
|
||||
if (creating) {
|
||||
auto object = Box(Ui::CreateInviteLinkBox, isGroup, isPublic, done);
|
||||
*box = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
} else {
|
||||
auto object = Box(
|
||||
Ui::EditInviteLinkBox,
|
||||
Fields{
|
||||
.link = data.link,
|
||||
.label = data.label,
|
||||
.expireDate = data.expireDate,
|
||||
.usageLimit = data.usageLimit,
|
||||
.requestApproval = data.requestApproval,
|
||||
.isGroup = isGroup,
|
||||
.isPublic = isPublic,
|
||||
},
|
||||
done);
|
||||
*box = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
}
|
||||
auto object = Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto fill = [=] {
|
||||
return Ui::FillCreateInviteLinkSubscriptionToggle(box, peer);
|
||||
};
|
||||
if (creating) {
|
||||
Ui::CreateInviteLinkBox(box, fill, isGroup, isPublic, done);
|
||||
} else {
|
||||
Ui::EditInviteLinkBox(
|
||||
box,
|
||||
fill,
|
||||
Fields{
|
||||
.link = data.link,
|
||||
.label = data.label,
|
||||
.expireDate = data.expireDate,
|
||||
.usageLimit = data.usageLimit,
|
||||
.subscriptionCredits = int(data.subscription.credits),
|
||||
.requestApproval = data.requestApproval,
|
||||
.isGroup = isGroup,
|
||||
.isPublic = isPublic,
|
||||
},
|
||||
done);
|
||||
}
|
||||
});
|
||||
*box = Ui::MakeWeak(object.data());
|
||||
return object;
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> RevokeLinkBox(
|
||||
|
||||
@@ -7,7 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/layers/generic_box.h"
|
||||
template <typename Object>
|
||||
class object_ptr;
|
||||
|
||||
class PeerData;
|
||||
|
||||
@@ -22,6 +23,7 @@ class Session;
|
||||
namespace Ui {
|
||||
class VerticalLayout;
|
||||
class Show;
|
||||
class BoxContent;
|
||||
} // namespace Ui
|
||||
|
||||
[[nodiscard]] bool IsExpiredLink(const Api::InviteLink &data, TimeId now);
|
||||
|
||||
@@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "main/session/session_show.h"
|
||||
#include "main/main_session.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
@@ -31,6 +33,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Color {
|
||||
@@ -39,6 +43,7 @@ enum class Color {
|
||||
ExpireSoon,
|
||||
Expired,
|
||||
Revoked,
|
||||
Subscription,
|
||||
|
||||
Count,
|
||||
};
|
||||
@@ -60,8 +65,12 @@ struct InviteLinkAction {
|
||||
|
||||
class Row;
|
||||
|
||||
using SubscriptionRightLabel = Settings::SubscriptionRightLabel;
|
||||
|
||||
class RowDelegate {
|
||||
public:
|
||||
virtual std::optional<SubscriptionRightLabel> rightLabel(
|
||||
int credits) const = 0;
|
||||
virtual void rowUpdateRow(not_null<Row*> row) = 0;
|
||||
virtual void rowPaintIcon(
|
||||
QPainter &p,
|
||||
@@ -91,6 +100,7 @@ public:
|
||||
bool forceRound) override;
|
||||
|
||||
QSize rightActionSize() const override;
|
||||
bool rightActionDisabled() const override;
|
||||
QMargins rightActionMargins() const override;
|
||||
void rightActionPaint(
|
||||
Painter &p,
|
||||
@@ -102,6 +112,7 @@ public:
|
||||
|
||||
private:
|
||||
const not_null<RowDelegate*> _delegate;
|
||||
std::optional<SubscriptionRightLabel> _rightLabel;
|
||||
InviteLinkData _data;
|
||||
QString _status;
|
||||
float64 _progressTillExpire = 0.;
|
||||
@@ -137,7 +148,9 @@ private:
|
||||
[[nodiscard]] Color ComputeColor(
|
||||
const InviteLinkData &link,
|
||||
float64 progress) {
|
||||
return link.revoked
|
||||
return link.subscription
|
||||
? Color::Subscription
|
||||
: link.revoked
|
||||
? Color::Revoked
|
||||
: (progress >= 1.)
|
||||
? Color::Expired
|
||||
@@ -230,11 +243,13 @@ Row::Row(
|
||||
, _data(data)
|
||||
, _progressTillExpire(ComputeProgress(data, now))
|
||||
, _color(ComputeColor(data, _progressTillExpire)) {
|
||||
_rightLabel = _delegate->rightLabel(_data.subscription.credits);
|
||||
setCustomStatus(ComputeStatus(data, now));
|
||||
}
|
||||
|
||||
void Row::update(const InviteLinkData &data, TimeId now) {
|
||||
_data = data;
|
||||
_rightLabel = _delegate->rightLabel(_data.subscription.credits);
|
||||
_progressTillExpire = ComputeProgress(data, now);
|
||||
_color = ComputeColor(data, _progressTillExpire);
|
||||
setCustomStatus(ComputeStatus(data, now));
|
||||
@@ -305,12 +320,22 @@ PaintRoundImageCallback Row::generatePaintUserpicCallback(bool forceRound) {
|
||||
}
|
||||
|
||||
QSize Row::rightActionSize() const {
|
||||
if (_rightLabel) {
|
||||
return _rightLabel->size;
|
||||
}
|
||||
return QSize(
|
||||
st::inviteLinkThreeDotsIcon.width(),
|
||||
st::inviteLinkThreeDotsIcon.height());
|
||||
}
|
||||
|
||||
bool Row::rightActionDisabled() const {
|
||||
return _rightLabel.has_value();
|
||||
}
|
||||
|
||||
QMargins Row::rightActionMargins() const {
|
||||
if (_rightLabel) {
|
||||
return QMargins(0, 0, st::boxRowPadding.right(), 0);
|
||||
}
|
||||
return QMargins(
|
||||
0,
|
||||
(st::inviteLinkList.item.height - rightActionSize().height()) / 2,
|
||||
@@ -325,6 +350,9 @@ void Row::rightActionPaint(
|
||||
int outerWidth,
|
||||
bool selected,
|
||||
bool actionSelected) {
|
||||
if (_rightLabel) {
|
||||
return _rightLabel->draw(p, x, y, st::inviteLinkList.item.height);
|
||||
}
|
||||
(actionSelected
|
||||
? st::inviteLinkThreeDotsIconOver
|
||||
: st::inviteLinkThreeDotsIcon).paint(p, x, y, outerWidth);
|
||||
@@ -354,6 +382,7 @@ public:
|
||||
not_null<PeerListRow*> row) override;
|
||||
Main::Session &session() const override;
|
||||
|
||||
std::optional<SubscriptionRightLabel> rightLabel(int) const override;
|
||||
void rowUpdateRow(not_null<Row*> row) override;
|
||||
void rowPaintIcon(
|
||||
QPainter &p,
|
||||
@@ -639,6 +668,17 @@ void LinksController::expiringProgressTimer() {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<SubscriptionRightLabel> LinksController::rightLabel(
|
||||
int credits) const {
|
||||
if (credits > 0) {
|
||||
return Settings::PaintSubscriptionRightLabelCallback(
|
||||
&session(),
|
||||
st::inviteLinkList.item,
|
||||
credits);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void LinksController::rowUpdateRow(not_null<Row*> row) {
|
||||
delegate()->peerListUpdateRow(row);
|
||||
}
|
||||
@@ -659,6 +699,7 @@ void LinksController::rowPaintIcon(
|
||||
case Color::ExpireSoon: return &st::msgFile4Bg;
|
||||
case Color::Expired: return &st::msgFile3Bg;
|
||||
case Color::Revoked: return &st::windowSubTextFg;
|
||||
case Color::Subscription: return &st::msgFile2Bg;
|
||||
}
|
||||
Unexpected("Color in LinksController::rowPaintIcon.");
|
||||
}();
|
||||
@@ -676,15 +717,25 @@ void LinksController::rowPaintIcon(
|
||||
p.setBrush(*bg);
|
||||
{
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto rect = QRect(0, 0, inner, inner);
|
||||
if (color == Color::Expiring || color == Color::ExpireSoon) {
|
||||
rect = rect.marginsRemoved({ stroke, stroke, stroke, stroke });
|
||||
}
|
||||
const auto rect = QRect(0, 0, inner, inner)
|
||||
- ((color == Color::Expiring || color == Color::ExpireSoon)
|
||||
? Margins(stroke)
|
||||
: Margins(0));
|
||||
p.drawEllipse(rect);
|
||||
}
|
||||
(color == Color::Revoked
|
||||
? st::inviteLinkRevokedIcon
|
||||
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
|
||||
if (color == Color::Subscription) {
|
||||
auto svg = QSvgRenderer(u":/gui/links_subscription.svg"_q);
|
||||
const auto r = QRect(
|
||||
(inner - st::inviteLinkSubscriptionSize) / 2,
|
||||
(inner - st::inviteLinkSubscriptionSize) / 2,
|
||||
st::inviteLinkSubscriptionSize,
|
||||
st::inviteLinkSubscriptionSize);
|
||||
svg.render(&p, r);
|
||||
} else {
|
||||
(color == Color::Revoked
|
||||
? st::inviteLinkRevokedIcon
|
||||
: st::inviteLinkIcon).paintInCenter(p, { 0, 0, inner, inner });
|
||||
}
|
||||
}
|
||||
p.drawImage(x + skip, y + skip, icon);
|
||||
if (progress >= 0. && progress < 1.) {
|
||||
|
||||
@@ -282,6 +282,9 @@ void SetupOnlyCustomEmojiField(
|
||||
const auto offset = size();
|
||||
if (unifiedId) {
|
||||
result.text.append('@');
|
||||
} else if (id.paid()) {
|
||||
result.text.append(QChar(0x2B50));
|
||||
unifiedId = reactions->lookupPaid()->selectAnimation->id;
|
||||
} else {
|
||||
result.text.append(id.emoji());
|
||||
const auto i = ranges::find(all, id, &Data::Reaction::id);
|
||||
@@ -312,6 +315,7 @@ struct ReactionsSelectorArgs {
|
||||
rpl::producer<QString> title;
|
||||
std::vector<Data::Reaction> list;
|
||||
std::vector<Data::ReactionId> selected;
|
||||
rpl::producer<bool> paid;
|
||||
Fn<void(std::vector<Data::ReactionId>, bool)> callback;
|
||||
rpl::producer<ReactionsSelectorState> stateValue;
|
||||
int customAllowed = 0;
|
||||
@@ -341,13 +345,18 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
std::unique_ptr<UnifiedFactoryOwner> unifiedFactoryOwner;
|
||||
UnifiedFactoryOwner::RecentFactory factory;
|
||||
base::flat_set<DocumentId> allowed;
|
||||
std::vector<Data::ReactionId> reactions;
|
||||
rpl::lifetime focusLifetime;
|
||||
};
|
||||
const auto paid = reactions->lookupPaid();
|
||||
auto normal = reactions->list(Data::Reactions::Type::Active);
|
||||
normal.push_back(*paid);
|
||||
const auto state = raw->lifetime().make_state<State>();
|
||||
state->unifiedFactoryOwner = std::make_unique<UnifiedFactoryOwner>(
|
||||
session,
|
||||
reactions->list(Data::Reactions::Type::Active));
|
||||
normal);
|
||||
state->factory = state->unifiedFactoryOwner->factory();
|
||||
state->reactions = std::move(args.selected);
|
||||
|
||||
const auto customEmojiPaused = [controller = args.controller] {
|
||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||
@@ -396,9 +405,32 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
state->allowed = std::move(allowed);
|
||||
raw->rawTextEdit()->update();
|
||||
}
|
||||
state->reactions = reactions;
|
||||
callback(std::move(reactions), hardLimitHit);
|
||||
}, isCustom, args.customHardLimit);
|
||||
raw->setTextWithTags(ComposeEmojiList(reactions, args.selected));
|
||||
const auto applyFromState = [=] {
|
||||
raw->setTextWithTags(ComposeEmojiList(reactions, state->reactions));
|
||||
};
|
||||
|
||||
applyFromState();
|
||||
std::move(
|
||||
args.paid
|
||||
) | rpl::start_with_next([=](bool paid) {
|
||||
const auto id = Data::ReactionId::Paid();
|
||||
if (paid && !ranges::contains(state->reactions, id)) {
|
||||
state->reactions.insert(begin(state->reactions), id);
|
||||
applyFromState();
|
||||
} else if (!paid && ranges::contains(state->reactions, id)) {
|
||||
state->reactions.erase(
|
||||
ranges::remove(state->reactions, id),
|
||||
end(state->reactions));
|
||||
applyFromState();
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto toggle = Ui::CreateChild<Ui::IconButton>(
|
||||
parent.get(),
|
||||
st::manageGroupReactions);
|
||||
|
||||
using SelectorState = ReactionsSelectorState;
|
||||
std::move(
|
||||
@@ -444,10 +476,6 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto toggle = Ui::CreateChild<Ui::IconButton>(
|
||||
parent.get(),
|
||||
st::manageGroupReactions);
|
||||
|
||||
const auto panel = Ui::CreateChild<TabbedPanel>(
|
||||
args.outer.get(),
|
||||
args.controller,
|
||||
@@ -458,8 +486,11 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
(args.all
|
||||
? TabbedSelector::Mode::FullReactions
|
||||
: TabbedSelector::Mode::RecentReactions)));
|
||||
panel->selector()->provideRecentEmoji(
|
||||
state->unifiedFactoryOwner->unifiedIdsList());
|
||||
auto panelList = state->unifiedFactoryOwner->unifiedIdsList();
|
||||
panelList.erase(
|
||||
ranges::remove(panelList, paid->selectAnimation->id),
|
||||
end(panelList));
|
||||
panel->selector()->provideRecentEmoji(panelList);
|
||||
panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
@@ -608,15 +639,17 @@ void EditAllowedReactionsBox(
|
||||
rpl::variable<SelectorState> selectorState;
|
||||
std::vector<Data::ReactionId> selected;
|
||||
rpl::variable<int> customCount;
|
||||
rpl::variable<bool> paidEnabled;
|
||||
};
|
||||
const auto allowed = args.allowed;
|
||||
const auto optionInitial = (allowed.type != AllowedReactionsType::Some)
|
||||
? Option::All
|
||||
: allowed.some.empty()
|
||||
: (allowed.some.empty() && !allowed.paidEnabled)
|
||||
? Option::None
|
||||
: Option::Some;
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.option = optionInitial,
|
||||
.paidEnabled = allowed.paidEnabled,
|
||||
});
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
@@ -702,13 +735,19 @@ void EditAllowedReactionsBox(
|
||||
| ranges::views::transform(&Data::Reaction::id)
|
||||
| ranges::to_vector)
|
||||
: allowed.some;
|
||||
if (allowed.paidEnabled) {
|
||||
selected.insert(begin(selected), Data::ReactionId::Paid());
|
||||
}
|
||||
const auto changed = [=](
|
||||
std::vector<Data::ReactionId> chosen,
|
||||
bool hardLimitHit) {
|
||||
std::vector<Data::ReactionId> chosen,
|
||||
bool hardLimitHit) {
|
||||
state->selected = std::move(chosen);
|
||||
state->customCount = ranges::count_if(
|
||||
state->selected,
|
||||
&Data::ReactionId::custom);
|
||||
state->paidEnabled = ranges::contains(
|
||||
state->selected,
|
||||
Data::ReactionId::Paid());
|
||||
if (hardLimitHit) {
|
||||
box->uiShow()->showToast(
|
||||
tr::lng_manage_peer_reactions_limit(tr::now));
|
||||
@@ -727,6 +766,7 @@ void EditAllowedReactionsBox(
|
||||
.title = tr::lng_manage_peer_reactions_available_ph(),
|
||||
.list = all,
|
||||
.selected = state->selected,
|
||||
.paid = state->paidEnabled.value(),
|
||||
.callback = changed,
|
||||
.stateValue = state->selectorState.value(),
|
||||
.customAllowed = args.allowedCustomReactions,
|
||||
@@ -735,7 +775,7 @@ void EditAllowedReactionsBox(
|
||||
}), st::boxRowPadding);
|
||||
|
||||
box->setFocusCallback([=] {
|
||||
if (!wrap || state->option.current() == Option::Some) {
|
||||
if (state->option.current() == Option::Some) {
|
||||
state->selectorState.force_assign(SelectorState::Active);
|
||||
}
|
||||
});
|
||||
@@ -847,6 +887,29 @@ void EditAllowedReactionsBox(
|
||||
) | rpl::map(rpl::mappers::_1 == SelectorState::Active)));
|
||||
|
||||
Ui::AddDividerText(inner, tr::lng_manage_peer_reactions_max_about());
|
||||
|
||||
Ui::AddSkip(inner);
|
||||
const auto paid = inner->add(object_ptr<Ui::SettingsButton>(
|
||||
inner,
|
||||
tr::lng_manage_peer_reactions_paid(),
|
||||
st::manageGroupNoIconButton.button));
|
||||
paid->toggleOn(state->paidEnabled.value());
|
||||
paid->toggledValue(
|
||||
) | rpl::start_with_next([=](bool value) {
|
||||
state->paidEnabled = value;
|
||||
}, paid->lifetime());
|
||||
Ui::AddSkip(inner);
|
||||
|
||||
Ui::AddDividerText(
|
||||
inner,
|
||||
tr::lng_manage_peer_reactions_paid_about(
|
||||
lt_link,
|
||||
tr::lng_manage_peer_reactions_paid_link([=](QString text) {
|
||||
return Ui::Text::Link(
|
||||
text,
|
||||
u"https://telegram.org/tos/stars"_q);
|
||||
}),
|
||||
Ui::Text::WithEntities));
|
||||
}
|
||||
const auto collect = [=] {
|
||||
auto result = AllowedReactions();
|
||||
@@ -856,6 +919,9 @@ void EditAllowedReactionsBox(
|
||||
: (enabled->toggled())) {
|
||||
result.some = state->selected;
|
||||
}
|
||||
if (!isGroup && enabled->toggled()) {
|
||||
result.paidEnabled = state->paidEnabled.current();
|
||||
}
|
||||
auto some = result.some;
|
||||
auto simple = all | ranges::views::transform(
|
||||
&Data::Reaction::id
|
||||
@@ -907,15 +973,20 @@ void SaveAllowedReactions(
|
||||
: allowed.some.empty()
|
||||
? MTP_chatReactionsNone()
|
||||
: MTP_chatReactionsSome(MTP_vector<MTPReaction>(ids));
|
||||
const auto editPaidEnabled = peer->isBroadcast();
|
||||
const auto paidEnabled = editPaidEnabled && allowed.paidEnabled;
|
||||
const auto maxCount = allowed.maxCount;
|
||||
peer->session().api().request(MTPmessages_SetChatAvailableReactions(
|
||||
allowed.maxCount ? MTP_flags(Flag::f_reactions_limit) : MTP_flags(0),
|
||||
MTP_flags(Flag()
|
||||
| (maxCount ? Flag::f_reactions_limit : Flag())
|
||||
| (editPaidEnabled ? Flag::f_paid_enabled : Flag())),
|
||||
peer->input,
|
||||
updated,
|
||||
MTP_int(allowed.maxCount)
|
||||
MTP_int(maxCount),
|
||||
MTP_bool(paidEnabled)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().applyUpdates(result);
|
||||
auto parsed = Data::Parse(updated);
|
||||
parsed.maxCount = allowed.maxCount;
|
||||
auto parsed = Data::Parse(updated, maxCount, paidEnabled);
|
||||
if (const auto chat = peer->asChat()) {
|
||||
chat->setAllowedReactions(parsed);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
|
||||
@@ -459,6 +459,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
|
||||
.customWallpaperLevel = group
|
||||
? levelLimits.groupCustomWallpaperLevelMin()
|
||||
: levelLimits.channelCustomWallpaperLevelMin(),
|
||||
.sponsoredLevel = levelLimits.channelRestrictSponsoredLevelMin(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/peer_list_dummy.h"
|
||||
#include "ui/effects/premium_bubble.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
@@ -136,6 +137,12 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] Ui::Premium::BubbleType ChooseBubbleType(bool premium) {
|
||||
return premium
|
||||
? Ui::Premium::BubbleType::Premium
|
||||
: Ui::Premium::BubbleType::NoPremium;
|
||||
}
|
||||
|
||||
void InactiveDelegate::peerListSetTitle(rpl::producer<QString> title) {
|
||||
}
|
||||
|
||||
@@ -418,8 +425,10 @@ void SimpleLimitBox(
|
||||
BoxShowFinishes(box),
|
||||
0,
|
||||
descriptor.current,
|
||||
descriptor.premiumLimit,
|
||||
premiumPossible,
|
||||
(descriptor.complexRatio
|
||||
? descriptor.premiumLimit
|
||||
: 2 * descriptor.current),
|
||||
ChooseBubbleType(premiumPossible),
|
||||
descriptor.phrase,
|
||||
descriptor.icon);
|
||||
Ui::AddSkip(top, st::premiumLineTextSkip);
|
||||
@@ -769,7 +778,7 @@ void FilterLinksLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconChats,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true */}); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
|
||||
@@ -856,7 +865,7 @@ void ShareableFiltersLimitBox(
|
||||
premiumLimit,
|
||||
&st::premiumIconFolders,
|
||||
std::nullopt,
|
||||
true });
|
||||
/*true*/ }); // Don't use real ratio, "Free" doesn't fit.
|
||||
}
|
||||
|
||||
void FilterPinsLimitBox(
|
||||
@@ -1107,7 +1116,7 @@ void AccountsLimitBox(
|
||||
: (current > defaultLimit)
|
||||
? (current + 1)
|
||||
: (defaultLimit * 2)),
|
||||
premiumPossible,
|
||||
ChooseBubbleType(premiumPossible),
|
||||
std::nullopt,
|
||||
&st::premiumIconAccounts);
|
||||
Ui::AddSkip(top, st::premiumLineTextSkip);
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_credits.h"
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#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.
|
||||
@@ -79,14 +81,12 @@ struct PaidMediaData {
|
||||
}
|
||||
}
|
||||
|
||||
const auto bot = item->viaBot();
|
||||
const auto sender = item->originalSender();
|
||||
const auto broadcast = (sender && sender->isBroadcast())
|
||||
? sender
|
||||
: message->peer.get();
|
||||
return {
|
||||
.invoice = invoice,
|
||||
.item = item,
|
||||
.peer = broadcast,
|
||||
.peer = (bot ? bot : sender ? sender : message->peer.get()),
|
||||
.photos = photos,
|
||||
.videos = videos,
|
||||
};
|
||||
@@ -129,6 +129,16 @@ struct PaidMediaData {
|
||||
lt_video,
|
||||
std::move(videosBold),
|
||||
Ui::Text::WithEntities);
|
||||
if (const auto user = data.peer->asUser()) {
|
||||
return tr::lng_credits_box_out_media_user(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_media,
|
||||
std::move(media),
|
||||
lt_user,
|
||||
rpl::single(Ui::Text::Bold(user->shortName())),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
return tr::lng_credits_box_out_media(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
@@ -257,6 +267,8 @@ void SendCreditsBox(
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
const auto show = box->uiShow();
|
||||
const auto weak = MakeWeak(box.get());
|
||||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
@@ -264,12 +276,31 @@ void SendCreditsBox(
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
}
|
||||
sent();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->uiShow()->showToast(error.type());
|
||||
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();
|
||||
});
|
||||
{
|
||||
@@ -279,7 +310,7 @@ void SendCreditsBox(
|
||||
st::giveawayGiftCodeStartButton.height / 2);
|
||||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
@@ -339,15 +370,11 @@ void SendCreditsBox(
|
||||
}
|
||||
|
||||
{
|
||||
session->credits().load(true);
|
||||
const auto balance = Settings::AddBalanceWidget(
|
||||
content,
|
||||
session->creditsValue(),
|
||||
session->credits().balanceValue(),
|
||||
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()
|
||||
|
||||
@@ -1409,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) {
|
||||
@@ -1612,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();
|
||||
@@ -1643,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()) {
|
||||
@@ -1675,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(
|
||||
@@ -1793,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,50 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/sticker_set_box.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "core/application.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "ui/effects/animation_value_f.h"
|
||||
#include "ui/effects/path_shift_gradient.h"
|
||||
#include "ui/emoji_config.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/gradient_round_button.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/cached_round_corners.h"
|
||||
#include "lottie/lottie_multi_player.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "chat_helpers/stickers_list_widget.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info.h"
|
||||
@@ -68,10 +73,12 @@ constexpr auto kEmojiPerRow = 8;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
constexpr auto kGrayLockOpacity = 0.3;
|
||||
constexpr auto kStickerMoveDuration = crl::time(200);
|
||||
|
||||
using Data::StickersSet;
|
||||
using Data::StickersPack;
|
||||
using SetFlag = Data::StickersSetFlag;
|
||||
using TLStickerSet = MTPmessages_StickerSet;
|
||||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
@@ -259,6 +266,20 @@ public:
|
||||
[[nodiscard]] rpl::producer<uint64> setArchived() const;
|
||||
[[nodiscard]] rpl::producer<> updateControls() const;
|
||||
|
||||
void setReorderState(bool enabled) {
|
||||
_dragging.enabled = enabled;
|
||||
if (enabled) {
|
||||
_shakeAnimation.init([=] { update(); });
|
||||
_shakeAnimation.start();
|
||||
} else {
|
||||
_shakeAnimation.stop();
|
||||
update();
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool reorderState() const {
|
||||
return _dragging.enabled;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<Error> errors() const;
|
||||
|
||||
void archiveStickers();
|
||||
@@ -271,6 +292,12 @@ public:
|
||||
: Data::StickersType::Stickers;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool amSetCreator() const {
|
||||
return _amSetCreator;
|
||||
}
|
||||
|
||||
void applySet(const TLStickerSet &set);
|
||||
|
||||
~Inner();
|
||||
|
||||
protected:
|
||||
@@ -306,6 +333,11 @@ private:
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const;
|
||||
void shakeTransform(
|
||||
QPainter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
crl::time now) const;
|
||||
void setupLottie(int index);
|
||||
void setupWebm(int index);
|
||||
void clipCallback(
|
||||
@@ -322,14 +354,19 @@ private:
|
||||
void startOverAnimation(int index, float64 from, float64 to);
|
||||
int stickerFromGlobalPos(const QPoint &p) const;
|
||||
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
void requestReorder(not_null<DocumentData*> document, int index);
|
||||
void fillDeleteStickerBox(not_null<Ui::GenericBox*> box, int index);
|
||||
|
||||
void chosen(
|
||||
int index,
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options);
|
||||
|
||||
[[nodiscard]] QPoint posFromIndex(int index) const;
|
||||
[[nodiscard]] bool isDraggedAnimating() const;
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
@@ -366,6 +403,24 @@ private:
|
||||
TimeId _setInstallDate = TimeId(0);
|
||||
StickerType _setThumbnailType = StickerType::Webp;
|
||||
ImageWithLocation _setThumbnail;
|
||||
bool _amSetCreator = false;
|
||||
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int index = -1;
|
||||
int lastSelected = -1;
|
||||
QPoint point;
|
||||
} _dragging;
|
||||
Ui::Animations::Basic _shakeAnimation;
|
||||
std::deque<Fn<void()>> _reorderRequests;
|
||||
std::optional<MTP::Sender> _apiReorder;
|
||||
|
||||
struct ShiftAnimation final {
|
||||
Ui::Animations::Simple animation;
|
||||
Ui::Animations::Simple yAnimation;
|
||||
int shift = 0;
|
||||
};
|
||||
base::flat_map<int, ShiftAnimation> _shiftAnimations;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
mutable StickerPremiumMark _premiumMark;
|
||||
@@ -538,9 +593,112 @@ void StickerSetBox::updateTitleAndButtons() {
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void ChangeSetNameBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Data::Session*> data,
|
||||
const StickerSetIdentifier &input,
|
||||
Fn<void(TLStickerSet)> done) {
|
||||
struct State final {
|
||||
rpl::variable<mtpRequestId> requestId = 0;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
};
|
||||
box->setTitle(tr::lng_stickers_box_edit_name_title());
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_stickers_box_edit_name_about(),
|
||||
st::boxLabel));
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto wasName = [&] {
|
||||
const auto &sets = data->stickers().sets();
|
||||
const auto it = sets.find(input.id);
|
||||
return (it == sets.end()) ? QString() : it->second->title;
|
||||
}();
|
||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::editStickerSetNameField.heightMin));
|
||||
auto owned = object_ptr<Ui::InputField>(
|
||||
wrap,
|
||||
st::editStickerSetNameField,
|
||||
tr::lng_stickers_context_edit_name(),
|
||||
wasName);
|
||||
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();
|
||||
constexpr auto kMaxSetNameLength = 50;
|
||||
field->setMaxLength(kMaxSetNameLength);
|
||||
Ui::AddLengthLimitLabel(field, kMaxSetNameLength, kMaxSetNameLength + 1);
|
||||
box->setFocusCallback([=] { field->setFocusFast(); });
|
||||
const auto close = crl::guard(box, [=] { box->closeBox(); });
|
||||
const auto save = [=, show = box->uiShow()] {
|
||||
if (state->requestId.current()) {
|
||||
return;
|
||||
}
|
||||
const auto text = field->getLastText().trimmed();
|
||||
if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength)
|
||||
|| text.isEmpty()) {
|
||||
field->showError();
|
||||
return;
|
||||
}
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->requestId = data->session().api().request(
|
||||
MTPstickers_RenameStickerSet(
|
||||
Data::InputStickerSet(input),
|
||||
MTP_string(text))
|
||||
).done([=](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
data->stickers().feedSetFull(d);
|
||||
data->stickers().notifyUpdated(Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
done(result);
|
||||
close();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
close();
|
||||
}).send();
|
||||
if (state->saveButton) {
|
||||
state->saveButton->resizeToWidth(buttonWidth);
|
||||
}
|
||||
};
|
||||
|
||||
state->saveButton = box->addButton(
|
||||
rpl::conditional(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
|
||||
rpl::single(QString()),
|
||||
tr::lng_box_done()),
|
||||
save);
|
||||
if (const auto saveButton = state->saveButton) {
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
saveButton,
|
||||
saveButton->height() / 2,
|
||||
&st::editStickerSetNameLoading);
|
||||
AddChildToWidgetCenter(saveButton, loadingAnimation);
|
||||
loadingAnimation->showOn(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
data->session().api().request(state->requestId.current()).cancel();
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::updateButtons() {
|
||||
clearButtons();
|
||||
if (_inner->loaded()) {
|
||||
if (_inner->reorderState()) {
|
||||
addButton(tr::lng_box_done(), [=] {
|
||||
_inner->setReorderState(false);
|
||||
updateButtons();
|
||||
});
|
||||
} else if (_inner->loaded()) {
|
||||
const auto type = _inner->setType();
|
||||
const auto share = [=] {
|
||||
copyStickersLink();
|
||||
@@ -548,6 +706,34 @@ void StickerSetBox::updateButtons() {
|
||||
? tr::lng_stickers_copied_emoji(tr::now)
|
||||
: tr::lng_stickers_copied(tr::now));
|
||||
};
|
||||
const auto fillSetCreatorMenu = [&] {
|
||||
using Filler = Fn<void(not_null<Ui::PopupMenu*>)>;
|
||||
if (!_inner->amSetCreator()) {
|
||||
return Filler(nullptr);
|
||||
}
|
||||
const auto data = &_session->data();
|
||||
return Filler([=, show = _show, set = _set](
|
||||
not_null<Ui::PopupMenu*> menu) {
|
||||
const auto done = [inner = _inner](const TLStickerSet &set) {
|
||||
if (const auto raw = inner.data()) {
|
||||
raw->applySet(set);
|
||||
}
|
||||
};
|
||||
menu->addAction(
|
||||
tr::lng_stickers_context_edit_name(tr::now),
|
||||
[=] {
|
||||
show->showBox(Box(ChangeSetNameBox, data, set, done));
|
||||
},
|
||||
&st::menuIconEdit);
|
||||
menu->addAction(
|
||||
tr::lng_stickers_context_reorder(tr::now),
|
||||
[=] {
|
||||
_inner->setReorderState(true);
|
||||
updateButtons();
|
||||
},
|
||||
&st::menuIconManage);
|
||||
});
|
||||
}();
|
||||
if (_inner->notInstalled()) {
|
||||
if (!_session->premium()
|
||||
&& _session->premiumPossible()
|
||||
@@ -586,6 +772,9 @@ void StickerSetBox::updateButtons() {
|
||||
*menu = base::make_unique_q<Ui::PopupMenu>(
|
||||
top,
|
||||
st::popupMenuWithIcons);
|
||||
if (fillSetCreatorMenu) {
|
||||
fillSetCreatorMenu(*menu);
|
||||
}
|
||||
(*menu)->addAction(
|
||||
((type == Data::StickersType::Emoji)
|
||||
? tr::lng_stickers_share_emoji
|
||||
@@ -636,6 +825,9 @@ void StickerSetBox::updateButtons() {
|
||||
remove,
|
||||
&st::menuIconRemove);
|
||||
} else {
|
||||
if (fillSetCreatorMenu) {
|
||||
fillSetCreatorMenu(*menu);
|
||||
}
|
||||
(*menu)->addAction(
|
||||
(type == Data::StickersType::Masks
|
||||
? tr::lng_masks_archive_pack(tr::now)
|
||||
@@ -687,8 +879,8 @@ StickerSetBox::Inner::Inner(
|
||||
_api.request(MTPmessages_GetStickerSet(
|
||||
Data::InputStickerSet(_input),
|
||||
MTP_int(0) // hash
|
||||
)).done([=](const MTPmessages_StickerSet &result) {
|
||||
gotSet(result);
|
||||
)).done([=](const TLStickerSet &result) {
|
||||
applySet(result);
|
||||
}).fail([=] {
|
||||
_loaded = true;
|
||||
_errors.fire(Error::NotFound);
|
||||
@@ -704,7 +896,7 @@ StickerSetBox::Inner::Inner(
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
void StickerSetBox::Inner::applySet(const TLStickerSet &set) {
|
||||
_pack.clear();
|
||||
_emoji.clear();
|
||||
_elements.clear();
|
||||
@@ -748,7 +940,9 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
}
|
||||
});
|
||||
}
|
||||
data.vset().match([&](const MTPDstickerSet &set) {
|
||||
|
||||
{
|
||||
const auto &set = data.vset().data();
|
||||
_setTitle = _session->data().stickers().getSetTitle(
|
||||
set);
|
||||
_setShortName = qs(set.vshort_name());
|
||||
@@ -759,6 +953,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
_setFlags = Data::ParseStickersSetFlags(set);
|
||||
_setInstallDate = set.vinstalled_date().value_or(0);
|
||||
_setThumbnailDocumentId = set.vthumb_document_id().value_or_empty();
|
||||
_amSetCreator = set.is_creator();
|
||||
_setThumbnail = [&] {
|
||||
if (const auto thumbs = set.vthumbs()) {
|
||||
for (const auto &thumb : thumbs->v) {
|
||||
@@ -791,7 +986,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
|
||||
set->emoji = _emoji;
|
||||
set->setThumbnail(_setThumbnail, _setThumbnailType);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [&](const MTPDmessages_stickerSetNotModified &data) {
|
||||
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
|
||||
});
|
||||
@@ -932,11 +1127,100 @@ void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
if (_dragging.enabled) {
|
||||
_previewTimer.cancel();
|
||||
if (isDraggedAnimating()) {
|
||||
return;
|
||||
}
|
||||
_dragging.index = index;
|
||||
_dragging.point = mapFromGlobal(QCursor::pos()) - posFromIndex(index);
|
||||
return;
|
||||
}
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
updateSelected();
|
||||
const auto draggedAnimating = isDraggedAnimating();
|
||||
if (_selected >= 0 && !draggedAnimating) {
|
||||
_dragging.lastSelected = _selected;
|
||||
}
|
||||
if (_dragging.index >= 0
|
||||
&& _dragging.index < _pack.size()
|
||||
&& _dragging.lastSelected >= 0
|
||||
&& !draggedAnimating) {
|
||||
for (auto i = 0; i < _pack.size(); i++) {
|
||||
if (i == _dragging.index) {
|
||||
continue;
|
||||
}
|
||||
auto &entry = _shiftAnimations[i];
|
||||
const auto wasShift = entry.shift;
|
||||
if ((i >= _dragging.index) && (i <= _dragging.lastSelected)) {
|
||||
if (entry.shift == 0) {
|
||||
entry.shift = -1;
|
||||
} else if (entry.shift == 1) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
} else if ((i < _dragging.index)
|
||||
&& (i >= _dragging.lastSelected)) {
|
||||
if (entry.shift == 0) {
|
||||
entry.shift = 1;
|
||||
} else if (entry.shift == -1) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
}
|
||||
if ((i < std::min(_dragging.index, _dragging.lastSelected))
|
||||
|| (i > std::max(_dragging.index, _dragging.lastSelected))) {
|
||||
entry.shift = 0;
|
||||
}
|
||||
if (wasShift != entry.shift) {
|
||||
const auto fromPoint = posFromIndex(i + wasShift);
|
||||
const auto toPoint = posFromIndex(i + entry.shift);
|
||||
const auto toX = float64(toPoint.x());
|
||||
const auto toY = float64(toPoint.y());
|
||||
const auto ratio = [&] {
|
||||
const auto fromX = entry.animation.value(toX);
|
||||
const auto ratioX = std::min(toX, fromX)
|
||||
/ std::max(toX, fromX);
|
||||
const auto fromY = entry.yAnimation.value(toY);
|
||||
const auto ratioY = std::min(toY, fromY)
|
||||
/ std::max(toY, fromY);
|
||||
return (ratioX == 1.)
|
||||
? ratioY
|
||||
: (ratioY == 1.)
|
||||
? ratioX
|
||||
: std::max(ratioX, ratioY);
|
||||
}();
|
||||
if (!entry.animation.animating()) {
|
||||
entry.animation.stop();
|
||||
entry.animation.start(
|
||||
[=] { update(); },
|
||||
fromPoint.x(),
|
||||
toX,
|
||||
kStickerMoveDuration);
|
||||
} else {
|
||||
entry.animation.change(
|
||||
toX,
|
||||
kStickerMoveDuration * (1. - ratio),
|
||||
anim::linear);
|
||||
}
|
||||
if (!entry.yAnimation.animating()) {
|
||||
entry.yAnimation.stop();
|
||||
entry.yAnimation.start(
|
||||
[=] { update(); },
|
||||
fromPoint.y(),
|
||||
toY,
|
||||
kStickerMoveDuration);
|
||||
} else {
|
||||
entry.yAnimation.change(
|
||||
toY,
|
||||
kStickerMoveDuration * (1. - ratio),
|
||||
anim::linear);
|
||||
}
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
if (_previewShown >= 0) {
|
||||
showPreviewAt(e->globalPos());
|
||||
}
|
||||
@@ -958,7 +1242,86 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
|
||||
setSelected(-1);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::requestReorder(
|
||||
not_null<DocumentData*> document,
|
||||
int index) {
|
||||
if (!_apiReorder) {
|
||||
_apiReorder.emplace(&_session->mtp());
|
||||
}
|
||||
_reorderRequests.emplace_back([document, index, this] {
|
||||
_apiReorder->request(
|
||||
MTPstickers_ChangeStickerPosition(
|
||||
document->mtpInput(),
|
||||
MTP_int(index))
|
||||
).done([this, document](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
document->owner().stickers().feedSetFull(d);
|
||||
document->owner().stickers().notifyUpdated(
|
||||
Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_reorderRequests.empty()) {
|
||||
_reorderRequests.pop_front();
|
||||
}
|
||||
if (_reorderRequests.empty()) {
|
||||
// applySet(result); // Causes stickers blink.
|
||||
} else {
|
||||
_reorderRequests.front()();
|
||||
}
|
||||
}).fail([show = _show](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
}).send();
|
||||
});
|
||||
if (_reorderRequests.size() == 1) {
|
||||
_reorderRequests.front()();
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_dragging.index >= 0 && !isDraggedAnimating()) {
|
||||
const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point;
|
||||
const auto toPos = posFromIndex(_dragging.lastSelected);
|
||||
const auto document = _pack[_dragging.index];
|
||||
const auto wasPosition = _dragging.index;
|
||||
const auto nowPosition = _dragging.lastSelected;
|
||||
const auto finish = [=, this] {
|
||||
requestReorder(document, nowPosition);
|
||||
base::reorder(_pack, wasPosition, nowPosition);
|
||||
base::reorder(_elements, wasPosition, nowPosition);
|
||||
_dragging = {};
|
||||
_dragging.enabled = true;
|
||||
_shiftAnimations.clear();
|
||||
};
|
||||
auto &entry = _shiftAnimations[_dragging.index];
|
||||
entry.animation.stop();
|
||||
entry.yAnimation.stop();
|
||||
entry.animation.start(
|
||||
[finish, toPos, this](float64 value) {
|
||||
const auto index = _dragging.index;
|
||||
if (value >= toPos.x()
|
||||
&& index >= 0
|
||||
&& !_shiftAnimations[index].yAnimation.animating()) {
|
||||
finish();
|
||||
}
|
||||
update();
|
||||
},
|
||||
fromPos.x(),
|
||||
toPos.x(),
|
||||
kStickerMoveDuration);
|
||||
entry.yAnimation.start(
|
||||
[finish, toPos, this](float64 value) {
|
||||
const auto index = _dragging.index;
|
||||
if (value >= toPos.y()
|
||||
&& index >= 0
|
||||
&& !_shiftAnimations[index].animation.animating()) {
|
||||
finish();
|
||||
}
|
||||
update();
|
||||
},
|
||||
fromPos.y(),
|
||||
toPos.y(),
|
||||
kStickerMoveDuration);
|
||||
}
|
||||
if (_previewShown >= 0) {
|
||||
_previewShown = -1;
|
||||
return;
|
||||
@@ -1061,6 +1424,20 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
(isFaved
|
||||
? &st::menuIconUnfave
|
||||
: &st::menuIconFave));
|
||||
if (amSetCreator()) {
|
||||
const auto addAction = Ui::Menu::CreateAddActionCallback(
|
||||
_menu.get());
|
||||
addAction({
|
||||
.text = tr::lng_stickers_context_delete(tr::now),
|
||||
.handler = [index, this, show = _show] {
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
fillDeleteStickerBox(box, index);
|
||||
}));
|
||||
},
|
||||
.icon = &st::menuIconDeleteAttention,
|
||||
.isAttention = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
@@ -1069,6 +1446,129 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::fillDeleteStickerBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
int index) {
|
||||
Expects(index >= 0 || index < _pack.size());
|
||||
const auto document = _pack[index];
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto show = _show;
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
|
||||
line->resize(line->width(), _singleSize.height());
|
||||
|
||||
const auto sticker = Ui::CreateChild<Ui::RpWidget>(line);
|
||||
auto &lifetime = sticker->lifetime();
|
||||
struct State final {
|
||||
rpl::variable<mtpRequestId> requestId = 0;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
sticker->resize(_singleSize);
|
||||
{
|
||||
const auto animation = lifetime.make_state<Ui::Animations::Basic>();
|
||||
animation->init([=] { sticker->update(); });
|
||||
animation->start();
|
||||
}
|
||||
sticker->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(sticker);
|
||||
if (const auto strong = weak.data()) {
|
||||
const auto paused = On(PowerSaving::kStickersPanel)
|
||||
|| show->paused(ChatHelpers::PauseReason::Layer);
|
||||
paintSticker(p, index, QPoint(), paused, crl::now());
|
||||
if (_lottiePlayer && !paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
}
|
||||
}
|
||||
}, sticker->lifetime());
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
line,
|
||||
tr::lng_stickers_context_delete(),
|
||||
box->getDelegate()->style().title);
|
||||
line->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
sticker->moveToLeft(st::boxRowPadding.left(), 0);
|
||||
const auto skip = st::defaultBoxCheckbox.textPosition.x();
|
||||
label->resizeToWidth(width
|
||||
- rect::right(sticker)
|
||||
- skip
|
||||
- st::boxRowPadding.right());
|
||||
label->moveToLeft(
|
||||
rect::right(sticker) + skip,
|
||||
((sticker->height() - label->height()) / 2));
|
||||
}, label->lifetime());
|
||||
|
||||
sticker->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(container);
|
||||
Ui::AddSkip(container);
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_stickers_context_delete_sure(),
|
||||
st::boxLabel));
|
||||
const auto save = [=] {
|
||||
if (state->requestId.current()) {
|
||||
return;
|
||||
}
|
||||
const auto weakBox = Ui::MakeWeak(box);
|
||||
const auto buttonWidth = state->saveButton
|
||||
? state->saveButton->width()
|
||||
: 0;
|
||||
state->requestId = document->owner().session().api().request(
|
||||
MTPstickers_RemoveStickerFromSet(document->mtpInput()
|
||||
)).done([=](const TLStickerSet &result) {
|
||||
result.match([&](const MTPDmessages_stickerSet &d) {
|
||||
document->owner().stickers().feedSetFull(d);
|
||||
document->owner().stickers().notifyUpdated(
|
||||
Data::StickersType::Stickers);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (const auto strong = weak.data()) {
|
||||
applySet(result);
|
||||
}
|
||||
if (const auto strongBox = weakBox.data()) {
|
||||
strongBox->closeBox();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto strongBox = weakBox.data()) {
|
||||
strongBox->uiShow()->showToast(error.type());
|
||||
}
|
||||
}).send();
|
||||
if (state->saveButton) {
|
||||
state->saveButton->resizeToWidth(buttonWidth);
|
||||
}
|
||||
};
|
||||
state->saveButton = box->addButton(
|
||||
rpl::conditional(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
|
||||
rpl::single(QString()),
|
||||
tr::lng_selected_delete()),
|
||||
save,
|
||||
st::attentionBoxButton);
|
||||
if (const auto saveButton = state->saveButton) {
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
saveButton,
|
||||
saveButton->height() / 2,
|
||||
&st::editStickerSetNameLoading);
|
||||
AddChildToWidgetCenter(saveButton, loadingAnimation);
|
||||
loadingAnimation->showOn(
|
||||
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
|
||||
}
|
||||
box->addButton(tr::lng_close(), [=] {
|
||||
document->owner().session().api().request(
|
||||
state->requestId.current()).cancel();
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateSelected() {
|
||||
auto selected = stickerFromGlobalPos(QCursor::pos());
|
||||
setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
|
||||
@@ -1079,7 +1579,11 @@ void StickerSetBox::Inner::setSelected(int selected) {
|
||||
startOverAnimation(_selected, 1., 0.);
|
||||
_selected = selected;
|
||||
startOverAnimation(_selected, 0., 1.);
|
||||
setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default);
|
||||
setCursor((_selected < 0)
|
||||
? style::cur_default
|
||||
: _dragging.enabled
|
||||
? style::cur_sizeall
|
||||
: style::cur_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1101,6 +1605,24 @@ void StickerSetBox::Inner::showPreview() {
|
||||
showPreviewAt(QCursor::pos());
|
||||
}
|
||||
|
||||
QPoint StickerSetBox::Inner::posFromIndex(int index) const {
|
||||
return {
|
||||
_padding.left() + (index % _perRow) * _singleSize.width(),
|
||||
_padding.top() + (index / _perRow) * _singleSize.height(),
|
||||
};
|
||||
}
|
||||
|
||||
bool StickerSetBox::Inner::isDraggedAnimating() const {
|
||||
if (_dragging.index < 0) {
|
||||
return false;
|
||||
}
|
||||
const auto it = _shiftAnimations.find(_dragging.index);
|
||||
return (it == _shiftAnimations.end())
|
||||
? false
|
||||
: (it->second.animation.animating()
|
||||
|| it->second.yAnimation.animating());
|
||||
}
|
||||
|
||||
not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
|
||||
if (!_lottiePlayer) {
|
||||
_lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
|
||||
@@ -1140,12 +1662,36 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
|
||||
_pathGradient->startFrame(0, width(), width() / 2);
|
||||
|
||||
const auto indexUnderCursor = (_dragging.index >= 0
|
||||
&& _dragging.index < _elements.size())
|
||||
? stickerFromGlobalPos(QCursor::pos())
|
||||
: -2;
|
||||
const auto lastIndex = indexUnderCursor >= 0
|
||||
? indexUnderCursor
|
||||
: _dragging.lastSelected;
|
||||
|
||||
const auto now = crl::now();
|
||||
const auto paused = On(PowerSaving::kStickersPanel)
|
||||
|| _show->paused(ChatHelpers::PauseReason::Layer);
|
||||
for (int32 i = from; i < to; ++i) {
|
||||
for (int32 j = 0; j < _perRow; ++j) {
|
||||
int32 index = i * _perRow + j;
|
||||
|
||||
if (lastIndex >= 0) {
|
||||
if (_dragging.index == index) {
|
||||
continue;
|
||||
}
|
||||
const auto it = _shiftAnimations.find(index);
|
||||
if (it != _shiftAnimations.end()) {
|
||||
const auto &entry = it->second;
|
||||
const auto toPos = posFromIndex(index + entry.shift);
|
||||
const auto pos = QPoint(
|
||||
entry.animation.value(toPos.x()),
|
||||
entry.yAnimation.value(toPos.y()));
|
||||
paintSticker(p, index, pos, paused, now);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (index >= _elements.size()) {
|
||||
break;
|
||||
}
|
||||
@@ -1155,6 +1701,14 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
|
||||
paintSticker(p, index, pos, paused, now);
|
||||
}
|
||||
}
|
||||
if (_dragging.index >= 0 && _dragging.index < _elements.size()) {
|
||||
const auto pos = isDraggedAnimating()
|
||||
? QPoint(
|
||||
_shiftAnimations[_dragging.index].animation.value(0),
|
||||
_shiftAnimations[_dragging.index].yAnimation.value(0))
|
||||
: (mapFromGlobal(QCursor::pos()) - _dragging.point);
|
||||
paintSticker(p, _dragging.index, pos, paused, now);
|
||||
}
|
||||
|
||||
if (_lottiePlayer && !paused) {
|
||||
_lottiePlayer->markFrameShown();
|
||||
@@ -1310,18 +1864,99 @@ void StickerSetBox::Inner::customEmojiRepaint() {
|
||||
update();
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::shakeTransform(
|
||||
QPainter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
crl::time now) const {
|
||||
constexpr auto kShakeADuration = crl::time(400);
|
||||
constexpr auto kShakeXDuration = crl::time(kShakeADuration * 1.2);
|
||||
constexpr auto kShakeYDuration = kShakeADuration;
|
||||
const auto diff = ((index % 2) ? 0 : kShakeYDuration / 2)
|
||||
+ (now - _shakeAnimation.started());
|
||||
const auto pX = (diff % kShakeXDuration)
|
||||
/ float64(kShakeXDuration);
|
||||
const auto pY = (diff % kShakeYDuration)
|
||||
/ float64(kShakeYDuration);
|
||||
const auto pA = (diff % kShakeADuration)
|
||||
/ float64(kShakeADuration);
|
||||
|
||||
constexpr auto kMaxA = 2.;
|
||||
constexpr auto kMaxTranslation = .5;
|
||||
constexpr auto kAStep = 1. / 5;
|
||||
constexpr auto kXStep = 1. / 5;
|
||||
constexpr auto kYStep = 1. / 4;
|
||||
|
||||
// 0, -kMaxA, 0, kMaxA, 0.
|
||||
const auto angle = (pA < kAStep)
|
||||
? anim::interpolateF(0., -kMaxA, pA / kAStep)
|
||||
: (pA < kAStep * 2.)
|
||||
? anim::interpolateF(-kMaxA, 0, (pA - kAStep) / kAStep)
|
||||
: (pA < kAStep * 3.)
|
||||
? anim::interpolateF(0, kMaxA, (pA - kAStep * 2.) / kAStep)
|
||||
: (pA < kAStep * 4.)
|
||||
? anim::interpolateF(kMaxA, 0, (pA - kAStep * 3.) / kAStep)
|
||||
: anim::interpolateF(0, 0., (pA - kAStep * 4.) / kAStep);
|
||||
|
||||
// 0, kMaxTranslation, 0, -kMaxTranslation, 0.
|
||||
const auto x = (pX < kXStep)
|
||||
? anim::interpolateF(0., kMaxTranslation, pX / kXStep)
|
||||
: (pX < kXStep * 2.)
|
||||
? anim::interpolateF(kMaxTranslation, 0, (pX - kXStep) / kXStep)
|
||||
: (pX < kXStep * 3.)
|
||||
? anim::interpolateF(0, -kMaxTranslation, (pX - kXStep * 2.) / kXStep)
|
||||
: (pX < kXStep * 4.)
|
||||
? anim::interpolateF(-kMaxTranslation, 0, (pX - kXStep * 3.) / kXStep)
|
||||
: anim::interpolateF(0, 0., (pX - kXStep * 4.) / kXStep);
|
||||
|
||||
// 0, kMaxTranslation, -kMaxTranslation, 0.
|
||||
const auto y = (pY < kYStep)
|
||||
? anim::interpolateF(0., kMaxTranslation, pY / kYStep)
|
||||
: (pY < kYStep * 2.)
|
||||
? anim::interpolateF(kMaxTranslation, 0, (pY - kYStep) / kYStep)
|
||||
: (pY < kYStep * 3.)
|
||||
? anim::interpolateF(0, -kMaxTranslation, (pY - kYStep * 2.) / kYStep)
|
||||
: anim::interpolateF(-kMaxTranslation, 0, (pY - kYStep * 3) / kYStep);
|
||||
|
||||
const auto center = position + QPoint(
|
||||
_singleSize.width() / 2,
|
||||
_singleSize.height() / 2);
|
||||
|
||||
p.translate(center);
|
||||
p.rotate(angle);
|
||||
p.translate(-center);
|
||||
p.translate(x, y);
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::paintSticker(
|
||||
Painter &p,
|
||||
int index,
|
||||
QPoint position,
|
||||
bool paused,
|
||||
crl::time now) const {
|
||||
if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) {
|
||||
p.setOpacity(over);
|
||||
auto tl = position;
|
||||
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
|
||||
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners);
|
||||
p.setOpacity(1);
|
||||
if (_dragging.index != index) {
|
||||
const auto over = _elements[index].overAnimation.value(
|
||||
(index == _selected) ? 1. : 0.);
|
||||
if (over) {
|
||||
p.setOpacity(over);
|
||||
Ui::FillRoundRect(
|
||||
p,
|
||||
QRect(
|
||||
rtl()
|
||||
? QPoint(
|
||||
width() - position.x() - _singleSize.width(),
|
||||
position.y())
|
||||
: position,
|
||||
_singleSize),
|
||||
st::emojiPanHover,
|
||||
Ui::StickerHoverCorners);
|
||||
p.setOpacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
const auto hasShake = _shakeAnimation.animating();
|
||||
if (hasShake) {
|
||||
shakeTransform(p, index, position, now);
|
||||
}
|
||||
|
||||
const auto &element = _elements[index];
|
||||
@@ -1390,6 +2025,9 @@ void StickerSetBox::Inner::paintSticker(
|
||||
_singleSize,
|
||||
width());
|
||||
}
|
||||
if (hasShake) {
|
||||
p.resetTransform();
|
||||
}
|
||||
}
|
||||
|
||||
bool StickerSetBox::Inner::loaded() const {
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace tgcalls {
|
||||
class InstanceImpl;
|
||||
class InstanceV2Impl;
|
||||
class InstanceV2ReferenceImpl;
|
||||
class InstanceV2_4_0_0Impl;
|
||||
class InstanceImplLegacy;
|
||||
void SetLegacyGlobalServerConfig(const std::string &serverConfig);
|
||||
} // namespace tgcalls
|
||||
@@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
|
||||
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
|
||||
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
|
||||
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
|
||||
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
|
||||
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
|
||||
|
||||
[[nodiscard]] base::flat_set<int64> CollectEndpointIds(
|
||||
|
||||
@@ -215,7 +215,7 @@ void Panel::initWindow() {
|
||||
}
|
||||
const auto shown = _layerBg->topShownLayer();
|
||||
return (!shown || !shown->geometry().contains(widgetPoint))
|
||||
? (Flag::Move | Flag::FullScreen)
|
||||
? (Flag::Move | Flag::Menu | Flag::FullScreen)
|
||||
: Flag::None;
|
||||
});
|
||||
|
||||
@@ -276,8 +276,8 @@ void Panel::initControls() {
|
||||
_layerBg->showBox(std::move(box));
|
||||
}
|
||||
} else if (const auto source = env->uniqueDesktopCaptureSource()) {
|
||||
if (_call->isSharingScreen()) {
|
||||
_call->toggleScreenSharing(std::nullopt);
|
||||
if (!chooseSourceActiveDeviceId().isEmpty()) {
|
||||
chooseSourceStop();
|
||||
} else {
|
||||
chooseSourceAccepted(*source, false);
|
||||
}
|
||||
@@ -554,7 +554,6 @@ void Panel::reinitWithCall(Call *call) {
|
||||
Ui::Toast::Show(widget(), Ui::Toast::Config{
|
||||
.text = { text },
|
||||
.st = &st::callErrorToast,
|
||||
.multiline = true,
|
||||
});
|
||||
}, _callLifetime);
|
||||
|
||||
|
||||
@@ -1195,24 +1195,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
const auto addVolumeItem = (!muted || isMe(participantPeer));
|
||||
const auto admin = IsGroupCallAdmin(_peer, participantPeer);
|
||||
const auto session = &_peer->session();
|
||||
const auto getCurrentWindow = [=]() -> Window::SessionController* {
|
||||
if (const auto window = Core::App().windowFor(participantPeer)) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
if (&controller->session() == session) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto getWindow = [=] {
|
||||
if (const auto current = getCurrentWindow()) {
|
||||
return current;
|
||||
} else if (&Core::App().domain().active() != &session->account()) {
|
||||
Core::App().domain().activate(&session->account());
|
||||
}
|
||||
return getCurrentWindow();
|
||||
};
|
||||
const auto account = &session->account();
|
||||
|
||||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
@@ -1223,7 +1206,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
: st::groupCallPopupMenu));
|
||||
const auto weakMenu = Ui::MakeWeak(result.get());
|
||||
const auto withActiveWindow = [=](auto callback) {
|
||||
if (const auto window = getWindow()) {
|
||||
if (const auto window = Core::App().activePrimaryWindow()) {
|
||||
if (const auto menu = weakMenu.data()) {
|
||||
menu->discardParentReActivate();
|
||||
|
||||
@@ -1232,8 +1215,13 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
|
||||
// PopupMenu::hide activates back the group call panel :(
|
||||
delete weakMenu;
|
||||
}
|
||||
callback(window);
|
||||
window->widget()->activate();
|
||||
window->invokeForSessionController(
|
||||
account,
|
||||
participantPeer,
|
||||
[&](not_null<Window::SessionController*> newController) {
|
||||
callback(newController);
|
||||
newController->widget()->activate();
|
||||
});
|
||||
}
|
||||
};
|
||||
const auto showProfile = [=] {
|
||||
|
||||
@@ -410,7 +410,7 @@ void Panel::initWindow() {
|
||||
}
|
||||
const auto shown = _layerBg->topShownLayer();
|
||||
return (!shown || !shown->geometry().contains(widgetPoint))
|
||||
? (Flag::Move | Flag::Maximize)
|
||||
? (Flag::Move | Flag::Menu | Flag::Maximize)
|
||||
: Flag::None;
|
||||
});
|
||||
|
||||
|
||||
@@ -645,9 +645,10 @@ void SettingsBox(
|
||||
shareLink = [=] {
|
||||
if (!copyLink() && !state->generatingLink) {
|
||||
state->generatingLink = true;
|
||||
peer->session().api().inviteLinks().create(
|
||||
peer->session().api().inviteLinks().create({
|
||||
peer,
|
||||
crl::guard(layout, [=](auto&&) { copyLink(); }));
|
||||
crl::guard(layout, [=](auto&&) { copyLink(); })
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,6 +284,10 @@ stickersTrendingSubheaderFont: normalFont;
|
||||
stickersTrendingSubheaderFg: windowSubTextFg;
|
||||
stickersTrendingSubheaderTop: 31px;
|
||||
|
||||
stickersHeaderBadgeFont: font(10px);
|
||||
stickersHeaderBadgeFontTop: 12px;
|
||||
stickersHeaderBadgeFontSkip: 12px;
|
||||
|
||||
emojiPanButtonRight: 7px;
|
||||
emojiPanButtonTop: 8px;
|
||||
emojiPanButton: RoundButton(defaultActiveButton) {
|
||||
@@ -1409,6 +1413,22 @@ editTagLimit: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
editStickerSetNameField: InputField(defaultInputField) {
|
||||
textMargins: margins(0px, 8px, 26px, 4px);
|
||||
heightMin: 36px;
|
||||
heightMax: 36px;
|
||||
placeholderFg: placeholderFg;
|
||||
placeholderFgActive: placeholderFgActive;
|
||||
placeholderFgError: placeholderFgActive;
|
||||
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||
placeholderScale: 0.;
|
||||
placeholderFont: normalFont;
|
||||
}
|
||||
editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
color: lightButtonFg;
|
||||
thickness: 2px;
|
||||
}
|
||||
|
||||
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
|
||||
paidStarIconTop: 7px;
|
||||
paidAmountAbout: FlatLabel(defaultFlatLabel) {
|
||||
@@ -1423,3 +1443,61 @@ paidTagLabel: FlatLabel(defaultFlatLabel) {
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
paidTagPadding: margins(16px, 6px, 16px, 6px);
|
||||
|
||||
pickLocationWindow: size(364px, 680px);
|
||||
pickLocationMapHeight: 220px;
|
||||
pickLocationCollapsedHeight: 92px;
|
||||
pickLocationRowHeight: 52px;
|
||||
pickLocationButton: FlatButton {
|
||||
height: pickLocationRowHeight;
|
||||
bgColor: contactsBg;
|
||||
overBgColor: contactsBgOver;
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
pickLocationButtonText: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 20px;
|
||||
style: semiboldTextStyle;
|
||||
textFg: windowBoldFg;
|
||||
}
|
||||
pickLocationButtonStatus: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 128px;
|
||||
maxHeight: 20px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
pickLocationButtonSkip: 6px;
|
||||
pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }};
|
||||
pickLocationVenueItem: PeerListItem(defaultPeerListItem) {
|
||||
height: pickLocationRowHeight;
|
||||
photoSize: 42px;
|
||||
photoPosition: point(18px, 5px);
|
||||
namePosition: point(70px, 7px);
|
||||
statusPosition: point(70px, 27px);
|
||||
button: OutlineButton(defaultPeerListButton) {
|
||||
textBg: contactsBg;
|
||||
textBgOver: contactsBgOver;
|
||||
font: normalFont;
|
||||
padding: margins(11px, 5px, 11px, 5px);
|
||||
ripple: defaultRippleAnimation;
|
||||
}
|
||||
statusFg: contactsStatusFg;
|
||||
statusFgOver: contactsStatusFgOver;
|
||||
statusFgActive: contactsStatusFgOnline;
|
||||
}
|
||||
pickLocationVenueList: PeerList(defaultPeerList) {
|
||||
item: pickLocationVenueItem;
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
pickLocationIconSkip: 6px;
|
||||
pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
|
||||
size: size(56px, 56px);
|
||||
color: windowSubTextFg;
|
||||
thickness: 4px;
|
||||
}
|
||||
pickLocationPromoHeight: 32px;
|
||||
pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
||||
height: 44px;
|
||||
textTop: 11px;
|
||||
width: -96px;
|
||||
font: font(15px semibold);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "menu/menu_send.h" // SendMenu::FillSendMenu
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/controls/tabbed_search.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
@@ -48,7 +49,6 @@ namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSearchRequestDelay = 400;
|
||||
constexpr auto kSearchBotUsername = "gif"_cs;
|
||||
constexpr auto kMinRepaintDelay = crl::time(33);
|
||||
constexpr auto kMinAfterScrollDelay = crl::time(33);
|
||||
|
||||
@@ -864,13 +864,11 @@ void GifsListWidget::searchForGifs(const QString &query) {
|
||||
}
|
||||
|
||||
if (!_searchBot && !_searchBotRequestId) {
|
||||
auto username = kSearchBotUsername.utf16();
|
||||
const auto username = session().serverConfig().gifSearchUsername;
|
||||
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||
MTP_string(username)
|
||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
auto &data = result.c_contacts_resolvedPeer();
|
||||
auto &data = result.data();
|
||||
session().data().processUsers(data.vusers());
|
||||
session().data().processChats(data.vchats());
|
||||
const auto peer = session().data().peerLoaded(
|
||||
|
||||
@@ -392,6 +392,9 @@ void InitMessageFieldHandlers(
|
||||
Fn<bool()> customEmojiPaused,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji,
|
||||
const style::InputField *fieldStyle) {
|
||||
const auto paused = [customEmojiPaused] {
|
||||
return customEmojiPaused && customEmojiPaused();
|
||||
};
|
||||
field->setTagMimeProcessor(
|
||||
FieldTagMimeProcessor(session, allowPremiumEmoji));
|
||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
@@ -399,10 +402,10 @@ void InitMessageFieldHandlers(
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kEmojiChat) || customEmojiPaused();
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kChatSpoiler) || customEmojiPaused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kEmojiChat) || paused();
|
||||
}, [paused] {
|
||||
return On(PowerSaving::kChatSpoiler) || paused();
|
||||
});
|
||||
field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
field->setInstantReplacesEnabled(
|
||||
@@ -525,7 +528,7 @@ void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
|
||||
st::historySendSize.height() - 2 * st::historySendPadding);
|
||||
field->setMaxHeight(st::historyComposeFieldMaxHeight);
|
||||
|
||||
field->document()->setDocumentMargin(4.);
|
||||
field->setDocumentMargin(4.);
|
||||
field->setAdditionalMargin(style::ConvertScale(4) - 4);
|
||||
}
|
||||
|
||||
@@ -1123,10 +1126,8 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
|
||||
: list.back();
|
||||
*toast = Ui::Toast::Show(parent, {
|
||||
.text = { tr::lng_send_text_no_about(tr::now, lt_types, types) },
|
||||
.st = &st::defaultMultilineToast,
|
||||
.attach = RectPart::Bottom,
|
||||
.duration = kTypesDuration,
|
||||
.multiline = true,
|
||||
.slideSide = RectPart::Bottom,
|
||||
});
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -8,11 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "chat_helpers/stickers_dice_pack.h"
|
||||
|
||||
#include "main/main_session.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "ui/image/image_location_factory.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
@@ -104,6 +102,11 @@ void DicePack::tryGenerateLocalZero() {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto generateLocal = [&](int index, const QString &name) {
|
||||
_map.emplace(
|
||||
index,
|
||||
ChatHelpers::GenerateLocalTgsSticker(_session, name));
|
||||
};
|
||||
if (_emoji == DicePacks::kDiceString) {
|
||||
generateLocal(0, u"dice_idle"_q);
|
||||
} else if (_emoji == DicePacks::kDartString) {
|
||||
@@ -123,32 +126,8 @@ void DicePack::tryGenerateLocalZero() {
|
||||
}
|
||||
}
|
||||
|
||||
void DicePack::generateLocal(int index, const QString &name) {
|
||||
const auto path = u":/gui/art/"_q + name + u".tgs"_q;
|
||||
auto task = FileLoadTask(
|
||||
_session,
|
||||
path,
|
||||
QByteArray(),
|
||||
nullptr,
|
||||
SendMediaType::File,
|
||||
FileLoadTo(0, {}, {}, 0),
|
||||
{},
|
||||
false);
|
||||
task.process({ .generateGoodThumbnail = false });
|
||||
const auto result = task.peekResult();
|
||||
Assert(result != nullptr);
|
||||
const auto document = _session->data().processDocument(
|
||||
result->document,
|
||||
Images::FromImageInMemory(result->thumb, "WEBP", result->thumbbytes));
|
||||
document->setLocation(Core::FileLocation(path));
|
||||
|
||||
_map.emplace(index, document);
|
||||
|
||||
Ensures(document->sticker());
|
||||
Ensures(document->sticker()->isLottie());
|
||||
}
|
||||
|
||||
DicePacks::DicePacks(not_null<Main::Session*> session) : _session(session) {
|
||||
DicePacks::DicePacks(not_null<Main::Session*> session)
|
||||
: _session(session) {
|
||||
}
|
||||
|
||||
DocumentData *DicePacks::lookup(const QString &emoji, int value) {
|
||||
|
||||
@@ -26,7 +26,6 @@ private:
|
||||
void load();
|
||||
void applySet(const MTPDmessages_stickerSet &data);
|
||||
void tryGenerateLocalZero();
|
||||
void generateLocal(int index, const QString &name);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
QString _emoji;
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
@@ -21,6 +22,16 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
|
||||
|
||||
GiftBoxPack::~GiftBoxPack() = default;
|
||||
|
||||
int GiftBoxPack::monthsForStars(int stars) const {
|
||||
if (stars <= 1000) {
|
||||
return 3;
|
||||
} else if (stars < 2500) {
|
||||
return 6;
|
||||
} else {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
DocumentData *GiftBoxPack::lookup(int months) const {
|
||||
const auto it = ranges::lower_bound(_localMonths, months);
|
||||
const auto fallback = _documents.empty() ? nullptr : _documents[0];
|
||||
@@ -38,6 +49,10 @@ DocumentData *GiftBoxPack::lookup(int months) const {
|
||||
return (index >= _documents.size()) ? fallback : _documents[index];
|
||||
}
|
||||
|
||||
Data::FileOrigin GiftBoxPack::origin() const {
|
||||
return Data::FileOriginStickerSet(_setId, _accessHash);
|
||||
}
|
||||
|
||||
void GiftBoxPack::load() {
|
||||
if (_requestId || !_documents.empty()) {
|
||||
return;
|
||||
@@ -59,6 +74,7 @@ void GiftBoxPack::load() {
|
||||
|
||||
void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
|
||||
_setId = data.vset().data().vid().v;
|
||||
_accessHash = data.vset().data().vaccess_hash().v;
|
||||
auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>();
|
||||
for (const auto &sticker : data.vdocuments().v) {
|
||||
const auto document = _session->data().processDocument(sticker);
|
||||
|
||||