Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f73edfec94 | |||
| a851bebc75 | |||
| c4e1e8415c | |||
| a507d28b49 | |||
| b2efc2ad14 | |||
| ec2faca145 | |||
| 17bb430006 | |||
| 8cbeadc68a | |||
| 3947056654 | |||
| ad7d1fddf0 | |||
| ab20f8eb31 | |||
| 257b8552f7 | |||
| bbeeaa95c0 | |||
| 81938514f1 | |||
| 4e0fde560c | |||
| 27d3721646 | |||
| dbb234097c | |||
| f75429cbaa | |||
| dc8c4a8332 | |||
| 096530c96a | |||
| ed0850d823 | |||
| 4a2173deaf | |||
| f40a584905 | |||
| e54204b136 | |||
| 715dff0a3e | |||
| 558daa3382 | |||
| 7265297b19 | |||
| 1fac6db8bd | |||
| c9bd776d1e | |||
| 179e81478e | |||
| 9ef74c510c | |||
| 36f6917bd3 | |||
| c185f40acd | |||
| 75bcd2b24b | |||
| ab7e4bce1a | |||
| da797a2b07 | |||
| 707951accb | |||
| 7eb98b50ec | |||
| 65f7bdb914 | |||
| 876c47c436 | |||
| e8f16840de | |||
| dd57ad567f | |||
| 54934fb835 | |||
| bd49887607 | |||
| aaa72b7c30 | |||
| 4cafacc8db | |||
| 374b3c68ac | |||
| 69d21f73ef | |||
| 6dc3bd65e8 | |||
| deb9aa435b | |||
| 68cb568898 | |||
| 1ed81b1c9c | |||
| a95fb5b28d | |||
| 4f7b5ca7da | |||
| 29b7673b88 | |||
| 66f375d2c6 | |||
| 77d214d2a5 | |||
| 09e6077e97 | |||
| 631d6abb06 | |||
| c833b8a1b0 | |||
| b58c03f0de | |||
| 1988435cdf | |||
| 7e704d9529 | |||
| a39a8dbd2c | |||
| 90dfae52f5 | |||
| dfc422b505 | |||
| 54cc12cf22 | |||
| d81e832ae6 | |||
| 1399d2501d | |||
| 57254ca259 | |||
| 043e3ae97e | |||
| 68b9a8bc6a | |||
| 157d5c743b | |||
| 54d0290ba2 | |||
| 0fc2df8eec | |||
| d9caf15d1d | |||
| b674826392 | |||
| 37181f9d0a | |||
| 93aebc747d | |||
| a84ac933dd | |||
| b79c306bfe | |||
| 57d62423b3 | |||
| f4674389d5 | |||
| 63c4c5064f | |||
| ae1f364730 | |||
| c6e322de86 | |||
| 68bf6f991c | |||
| b15f5f8596 | |||
| 27a71a8dcd | |||
| 2044f8f9ad | |||
| ffa4b1db87 | |||
| cad25ae644 | |||
| 21094fe11b | |||
| 101dbdf243 | |||
| 0dc92762bc | |||
| 5fdaa6b91f | |||
| 968d036834 | |||
| d47c5df73d | |||
| 6c1e7357c6 | |||
| 479b63c33a | |||
| 950a946a16 | |||
| 5f8da27c86 | |||
| a9bd7803e6 | |||
| 3ece9b1566 | |||
| e71a067f4b | |||
| ebf456abe4 | |||
| 3552da5ce7 | |||
| b5bd0f53ad | |||
| 7d115b3fab | |||
| d0a030ab58 | |||
| 712c06756e | |||
| 301ffc15ef | |||
| 3c4a711b5d | |||
| 989145726d | |||
| 9eebd3b514 | |||
| eb997ae9e3 | |||
| db4c9b83f3 | |||
| 1196b6a3fb | |||
| bef216bc93 | |||
| 811d75e383 | |||
| 049cde48ee | |||
| cb65c50c19 | |||
| f23c9a61bc | |||
| b5d5ff3cbb | |||
| c5ba0fa705 | |||
| 71893f4ef7 | |||
| 4a60c57661 | |||
| fbbcc21198 | |||
| d993386756 | |||
| 30819509d3 | |||
| 10c3fe0f63 | |||
| bad2ff00a8 | |||
| 3498a7f0ee | |||
| 648b23b548 | |||
| ba89912834 | |||
| c1bc7e6ab1 | |||
| eea50ed6b0 | |||
| 006d6fe2c0 | |||
| 5180e0ec57 | |||
| 490ec7949f | |||
| 671a15d763 | |||
| d2d5226dc7 | |||
| b58ece3a38 | |||
| 2b4a2b5b97 | |||
| 382b175db2 | |||
| 2db81211c8 | |||
| 46157c99c4 | |||
| c3ed5224c2 | |||
| acee7c7cfc | |||
| 002fe9a72a | |||
| 93b7c47cda | |||
| 053f8ad1c0 | |||
| 60ca6895db | |||
| fc5e9414b7 | |||
| f768e405fa | |||
| a22cf8e303 | |||
| 8a5797e1bd | |||
| 750ad600be | |||
| eaf4575eb8 | |||
| b9677fe1db | |||
| 4047f1733d | |||
| 3d114131e0 | |||
| 5810149a77 | |||
| 032fe3e0fc | |||
| 81d7fcba7e | |||
| 4e9b5b0d33 | |||
| 90068f6261 | |||
| f37d056c14 | |||
| f748988ae3 | |||
| 9a25d2c413 | |||
| ec40292cbf | |||
| 5e8c3fb146 | |||
| fa8ed186d8 | |||
| c1f36d43d0 | |||
| 501cae2200 | |||
| 8ad5117495 | |||
| d79da3d884 | |||
| e4d88f829c | |||
| 2673e1df53 | |||
| 081890ef61 | |||
| cbcde7d2f5 | |||
| 68ad4aeaf8 | |||
| 99e88d74bc | |||
| 43ffc9d67c | |||
| 053c462dc0 | |||
| 174b627a78 | |||
| bb33d0b997 | |||
| 6f6fb3d1b6 | |||
| 3f216ad946 | |||
| c49eb7041f | |||
| a1d8202644 | |||
| 82428aef28 | |||
| 10f7b985c7 | |||
| 06075411a5 | |||
| 7e01b12825 | |||
| 273119fc55 | |||
| e47e4ba338 | |||
| cbcdeae200 | |||
| c585112e37 | |||
| 392df8b56f | |||
| 0e6470a087 | |||
| 6c0ea0eb9f | |||
| 9229de2658 | |||
| 2935721cd0 | |||
| a48cd5f15a | |||
| 9e77e80f92 | |||
| deb50ee528 | |||
| 14909ae913 | |||
| d5d9da7d0a | |||
| 86a048a021 | |||
| aafa8631e0 | |||
| 9176bf2e47 | |||
| 72c667b153 | |||
| fe6f65b3ab | |||
| 5db2821f8c | |||
| 9e3e7265d2 | |||
| 749b2e0e95 | |||
| 5b45397383 | |||
| 61c17c0a93 | |||
| 93e592472c |
@@ -57,14 +57,6 @@ include(cmake/validate_d3d_compiler.cmake)
|
||||
include(cmake/target_prepare_qrc.cmake)
|
||||
|
||||
include(cmake/options.cmake)
|
||||
|
||||
if (NOT DESKTOP_APP_USE_PACKAGED)
|
||||
if (WIN32)
|
||||
set(qt_version 5.15.13)
|
||||
elseif (APPLE)
|
||||
set(qt_version 6.2.8)
|
||||
endif()
|
||||
endif()
|
||||
include(cmake/external/qt/package.cmake)
|
||||
|
||||
set(desktop_app_skip_libs
|
||||
|
||||
@@ -195,6 +195,7 @@ PRIVATE
|
||||
api/api_earn.h
|
||||
api/api_editing.cpp
|
||||
api/api_editing.h
|
||||
api/api_filter_updates.h
|
||||
api/api_global_privacy.cpp
|
||||
api/api_global_privacy.h
|
||||
api/api_hash.cpp
|
||||
@@ -233,6 +234,10 @@ PRIVATE
|
||||
api/api_single_message_search.h
|
||||
api/api_statistics.cpp
|
||||
api/api_statistics.h
|
||||
api/api_statistics_data_deserialize.cpp
|
||||
api/api_statistics_data_deserialize.h
|
||||
api/api_statistics_sender.cpp
|
||||
api/api_statistics_sender.h
|
||||
api/api_text_entities.cpp
|
||||
api/api_text_entities.h
|
||||
api/api_toggling_media.cpp
|
||||
@@ -791,8 +796,6 @@ PRIVATE
|
||||
history/view/media/history_view_dice.h
|
||||
history/view/media/history_view_document.cpp
|
||||
history/view/media/history_view_document.h
|
||||
history/view/media/history_view_extended_preview.cpp
|
||||
history/view/media/history_view_extended_preview.h
|
||||
history/view/media/history_view_file.cpp
|
||||
history/view/media/history_view_file.h
|
||||
history/view/media/history_view_game.cpp
|
||||
@@ -956,6 +959,10 @@ PRIVATE
|
||||
history/history_view_highlight_manager.h
|
||||
history/history_widget.cpp
|
||||
history/history_widget.h
|
||||
info/bot/earn/info_bot_earn_list.cpp
|
||||
info/bot/earn/info_bot_earn_list.h
|
||||
info/bot/earn/info_bot_earn_widget.cpp
|
||||
info/bot/earn/info_bot_earn_widget.h
|
||||
info/channel_statistics/boosts/create_giveaway_box.cpp
|
||||
info/channel_statistics/boosts/create_giveaway_box.h
|
||||
info/channel_statistics/boosts/giveaway/giveaway_list_controllers.cpp
|
||||
@@ -964,10 +971,10 @@ PRIVATE
|
||||
info/channel_statistics/boosts/info_boosts_inner_widget.h
|
||||
info/channel_statistics/boosts/info_boosts_widget.cpp
|
||||
info/channel_statistics/boosts/info_boosts_widget.h
|
||||
info/channel_statistics/earn/info_earn_inner_widget.cpp
|
||||
info/channel_statistics/earn/info_earn_inner_widget.h
|
||||
info/channel_statistics/earn/info_earn_widget.cpp
|
||||
info/channel_statistics/earn/info_earn_widget.h
|
||||
info/channel_statistics/earn/info_channel_earn_list.cpp
|
||||
info/channel_statistics/earn/info_channel_earn_list.h
|
||||
info/channel_statistics/earn/info_channel_earn_widget.cpp
|
||||
info/channel_statistics/earn/info_channel_earn_widget.h
|
||||
info/common_groups/info_common_groups_inner_widget.cpp
|
||||
info/common_groups/info_common_groups_inner_widget.h
|
||||
info/common_groups/info_common_groups_widget.cpp
|
||||
@@ -1608,6 +1615,8 @@ PRIVATE
|
||||
window/window_peer_menu.cpp
|
||||
window/window_peer_menu.h
|
||||
window/window_section_common.h
|
||||
window/window_separate_id.cpp
|
||||
window/window_separate_id.h
|
||||
window/window_session_controller.cpp
|
||||
window/window_session_controller.h
|
||||
window/window_session_controller_link_info.h
|
||||
@@ -1899,12 +1908,44 @@ if (WIN32)
|
||||
/DELAYLOAD:uxtheme.dll
|
||||
/DELAYLOAD:crypt32.dll
|
||||
/DELAYLOAD:bcrypt.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:netapi32.dll
|
||||
/DELAYLOAD:imm32.dll
|
||||
/DELAYLOAD:userenv.dll
|
||||
/DELAYLOAD:wtsapi32.dll
|
||||
/DELAYLOAD:propsys.dll
|
||||
)
|
||||
if (QT_VERSION GREATER 6)
|
||||
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
|
||||
/DELAYLOAD:API-MS-Win-Core-File-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-LibraryLoader-l1-2-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Localization-l1-2-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Memory-l1-1-1.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-ProcessThreads-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Synch-l1-2-0.dll # Synchronization.lib
|
||||
/DELAYLOAD:API-MS-Win-Core-SysInfo-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-Timezone-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Core-WinRT-String-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
|
||||
/DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll
|
||||
/DELAYLOAD:authz.dll # Authz.lib
|
||||
/DELAYLOAD:comdlg32.dll
|
||||
/DELAYLOAD:dwrite.dll # DWrite.lib
|
||||
/DELAYLOAD:dxgi.dll # DXGI.lib
|
||||
/DELAYLOAD:d3d9.dll # D3D9.lib
|
||||
/DELAYLOAD:d3d11.dll # D3D11.lib
|
||||
/DELAYLOAD:d3d12.dll # D3D12.lib
|
||||
/DELAYLOAD:setupapi.dll # SetupAPI.lib
|
||||
/DELAYLOAD:winhttp.dll
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_prepare_qrc(Telegram)
|
||||
|
||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 371 B |
|
After Width: | Height: | Size: 609 B |
|
After Width: | Height: | Size: 887 B |
|
After Width: | Height: | Size: 480 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 766 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 438 B |
|
After Width: | Height: | Size: 631 B |
|
After Width: | Height: | Size: 886 B |
|
After Width: | Height: | Size: 454 B |
|
After Width: | Height: | Size: 772 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 484 B |
|
After Width: | Height: | Size: 860 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -683,6 +683,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
"lng_settings_passcode_disable" = "Disable Passcode";
|
||||
"lng_settings_passcode_disable_sure" = "Are you sure you want to disable passcode?";
|
||||
"lng_settings_use_winhello" = "Unlock with Windows Hello";
|
||||
"lng_settings_use_winhello_about" = "You need to enter your passcode once before unlocking Telegram with Windows Hello.";
|
||||
"lng_settings_use_touchid" = "Unlock with Touch ID";
|
||||
"lng_settings_use_touchid_about" = "You need to enter your passcode once before unlocking Telegram with Touch ID.";
|
||||
"lng_settings_use_applewatch" = "Unlock with Apple Watch";
|
||||
"lng_settings_use_applewatch_about" = "You need to enter your passcode once before unlocking Telegram with Apple Watch.";
|
||||
"lng_settings_use_systempwd" = "Unlock with System Password";
|
||||
"lng_settings_use_systempwd_about" = "You need to enter your passcode once before unlocking Telegram with System Password.";
|
||||
"lng_settings_password_disable" = "Disable Cloud Password";
|
||||
"lng_settings_password_abort" = "Abort two-step verification setup";
|
||||
"lng_settings_about_bio" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco";
|
||||
@@ -897,6 +905,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_settings_gift_premium_users_confirm" = "Proceed";
|
||||
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
|
||||
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
|
||||
"lng_settings_gift_premium_choose" = "Please choose at least one recipient.";
|
||||
|
||||
"lng_backgrounds_header" = "Choose Wallpaper";
|
||||
"lng_theme_sure_keep" = "Keep this theme?";
|
||||
@@ -991,6 +1000,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passcode_ph" = "Your passcode";
|
||||
"lng_passcode_submit" = "Submit";
|
||||
"lng_passcode_logout" = "Log out";
|
||||
"lng_passcode_winhello" = "You need to enter your passcode\nbefore you can use Windows Hello.";
|
||||
"lng_passcode_touchid" = "You need to enter your passcode\nbefore you can use Touch ID.";
|
||||
"lng_passcode_applewatch" = "You need to enter your passcode\nbefore you can use Watch to unlock.";
|
||||
"lng_passcode_systempwd" = "You need to enter your passcode\nbefore you can use system password.";
|
||||
"lng_passcode_winhello_unlock" = "Telegram wants to unlock with Windows Hello.";
|
||||
"lng_passcode_touchid_unlock" = "unlock";
|
||||
"lng_passcode_create_button" = "Save Passcode";
|
||||
"lng_passcode_check_button" = "Submit";
|
||||
"lng_passcode_change_button" = "Save Passcode";
|
||||
@@ -1560,6 +1575,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_manage_peer_bot_public_link" = "Public Link";
|
||||
"lng_manage_peer_bot_public_links" = "Public Links";
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
@@ -2185,6 +2201,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages.";
|
||||
"lng_premium_summary_subtitle_business" = "Telegram Business";
|
||||
"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies.";
|
||||
"lng_premium_summary_subtitle_effects" = "Message Effects";
|
||||
"lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages.";
|
||||
"lng_premium_summary_bottom_subtitle" = "About Telegram Premium";
|
||||
"lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||
"lng_premium_summary_button" = "Subscribe for {cost} per month";
|
||||
@@ -2316,6 +2334,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_summary_options_more" = "More Options";
|
||||
"lng_credits_summary_options_about" = "By proceeding and purchasing Stars, you agree with the {link}.";
|
||||
"lng_credits_summary_options_about_link" = "Terms and Conditions";
|
||||
"lng_credits_summary_options_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_credits_summary_history_tab_full" = "All Transactions";
|
||||
"lng_credits_summary_history_tab_in" = "Incoming";
|
||||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
@@ -2324,15 +2343,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
"lng_credits_box_out_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
|
||||
"lng_credits_box_out_media#one" = "Do you want to unlock {media} in {chat} for **{count} Star**?";
|
||||
"lng_credits_box_out_media#other" = "Do you want to unlock {media} in {chat} for **{count} Stars**?";
|
||||
"lng_credits_box_out_photo" = "a photo";
|
||||
"lng_credits_box_out_photos#one" = "{count} photo";
|
||||
"lng_credits_box_out_photos#other" = "{count} photos";
|
||||
"lng_credits_box_out_video" = "a video";
|
||||
"lng_credits_box_out_videos#one" = "{count} video";
|
||||
"lng_credits_box_out_videos#other" = "{count} videos";
|
||||
"lng_credits_box_out_both" = "{photo} and {video}";
|
||||
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
|
||||
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
|
||||
"lng_credits_box_out_about" = "Review the {link} for Stars.";
|
||||
"lng_credits_media_done_title" = "Media Unlocked";
|
||||
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
|
||||
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
|
||||
"lng_credits_summary_in_toast_title" = "Stars Acquired";
|
||||
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance.";
|
||||
"lng_credits_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
|
||||
"lng_credits_box_history_entry_peer" = "Recipient";
|
||||
"lng_credits_box_history_entry_via" = "Via";
|
||||
"lng_credits_box_history_entry_play_market" = "Play Market";
|
||||
"lng_credits_box_history_entry_app_store" = "App Store";
|
||||
"lng_credits_box_history_entry_fragment" = "Fragment";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_small_balance_title#one" = "{count} Star Needed";
|
||||
@@ -3304,6 +3343,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
@@ -3315,6 +3356,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_factcheck_bottom" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country ({country}) responsible for combatting misinformation.";
|
||||
"lng_factcheck_links" = "Only **t.me/** links are allowed.";
|
||||
|
||||
"lng_paid_title" = "Paid Content";
|
||||
"lng_paid_enter_cost" = "Enter Unlock Cost";
|
||||
"lng_paid_cost_placeholder" = "Stars to Unlock";
|
||||
"lng_paid_about" = "Users will have to transfer this amount of Stars to your channel in order to view this media. {link}";
|
||||
"lng_paid_about_link" = "More about stars >";
|
||||
"lng_paid_about_link_url" = "https://telegram.org/blog/telegram-stars";
|
||||
"lng_paid_price" = "Unlock for {price}";
|
||||
|
||||
"lng_translate_show_original" = "Show Original";
|
||||
"lng_translate_bar_to" = "Translate to {name}";
|
||||
"lng_translate_bar_to_other" = "Translate to {name}";
|
||||
@@ -3581,6 +3630,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_formatting_link_text" = "Text";
|
||||
"lng_formatting_link_url" = "URL";
|
||||
"lng_formatting_link_create" = "Create";
|
||||
"lng_formatting_code_title" = "Code Language";
|
||||
"lng_formatting_code_language" = "Language for syntax highlighting.";
|
||||
"lng_formatting_code_auto" = "Auto-Detect";
|
||||
|
||||
"lng_text_copied" = "Text copied to clipboard.";
|
||||
"lng_code_copied" = "Block copied to clipboard.";
|
||||
@@ -4131,6 +4183,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_restricted_send_polls_all" = "Posting polls isn't allowed in this group.";
|
||||
|
||||
"lng_restricted_send_public_polls" = "Sorry, public polls can't be forwarded to channels.";
|
||||
"lng_restricted_send_paid_media" = "Sorry, paid media can't be sent to this channel.";
|
||||
|
||||
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
|
||||
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
|
||||
@@ -5158,6 +5211,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_history_return" = "Refund";
|
||||
"lng_channel_earn_history_return_about" = "Refunded back";
|
||||
"lng_channel_earn_history_pending" = "Pending";
|
||||
"lng_channel_earn_history_failed" = "Failed";
|
||||
"lng_channel_earn_history_show_more#one" = "Show {count} More Transaction";
|
||||
"lng_channel_earn_history_show_more#other" = "Show {count} More Transactions";
|
||||
"lng_channel_earn_off" = "Switch Off Ads";
|
||||
@@ -5180,6 +5234,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_channel_earn_chart_revenue" = "Ad revenue";
|
||||
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";
|
||||
"lng_channel_earn_chart_overriden_detail_usd" = "Revenue in USD";
|
||||
"lng_channel_earn_currency_history" = "TON Transactions";
|
||||
"lng_channel_earn_credits_history" = "Stars Transactions";
|
||||
"lng_channel_earn_out_check_password_about" = "You can withdraw only if you have:";
|
||||
|
||||
"lng_bot_earn_title" = "Stars Balance";
|
||||
"lng_bot_earn_chart_revenue" = "Revenue";
|
||||
"lng_bot_earn_overview_title" = "Proceeds overview";
|
||||
"lng_bot_earn_available" = "Available balance";
|
||||
"lng_bot_earn_total" = "Total lifetime proceeds";
|
||||
"lng_bot_earn_balance_title" = "Available balance";
|
||||
"lng_bot_earn_balance_about" = "Stars from your total balance become available for spending on ads and rewards 21 days after they are earned.";
|
||||
"lng_bot_earn_balance_about_url" = "https://telegram.org/tos/stars";
|
||||
"lng_bot_earn_balance_button#one" = "Withdraw {emoji} {count}";
|
||||
"lng_bot_earn_balance_button#other" = "Withdraw {emoji} {count}";
|
||||
"lng_bot_earn_balance_button_all" = "Withdraw all stars";
|
||||
"lng_bot_earn_balance_button_locked" = "Withdraw";
|
||||
"lng_bot_earn_balance_button_buy_ads" = "Buy Ads";
|
||||
"lng_bot_earn_learn_credits_out_about" = "You can withdraw Stars using Fragment, or use Stars to advertise your bot. {link}";
|
||||
"lng_bot_earn_out_ph" = "Enter amount to withdraw";
|
||||
"lng_bot_earn_balance_password_title" = "Two-step verification";
|
||||
"lng_bot_earn_balance_password_description" = "Please enter your password to collect.";
|
||||
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less then {link}.";
|
||||
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
|
||||
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
|
||||
|
||||
"lng_contact_add" = "Add";
|
||||
"lng_contact_send_message" = "message";
|
||||
@@ -5384,7 +5462,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_EditedMarkText" = "Edited Mark";
|
||||
"ayu_SemiTransparentDeletedMessages" = "Translucent Deleted Messages";
|
||||
"ayu_HideNotificationCounters" = "Hide Notification Counters";
|
||||
"ayu_HideNotificationBadge" = "Hide Notification Badge";
|
||||
"ayu_HideAllChats" = "Hide \"All Chats\" Tab";
|
||||
"ayu_ChannelBottomButton" = "Channel Bottom Button";
|
||||
"ayu_ChannelBottomButtonHide" = "Hide";
|
||||
"ayu_ChannelBottomButtonMute" = "Mute";
|
||||
"ayu_ChannelBottomButtonDiscuss" = "Discuss";
|
||||
"ayu_SettingsShowID" = "Show Peer ID";
|
||||
"ayu_SettingsShowID_Hide" = "Hide";
|
||||
"ayu_SettingsShowMessageShot" = "Show Message Shot";
|
||||
@@ -5400,6 +5483,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_ContextMenuElementsHeader" = "Context Menu Elements";
|
||||
"ayu_DrawerElementsHeader" = "Drawer Elements";
|
||||
"ayu_TrayElementsHeader" = "Tray Elements";
|
||||
"ayu_SettingsSpoofWebviewAsAndroid" = "Spoof Client as Android";
|
||||
"ayu_SettingsBiggerWindow" = "Bigger Window";
|
||||
"ayu_SettingsIncreaseWebviewHeight" = "Increase Content Height";
|
||||
"ayu_SettingsIncreaseWebviewWidth" = "Increase Content Width";
|
||||
"ayu_ExportDataTitle" = "Export AyuGram Database?";
|
||||
"ayu_ExportDataDescription" = "It will be exported to the public folder, to Saved Attachments.";
|
||||
"ayu_ExportDataConfirm" = "Yes, export";
|
||||
@@ -5595,10 +5682,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"ayu_MessageShotShowReactions" = "Show reactions";
|
||||
"ayu_MessageShotShowColorfulReplies" = "Show colorful replies";
|
||||
"ayu_SendAsSticker" = "Send as Sticker";
|
||||
"ayu_AyuForwardStatusForwarding" = "Forwarding messages…";
|
||||
"ayu_AyuForwardStatusLoadingMedia" = "Loading media…";
|
||||
"ayu_AyuForwardForwardingDescription" = "Please keep this window open while AyuGram is forwarding your messages.";
|
||||
"ayu_AyuForwardLoadingMediaDescription" = "Please keep this window open while AyuGram is downloading media from your messages.";
|
||||
"ayu_AyuForwardStatusPreparing" = "Forwarding messages";
|
||||
"ayu_AyuForwardStatusLoadingMedia" = "Loading media";
|
||||
"ayu_AyuForwardStatusForwarding" = "Forwarding messages";
|
||||
"ayu_AyuForwardStatusFinished" = "Done";
|
||||
"ayu_AyuForwardStatusSentCount" = "sent %1$d of %2$d";
|
||||
"ayu_AyuForwardStatusChunkCount" = "chunk %1$d of %2$d";
|
||||
"ayu_JumpToBeginning" = "To Beginning";
|
||||
"ayu_ExpireMediaContextMenuText" = "Burn";
|
||||
"ayu_ExpiringVoiceMessageNote" = "This voice message can be played as many times as you want.";
|
||||
"ayu_ExpiringVideoMessageNote" = "This video message can be played as many times as you want.";
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
<file alias="art/ayu/bard/app.png">../../art/ayu/bard/app.png</file>
|
||||
<file alias="art/ayu/bard/app_preview.png">../../art/ayu/bard/app_preview.png</file>
|
||||
<file alias="art/ayu/bard/app_icon.ico">../../art/ayu/bard/app_icon.ico</file>
|
||||
<file alias="art/ayu/chibi/app.png">../../art/ayu/chibi/app.png</file>
|
||||
<file alias="art/ayu/chibi/app_preview.png">../../art/ayu/chibi/app_preview.png</file>
|
||||
<file alias="art/ayu/chibi/app_icon.ico">../../art/ayu/chibi/app_icon.ico</file>
|
||||
<file alias="art/ayu/chibi2/app.png">../../art/ayu/chibi2/app.png</file>
|
||||
<file alias="art/ayu/chibi2/app_preview.png">../../art/ayu/chibi2/app_preview.png</file>
|
||||
<file alias="art/ayu/chibi2/app_icon.ico">../../art/ayu/chibi2/app_icon.ico</file>
|
||||
<file alias="art/ayu/default/app.png">../../art/ayu/default/app.png</file>
|
||||
<file alias="art/ayu/default/app_preview.png">../../art/ayu/default/app_preview.png</file>
|
||||
<file alias="art/ayu/default/app_icon.ico">../../art/ayu/default/app_icon.ico</file>
|
||||
@@ -21,6 +27,9 @@
|
||||
<file alias="art/ayu/spotify/app.png">../../art/ayu/spotify/app.png</file>
|
||||
<file alias="art/ayu/spotify/app_preview.png">../../art/ayu/spotify/app_preview.png</file>
|
||||
<file alias="art/ayu/spotify/app_icon.ico">../../art/ayu/spotify/app_icon.ico</file>
|
||||
<file alias="art/ayu/win95/app.png">../../art/ayu/win95/app.png</file>
|
||||
<file alias="art/ayu/win95/app_preview.png">../../art/ayu/win95/app_preview.png</file>
|
||||
<file alias="art/ayu/win95/app_icon.ico">../../art/ayu/win95/app_icon.ico</file>
|
||||
<file alias="art/ayu/yaplus/app.png">../../art/ayu/yaplus/app.png</file>
|
||||
<file alias="art/ayu/yaplus/app_preview.png">../../art/ayu/yaplus/app_preview.png</file>
|
||||
<file alias="art/ayu/yaplus/app_icon.ico">../../art/ayu/yaplus/app_icon.ico</file>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.1.2.0" />
|
||||
Version="5.2.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,1,2,0
|
||||
PRODUCTVERSION 5,1,2,0
|
||||
FILEVERSION 5,2,2,0
|
||||
PRODUCTVERSION 5,2,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.1.2.0"
|
||||
VALUE "FileVersion", "5.2.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.1.2.0"
|
||||
VALUE "ProductVersion", "5.2.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,1,2,0
|
||||
PRODUCTVERSION 5,1,2,0
|
||||
FILEVERSION 5,2,2,0
|
||||
PRODUCTVERSION 5,2,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.1.2.0"
|
||||
VALUE "FileVersion", "5.2.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.1.2.0"
|
||||
VALUE "ProductVersion", "5.2.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Api {
|
||||
inline constexpr auto kScheduledUntilOnlineTimestamp = TimeId(0x7FFFFFFE);
|
||||
|
||||
struct SendOptions {
|
||||
uint64 price = 0;
|
||||
PeerData *sendAs = nullptr;
|
||||
TimeId scheduled = 0;
|
||||
BusinessShortcutId shortcutId = 0;
|
||||
|
||||
@@ -7,9 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_credits.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -20,25 +23,62 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kTransactionsLimit = 100;
|
||||
|
||||
[[nodiscard]] Data::CreditsHistoryEntry HistoryFromTL(
|
||||
const MTPStarsTransaction &tl,
|
||||
not_null<PeerData*> peer) {
|
||||
using HistoryPeerTL = MTPDstarsTransactionPeer;
|
||||
using namespace Data;
|
||||
const auto owner = &peer->owner();
|
||||
const auto photo = tl.data().vphoto()
|
||||
? peer->owner().photoFromWeb(*tl.data().vphoto(), ImageLocation())
|
||||
? owner->photoFromWeb(*tl.data().vphoto(), ImageLocation())
|
||||
: nullptr;
|
||||
auto extended = std::vector<CreditsHistoryMedia>();
|
||||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
.type = CreditsHistoryMediaType::Photo,
|
||||
.id = photo->id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
.type = CreditsHistoryMediaType::Video,
|
||||
.id = document->id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [&](const auto &) {});
|
||||
}
|
||||
}
|
||||
const auto barePeerId = tl.data().vpeer().match([](
|
||||
const HistoryPeerTL &p) {
|
||||
return peerFromMTP(p.vpeer());
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareId = tl.data().vpeer().match([](const HistoryPeerTL &p) {
|
||||
return peerFromMTP(p.vpeer());
|
||||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
@@ -51,8 +91,17 @@ namespace {
|
||||
return Data::CreditsHistoryEntry::PeerType::Unsupported;
|
||||
}, [](const MTPDstarsTransactionPeerPremiumBot &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.successDate = tl.data().vtransaction_date()
|
||||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,7 +201,8 @@ void CreditsHistory::request(
|
||||
_requestId = _api.request(MTPpayments_GetStarsTransactions(
|
||||
MTP_flags(_flags),
|
||||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input,
|
||||
MTP_string(token)
|
||||
MTP_string(token),
|
||||
MTP_int(kTransactionsLimit)
|
||||
)).done([=](const MTPpayments_StarsStatus &result) {
|
||||
_requestId = 0;
|
||||
done(StatusFromTL(result, _peer));
|
||||
@@ -199,24 +249,58 @@ rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
};
|
||||
}
|
||||
|
||||
void CreditsRefund(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &entryId,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
const auto user = peer->asUser();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
peer->session().api().request(MTPpayments_RefundStarsCharge(
|
||||
user->inputUser,
|
||||
MTP_string(entryId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
peer->session().api().updates().applyUpdates(result);
|
||||
done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
CreditsEarnStatistics::CreditsEarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> CreditsEarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
const auto finish = [=](const QString &url) {
|
||||
makeRequest(MTPpayments_GetStarsRevenueStats(
|
||||
MTP_flags(0),
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPpayments_StarsRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &status = data.vstatus().data();
|
||||
_data = Data::CreditsEarnStatistics{
|
||||
.revenueGraph = StatisticalGraphFromTL(
|
||||
data.vrevenue_graph()),
|
||||
.currentBalance = status.vcurrent_balance().v,
|
||||
.availableBalance = status.vavailable_balance().v,
|
||||
.overallRevenue = status.voverall_revenue().v,
|
||||
.usdRate = data.vusd_rate().v,
|
||||
.isWithdrawalEnabled = status.is_withdrawal_enabled(),
|
||||
.nextWithdrawalAt = status.vnext_withdrawal_at()
|
||||
? base::unixtime::parse(
|
||||
status.vnext_withdrawal_at()->v)
|
||||
: QDateTime(),
|
||||
.buyAdsUrl = url,
|
||||
};
|
||||
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
};
|
||||
|
||||
makeRequest(
|
||||
MTPpayments_GetStarsRevenueAdsAccountUrl(
|
||||
(_isUser ? user()->input : channel()->input))
|
||||
).done([=](const MTPpayments_StarsRevenueAdsAccountUrl &result) {
|
||||
finish(qs(result.data().vurl()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
finish({});
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
Data::CreditsEarnStatistics CreditsEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -7,13 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "api/api_statistics_sender.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_credits_earn.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class CreditsTopupOptions final {
|
||||
@@ -68,11 +72,20 @@ private:
|
||||
|
||||
};
|
||||
|
||||
void CreditsRefund(
|
||||
not_null<PeerData*> peer,
|
||||
const QString &entryId,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
class CreditsEarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit CreditsEarnStatistics(not_null<PeerData*>);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> PremiumPeerBot(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
@@ -34,22 +35,33 @@ void RestrictSponsored(
|
||||
}
|
||||
|
||||
void HandleWithdrawalButton(
|
||||
not_null<ChannelData*> channel,
|
||||
RewardReceiver receiver,
|
||||
not_null<Ui::RippleButton*> button,
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
};
|
||||
|
||||
const auto channel = receiver.currencyReceiver;
|
||||
const auto peer = receiver.creditsReceiver;
|
||||
|
||||
const auto state = button->lifetime().make_state<State>();
|
||||
const auto session = &channel->session();
|
||||
const auto session = (channel ? &channel->session() : &peer->session());
|
||||
|
||||
using ChannelOutUrl = MTPstats_BroadcastRevenueWithdrawalUrl;
|
||||
using CreditsOutUrl = MTPpayments_StarsRevenueWithdrawalUrl;
|
||||
|
||||
session->api().cloudPassword().reload();
|
||||
button->setClickedCallback([=] {
|
||||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
state->lifetime = session->api().cloudPassword().state(
|
||||
) | rpl::take(
|
||||
@@ -58,10 +70,12 @@ void HandleWithdrawalButton(
|
||||
state->loading = false;
|
||||
|
||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||
fields.customTitle
|
||||
= tr::lng_channel_earn_balance_password_title();
|
||||
fields.customDescription
|
||||
= tr::lng_channel_earn_balance_password_description(tr::now);
|
||||
fields.customTitle = channel
|
||||
? tr::lng_channel_earn_balance_password_title()
|
||||
: tr::lng_bot_earn_balance_password_title();
|
||||
fields.customDescription = channel
|
||||
? tr::lng_channel_earn_balance_password_description(tr::now)
|
||||
: tr::lng_bot_earn_balance_password_description(tr::now);
|
||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||
fields.customCheckCallback = crl::guard(button, [=](
|
||||
const Core::CloudPasswordResult &result,
|
||||
@@ -74,22 +88,63 @@ void HandleWithdrawalButton(
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto fail = [=](const QString &error) {
|
||||
show->showToast(error);
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
};
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
result.result
|
||||
)).done([=](const MTPstats_BroadcastRevenueWithdrawalUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
fail(error.type());
|
||||
}).send();
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail(fail).send();
|
||||
} else if (peer) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
MTP_long(receiver.creditsAmount()),
|
||||
result.result
|
||||
)).done([=](const CreditsOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
}).fail(fail).send();
|
||||
}
|
||||
});
|
||||
show->show(Box<PasscodeBox>(session, fields));
|
||||
});
|
||||
|
||||
};
|
||||
button->setClickedCallback([=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
auto box = PrePasswordErrorBox(
|
||||
error.type(),
|
||||
session,
|
||||
TextWithEntities{
|
||||
tr::lng_channel_earn_out_check_password_about(tr::now),
|
||||
});
|
||||
if (box) {
|
||||
show->show(std::move(box));
|
||||
state->loading = false;
|
||||
} else {
|
||||
processOut();
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
session->api().request(
|
||||
MTPpayments_GetStarsRevenueWithdrawalUrl(
|
||||
peer->input,
|
||||
MTP_long(std::numeric_limits<int64_t>::max()),
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,14 @@ void RestrictSponsored(
|
||||
bool restricted,
|
||||
Fn<void(QString)> failed);
|
||||
|
||||
struct RewardReceiver final {
|
||||
ChannelData *currencyReceiver = nullptr;
|
||||
PeerData *creditsReceiver = nullptr;
|
||||
Fn<uint64()> creditsAmount;
|
||||
};
|
||||
|
||||
void HandleWithdrawalButton(
|
||||
not_null<ChannelData*> channel,
|
||||
RewardReceiver receiver,
|
||||
not_null<Ui::RippleButton*> button,
|
||||
std::shared_ptr<Ui::Show> show);
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Api {
|
||||
|
||||
template <typename Type>
|
||||
void PerformForUpdate(
|
||||
const MTPUpdates &updates,
|
||||
Fn<void(const Type &)> callback) {
|
||||
updates.match([&](const MTPDupdates &updates) {
|
||||
for (const auto &update : updates.vupdates().v) {
|
||||
update.match([&](const Type &d) {
|
||||
callback(d);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics.h"
|
||||
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_channel.h"
|
||||
@@ -15,33 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_story.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
using namespace Statistic;
|
||||
const auto zoomToken = d.vzoom_token().has_value()
|
||||
? qs(*d.vzoom_token()).toUtf8()
|
||||
: QByteArray();
|
||||
return Data::StatisticalGraph{
|
||||
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||
zoomToken,
|
||||
};
|
||||
}, [&](const MTPDstatsGraphAsync &data) {
|
||||
return Data::StatisticalGraph{
|
||||
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||
};
|
||||
}, [&](const MTPDstatsGraphError &data) {
|
||||
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::StatisticalValue StatisticalValueFromTL(
|
||||
const MTPStatsAbsValueAndPrev &tl) {
|
||||
const auto current = tl.data().vcurrent().v;
|
||||
@@ -223,61 +201,6 @@ Statistics::Statistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
}
|
||||
|
||||
StatisticsRequestSender::StatisticsRequestSender(not_null<ChannelData *> channel)
|
||||
: _channel(channel)
|
||||
, _api(&_channel->session().api().instance())
|
||||
, _timer([=] { checkRequests(); }) {
|
||||
}
|
||||
|
||||
StatisticsRequestSender::~StatisticsRequestSender() {
|
||||
for (const auto &[dcId, ids] : _requests) {
|
||||
for (const auto id : ids) {
|
||||
_channel->session().api().unregisterStatsRequest(dcId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatisticsRequestSender::checkRequests() {
|
||||
for (auto i = begin(_requests); i != end(_requests);) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (_api.pending(*j)) {
|
||||
++j;
|
||||
} else {
|
||||
_channel->session().api().unregisterStatsRequest(
|
||||
i->first,
|
||||
*j);
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
i = _requests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_requests.empty()) {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Request, typename, typename>
|
||||
auto StatisticsRequestSender::makeRequest(Request &&request) {
|
||||
const auto id = _api.allocateRequestId();
|
||||
const auto dcId = _channel->owner().statsDcId(_channel);
|
||||
if (dcId) {
|
||||
_channel->session().api().registerStatsRequest(dcId, id);
|
||||
_requests[dcId].emplace(id);
|
||||
if (!_timer.isActive()) {
|
||||
_timer.callEach(kCheckRequestsTimer);
|
||||
}
|
||||
}
|
||||
return std::move(_api.request(
|
||||
std::forward<Request>(request)
|
||||
).toDC(
|
||||
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
|
||||
).overrideId(id));
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> Statistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
@@ -747,11 +670,11 @@ Data::BoostStatus Boosts::boostStatus() const {
|
||||
return _boostStatus;
|
||||
}
|
||||
|
||||
EarnStatistics::EarnStatistics(not_null<ChannelData*> channel)
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
@@ -795,7 +718,7 @@ rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
};
|
||||
}
|
||||
|
||||
void EarnStatistics::requestHistory(
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
@@ -865,7 +788,7 @@ void EarnStatistics::requestHistory(
|
||||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,45 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "api/api_statistics_sender.h"
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_channel_earn.h"
|
||||
#include "data/data_statistics.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class StatisticsRequestSender {
|
||||
protected:
|
||||
explicit StatisticsRequestSender(not_null<ChannelData*> channel);
|
||||
~StatisticsRequestSender();
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] auto makeRequest(Request &&request);
|
||||
|
||||
[[nodiscard]] MTP::Sender &api() {
|
||||
return _api;
|
||||
}
|
||||
[[nodiscard]] not_null<ChannelData*> channel() {
|
||||
return _channel;
|
||||
}
|
||||
|
||||
private:
|
||||
void checkRequests();
|
||||
|
||||
const not_null<ChannelData*> _channel;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
|
||||
|
||||
};
|
||||
|
||||
class Statistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit Statistics(not_null<ChannelData*> channel);
|
||||
@@ -108,9 +79,9 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit EarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
|
||||
#include "data/data_statistics_chart.h"
|
||||
#include "statistics/statistics_data_deserialize.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
Data::StatisticalGraph StatisticalGraphFromTL(const MTPStatsGraph &tl) {
|
||||
return tl.match([&](const MTPDstatsGraph &d) {
|
||||
using namespace Statistic;
|
||||
const auto zoomToken = d.vzoom_token().has_value()
|
||||
? qs(*d.vzoom_token()).toUtf8()
|
||||
: QByteArray();
|
||||
return Data::StatisticalGraph{
|
||||
StatisticalChartFromJSON(qs(d.vjson().data().vdata()).toUtf8()),
|
||||
zoomToken,
|
||||
};
|
||||
}, [&](const MTPDstatsGraphAsync &data) {
|
||||
return Data::StatisticalGraph{
|
||||
.zoomToken = qs(data.vtoken()).toUtf8(),
|
||||
};
|
||||
}, [&](const MTPDstatsGraphError &data) {
|
||||
return Data::StatisticalGraph{ .error = qs(data.verror()) };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Data {
|
||||
struct StatisticalGraph;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
[[nodiscard]] Data::StatisticalGraph StatisticalGraphFromTL(
|
||||
const MTPStatsGraph &tl);
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "api/api_statistics_sender.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Api {
|
||||
|
||||
StatisticsRequestSender::StatisticsRequestSender(
|
||||
not_null<PeerData*> peer)
|
||||
: _peer(peer)
|
||||
, _channel(peer->asChannel())
|
||||
, _user(peer->asUser())
|
||||
, _api(&_peer->session().api().instance())
|
||||
, _timer([=] { checkRequests(); }) {
|
||||
}
|
||||
|
||||
MTP::Sender &StatisticsRequestSender::api() {
|
||||
return _api;
|
||||
}
|
||||
|
||||
not_null<ChannelData*> StatisticsRequestSender::channel() {
|
||||
Expects(_channel);
|
||||
return _channel;
|
||||
}
|
||||
|
||||
not_null<UserData*> StatisticsRequestSender::user() {
|
||||
Expects(_user);
|
||||
return _user;
|
||||
}
|
||||
|
||||
void StatisticsRequestSender::checkRequests() {
|
||||
for (auto i = begin(_requests); i != end(_requests);) {
|
||||
for (auto j = begin(i->second); j != end(i->second);) {
|
||||
if (_api.pending(*j)) {
|
||||
++j;
|
||||
} else {
|
||||
_peer->session().api().unregisterStatsRequest(
|
||||
i->first,
|
||||
*j);
|
||||
j = i->second.erase(j);
|
||||
}
|
||||
}
|
||||
if (i->second.empty()) {
|
||||
i = _requests.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (_requests.empty()) {
|
||||
_timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
auto StatisticsRequestSender::ensureRequestIsRegistered()
|
||||
-> StatisticsRequestSender::Registered {
|
||||
const auto id = _api.allocateRequestId();
|
||||
const auto dcId = _peer->owner().statsDcId(_peer);
|
||||
if (dcId) {
|
||||
_peer->session().api().registerStatsRequest(dcId, id);
|
||||
_requests[dcId].emplace(id);
|
||||
if (!_timer.isActive()) {
|
||||
constexpr auto kCheckRequestsTimer = 10 * crl::time(1000);
|
||||
_timer.callEach(kCheckRequestsTimer);
|
||||
}
|
||||
}
|
||||
return StatisticsRequestSender::Registered{ id, dcId };
|
||||
}
|
||||
|
||||
StatisticsRequestSender::~StatisticsRequestSender() {
|
||||
for (const auto &[dcId, ids] : _requests) {
|
||||
for (const auto id : ids) {
|
||||
_peer->session().api().unregisterStatsRequest(dcId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
|
||||
class StatisticsRequestSender {
|
||||
protected:
|
||||
explicit StatisticsRequestSender(not_null<PeerData*> peer);
|
||||
~StatisticsRequestSender();
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] auto makeRequest(Request &&request) {
|
||||
const auto [id, dcId] = ensureRequestIsRegistered();
|
||||
return std::move(_api.request(
|
||||
std::forward<Request>(request)
|
||||
).toDC(
|
||||
dcId ? MTP::ShiftDcId(dcId, MTP::kStatsDcShift) : 0
|
||||
).overrideId(id));
|
||||
}
|
||||
|
||||
[[nodiscard]] MTP::Sender &api();
|
||||
[[nodiscard]] not_null<ChannelData*> channel();
|
||||
[[nodiscard]] not_null<UserData*> user();
|
||||
|
||||
private:
|
||||
struct Registered final {
|
||||
mtpRequestId id;
|
||||
MTP::DcId dcId;
|
||||
};
|
||||
[[nodiscard]] Registered ensureRequestIsRegistered();
|
||||
void checkRequests();
|
||||
|
||||
const not_null<PeerData*> _peer;
|
||||
ChannelData * const _channel;
|
||||
UserData * const _user;
|
||||
MTP::Sender _api;
|
||||
base::Timer _timer;
|
||||
base::flat_map<MTP::DcId, base::flat_set<mtpRequestId>> _requests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Api
|
||||
@@ -1704,7 +1704,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
const auto peerId = peerFromMTP(d.vpeer());
|
||||
const auto msgId = d.vmsg_id().v;
|
||||
if (const auto item = session().data().message(peerId, msgId)) {
|
||||
item->applyEdition(d.vextended_media());
|
||||
item->applyEdition(d.vextended_media().v);
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -2129,6 +2129,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
};
|
||||
if (IsForceLogoutNotification(d)) {
|
||||
Core::App().forceLogOut(&session().account(), text);
|
||||
} else if (IsWithdrawalNotification(d)) {
|
||||
return;
|
||||
} else if (d.is_popup()) {
|
||||
const auto &windows = session().windows();
|
||||
if (!windows.empty()) {
|
||||
@@ -2630,4 +2632,8 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWithdrawalNotification(const MTPDupdateServiceNotification &data) {
|
||||
return qs(data.vtype()).startsWith(u"API_WITHDRAWAL_FEATURE_DISABLED_"_q);
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -211,4 +211,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool IsWithdrawalNotification(
|
||||
const MTPDupdateServiceNotification &);
|
||||
|
||||
} // namespace Api
|
||||
|
||||
@@ -55,7 +55,9 @@ void ViewsManager::removeIncremented(not_null<PeerData*> peer) {
|
||||
_incremented.remove(peer);
|
||||
}
|
||||
|
||||
void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
void ViewsManager::pollExtendedMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
bool force) {
|
||||
if (!item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
@@ -63,14 +65,20 @@ void ViewsManager::pollExtendedMedia(not_null<HistoryItem*> item) {
|
||||
const auto peer = item->history()->peer;
|
||||
auto &request = _pollRequests[peer];
|
||||
if (request.ids.contains(id) || request.sent.contains(id)) {
|
||||
return;
|
||||
if (!force || request.forced) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
request.ids.emplace(id);
|
||||
if (!request.id && !request.when) {
|
||||
request.when = crl::now() + kPollExtendedMediaPeriod;
|
||||
if (force) {
|
||||
request.forced = true;
|
||||
}
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = force ? 1 : kPollExtendedMediaPeriod;
|
||||
if (!request.id && (!request.when || force)) {
|
||||
request.when = crl::now() + delay;
|
||||
}
|
||||
if (!_pollTimer.isActive() || force) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +168,12 @@ void ViewsManager::sendPollRequests(
|
||||
if (i->second.ids.empty()) {
|
||||
i = _pollRequests.erase(i);
|
||||
} else {
|
||||
i->second.when = now + kPollExtendedMediaPeriod;
|
||||
if (!_pollTimer.isActive()) {
|
||||
_pollTimer.callOnce(kPollExtendedMediaPeriod);
|
||||
const auto delay = i->second.forced
|
||||
? 1
|
||||
: kPollExtendedMediaPeriod;
|
||||
i->second.when = now + delay;
|
||||
if (!_pollTimer.isActive() || i->second.forced) {
|
||||
_pollTimer.callOnce(delay);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void scheduleIncrement(not_null<HistoryItem*> item);
|
||||
void removeIncremented(not_null<PeerData*> peer);
|
||||
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item);
|
||||
void pollExtendedMedia(not_null<HistoryItem*> item, bool force = false);
|
||||
|
||||
private:
|
||||
struct PollExtendedMediaRequest {
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
mtpRequestId id = 0;
|
||||
base::flat_set<MsgId> ids;
|
||||
base::flat_set<MsgId> sent;
|
||||
bool forced = false;
|
||||
};
|
||||
|
||||
void viewsIncrement();
|
||||
|
||||
@@ -1352,27 +1352,30 @@ void ApiWrap::migrateFail(not_null<PeerData*> peer, const QString &error) {
|
||||
|
||||
void ApiWrap::markContentsRead(
|
||||
const base::flat_set<not_null<HistoryItem*>> &items) {
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
|
||||
auto markedIds = QVector<MTPint>();
|
||||
auto channelMarkedIds = base::flat_map<
|
||||
not_null<ChannelData*>,
|
||||
QVector<MTPint>>();
|
||||
markedIds.reserve(items.size());
|
||||
for (const auto &item : items) {
|
||||
const auto passthrough = (item->isUnreadMention() || item->hasUnreadReaction()) && !item->isUnreadMedia();
|
||||
|
||||
if (!item->markContentsRead(true) || !item->isRegular()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!settings->sendReadMessages && !passthrough) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const auto channel = item->history()->peer->asChannel()) {
|
||||
channelMarkedIds[channel].push_back(MTP_int(item->id));
|
||||
} else {
|
||||
markedIds.push_back(MTP_int(item->id));
|
||||
}
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->sendReadMessages) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!markedIds.isEmpty()) {
|
||||
request(MTPmessages_ReadMessageContents(
|
||||
MTP_vector<MTPint>(markedIds)
|
||||
@@ -1389,12 +1392,14 @@ void ApiWrap::markContentsRead(
|
||||
}
|
||||
|
||||
void ApiWrap::markContentsRead(not_null<HistoryItem*> item) {
|
||||
const auto passthrough = (item->isUnreadMention() || item->hasUnreadReaction()) && !item->isUnreadMedia();
|
||||
|
||||
if (!item->markContentsRead(true) || !item->isRegular()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->sendReadMessages) {
|
||||
if (!settings->sendReadMessages && !passthrough) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2204,7 +2209,8 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
entities,
|
||||
Data::WebPageForMTP(
|
||||
cloudDraft->webpage,
|
||||
textWithTags.text.isEmpty())
|
||||
textWithTags.text.isEmpty()),
|
||||
MTP_long(0) // effect
|
||||
)).done([=](const MTPBool &result, const MTP::Response &response) {
|
||||
const auto requestId = response.requestId;
|
||||
history->finishSavingCloudDraft(
|
||||
@@ -4259,7 +4265,11 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
media,
|
||||
(options.price
|
||||
? MTPInputMedia(MTP_inputMediaPaidMedia(
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(1, media)))
|
||||
: media),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
@@ -4281,6 +4291,82 @@ void ApiWrap::sendMediaWithRandomId(
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendMultiPaidMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<SendingAlbum*> album,
|
||||
Fn<void(bool)> done) {
|
||||
Expects(album->options.price > 0);
|
||||
|
||||
const auto groupId = album->groupId;
|
||||
const auto &options = album->options;
|
||||
const auto randomId = album->items.front().randomId;
|
||||
auto medias = album->items | ranges::view::transform([](
|
||||
const SendingAlbum::Item &part) {
|
||||
Assert(part.media.has_value());
|
||||
return MTPInputMedia(part.media->data().vmedia());
|
||||
}) | ranges::to<QVector<MTPInputMedia>>();
|
||||
|
||||
const auto history = item->history();
|
||||
const auto replyTo = item->replyTo();
|
||||
|
||||
auto caption = item->originalText();
|
||||
TextUtilities::Trim(caption);
|
||||
auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
caption.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
|
||||
using Flag = MTPmessages_SendMedia::Flag;
|
||||
const auto flags = Flag(0)
|
||||
| (replyTo ? Flag::f_reply_to : Flag(0))
|
||||
| (ShouldSendSilent(history->peer, options)
|
||||
? Flag::f_silent
|
||||
: Flag(0))
|
||||
| (!sentEntities.v.isEmpty() ? Flag::f_entities : Flag(0))
|
||||
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
|
||||
| (options.sendAs ? Flag::f_send_as : Flag(0))
|
||||
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
|
||||
| (options.effectId ? Flag::f_effect : Flag(0))
|
||||
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
randomId,
|
||||
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||
MTP_flags(flags),
|
||||
peer->input,
|
||||
Data::Histories::ReplyToPlaceholder(),
|
||||
MTP_inputMediaPaidMedia(
|
||||
MTP_long(options.price),
|
||||
MTP_vector<MTPInputMedia>(std::move(medias))),
|
||||
MTP_string(caption.text),
|
||||
MTP_long(randomId),
|
||||
MTPReplyMarkup(),
|
||||
sentEntities,
|
||||
MTP_int(options.scheduled),
|
||||
(options.sendAs ? options.sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, options.shortcutId),
|
||||
MTP_long(options.effectId)
|
||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
||||
if (const auto album = _sendingAlbums.take(groupId)) {
|
||||
const auto copy = (*album)->items;
|
||||
for (const auto &part : copy) {
|
||||
if (const auto item = history->owner().message(part.msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (done) done(true);
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
if (done) done(false);
|
||||
sendMessageFail(error, peer, randomId, itemId);
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::sendAlbumWithUploaded(
|
||||
not_null<HistoryItem*> item,
|
||||
const MessageGroupId &groupId,
|
||||
@@ -4334,8 +4420,11 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (!sample) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
return;
|
||||
} else if (album->options.price > 0) {
|
||||
sendMultiPaidMedia(sample, album);
|
||||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().c_inputSingleMedia();
|
||||
const auto &single = medias.front().data();
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
|
||||
@@ -545,6 +545,10 @@ private:
|
||||
Api::SendOptions options,
|
||||
uint64 randomId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMultiPaidMedia(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<SendingAlbum*> album,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
|
||||
void getTopPromotionDelayed(TimeId now, TimeId next);
|
||||
void topPromotionDone(const MTPhelp_PromoData &proxy);
|
||||
|
||||
@@ -164,6 +164,15 @@ void load() {
|
||||
} catch (...) {
|
||||
LOG(("AyuGramSettings: failed to read settings file (not json-like)"));
|
||||
}
|
||||
|
||||
if (cGhost()) {
|
||||
settings->sendReadMessages = false;
|
||||
settings->sendReadStories = false;
|
||||
settings->sendOnlinePackets = false;
|
||||
settings->sendUploadProgress = false;
|
||||
settings->sendOfflinePacketAfterOnline = true;
|
||||
}
|
||||
|
||||
postinitialize();
|
||||
}
|
||||
|
||||
@@ -205,6 +214,10 @@ AyuGramSettings::AyuGramSettings() {
|
||||
collapseSimilarChannels = true;
|
||||
hideSimilarChannels = false;
|
||||
|
||||
spoofWebviewAsAndroid = false;
|
||||
increaseWebviewHeight = false;
|
||||
increaseWebviewWidth = false;
|
||||
|
||||
disableNotificationsDelay = false;
|
||||
localPremium = false;
|
||||
|
||||
@@ -236,8 +249,16 @@ AyuGramSettings::AyuGramSettings() {
|
||||
monoFont = "";
|
||||
|
||||
hideNotificationCounters = false;
|
||||
hideNotificationBadge = false;
|
||||
hideAllChatsFolder = false;
|
||||
|
||||
/*
|
||||
* channelBottomButton = 0 means "Hide"
|
||||
* channelBottomButton = 1 means "Mute"/"Unmute"
|
||||
* channelBottomButton = 2 means "Discuss" + fallback to "Mute"/"Unmute"
|
||||
*/
|
||||
channelBottomButton = 2;
|
||||
|
||||
/*
|
||||
* showPeerId = 0 means no ID shown
|
||||
* showPeerId = 1 means ID shown as for Telegram API devs
|
||||
@@ -332,6 +353,18 @@ void AyuGramSettings::set_hideSimilarChannels(bool val) {
|
||||
hideSimilarChannels = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_spoofWebviewAsAndroid(bool val) {
|
||||
spoofWebviewAsAndroid = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_increaseWebviewHeight(bool val) {
|
||||
increaseWebviewHeight = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_increaseWebviewWidth(bool val) {
|
||||
increaseWebviewWidth = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_disableNotificationsDelay(bool val) {
|
||||
disableNotificationsDelay = val;
|
||||
}
|
||||
@@ -419,10 +452,18 @@ void AyuGramSettings::set_hideNotificationCounters(bool val) {
|
||||
hideNotificationCounters = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_hideNotificationBadge(bool val) {
|
||||
hideNotificationBadge = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_hideAllChatsFolder(bool val) {
|
||||
hideAllChatsFolder = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_channelBottomButton(int val) {
|
||||
channelBottomButton = val;
|
||||
}
|
||||
|
||||
void AyuGramSettings::set_showMessageSeconds(bool val) {
|
||||
showMessageSeconds = val;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ public:
|
||||
bool collapseSimilarChannels;
|
||||
bool hideSimilarChannels;
|
||||
|
||||
bool spoofWebviewAsAndroid;
|
||||
bool increaseWebviewHeight;
|
||||
bool increaseWebviewWidth;
|
||||
|
||||
bool disableNotificationsDelay;
|
||||
bool localPremium;
|
||||
|
||||
@@ -63,8 +67,11 @@ public:
|
||||
QString monoFont;
|
||||
|
||||
bool hideNotificationCounters;
|
||||
bool hideNotificationBadge;
|
||||
bool hideAllChatsFolder;
|
||||
|
||||
int channelBottomButton;
|
||||
|
||||
int showPeerId;
|
||||
bool showMessageSeconds;
|
||||
bool showMessageShot;
|
||||
@@ -95,6 +102,10 @@ public:
|
||||
void set_collapseSimilarChannels(bool val);
|
||||
void set_hideSimilarChannels(bool val);
|
||||
|
||||
void set_spoofWebviewAsAndroid(bool val);
|
||||
void set_increaseWebviewHeight(bool val);
|
||||
void set_increaseWebviewWidth(bool val);
|
||||
|
||||
void set_disableNotificationsDelay(bool val);
|
||||
void set_localPremium(bool val);
|
||||
|
||||
@@ -121,8 +132,11 @@ public:
|
||||
void set_monoFont(QString val);
|
||||
|
||||
void set_hideNotificationCounters(bool val);
|
||||
void set_hideNotificationBadge(bool val);
|
||||
void set_hideAllChatsFolder(bool val);
|
||||
|
||||
void set_channelBottomButton(int val);
|
||||
|
||||
void set_showPeerId(int val);
|
||||
void set_showMessageSeconds(bool val);
|
||||
void set_showMessageShot(bool val);
|
||||
@@ -149,6 +163,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
disableCustomBackgrounds,
|
||||
collapseSimilarChannels,
|
||||
hideSimilarChannels,
|
||||
spoofWebviewAsAndroid,
|
||||
increaseWebviewHeight,
|
||||
increaseWebviewWidth,
|
||||
disableNotificationsDelay,
|
||||
localPremium,
|
||||
appIcon,
|
||||
@@ -169,7 +186,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
showStreamerToggleInTray,
|
||||
monoFont,
|
||||
hideNotificationCounters,
|
||||
hideNotificationBadge,
|
||||
hideAllChatsFolder,
|
||||
channelBottomButton,
|
||||
showPeerId,
|
||||
showMessageSeconds,
|
||||
showMessageShot,
|
||||
|
||||
@@ -24,6 +24,10 @@ void enable() {
|
||||
auto handle = Core::App().activeWindow()->widget()->psHwnd();
|
||||
SetWindowDisplayAffinity(handle, WDA_EXCLUDEFROMCAPTURE);
|
||||
|
||||
Core::App().enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
SetWindowDisplayAffinity(w->widget()->psHwnd(), WDA_EXCLUDEFROMCAPTURE);
|
||||
});
|
||||
|
||||
isEnabledVal = true;
|
||||
}
|
||||
|
||||
@@ -31,6 +35,10 @@ void disable() {
|
||||
auto handle = Core::App().activeWindow()->widget()->psHwnd();
|
||||
SetWindowDisplayAffinity(handle, WDA_NONE);
|
||||
|
||||
Core::App().enumerateWindows([&](not_null<Window::Controller*> w) {
|
||||
SetWindowDisplayAffinity(w->widget()->psHwnd(), WDA_NONE);
|
||||
});
|
||||
|
||||
isEnabledVal = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,13 @@ ayuLReadMenuIcon: icon {{ "ayu/lread", menuIconColor }};
|
||||
ayuSReadMenuIcon: icon {{ "ayu/sread", menuIconColor }};
|
||||
ayuStreamerModeMenuIcon: icon {{ "ayu/streamer", menuIconColor }};
|
||||
|
||||
ayuMenuIconToBeginning: icon {{ "ayu/to_beginning", menuIconColor }};
|
||||
|
||||
inChannelBadgeIcon: icon {{ "ayu/channel", msgInDateFg }};
|
||||
inChannelBadgeSelectedIcon: icon {{ "ayu/channel", msgInDateFgSelected }};
|
||||
outChannelBadgeIcon: icon {{ "ayu/channel", msgOutDateFg }};
|
||||
outChannelBadgeSelectedIcon: icon {{ "ayu/channel", msgOutDateFgSelected }};
|
||||
|
||||
infoExteraBadge: icon {{ "ayu/exterabadge", profileVerifiedCheckBg }};
|
||||
|
||||
winEnterWithGuestIcon: icon {{ "ayu/ghost_tray", windowFg }};
|
||||
|
||||
@@ -18,6 +18,9 @@ ICON(EXTERA, "extera");
|
||||
ICON(NOTHING, "nothing");
|
||||
ICON(BARD, "bard");
|
||||
ICON(YAPLUS, "yaplus");
|
||||
ICON(WIN95, "win95");
|
||||
ICON(CHIBI, "chibi");
|
||||
ICON(CHIBI2, "chibi2");
|
||||
|
||||
void loadAppIco();
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@ using "ui/colors.palette";
|
||||
using "ui/widgets/widgets.style";
|
||||
using "info/info.style";
|
||||
|
||||
iconPreviewStroke: activeButtonBg;
|
||||
|
||||
/* Color Picker */
|
||||
cpPadding: 14px;
|
||||
cpSelectedPadding: 2px;
|
||||
cpPenSize: 2px;
|
||||
cpSelectedRounding: 12px;
|
||||
cpImagePadding: 4px;
|
||||
cpSpacingX: 16px;
|
||||
cpSpacingY: 8px;
|
||||
@@ -27,6 +25,11 @@ recentStickersLimitPadding: margins(22px, 4px, 22px, 8px);
|
||||
imageViewPadding: margins(22px, 10px, 22px, 10px);
|
||||
imageViewInnerPadding: margins(16px, 16px, 16px, 16px);
|
||||
|
||||
botWebViewPanelHeightIncreased: 782px;
|
||||
botWebViewPanelWidthIncreased: 512px;
|
||||
|
||||
stickerRoundingSize: 5px;
|
||||
|
||||
messageShotPadding: 4px;
|
||||
|
||||
topBarAdmins: IconButton {
|
||||
|
||||
@@ -626,6 +626,10 @@ QString InnerWidget::elementAuthorRank(not_null<const Element*> view) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool InnerWidget::elementHideTopicButton(not_null<const Element*> view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||
if (!_filterChanged) {
|
||||
for (auto &item : _items) {
|
||||
|
||||
@@ -132,6 +132,7 @@ public:
|
||||
not_null<const HistoryView::Element*> view,
|
||||
HistoryView::Element *replacing) override;
|
||||
QString elementAuthorRank(not_null<const HistoryView::Element*> view) override;
|
||||
bool elementHideTopicButton(not_null<const HistoryView::Element*> view) override;
|
||||
|
||||
~InnerWidget();
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ const QVector<QString> icons{
|
||||
AyuAssets::EXTERA_ICON,
|
||||
AyuAssets::NOTHING_ICON,
|
||||
AyuAssets::BARD_ICON,
|
||||
AyuAssets::YAPLUS_ICON
|
||||
AyuAssets::YAPLUS_ICON,
|
||||
AyuAssets::WIN95_ICON,
|
||||
AyuAssets::CHIBI_ICON,
|
||||
AyuAssets::CHIBI2_ICON,
|
||||
};
|
||||
|
||||
const auto rows = static_cast<int>(icons.size()) / 4 + std::min(1, static_cast<int>(icons.size()) % 4);
|
||||
@@ -36,18 +39,26 @@ const auto rows = static_cast<int>(icons.size()) / 4 + std::min(1, static_cast<i
|
||||
void drawIcon(QPainter &p, const QImage &icon, int xOffset, int yOffset, float strokeOpacity) {
|
||||
xOffset += st::cpPadding;
|
||||
|
||||
// stroke
|
||||
p.save();
|
||||
p.setPen(QPen(st::iconPreviewStroke, st::cpPenSize));
|
||||
p.setPen(QPen(st::boxDividerBg, 0));
|
||||
p.setBrush(QBrush(st::boxDividerBg));
|
||||
p.setOpacity(strokeOpacity);
|
||||
p.drawEllipse(xOffset + st::cpSelectedPadding,
|
||||
yOffset + st::cpSelectedPadding,
|
||||
st::cpIconSize + st::cpSelectedPadding * 2,
|
||||
st::cpIconSize + st::cpSelectedPadding * 2);
|
||||
p.drawRoundedRect(
|
||||
xOffset + st::cpSelectedPadding,
|
||||
yOffset + st::cpSelectedPadding,
|
||||
st::cpIconSize + st::cpSelectedPadding * 2,
|
||||
st::cpIconSize + st::cpSelectedPadding * 2,
|
||||
st::cpSelectedRounding,
|
||||
st::cpSelectedRounding
|
||||
);
|
||||
p.restore();
|
||||
// /stroke
|
||||
|
||||
auto rect = QRect(xOffset + st::cpImagePadding, yOffset + st::cpImagePadding, st::cpIconSize, st::cpIconSize);
|
||||
auto rect = QRect(
|
||||
xOffset + st::cpImagePadding,
|
||||
yOffset + st::cpImagePadding,
|
||||
st::cpIconSize,
|
||||
st::cpIconSize
|
||||
);
|
||||
p.drawImage(rect, icon);
|
||||
}
|
||||
|
||||
@@ -65,7 +76,7 @@ void applyIcon() {
|
||||
|
||||
IconPicker::IconPicker(QWidget *parent)
|
||||
: RpWidget(parent) {
|
||||
setMinimumSize(st::boxWidth, (st::cpIconSize + st::cpPadding) * rows);
|
||||
setMinimumSize(st::boxWidth, (st::cpIconSize + st::cpPadding) * rows - st::cpPadding);
|
||||
}
|
||||
|
||||
void IconPicker::paintEvent(QPaintEvent *e) {
|
||||
@@ -80,21 +91,27 @@ void IconPicker::paintEvent(QPaintEvent *e) {
|
||||
auto const idx = i + row * 4;
|
||||
|
||||
const auto &iconName = icons[idx];
|
||||
if (iconName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto icon = AyuAssets::loadPreview(iconName)
|
||||
.scaled(st::cpIconSize, st::cpIconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
auto opacity = 0.0f;
|
||||
if (iconName == wasSelected) {
|
||||
if (iconName == wasSelected) {
|
||||
opacity = 1.0f - animation.value(1.0f);
|
||||
} else if (iconName == AyuAssets::currentAppLogoName()) {
|
||||
opacity = wasSelected.isEmpty() ? 1.0f : animation.value(1.0f);
|
||||
}
|
||||
|
||||
drawIcon(p,
|
||||
icon,
|
||||
(st::cpIconSize + st::cpSpacingX) * i + offset,
|
||||
row * (st::cpIconSize + st::cpSpacingY),
|
||||
opacity);
|
||||
drawIcon(
|
||||
p,
|
||||
icon,
|
||||
(st::cpIconSize + st::cpSpacingX) * i + offset,
|
||||
row * (st::cpIconSize + st::cpSpacingY),
|
||||
opacity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,6 +131,10 @@ void IconPicker::mousePressEvent(QMouseEvent *e) {
|
||||
if (x >= xOffset && x <= xOffset + st::cpIconSize && e->pos().y() >= yOffset
|
||||
&& e->pos().y() <= yOffset + st::cpIconSize) {
|
||||
const auto &iconName = icons[idx];
|
||||
if (iconName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings->appIcon != iconName) {
|
||||
wasSelected = settings->appIcon;
|
||||
animation.start(
|
||||
@@ -124,7 +145,8 @@ void IconPicker::mousePressEvent(QMouseEvent *e) {
|
||||
0.0,
|
||||
1.0,
|
||||
200,
|
||||
anim::easeOutCubic);
|
||||
anim::easeOutCubic
|
||||
);
|
||||
|
||||
settings->set_appIcon(iconName);
|
||||
changed = true;
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include "icon_picker.h"
|
||||
#include "tray.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_domain.h"
|
||||
#include "styles/style_ayu_icons.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
@@ -343,13 +345,14 @@ void AddCollapsibleToggle(not_null<Ui::VerticalLayout*> container,
|
||||
raw->lifetime());
|
||||
}
|
||||
|
||||
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
|
||||
void AddChooseButtonWithIconAndRightTextInner(not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
int initialState,
|
||||
std::vector<QString> options,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<QString> boxTitle,
|
||||
const style::icon &icon,
|
||||
const style::SettingsButton & st,
|
||||
Settings::IconDescriptor && descriptor,
|
||||
const Fn<void(int)> &setter) {
|
||||
auto reactiveVal = container->lifetime().make_state<rpl::variable<int>>(initialState);
|
||||
|
||||
@@ -363,8 +366,8 @@ void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container
|
||||
container,
|
||||
std::move(text),
|
||||
rightTextReactive,
|
||||
st::settingsButton,
|
||||
{&icon})->addClickHandler(
|
||||
st,
|
||||
std::move(descriptor))->addClickHandler(
|
||||
[=]
|
||||
{
|
||||
controller->show(Box(
|
||||
@@ -387,6 +390,45 @@ void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container
|
||||
});
|
||||
}
|
||||
|
||||
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
int initialState,
|
||||
std::vector<QString> options,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<QString> boxTitle,
|
||||
const style::icon &icon,
|
||||
const Fn<void(int)> &setter) {
|
||||
AddChooseButtonWithIconAndRightTextInner(
|
||||
container,
|
||||
controller,
|
||||
initialState,
|
||||
options,
|
||||
std::move(text),
|
||||
std::move(boxTitle),
|
||||
st::settingsButton,
|
||||
{&icon},
|
||||
setter);
|
||||
}
|
||||
|
||||
void AddChooseButtonWithIconAndRightText(not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller,
|
||||
int initialState,
|
||||
std::vector<QString> options,
|
||||
rpl::producer<QString> text,
|
||||
rpl::producer<QString> boxTitle,
|
||||
const Fn<void(int)> &setter) {
|
||||
AddChooseButtonWithIconAndRightTextInner(
|
||||
container,
|
||||
controller,
|
||||
initialState,
|
||||
options,
|
||||
std::move(text),
|
||||
std::move(boxTitle),
|
||||
st::settingsButtonNoIcon,
|
||||
{},
|
||||
setter);
|
||||
}
|
||||
|
||||
namespace Settings {
|
||||
|
||||
rpl::producer<QString> Ayu::title() {
|
||||
@@ -1163,7 +1205,7 @@ void SetupMarks(not_null<Ui::VerticalLayout*> container) {
|
||||
});
|
||||
}
|
||||
|
||||
void SetupFolderSettings(not_null<Ui::VerticalLayout*> container) {
|
||||
void SetupFolderSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddButtonWithIcon(
|
||||
@@ -1185,6 +1227,32 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container) {
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
// not about folders, but it's a good place for it
|
||||
#ifdef Q_OS_WIN
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_HideNotificationBadge(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->hideNotificationBadge)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->hideNotificationBadge);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_hideNotificationBadge(enabled);
|
||||
AyuSettings::save();
|
||||
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().tray().updateIconCounters();
|
||||
Core::App().domain().notifyUnreadBadgeChanged();
|
||||
},
|
||||
container->lifetime());
|
||||
#endif
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_HideAllChats(),
|
||||
@@ -1205,6 +1273,29 @@ void SetupFolderSettings(not_null<Ui::VerticalLayout*> container) {
|
||||
container->lifetime());
|
||||
}
|
||||
|
||||
void SetupChannelSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
const auto options = std::vector{
|
||||
tr::ayu_ChannelBottomButtonHide(tr::now),
|
||||
tr::ayu_ChannelBottomButtonMute(tr::now),
|
||||
tr::ayu_ChannelBottomButtonDiscuss(tr::now),
|
||||
};
|
||||
|
||||
AddChooseButtonWithIconAndRightText(
|
||||
container,
|
||||
controller,
|
||||
settings->channelBottomButton,
|
||||
options,
|
||||
tr::ayu_ChannelBottomButton(),
|
||||
tr::ayu_ChannelBottomButton(),
|
||||
[=](int index)
|
||||
{
|
||||
settings->set_channelBottomButton(index);
|
||||
AyuSettings::save();
|
||||
});
|
||||
}
|
||||
|
||||
void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window::SessionController*> controller) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
@@ -1249,6 +1340,50 @@ void SetupNerdSettings(not_null<Ui::VerticalLayout*> container, not_null<Window:
|
||||
container->lifetime());
|
||||
}
|
||||
|
||||
void SetupWebviewSettings(not_null<Ui::VerticalLayout*> container) {
|
||||
auto settings = &AyuSettings::getInstance();
|
||||
|
||||
AddSubsectionTitle(container, rpl::single(QString("Webview")));
|
||||
|
||||
AddButtonWithIcon(
|
||||
container,
|
||||
tr::ayu_SettingsSpoofWebviewAsAndroid(),
|
||||
st::settingsButtonNoIcon
|
||||
)->toggleOn(
|
||||
rpl::single(settings->spoofWebviewAsAndroid)
|
||||
)->toggledValue(
|
||||
) | rpl::filter(
|
||||
[=](bool enabled)
|
||||
{
|
||||
return (enabled != settings->spoofWebviewAsAndroid);
|
||||
}) | start_with_next(
|
||||
[=](bool enabled)
|
||||
{
|
||||
settings->set_spoofWebviewAsAndroid(enabled);
|
||||
AyuSettings::save();
|
||||
},
|
||||
container->lifetime());
|
||||
|
||||
std::vector checkboxes = {
|
||||
NestedEntry{
|
||||
tr::ayu_SettingsIncreaseWebviewHeight(tr::now), settings->increaseWebviewHeight, [=](bool enabled)
|
||||
{
|
||||
settings->set_increaseWebviewHeight(enabled);
|
||||
AyuSettings::save();
|
||||
}
|
||||
},
|
||||
NestedEntry{
|
||||
tr::ayu_SettingsIncreaseWebviewWidth(tr::now), settings->increaseWebviewWidth, [=](bool enabled)
|
||||
{
|
||||
settings->set_increaseWebviewWidth(enabled);
|
||||
AyuSettings::save();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddCollapsibleToggle(container, tr::ayu_SettingsBiggerWindow(), checkboxes, false);
|
||||
}
|
||||
|
||||
void SetupCustomization(not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Window::SessionController*> controller) {
|
||||
AddSubsectionTitle(container, tr::ayu_CustomizationHeader());
|
||||
@@ -1270,7 +1405,12 @@ void SetupCustomization(not_null<Ui::VerticalLayout*> container,
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
|
||||
SetupFolderSettings(container);
|
||||
SetupFolderSettings(container, controller);
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
|
||||
AddSkip(container);
|
||||
SetupChannelSettings(container, controller);
|
||||
AddSkip(container);
|
||||
AddDivider(container);
|
||||
|
||||
@@ -1318,6 +1458,12 @@ void SetupAyuGramSettings(not_null<Ui::VerticalLayout*> container,
|
||||
AddSkip(container);
|
||||
AddDividerText(container, tr::ayu_SettingsCustomizationHint());
|
||||
|
||||
AddSkip(container);
|
||||
SetupWebviewSettings(container);
|
||||
AddSkip(container);
|
||||
|
||||
AddDivider(container);
|
||||
|
||||
AddSkip(container);
|
||||
SetupSendConfirmations(container);
|
||||
AddSkip(container);
|
||||
|
||||
@@ -517,6 +517,50 @@ QString getMediaDC(not_null<HistoryItem*> message) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getPeerDC(not_null<PeerData*> peer) {
|
||||
if (peer->hasUserpic()) {
|
||||
const auto dc = v::match(
|
||||
peer->userpicLocation().file().data,
|
||||
[&](const StorageFileLocation &data)
|
||||
{
|
||||
return data.dcId();
|
||||
},
|
||||
[&](const WebFileLocation &)
|
||||
{
|
||||
// should't happen, but still
|
||||
// all webpages are on DC4
|
||||
return 4;
|
||||
},
|
||||
[&](const GeoPointLocation &)
|
||||
{
|
||||
// shouldn't happen naturally
|
||||
return 0;
|
||||
},
|
||||
[&](const AudioAlbumThumbLocation &)
|
||||
{
|
||||
// shouldn't happen naturally
|
||||
return 0;
|
||||
},
|
||||
[&](const PlainUrlLocation &)
|
||||
{
|
||||
// should't happen, but still
|
||||
// all webpages are on DC4
|
||||
return 4;
|
||||
},
|
||||
[&](const InMemoryLocation &)
|
||||
{
|
||||
// shouldn't happen naturally
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (dc > 0) {
|
||||
return getDCName(dc);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int getScheduleTime(int64 sumSize) {
|
||||
auto time = 12;
|
||||
time += (int) std::ceil(std::max(6.0, std::ceil(sumSize / 1024.0 / 1024.0 * 0.7))) + 1;
|
||||
|
||||
@@ -43,6 +43,8 @@ QString getMediaName(not_null<HistoryItem*> message);
|
||||
QString getMediaResolution(not_null<HistoryItem*> message);
|
||||
QString getMediaDC(not_null<HistoryItem*> message);
|
||||
|
||||
QString getPeerDC(not_null<PeerData*> peer);
|
||||
|
||||
int getScheduleTime(int64 sumSize);
|
||||
|
||||
void searchById(ID userId, Main::Session *session, bool retry, const Callback &callback);
|
||||
|
||||
@@ -237,7 +237,7 @@ shareColumnSkip: 6px;
|
||||
shareActivateDuration: 150;
|
||||
shareScrollDuration: 300;
|
||||
shareComment: InputField(defaultInputField) {
|
||||
font: normalFont;
|
||||
style: defaultTextStyle;
|
||||
textMargins: margins(8px, 8px, 8px, 6px);
|
||||
heightMin: 36px;
|
||||
heightMax: 72px;
|
||||
@@ -290,6 +290,26 @@ passcodeTextLine: 28px;
|
||||
passcodeLittleSkip: 5px;
|
||||
passcodeAboutSkip: 7px;
|
||||
passcodeSkip: 23px;
|
||||
passcodeSystemUnlock: IconButton(defaultIconButton) {
|
||||
width: 32px;
|
||||
height: 36px;
|
||||
icon: icon{{ "menu/passcode_winhello", lightButtonFg }};
|
||||
iconOver: icon{{ "menu/passcode_winhello", lightButtonFg }};
|
||||
iconPosition: point(4px, 4px);
|
||||
rippleAreaSize: 32px;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: lightButtonBgOver;
|
||||
}
|
||||
}
|
||||
passcodeSystemTouchID: icon{{ "menu/passcode_finger", lightButtonFg }};
|
||||
passcodeSystemAppleWatch: icon{{ "menu/passcode_watch", lightButtonFg }};
|
||||
passcodeSystemSystemPwd: icon{{ "menu/permissions", lightButtonFg }};
|
||||
passcodeSystemUnlockLater: FlatLabel(defaultFlatLabel) {
|
||||
align: align(top);
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
passcodeSystemUnlockSkip: 12px;
|
||||
|
||||
newGroupAboutFg: windowSubTextFg;
|
||||
newGroupPadding: margins(4px, 6px, 4px, 3px);
|
||||
@@ -585,7 +605,7 @@ groupStickersRemovePosition: point(6px, 6px);
|
||||
groupStickersFieldPadding: margins(8px, 6px, 8px, 6px);
|
||||
groupStickersField: InputField(defaultMultiSelectSearchField) {
|
||||
placeholderFont: boxTextFont;
|
||||
font: boxTextFont;
|
||||
style: boxTextStyle;
|
||||
placeholderMargins: margins(0px, 0px, 0px, 0px);
|
||||
textMargins: margins(0px, 7px, 0px, 0px);
|
||||
textBg: boxBg;
|
||||
@@ -672,7 +692,6 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
createPollField: InputField(defaultInputField) {
|
||||
font: boxTextFont;
|
||||
textMargins: margins(0px, 4px, 0px, 4px);
|
||||
textAlign: align(left);
|
||||
heightMin: 36px;
|
||||
@@ -877,7 +896,6 @@ scheduleDateField: InputField(defaultInputField) {
|
||||
placeholderScale: 0.;
|
||||
heightMin: 30px;
|
||||
textAlign: align(top);
|
||||
font: font(14px);
|
||||
}
|
||||
scheduleTimeField: InputField(scheduleDateField) {
|
||||
border: 0px;
|
||||
@@ -905,7 +923,6 @@ muteBoxTimeField: InputField(scheduleDateField) {
|
||||
placeholderScale: 0.;
|
||||
heightMin: 30px;
|
||||
textAlign: align(left);
|
||||
font: font(14px);
|
||||
}
|
||||
muteBoxTimeFieldPadding: margins(5px, 0px, 5px, 0px);
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ void ChangeFilterById(
|
||||
MTP_int(filter.id()),
|
||||
filter.tl()
|
||||
)).done([=, chat = history->peer->name(), name = filter.title()] {
|
||||
const auto account = &history->session().account();
|
||||
const auto account = not_null(&history->session().account());
|
||||
if (const auto controller = Core::App().windowFor(account)) {
|
||||
controller->showToast((add
|
||||
? tr::lng_filters_toast_add
|
||||
|
||||
@@ -1044,7 +1044,16 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
|
||||
solution->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
solution->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
solution->setMarkdownReplacesEnabled(true);
|
||||
solution->setMarkdownReplacesEnabled(rpl::single(
|
||||
Ui::MarkdownEnabledState{ Ui::MarkdownEnabled{ {
|
||||
Ui::InputField::kTagBold,
|
||||
Ui::InputField::kTagItalic,
|
||||
Ui::InputField::kTagUnderline,
|
||||
Ui::InputField::kTagStrikeOut,
|
||||
Ui::InputField::kTagCode,
|
||||
Ui::InputField::kTagSpoiler,
|
||||
} } }
|
||||
));
|
||||
solution->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(_controller->uiShow(), solution));
|
||||
solution->customTab(true);
|
||||
|
||||
@@ -463,6 +463,7 @@ void EditCaptionBox::rebuildPreview() {
|
||||
st::defaultComposeControls,
|
||||
gifPaused,
|
||||
file,
|
||||
[] { return true; },
|
||||
Ui::AttachControls::Type::EditOnly);
|
||||
_isPhoto = (media && media->isPhoto());
|
||||
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
|
||||
|
||||
@@ -1011,14 +1011,16 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
}) | ranges::views::filter([](UserData *u) -> bool {
|
||||
return u;
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
if (!users.empty()) {
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
if (users.empty()) {
|
||||
show->showToast(
|
||||
tr::lng_settings_gift_premium_choose(tr::now));
|
||||
}
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
(*ignoreClose) = true;
|
||||
peersBox->closeBox();
|
||||
};
|
||||
@@ -1644,12 +1646,60 @@ void AddCreditsHistoryEntryTable(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
if (entry.bareId) {
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
if (peerId) {
|
||||
auto text = tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto session = &controller->session();
|
||||
const auto peer = session->data().peer(peerId);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
const auto username = channel->username();
|
||||
const auto base = username.isEmpty()
|
||||
? u"c/%1"_q.arg(peerToChannel(channel->id).bare)
|
||||
: username;
|
||||
const auto query = base + '/' + QString::number(msgId.bare);
|
||||
const auto link = session->createInternalLink(query);
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(Ui::Text::Link(link)),
|
||||
st::giveawayGiftCodeValue);
|
||||
label->setClickHandlerFilter([=](const auto &...) {
|
||||
controller->showPeerHistory(channel, {}, msgId);
|
||||
return false;
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_media(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
}
|
||||
using Type = Data::CreditsHistoryEntry::PeerType;
|
||||
if (entry.peerType == Type::AppStore) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer(),
|
||||
controller,
|
||||
PeerId(entry.bareId));
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_app_store(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::PlayMarket) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_play_market(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::Fragment) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_fragment(
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (entry.peerType == Type::Ads) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_via(),
|
||||
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
@@ -1680,4 +1730,17 @@ void AddCreditsHistoryEntryTable(
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
if (!entry.successDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_success_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
if (!entry.successLink.isEmpty()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_success_url(),
|
||||
rpl::single(
|
||||
Ui::Text::Link(entry.successLink, entry.successLink)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +133,8 @@ void MaxInviteBox::paintEvent(QPaintEvent *e) {
|
||||
auto option = QTextOption(style::al_left);
|
||||
option.setWrapMode(QTextOption::WrapAnywhere);
|
||||
p.setFont(_linkOver
|
||||
? st::defaultInputField.font->underline()
|
||||
: st::defaultInputField.font);
|
||||
? st::defaultInputField.style.font->underline()
|
||||
: st::defaultInputField.style.font);
|
||||
p.setPen(st::defaultLinkButton.color);
|
||||
const auto inviteLinkText = _channel->inviteLink().isEmpty()
|
||||
? tr::lng_group_invite_create(tr::now)
|
||||
|
||||
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/peers/edit_peer_info_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/info_memento.h"
|
||||
@@ -52,6 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/toast/toast.h"
|
||||
@@ -71,6 +75,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
#include <QtSvg/QSvgRenderer>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kBotManagerUsername = "BotFather"_cs;
|
||||
@@ -343,6 +349,7 @@ private:
|
||||
void fillPendingRequestsButton();
|
||||
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotBalanceButton();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
@@ -1126,6 +1133,7 @@ void Controller::fillManageSection() {
|
||||
|
||||
::AddSkip(container, 0);
|
||||
fillBotUsernamesButton();
|
||||
fillBotBalanceButton();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
@@ -1536,6 +1544,84 @@ void Controller::fillBotUsernamesButton() {
|
||||
{ &st::menuIconLinks });
|
||||
}
|
||||
|
||||
void Controller::fillBotBalanceButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
rpl::variable<QString> balance;
|
||||
};
|
||||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::BotEarn::Make(peer));
|
||||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
const auto api = button->lifetime().make_state<Api::CreditsStatus>(
|
||||
_peer);
|
||||
api->request({}, [=](Data::CreditsStatusSlice data) {
|
||||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = QString::number(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
constexpr auto kSizeShift = 3;
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(
|
||||
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
|
||||
colorized.toUtf8());
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(icon->height());
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
svg->render(&p, Rect(starSize));
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft + kSizeShift / 2.,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Controller::fillBotEditIntroButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
@@ -2114,8 +2200,11 @@ void Controller::saveForum() {
|
||||
channel->inputChannel,
|
||||
MTP_bool(*_savingData.forum)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
const auto weak = base::make_weak(this);
|
||||
channel->session().api().applyUpdates(result);
|
||||
continueSave();
|
||||
if (weak) { // todo better to be able to save in closed already box.
|
||||
continueSave();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (error.type() == u"CHAT_NOT_MODIFIED"_q) {
|
||||
continueSave();
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_document.h"
|
||||
@@ -351,8 +352,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
const auto customEmojiPaused = [controller = args.controller] {
|
||||
return controller->isGifPausedAtLeastFor(PauseReason::Layer);
|
||||
};
|
||||
raw->setCustomEmojiFactory([=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
auto factory = [=](QStringView data, Fn<void()> update)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto id = Data::ParseCustomEmojiData(data);
|
||||
auto result = owner->customEmojiManager().create(
|
||||
data,
|
||||
@@ -364,7 +365,13 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
||||
}
|
||||
using namespace Ui::Text;
|
||||
return std::make_unique<FirstFrameEmoji>(std::move(result));
|
||||
}, std::move(customEmojiPaused));
|
||||
};
|
||||
raw->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, customEmojiPaused, customEmojiPaused, std::move(factory));
|
||||
|
||||
const auto callback = args.callback;
|
||||
const auto isCustom = [=](DocumentId id) {
|
||||
|
||||
@@ -131,6 +131,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_subtitle_translation();
|
||||
case PremiumFeature::Business:
|
||||
return tr::lng_premium_summary_subtitle_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_subtitle_effects();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_subtitle_location();
|
||||
@@ -192,6 +194,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
||||
return tr::lng_premium_summary_about_translation();
|
||||
case PremiumFeature::Business:
|
||||
return tr::lng_premium_summary_about_business();
|
||||
case PremiumFeature::Effects:
|
||||
return tr::lng_premium_summary_about_effects();
|
||||
|
||||
case PremiumFeature::BusinessLocation:
|
||||
return tr::lng_business_about_location();
|
||||
@@ -529,6 +533,7 @@ struct VideoPreviewDocument {
|
||||
case PremiumFeature::Wallpapers: return "wallpapers";
|
||||
case PremiumFeature::LastSeen: return "last_seen";
|
||||
case PremiumFeature::MessagePrivacy: return "message_privacy";
|
||||
case PremiumFeature::Effects: return "effects";
|
||||
|
||||
case PremiumFeature::BusinessLocation: return "business_location";
|
||||
case PremiumFeature::BusinessHours: return "business_hours";
|
||||
|
||||
@@ -70,6 +70,7 @@ enum class PremiumFeature {
|
||||
LastSeen,
|
||||
MessagePrivacy,
|
||||
Business,
|
||||
Effects,
|
||||
|
||||
// Business features.
|
||||
BusinessLocation,
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "apiwrap.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
@@ -39,6 +40,147 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
struct PaidMediaData {
|
||||
const Data::Invoice *invoice = nullptr;
|
||||
HistoryItem *item = nullptr;
|
||||
PeerData *peer = nullptr;
|
||||
int photos = 0;
|
||||
int videos = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return invoice && item && peer && (photos || videos);
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] PaidMediaData LookupPaidMediaData(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form) {
|
||||
using namespace Payments;
|
||||
const auto message = std::get_if<InvoiceMessage>(&form->id.value);
|
||||
const auto item = message
|
||||
? session->data().message(message->peer, message->itemId)
|
||||
: nullptr;
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto invoice = media ? media->invoice() : nullptr;
|
||||
if (!invoice || !invoice->isPaidMedia) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto photos = 0;
|
||||
auto videos = 0;
|
||||
for (const auto &media : invoice->extendedMedia) {
|
||||
const auto photo = media->photo();
|
||||
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
|
||||
++photos;
|
||||
} else {
|
||||
++videos;
|
||||
}
|
||||
}
|
||||
|
||||
const auto sender = item->originalSender();
|
||||
const auto broadcast = (sender && sender->isBroadcast())
|
||||
? sender
|
||||
: message->peer.get();
|
||||
return {
|
||||
.invoice = invoice,
|
||||
.item = item,
|
||||
.peer = broadcast,
|
||||
.photos = photos,
|
||||
.videos = videos,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> SendCreditsConfirmText(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form) {
|
||||
if (const auto data = LookupPaidMediaData(session, form)) {
|
||||
auto photos = 0;
|
||||
auto videos = 0;
|
||||
for (const auto &media : data.invoice->extendedMedia) {
|
||||
const auto photo = media->photo();
|
||||
if (photo && !photo->extendedMediaVideoDuration().has_value()) {
|
||||
++photos;
|
||||
} else {
|
||||
++videos;
|
||||
}
|
||||
}
|
||||
|
||||
auto photosBold = tr::lng_credits_box_out_photos(
|
||||
lt_count,
|
||||
rpl::single(photos) | tr::to_count(),
|
||||
Ui::Text::Bold);
|
||||
auto videosBold = tr::lng_credits_box_out_videos(
|
||||
lt_count,
|
||||
rpl::single(videos) | tr::to_count(),
|
||||
Ui::Text::Bold);
|
||||
auto media = (!videos)
|
||||
? ((photos > 1)
|
||||
? std::move(photosBold)
|
||||
: tr::lng_credits_box_out_photo(Ui::Text::WithEntities))
|
||||
: (!photos)
|
||||
? ((videos > 1)
|
||||
? std::move(videosBold)
|
||||
: tr::lng_credits_box_out_video(Ui::Text::WithEntities))
|
||||
: tr::lng_credits_box_out_both(
|
||||
lt_photo,
|
||||
std::move(photosBold),
|
||||
lt_video,
|
||||
std::move(videosBold),
|
||||
Ui::Text::WithEntities);
|
||||
return tr::lng_credits_box_out_media(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_media,
|
||||
std::move(media),
|
||||
lt_chat,
|
||||
rpl::single(Ui::Text::Bold(data.peer->name())),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
|
||||
const auto bot = session->data().user(form->botId);
|
||||
return tr::lng_credits_box_out_sure(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_text,
|
||||
rpl::single(TextWithEntities{ form->title }),
|
||||
lt_bot,
|
||||
rpl::single(TextWithEntities{ bot->name() }),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> SendCreditsThumbnail(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Main::Session*> session,
|
||||
not_null<Payments::CreditsFormData*> form,
|
||||
int photoSize) {
|
||||
if (const auto data = LookupPaidMediaData(session, form)) {
|
||||
const auto first = data.invoice->extendedMedia[0]->photo();
|
||||
const auto second = (data.photos > 1)
|
||||
? data.invoice->extendedMedia[1]->photo()
|
||||
: nullptr;
|
||||
const auto totalCount = int(data.invoice->extendedMedia.size());
|
||||
if (first && first->extendedMediaPreview()) {
|
||||
return Settings::PaidMediaThumbnail(
|
||||
parent,
|
||||
first,
|
||||
second,
|
||||
totalCount,
|
||||
photoSize);
|
||||
}
|
||||
}
|
||||
if (form->photo) {
|
||||
return Settings::HistoryEntryPhoto(parent, form->photo, photoSize);
|
||||
}
|
||||
const auto bot = session->data().user(form->botId);
|
||||
return object_ptr<Ui::UserpicButton>(
|
||||
parent,
|
||||
bot,
|
||||
st::defaultUserpicButton);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
@@ -89,22 +231,10 @@ void SendCreditsBox(
|
||||
}, ministarsContainer->lifetime());
|
||||
}
|
||||
|
||||
const auto bot = session->data().user(form->botId);
|
||||
|
||||
if (form->photo) {
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
Settings::HistoryEntryPhoto(content, form->photo, photoSize)));
|
||||
} else {
|
||||
const auto widget = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(
|
||||
content,
|
||||
bot,
|
||||
st::defaultUserpicButton)));
|
||||
widget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
const auto thumb = box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
SendCreditsThumbnail(content, session, form.get(), photoSize)));
|
||||
thumb->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
box->addRow(object_ptr<Ui::CenterWrap<>>(
|
||||
@@ -118,14 +248,7 @@ void SendCreditsBox(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_credits_box_out_sure(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_text,
|
||||
rpl::single(TextWithEntities{ form->title }),
|
||||
lt_bot,
|
||||
rpl::single(TextWithEntities{ bot->name() }),
|
||||
Ui::Text::RichLangValue),
|
||||
SendCreditsConfirmText(session, form.get()),
|
||||
st::creditsBoxAbout)));
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
@@ -158,26 +281,16 @@ void SendCreditsBox(
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
{
|
||||
const auto emojiMargin = QMargins(
|
||||
0,
|
||||
-st::moderateBoxExpandInnerSkip,
|
||||
0,
|
||||
0);
|
||||
const auto buttonEmoji = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::settingsPremiumIconStar,
|
||||
emojiMargin,
|
||||
true));
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(buttonEmoji),
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st::defaultFlatLabel);
|
||||
st::creditsBoxButtonLabel);
|
||||
std::move(
|
||||
buttonText
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
@@ -247,4 +360,22 @@ void SendCreditsBox(
|
||||
}
|
||||
}
|
||||
|
||||
TextWithEntities CreditsEmoji(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::settingsPremiumIconStar,
|
||||
QMargins{ 0, -st::moderateBoxExpandInnerSkip, 0, 0 },
|
||||
true),
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
||||
return Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::starIconSmall,
|
||||
st::starIconSmallPadding,
|
||||
true),
|
||||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
class HistoryItem;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Payments {
|
||||
struct CreditsFormData;
|
||||
} // namespace Payments
|
||||
@@ -22,4 +26,10 @@ void SendCreditsBox(
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void()> sent);
|
||||
|
||||
[[nodiscard]] TextWithEntities CreditsEmoji(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
@@ -24,11 +26,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/controls/history_view_characters_limit.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "ui/effects/scroll_content_shadow.h"
|
||||
#include "ui/widgets/fields/number_input.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
@@ -36,10 +41,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/attach/attach_single_file_preview.h"
|
||||
#include "ui/chat/attach/attach_single_media_preview.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||
@@ -111,6 +119,84 @@ rpl::producer<QString> FieldPlaceholder(
|
||||
: tr::lng_photos_comment();
|
||||
}
|
||||
|
||||
void EditPriceBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session,
|
||||
uint64 price,
|
||||
Fn<void(uint64)> apply) {
|
||||
box->setTitle(tr::lng_paid_title());
|
||||
AddSubsectionTitle(
|
||||
box->verticalLayout(),
|
||||
tr::lng_paid_enter_cost(),
|
||||
(st::boxRowPadding - QMargins(
|
||||
st::defaultSubsectionTitlePadding.left(),
|
||||
0,
|
||||
st::defaultSubsectionTitlePadding.right(),
|
||||
0)));
|
||||
const auto limit = session->appConfig().get<int>(
|
||||
u"stars_paid_post_amount_max"_q,
|
||||
10'000);
|
||||
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::editTagField.heightMin));
|
||||
auto owned = object_ptr<Ui::NumberInput>(
|
||||
wrap,
|
||||
st::editTagField,
|
||||
tr::lng_paid_cost_placeholder(),
|
||||
price ? QString::number(price) : QString(),
|
||||
limit);
|
||||
const auto field = owned.data();
|
||||
wrap->widthValue() | rpl::start_with_next([=](int width) {
|
||||
field->move(0, 0);
|
||||
field->resize(width, field->height());
|
||||
wrap->resize(width, field->height());
|
||||
}, wrap->lifetime());
|
||||
field->selectAll();
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
});
|
||||
const auto about = box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_paid_about(
|
||||
lt_link,
|
||||
tr::lng_paid_about_link() | Ui::Text::ToLink(),
|
||||
Ui::Text::WithEntities),
|
||||
st::paidAmountAbout),
|
||||
st::boxRowPadding + QMargins(0, st::sendMediaRowSkip, 0, 0));
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
Core::App().iv().openWithIvPreferred(
|
||||
session,
|
||||
tr::lng_paid_about_link_url(tr::now));
|
||||
return false;
|
||||
});
|
||||
|
||||
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||
auto p = QPainter(field);
|
||||
st::paidStarIcon.paint(p, 0, st::paidStarIconTop, field->width());
|
||||
}, field->lifetime());
|
||||
|
||||
const auto save = [=] {
|
||||
const auto now = field->getLastText().toULongLong();
|
||||
if (now > limit) {
|
||||
field->showError();
|
||||
return;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
apply(now);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
};
|
||||
|
||||
QObject::connect(field, &Ui::NumberInput::submitted, box, save);
|
||||
|
||||
box->addButton(tr::lng_settings_save(), save);
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
|
||||
@@ -161,7 +247,8 @@ SendFilesBox::Block::Block(
|
||||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
SendFilesWay way)
|
||||
SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler)
|
||||
: _items(items)
|
||||
, _from(from)
|
||||
, _till(till) {
|
||||
@@ -178,14 +265,16 @@ SendFilesBox::Block::Block(
|
||||
parent.get(),
|
||||
st,
|
||||
my,
|
||||
way);
|
||||
way,
|
||||
std::move(canToggleSpoiler));
|
||||
_preview.reset(preview);
|
||||
} else {
|
||||
const auto media = Ui::SingleMediaPreview::Create(
|
||||
parent,
|
||||
st,
|
||||
gifPaused,
|
||||
first);
|
||||
first,
|
||||
std::move(canToggleSpoiler));
|
||||
if (media) {
|
||||
_isSingleMedia = true;
|
||||
_preview.reset(media);
|
||||
@@ -261,6 +350,14 @@ rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
return album->orderUpdated();
|
||||
}
|
||||
return rpl::never<>();
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
|
||||
if (!_isAlbum) {
|
||||
if (_isSingleMedia) {
|
||||
@@ -329,6 +426,18 @@ void SendFilesBox::Block::applyChanges() {
|
||||
}
|
||||
}
|
||||
|
||||
QImage SendFilesBox::Block::generatePriceTagBackground() const {
|
||||
const auto preview = _preview.get();
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->generatePriceTagBackground();
|
||||
} else if (_isSingleMedia) {
|
||||
const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
|
||||
return media->generatePriceTagBackground();
|
||||
}
|
||||
return QImage();
|
||||
}
|
||||
|
||||
SendFilesBox::SendFilesBox(
|
||||
QWidget*,
|
||||
not_null<Window::SessionController*> controller,
|
||||
@@ -393,6 +502,9 @@ Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
|
||||
: _invertCaption
|
||||
? SendMenu::CaptionState::Above
|
||||
: SendMenu::CaptionState::Below;
|
||||
result.price = canChangePrice()
|
||||
? _price.current()
|
||||
: std::optional<uint64>();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
@@ -406,6 +518,7 @@ auto SendFilesBox::prepareSendMenuCallback()
|
||||
case Type::CaptionUp: _invertCaption = true; break;
|
||||
case Type::SpoilerOn: toggleSpoilers(true); break;
|
||||
case Type::SpoilerOff: toggleSpoilers(false); break;
|
||||
case Type::ChangePrice: changePrice(); break;
|
||||
default:
|
||||
SendMenu::DefaultCallback(
|
||||
_show,
|
||||
@@ -596,14 +709,27 @@ void SendFilesBox::refreshButtons() {
|
||||
addMenuButton();
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
|
||||
bool SendFilesBox::hasSendMenu(const MenuDetails &details) const {
|
||||
return (details.type != SendMenu::Type::Disabled)
|
||||
|| (details.spoiler != SendMenu::SpoilerState::None)
|
||||
|| (details.caption != SendMenu::CaptionState::None);
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSpoilerMenu() const {
|
||||
return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
return !hasPrice()
|
||||
&& _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
bool SendFilesBox::canChangePrice() const {
|
||||
const auto way = _sendWay.current();
|
||||
const auto broadcast = _captionToPeer
|
||||
? _captionToPeer->asBroadcast()
|
||||
: nullptr;
|
||||
return broadcast
|
||||
&& broadcast->canPostPaidMedia()
|
||||
&& _list.canChangePrice(
|
||||
way.groupFiles() && way.sendImagesAsPhotos(),
|
||||
way.sendImagesAsPhotos());
|
||||
}
|
||||
|
||||
void SendFilesBox::applyBlockChanges() {
|
||||
@@ -626,6 +752,118 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::changePrice() {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
const auto session = &_show->session();
|
||||
const auto now = _price.current();
|
||||
_show->show(Box(EditPriceBox, session, now, [=](uint64 price) {
|
||||
if (weak && price != now) {
|
||||
_price = price;
|
||||
refreshPriceTag();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasPrice() const {
|
||||
return canChangePrice() && _price.current() > 0;
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshPriceTag() {
|
||||
const auto resetSpoilers = hasPrice() || _priceTag;
|
||||
if (resetSpoilers) {
|
||||
for (auto &file : _list.files) {
|
||||
file.spoiler = false;
|
||||
}
|
||||
for (auto &block : _blocks) {
|
||||
block.toggleSpoilers(hasPrice());
|
||||
}
|
||||
}
|
||||
if (!hasPrice()) {
|
||||
_priceTag = nullptr;
|
||||
_priceTagBg = QImage();
|
||||
} else if (!_priceTag) {
|
||||
_priceTag = std::make_unique<Ui::RpWidget>(_inner.data());
|
||||
const auto raw = _priceTag.get();
|
||||
|
||||
raw->show();
|
||||
raw->paintRequest() | rpl::start_with_next([=] {
|
||||
if (_priceTagBg.isNull()) {
|
||||
_priceTagBg = preparePriceTagBg(raw->size());
|
||||
}
|
||||
QPainter(raw).drawImage(0, 0, _priceTagBg);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &_show->session();
|
||||
auto price = _price.value() | rpl::map([=](uint64 amount) {
|
||||
auto result = Ui::Text::Colorized(Ui::CreditsEmoji(session));
|
||||
result.append(Lang::FormatCountDecimal(amount));
|
||||
return result;
|
||||
});
|
||||
auto text = tr::lng_paid_price(
|
||||
lt_price,
|
||||
std::move(price),
|
||||
Ui::Text::WithEntities);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
QString(),
|
||||
st::paidTagLabel);
|
||||
std::move(text) | rpl::start_with_next([=](TextWithEntities &&text) {
|
||||
label->setMarkedText(text, Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { label->update(); },
|
||||
});
|
||||
}, label->lifetime());
|
||||
label->show();
|
||||
label->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
const auto inner = QRect(QPoint(), size);
|
||||
const auto rect = inner.marginsAdded(st::paidTagPadding);
|
||||
raw->resize(rect.size());
|
||||
label->move(-rect.topLeft());
|
||||
}, label->lifetime());
|
||||
_inner->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
raw->move(
|
||||
(size.width() - raw->width()) / 2,
|
||||
(size.height() - raw->height()) / 2);
|
||||
}, raw->lifetime());
|
||||
} else {
|
||||
_priceTag->raise();
|
||||
_priceTag->update();
|
||||
_priceTagBg = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
QImage SendFilesBox::preparePriceTagBg(QSize size) const {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto outer = _blocks.empty()
|
||||
? size
|
||||
: _inner->widgetAt(0)->geometry().size();
|
||||
auto bg = _blocks.empty()
|
||||
? QImage()
|
||||
: _blocks.front().generatePriceTagBackground();
|
||||
if (bg.isNull()) {
|
||||
bg = QImage(ratio, ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
bg.fill(Qt::black);
|
||||
}
|
||||
|
||||
auto result = QImage(size * ratio, QImage::Format_ARGB32_Premultiplied);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
result.fill(Qt::black);
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawImage(
|
||||
QRect(
|
||||
(size.width() - outer.width()) / 2,
|
||||
(size.height() - outer.height()) / 2,
|
||||
outer.width(),
|
||||
outer.height()),
|
||||
bg);
|
||||
p.fillRect(QRect(QPoint(), size), st::msgDateImgBg);
|
||||
p.end();
|
||||
|
||||
const auto radius = std::min(size.width(), size.height()) / 2;
|
||||
return Images::Round(std::move(result), Images::CornersMask(radius));
|
||||
}
|
||||
|
||||
void SendFilesBox::addMenuButton() {
|
||||
const auto details = _sendMenuDetails();
|
||||
if (!hasSendMenu(details)) {
|
||||
@@ -719,6 +957,7 @@ void SendFilesBox::initSendWay() {
|
||||
block.setSendWay(value);
|
||||
}
|
||||
refreshButtons();
|
||||
refreshPriceTag();
|
||||
if (was != hidden()) {
|
||||
updateBoxSize();
|
||||
updateControlsGeometry();
|
||||
@@ -804,7 +1043,8 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
from,
|
||||
till,
|
||||
gifPaused,
|
||||
_sendWay.current());
|
||||
_sendWay.current(),
|
||||
[=] { return !hasPrice(); });
|
||||
auto &block = _blocks.back();
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
@@ -927,10 +1167,18 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
st::sendMediaPreviewSize,
|
||||
[=] { refreshAllAfterChanges(from); });
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::start_with_next([=]{
|
||||
if (_priceTag) {
|
||||
_priceTagBg = QImage();
|
||||
_priceTag->update();
|
||||
}
|
||||
}, widget->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshControls(bool initial) {
|
||||
refreshButtons();
|
||||
refreshPriceTag();
|
||||
refreshTitleText();
|
||||
updateSendWayControls();
|
||||
updateCaptionPlaceholder();
|
||||
@@ -1499,6 +1747,7 @@ void SendFilesBox::send(
|
||||
auto child = _sendMenuDetails();
|
||||
child.spoiler = SendMenu::SpoilerState::None;
|
||||
child.caption = SendMenu::CaptionState::None;
|
||||
child.price = std::nullopt;
|
||||
return SendMenu::DefaultCallback(_show, sendCallback())(
|
||||
{ .type = SendMenu::ActionType::Schedule },
|
||||
child);
|
||||
@@ -1526,10 +1775,16 @@ void SendFilesBox::send(
|
||||
auto caption = (_caption && !_caption->isHidden())
|
||||
? _caption->getTextWithAppliedMarkdown()
|
||||
: TextWithTags();
|
||||
options.invertCaption = _invertCaption;
|
||||
if (!validateLength(caption.text)) {
|
||||
return;
|
||||
}
|
||||
options.invertCaption = _invertCaption;
|
||||
options.price = hasPrice() ? _price.current() : 0;
|
||||
if (options.price > 0) {
|
||||
for (auto &file : _list.files) {
|
||||
file.spoiler = false;
|
||||
}
|
||||
}
|
||||
_confirmedCallback(
|
||||
std::move(_list),
|
||||
_sendWay.current(),
|
||||
|
||||
@@ -149,7 +149,8 @@ private:
|
||||
int from,
|
||||
int till,
|
||||
Fn<bool()> gifPaused,
|
||||
Ui::SendFilesWay way);
|
||||
Ui::SendFilesWay way,
|
||||
Fn<bool()> canToggleSpoiler);
|
||||
Block(Block &&other) = default;
|
||||
Block &operator=(Block &&other) = default;
|
||||
|
||||
@@ -160,11 +161,14 @@ private:
|
||||
[[nodiscard]] rpl::producer<int> itemDeleteRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
void toggleSpoilers(bool enabled);
|
||||
void applyChanges();
|
||||
|
||||
[[nodiscard]] QImage generatePriceTagBackground() const;
|
||||
|
||||
private:
|
||||
base::unique_qptr<Ui::RpWidget> _preview;
|
||||
not_null<std::vector<Ui::PreparedFile>*> _items;
|
||||
@@ -190,6 +194,12 @@ private:
|
||||
void addMenuButton();
|
||||
void applyBlockChanges();
|
||||
void toggleSpoilers(bool enabled);
|
||||
void changePrice();
|
||||
|
||||
[[nodiscard]] bool canChangePrice() const;
|
||||
[[nodiscard]] bool hasPrice() const;
|
||||
void refreshPriceTag();
|
||||
[[nodiscard]] QImage preparePriceTagBg(QSize size) const;
|
||||
|
||||
bool validateLength(const QString &text) const;
|
||||
void refreshButtons();
|
||||
@@ -251,6 +261,9 @@ private:
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
Fn<void()> _cancelledCallback;
|
||||
rpl::variable<uint64> _price = 0;
|
||||
std::unique_ptr<Ui::RpWidget> _priceTag;
|
||||
QImage _priceTagBg;
|
||||
bool _confirmed = false;
|
||||
bool _invertCaption = false;
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "styles/style_ayu_styles.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "data/data_user.h"
|
||||
|
||||
@@ -1392,6 +1393,18 @@ void StickerSetBox::Inner::paintSticker(
|
||||
(_singleSize.width() - size.width()) / 2,
|
||||
(_singleSize.height() - size.height()) / 2);
|
||||
auto lottieFrame = QImage();
|
||||
|
||||
if (sticker->setType == Data::StickersType::Stickers) {
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(ppos, size), st::stickerRoundingSize, st::stickerRoundingSize);
|
||||
|
||||
p.save();
|
||||
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setClipPath(path);
|
||||
p.setRenderHint(QPainter::Antialiasing, false);
|
||||
}
|
||||
|
||||
if (element.emoji) {
|
||||
element.emoji->paint(p, {
|
||||
.textColor = st::windowFg->c,
|
||||
@@ -1425,6 +1438,11 @@ void StickerSetBox::Inner::paintSticker(
|
||||
QRect(ppos, size),
|
||||
_pathGradient.get());
|
||||
}
|
||||
|
||||
if (sticker->setType == Data::StickersType::Stickers) {
|
||||
p.restore();
|
||||
}
|
||||
|
||||
if (premium) {
|
||||
_premiumMark.paint(
|
||||
p,
|
||||
|
||||
@@ -1393,7 +1393,6 @@ groupCallScheduleDateField: InputField(groupCallField) {
|
||||
placeholderScale: 0.;
|
||||
heightMin: 30px;
|
||||
textAlign: align(top);
|
||||
font: font(14px);
|
||||
}
|
||||
groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
|
||||
textBg: groupCallMembersBg;
|
||||
|
||||
@@ -706,7 +706,8 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
|
||||
}
|
||||
}
|
||||
if (false && data.is_need_rating() && _id && _accessHash) {
|
||||
const auto window = Core::App().windowFor(_user);
|
||||
const auto window = Core::App().windowFor(
|
||||
Window::SeparateId(_user));
|
||||
const auto session = &_user->session();
|
||||
const auto callId = _id;
|
||||
const auto callAccessHash = _accessHash;
|
||||
@@ -1402,7 +1403,8 @@ void Call::handleRequestError(const QString &error) {
|
||||
_user->name())
|
||||
: QString();
|
||||
if (!inform.isEmpty()) {
|
||||
if (const auto window = Core::App().windowFor(_user)) {
|
||||
if (const auto window = Core::App().windowFor(
|
||||
Window::SeparateId(_user))) {
|
||||
window->show(Ui::MakeInformBox(inform));
|
||||
} else {
|
||||
Ui::show(Ui::MakeInformBox(inform));
|
||||
@@ -1420,7 +1422,8 @@ void Call::handleControllerError(const QString &error) {
|
||||
? tr::lng_call_error_audio_io(tr::now)
|
||||
: QString();
|
||||
if (!inform.isEmpty()) {
|
||||
if (const auto window = Core::App().windowFor(_user)) {
|
||||
if (const auto window = Core::App().windowFor(
|
||||
Window::SeparateId(_user))) {
|
||||
window->show(Ui::MakeInformBox(inform));
|
||||
} else {
|
||||
Ui::show(Ui::MakeInformBox(inform));
|
||||
|
||||
@@ -383,20 +383,14 @@ void Panel::initWindow() {
|
||||
&& _fullScreenOrMaximized.current()) {
|
||||
toggleFullScreen();
|
||||
}
|
||||
} else if (e->type() == QEvent::WindowStateChange && _call->rtmp()) {
|
||||
const auto state = window()->windowState();
|
||||
_fullScreenOrMaximized = (state & Qt::WindowFullScreen)
|
||||
|| (state & Qt::WindowMaximized);
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
if (_call->rtmp()) {
|
||||
QObject::connect(
|
||||
window()->windowHandle(),
|
||||
&QWindow::windowStateChanged,
|
||||
[=](Qt::WindowState state) {
|
||||
_fullScreenOrMaximized = (state == Qt::WindowFullScreen)
|
||||
|| (state == Qt::WindowMaximized);
|
||||
});
|
||||
}
|
||||
|
||||
window()->setBodyTitleArea([=](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
const auto titleRect = QRect(
|
||||
|
||||
@@ -585,7 +585,6 @@ void ChooseSourceProcess::setupSourcesGeometry() {
|
||||
|
||||
void ChooseSourceProcess::setupGeometryWithParent(
|
||||
not_null<QWidget*> parent) {
|
||||
_window->createWinId();
|
||||
const auto parentScreen = [&] {
|
||||
if (const auto screen = QGuiApplication::screenAt(
|
||||
parent->geometry().center())) {
|
||||
@@ -595,7 +594,12 @@ void ChooseSourceProcess::setupGeometryWithParent(
|
||||
}();
|
||||
const auto myScreen = _window->screen();
|
||||
if (parentScreen && myScreen != parentScreen) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
_window->setScreen(parentScreen);
|
||||
#else // Qt >= 6.0.0
|
||||
_window->createWinId();
|
||||
_window->windowHandle()->setScreen(parentScreen);
|
||||
#endif // Qt < 6.0.0
|
||||
}
|
||||
_window->setFixedSize(_fixedSize);
|
||||
_window->move(
|
||||
|
||||
@@ -71,6 +71,7 @@ ComposeIcons {
|
||||
menuSpoilerOff: icon;
|
||||
menuBelow: icon;
|
||||
menuAbove: icon;
|
||||
menuPrice: icon;
|
||||
|
||||
stripBubble: icon;
|
||||
stripExpandPanel: icon;
|
||||
@@ -610,6 +611,7 @@ defaultComposeIcons: ComposeIcons {
|
||||
menuSpoilerOff: menuIconSpoilerOff;
|
||||
menuBelow: menuIconBelow;
|
||||
menuAbove: menuIconAbove;
|
||||
menuPrice: menuIconEarn;
|
||||
|
||||
stripBubble: icon{
|
||||
{ "chat/reactions_bubble_shadow", windowShadowFg },
|
||||
@@ -989,8 +991,33 @@ historyUnreadReactions: TwoIconButton(historyToDown) {
|
||||
}
|
||||
historyUnreadThingsSkip: 4px;
|
||||
|
||||
historyQuoteStyle: QuoteStyle(defaultQuoteStyle) {
|
||||
padding: margins(10px, 2px, 4px, 2px);
|
||||
verticalSkip: 4px;
|
||||
outline: 3px;
|
||||
outlineShift: 2px;
|
||||
radius: 5px;
|
||||
}
|
||||
historyTextStyle: TextStyle(defaultTextStyle) {
|
||||
blockquote: QuoteStyle(historyQuoteStyle) {
|
||||
padding: margins(10px, 2px, 20px, 2px);
|
||||
icon: icon{{ "chat/mini_quote", windowFg }};
|
||||
iconPosition: point(4px, 4px);
|
||||
expand: icon{{ "intro_country_dropdown", windowFg }};
|
||||
expandPosition: point(6px, 4px);
|
||||
collapse: icon{{ "intro_country_dropdown-flip_vertical", windowFg }};
|
||||
collapsePosition: point(6px, 4px);
|
||||
}
|
||||
pre: QuoteStyle(historyQuoteStyle) {
|
||||
header: 20px;
|
||||
headerPosition: point(10px, 2px);
|
||||
scrollable: true;
|
||||
icon: icon{{ "chat/mini_copy", windowFg }};
|
||||
iconPosition: point(4px, 2px);
|
||||
}
|
||||
}
|
||||
historyComposeField: InputField(defaultInputField) {
|
||||
font: normalFont;
|
||||
style: historyTextStyle;
|
||||
textMargins: margins(0px, 0px, 0px, 0px);
|
||||
textAlign: align(left);
|
||||
textFg: historyComposeAreaFg;
|
||||
@@ -1381,3 +1408,18 @@ editTagField: InputField(defaultInputField) {
|
||||
editTagLimit: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
|
||||
paidStarIconTop: 7px;
|
||||
paidAmountAbout: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 256px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
paidTagLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: radialFg;
|
||||
palette: TextPalette(defaultTextPalette) {
|
||||
linkFg: creditsBg1;
|
||||
}
|
||||
style: semiboldTextStyle;
|
||||
}
|
||||
paidTagPadding: margins(16px, 6px, 16px, 6px);
|
||||
|
||||
@@ -30,15 +30,15 @@ ResolveWindow ResolveWindowDefault() {
|
||||
return (Window::SessionController*)nullptr;
|
||||
};
|
||||
auto &app = Core::App();
|
||||
const auto account = not_null(&session->account());
|
||||
if (const auto a = check(app.activeWindow())) {
|
||||
return a;
|
||||
} else if (const auto b = check(app.activePrimaryWindow())) {
|
||||
return b;
|
||||
} else if (const auto c = check(app.windowFor(&session->account()))) {
|
||||
} else if (const auto c = check(app.windowFor(account))) {
|
||||
return c;
|
||||
} else if (const auto d = check(
|
||||
app.ensureSeparateWindowForAccount(
|
||||
&session->account()))) {
|
||||
} else if (const auto d = check(app.ensureSeparateWindowFor(
|
||||
account))) {
|
||||
return d;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -14,11 +14,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
@@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
@@ -58,6 +61,7 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection;
|
||||
|
||||
constexpr auto kParseLinksTimeout = crl::time(500);
|
||||
constexpr auto kTypesDuration = 4 * crl::time(1000);
|
||||
constexpr auto kCodeLanguageLimit = 32;
|
||||
|
||||
// For mention / custom emoji tags save and validate selfId,
|
||||
// ignore tags for different users.
|
||||
@@ -222,6 +226,51 @@ void EditLinkBox(
|
||||
}, text->lifetime());
|
||||
}
|
||||
|
||||
void EditCodeLanguageBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
QString now,
|
||||
Fn<void(QString)> save) {
|
||||
Expects(save != nullptr);
|
||||
|
||||
box->setTitle(tr::lng_formatting_code_title());
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_formatting_code_language(),
|
||||
st::settingsAddReplyLabel));
|
||||
const auto field = box->addRow(object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st::settingsAddReplyField,
|
||||
tr::lng_formatting_code_auto(),
|
||||
now.trimmed()));
|
||||
box->setFocusCallback([=] {
|
||||
field->setFocusFast();
|
||||
});
|
||||
field->selectAll();
|
||||
field->setMaxLength(kCodeLanguageLimit);
|
||||
|
||||
Ui::AddLengthLimitLabel(field, kCodeLanguageLimit);
|
||||
|
||||
const auto callback = [=] {
|
||||
const auto name = field->getLastText().trimmed();
|
||||
const auto check = QRegularExpression("^[a-zA-Z0-9\\+\\-]*$");
|
||||
if (check.match(name).hasMatch()) {
|
||||
auto weak = Ui::MakeWeak(box);
|
||||
save(name);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
} else {
|
||||
field->showError();
|
||||
}
|
||||
};
|
||||
field->submits(
|
||||
) | rpl::start_with_next(callback, field->lifetime());
|
||||
box->addButton(tr::lng_settings_save(), callback);
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
TextWithEntities StripSupportHashtag(TextWithEntities text) {
|
||||
static const auto expression = QRegularExpression(
|
||||
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
|
||||
@@ -274,15 +323,24 @@ TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
|
||||
|
||||
bool EditTextChanged(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithTags &updated) {
|
||||
TextWithTags updated) {
|
||||
const auto original = PrepareEditText(item);
|
||||
|
||||
auto originalWithEntities = TextWithEntities{
|
||||
std::move(original.text),
|
||||
TextUtilities::ConvertTextTagsToEntities(original.tags)
|
||||
};
|
||||
auto updatedWithEntities = TextWithEntities{
|
||||
std::move(updated.text),
|
||||
TextUtilities::ConvertTextTagsToEntities(updated.tags)
|
||||
};
|
||||
TextUtilities::PrepareForSending(originalWithEntities, 0);
|
||||
TextUtilities::PrepareForSending(updatedWithEntities, 0);
|
||||
|
||||
// Tags can be different for the same entities, because for
|
||||
// animated emoji each tag contains a different random number.
|
||||
// So we compare entities instead of tags.
|
||||
return (original.text != updated.text)
|
||||
|| (TextUtilities::ConvertTextTagsToEntities(original.tags)
|
||||
!= TextUtilities::ConvertTextTagsToEntities(updated.tags));
|
||||
return originalWithEntities != updatedWithEntities;
|
||||
}
|
||||
|
||||
Fn<bool(
|
||||
@@ -320,6 +378,13 @@ Fn<bool(
|
||||
};
|
||||
}
|
||||
|
||||
Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
|
||||
std::shared_ptr<Ui::Show> show) {
|
||||
return [=](QString now, Fn<void(QString)> save) {
|
||||
show->showBox(Box(EditCodeLanguageBox, now, save));
|
||||
};
|
||||
}
|
||||
|
||||
void InitMessageFieldHandlers(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
@@ -329,12 +394,16 @@ void InitMessageFieldHandlers(
|
||||
const style::InputField *fieldStyle) {
|
||||
field->setTagMimeProcessor(
|
||||
FieldTagMimeProcessor(session, allowPremiumEmoji));
|
||||
const auto paused = [customEmojiPaused] {
|
||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(repaint),
|
||||
});
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kEmojiChat) || customEmojiPaused();
|
||||
};
|
||||
field->setCustomEmojiFactory(
|
||||
session->data().customEmojiManager().factory(),
|
||||
std::move(customEmojiPaused));
|
||||
}, [customEmojiPaused] {
|
||||
return On(PowerSaving::kChatSpoiler) || customEmojiPaused();
|
||||
});
|
||||
field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
field->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
@@ -342,8 +411,18 @@ void InitMessageFieldHandlers(
|
||||
if (show) {
|
||||
field->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(show, field, fieldStyle));
|
||||
field->setEditLanguageCallback(DefaultEditLanguageCallback(show));
|
||||
InitSpellchecker(show, field, fieldStyle != nullptr);
|
||||
}
|
||||
const auto style = field->lifetime().make_state<Ui::ChatStyle>(
|
||||
session->colorIndicesValue());
|
||||
field->setPreCache([=] {
|
||||
return style->messageStyle(false, false).preCache.get();
|
||||
});
|
||||
field->setBlockquoteCache([=] {
|
||||
const auto colorIndex = session->user()->colorIndex();
|
||||
return style->coloredQuoteCache(false, colorIndex).get();
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGoodFactcheckUrl(QStringView url) {
|
||||
@@ -569,12 +648,26 @@ void InitMessageFieldFade(
|
||||
Ui::DestroyChild(b.data());
|
||||
}, topFade->lifetime());
|
||||
|
||||
topFade->show();
|
||||
bottomFade->showOn(
|
||||
field->scrollTop().value(
|
||||
) | rpl::map([field, descent = field->st().font->descent](int scroll) {
|
||||
return (scroll + descent) < field->scrollTopMax();
|
||||
}) | rpl::distinct_until_changed());
|
||||
const auto descent = field->st().style.font->descent;
|
||||
rpl::merge(
|
||||
field->changes(),
|
||||
field->scrollTop().changes() | rpl::to_empty,
|
||||
field->sizeValue() | rpl::to_empty
|
||||
) | rpl::start_with_next([=] {
|
||||
// InputField::changes fires before the auto-resize is being applied,
|
||||
// so for the scroll values to be accurate we enqueue the check.
|
||||
InvokeQueued(field, [=] {
|
||||
const auto topHidden = !field->scrollTop().current();
|
||||
if (topFade->isHidden() != topHidden) {
|
||||
topFade->setVisible(!topHidden);
|
||||
}
|
||||
const auto adjusted = field->scrollTop().current() + descent;
|
||||
const auto bottomHidden = (adjusted >= field->scrollTopMax());
|
||||
if (bottomFade->isHidden() != bottomHidden) {
|
||||
bottomFade->setVisible(!bottomHidden);
|
||||
}
|
||||
});
|
||||
}, topFade->lifetime());
|
||||
}
|
||||
|
||||
InlineBotQuery ParseInlineBotQuery(
|
||||
@@ -766,11 +859,7 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
|
||||
const auto text = static_cast<QKeyEvent*>(event)->text();
|
||||
if (!text.isEmpty() && text.size() < 3) {
|
||||
const auto ch = text[0];
|
||||
if (false
|
||||
|| ch == '\n'
|
||||
|| ch == '\r'
|
||||
|| ch.isSpace()
|
||||
|| ch == QChar::LineSeparator) {
|
||||
if (IsSpace(ch)) {
|
||||
_timer.callOnce(0);
|
||||
}
|
||||
}
|
||||
@@ -1097,7 +1186,9 @@ base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
|
||||
const auto margins = (st.textMargins + st.placeholderMargins);
|
||||
const auto available = width - margins.left() - margins.right();
|
||||
label->resizeToWidth(available);
|
||||
label->moveToLeft(margins.left(), margins.top(), width);
|
||||
const auto height = label->height() + link->height();
|
||||
const auto top = (raw->height() - height) / 2;
|
||||
label->moveToLeft(margins.left(), top, width);
|
||||
link->move(
|
||||
(width - link->width()) / 2,
|
||||
label->y() + label->height());
|
||||
@@ -1117,8 +1208,8 @@ void SelectTextInFieldWithMargins(
|
||||
auto textCursor = field->textCursor();
|
||||
// Try to set equal margins for top and bottom sides.
|
||||
const auto charsCountInLine = field->width()
|
||||
/ field->st().font->width('W');
|
||||
const auto linesCount = (field->height() / field->st().font->height);
|
||||
/ field->st().style.font->width('W');
|
||||
const auto linesCount = field->height() / field->st().style.font->height;
|
||||
const auto selectedLines = (selection.to - selection.from)
|
||||
/ charsCountInLine;
|
||||
constexpr auto kMinDiff = ushort(3);
|
||||
|
||||