Merge tag 'v5.3.2' into dev

# Conflicts:
#	.github/workflows/mac_packaged.yml
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/boxes/sticker_set_box.cpp
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
#	Telegram/lib_ui
#	snap/snapcraft.yaml
This commit is contained in:
AlexeyZavar 2024-08-05 20:55:23 +03:00
commit 92dab0438f
234 changed files with 9807 additions and 2706 deletions

View file

@ -169,6 +169,8 @@ jobs:
%TDESKTOP_BUILD_GENERATOR% ^ %TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^ %TDESKTOP_BUILD_ARCH% ^
%TDESKTOP_BUILD_API% ^ %TDESKTOP_BUILD_API% ^
-D CMAKE_C_FLAGS="/WX" ^
-D CMAKE_CXX_FLAGS="/WX" ^
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^ -D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^ -D DESKTOP_APP_NO_PDB=ON ^

View file

@ -341,6 +341,8 @@ PRIVATE
boxes/edit_caption_box.h boxes/edit_caption_box.h
boxes/edit_privacy_box.cpp boxes/edit_privacy_box.cpp
boxes/edit_privacy_box.h boxes/edit_privacy_box.h
boxes/gift_credits_box.cpp
boxes/gift_credits_box.h
boxes/gift_premium_box.cpp boxes/gift_premium_box.cpp
boxes/gift_premium_box.h boxes/gift_premium_box.h
boxes/language_box.cpp boxes/language_box.cpp
@ -544,6 +546,8 @@ PRIVATE
data/business/data_shortcut_messages.h data/business/data_shortcut_messages.h
data/components/factchecks.cpp data/components/factchecks.cpp
data/components/factchecks.h data/components/factchecks.h
data/components/location_pickers.cpp
data/components/location_pickers.h
data/components/recent_peers.cpp data/components/recent_peers.cpp
data/components/recent_peers.h data/components/recent_peers.h
data/components/scheduled_messages.cpp data/components/scheduled_messages.cpp
@ -1541,6 +1545,8 @@ PRIVATE
ui/chat/choose_send_as.h ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h ui/chat/choose_theme_controller.h
ui/controls/location_picker.cpp
ui/controls/location_picker.h
ui/controls/silent_toggle.cpp ui/controls/silent_toggle.cpp
ui/controls/silent_toggle.h ui/controls/silent_toggle.h
ui/controls/userpic_button.cpp ui/controls/userpic_button.cpp
@ -1562,6 +1568,10 @@ PRIVATE
ui/image/image_location.h ui/image/image_location.h
ui/image/image_location_factory.cpp ui/image/image_location_factory.cpp
ui/image/image_location_factory.h ui/image/image_location_factory.h
ui/text/format_song_document_name.cpp
ui/text/format_song_document_name.h
ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h
ui/countryinput.cpp ui/countryinput.cpp
ui/countryinput.h ui/countryinput.h
ui/dynamic_thumbnails.cpp ui/dynamic_thumbnails.cpp
@ -1575,10 +1585,6 @@ PRIVATE
ui/resize_area.h ui/resize_area.h
ui/search_field_controller.cpp ui/search_field_controller.cpp
ui/search_field_controller.h ui/search_field_controller.h
ui/text/format_song_document_name.cpp
ui/text/format_song_document_name.h
ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h
ui/unread_badge.cpp ui/unread_badge.cpp
ui/unread_badge.h ui/unread_badge.h
window/main_window.cpp window/main_window.cpp
@ -1685,6 +1691,7 @@ PRIVATE
qrc/telegram/animations.qrc qrc/telegram/animations.qrc
qrc/telegram/export.qrc qrc/telegram/export.qrc
qrc/telegram/iv.qrc qrc/telegram/iv.qrc
qrc/telegram/picker.qrc
qrc/telegram/telegram.qrc qrc/telegram/telegram.qrc
qrc/telegram/sounds.qrc qrc/telegram/sounds.qrc
winrc/Telegram.rc winrc/Telegram.rc
@ -1915,9 +1922,14 @@ if (WIN32)
/DELAYLOAD:propsys.dll /DELAYLOAD:propsys.dll
) )
if (QT_VERSION GREATER 6) if (QT_VERSION GREATER 6)
if (NOT build_winarm)
target_link_options(Telegram PRIVATE
/DELAYLOAD:API-MS-Win-EventLog-Legacy-l1-1-0.dll
)
endif()
target_link_options(Telegram target_link_options(Telegram
PRIVATE 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-Console-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Core-Fibers-l2-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-Fibers-l2-1-1.dll
@ -1934,7 +1946,7 @@ if (WIN32)
/DELAYLOAD:API-MS-Win-Core-WinRT-Error-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-Core-WinRT-String-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll /DELAYLOAD:API-MS-Win-Security-CryptoAPI-l1-1-0.dll
/DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # /DELAYLOAD:API-MS-Win-Shcore-Scaling-l1-1-1.dll # We shadowed GetDpiForMonitor
/DELAYLOAD:authz.dll # Authz.lib /DELAYLOAD:authz.dll # Authz.lib
/DELAYLOAD:comdlg32.dll /DELAYLOAD:comdlg32.dll
/DELAYLOAD:dwrite.dll # DWrite.lib /DELAYLOAD:dwrite.dll # DWrite.lib

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -618,9 +618,6 @@ var IV = {
element.getAnimations().forEach( element.getAnimations().forEach(
(animation) => animation.finish()); (animation) => animation.finish());
}, },
back: function () {
window.history.back();
},
menuShown: function (shown) { menuShown: function (shown) {
var already = document.getElementById('menu_page_blocker'); var already = document.getElementById('menu_page_blocker');
if (already && shown) { if (already && shown) {

View file

@ -1115,6 +1115,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq" = "Telegram FAQ"; "lng_settings_faq" = "Telegram FAQ";
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions"; "lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features"; "lng_settings_features" = "Telegram Features";
"lng_settings_credits" = "Your Stars";
"lng_settings_logout" = "Log Out"; "lng_settings_logout" = "Log Out";
"lng_sure_logout" = "Are you sure you want to log out?"; "lng_sure_logout" = "Are you sure you want to log out?";
@ -1447,6 +1448,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_info_topic_title" = "Topic Info"; "lng_info_topic_title" = "Topic Info";
"lng_profile_enable_notifications" = "Notifications"; "lng_profile_enable_notifications" = "Notifications";
"lng_profile_send_message" = "Send Message"; "lng_profile_send_message" = "Send Message";
"lng_profile_open_app" = "Open App";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
"lng_info_add_as_contact" = "Add to contacts"; "lng_info_add_as_contact" = "Add to contacts";
"lng_profile_shared_media" = "Shared media"; "lng_profile_shared_media" = "Shared media";
"lng_profile_suggest_photo" = "Suggest Profile Photo"; "lng_profile_suggest_photo" = "Suggest Profile Photo";
@ -1844,6 +1848,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot."; "lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
"lng_action_gift_received" = "{user} sent you a gift for {cost}"; "lng_action_gift_received" = "{user} sent you a gift for {cost}";
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; "lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
"lng_action_suggested_photo_me" = "You suggested {user} to use this profile photo."; "lng_action_suggested_photo_me" = "You suggested {user} to use this profile photo.";
"lng_action_suggested_photo" = "{user} suggests you to use this profile photo."; "lng_action_suggested_photo" = "{user} suggests you to use this profile photo.";
"lng_action_suggested_photo_button" = "View Photo"; "lng_action_suggested_photo_button" = "View Photo";
@ -1888,6 +1893,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_boost_apply#one" = "{from} boosted the group"; "lng_action_boost_apply#one" = "{from} boosted the group";
"lng_action_boost_apply#other" = "{from} boosted the group {count} times"; "lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?"; "lng_action_set_chat_intro" = "{from} added the message below for all empty chats. How?";
"lng_action_payment_refunded" = "{peer} refunded back {amount}";
"lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all"; "lng_similar_channels_view_all" = "View all";
@ -2340,6 +2346,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_history_tab_out" = "Outgoing"; "lng_credits_summary_history_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; "lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance"; "lng_credits_summary_balance" = "Balance";
"lng_credits_gift_button" = "Gift Stars to Friends";
"lng_credits_box_out_title" = "Confirm Your Purchase"; "lng_credits_box_out_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#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_sure#other" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Stars**?";
@ -2355,6 +2362,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star"; "lng_credits_box_out_confirm#one" = "Confirm and Pay {emoji} {count} Star";
"lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars"; "lng_credits_box_out_confirm#other" = "Confirm and Pay {emoji} {count} Stars";
"lng_credits_box_out_about" = "Review the {link} for Stars."; "lng_credits_box_out_about" = "Review the {link} for Stars.";
"lng_credits_box_out_about_link" = "https://telegram.org/tos/stars";
"lng_credits_media_done_title" = "Media Unlocked"; "lng_credits_media_done_title" = "Media Unlocked";
"lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}."; "lng_credits_media_done_text#one" = "**{count} Star** transferred to {chat}.";
"lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}."; "lng_credits_media_done_text#other" = "**{count} Stars** transferred to {chat}.";
@ -2362,11 +2370,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_in_toast_about#one" = "**{count}** Star added to your balance."; "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_summary_in_toast_about#other" = "**{count}** Stars added to your balance.";
"lng_credits_box_history_entry_peer" = "Recipient"; "lng_credits_box_history_entry_peer" = "Recipient";
"lng_credits_box_history_entry_peer_in" = "From";
"lng_credits_box_history_entry_via" = "Via"; "lng_credits_box_history_entry_via" = "Via";
"lng_credits_box_history_entry_play_market" = "Play Market"; "lng_credits_box_history_entry_play_market" = "Play Market";
"lng_credits_box_history_entry_app_store" = "App Store"; "lng_credits_box_history_entry_app_store" = "App Store";
"lng_credits_box_history_entry_fragment" = "Fragment"; "lng_credits_box_history_entry_fragment" = "Fragment";
"lng_credits_box_history_entry_anonymous" = "Unknown User";
"lng_credits_box_history_entry_gift_name" = "Received Gift";
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
"lng_credits_box_history_entry_gift_about_url" = "https://telegram.org/blog/telegram-stars";
"lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
"lng_credits_box_history_entry_id" = "Transaction ID"; "lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard."; "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_date" = "Transaction date";
@ -2379,9 +2397,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps."; "lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_gift_title" = "Gift Telegram Stars";
"lng_location_title" = "Location"; "lng_location_title" = "Location";
"lng_location_about" = "Display the location of your business on your account."; "lng_location_about" = "Display the location of your business on your account.";
"lng_location_address" = "Enter Address"; "lng_location_address" = "Enter Address";
"lng_location_set_map" = "Set Location on Map";
"lng_location_fallback" = "You can set your location on the map from your mobile device."; "lng_location_fallback" = "You can set your location on the map from your mobile device.";
"lng_hours_title" = "Business Hours"; "lng_hours_title" = "Business Hours";
@ -2810,12 +2831,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_badge" = "x{amount}"; "lng_prizes_badge" = "x{amount}";
"lng_prizes_results_title" = "Winners Selected!"; "lng_prizes_results_title" = "Winners Selected!";
"lng_prizes_results_title_one" = "Winner Selected!";
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram."; "lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram."; "lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
"lng_prizes_results_link" = "Giveaway"; "lng_prizes_results_link" = "Giveaway";
"lng_prizes_results_winner" = "Winner";
"lng_prizes_results_winners" = "Winners"; "lng_prizes_results_winners" = "Winners";
"lng_prizes_results_more#one" = "and {count} more!"; "lng_prizes_results_more#one" = "and {count} more!";
"lng_prizes_results_more#other" = "and {count} more!"; "lng_prizes_results_more#other" = "and {count} more!";
"lng_prizes_results_one" = "The winner received their gift link in a private message.";
"lng_prizes_results_all" = "All winners received gift links in private messages."; "lng_prizes_results_all" = "All winners received gift links in private messages.";
"lng_prizes_results_some" = "Some winners couldn't be selected."; "lng_prizes_results_some" = "Some winners couldn't be selected.";
@ -2845,6 +2869,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_link_pending_toast" = "Only the recipient can see the link."; "lng_gift_link_pending_toast" = "Only the recipient can see the link.";
"lng_gift_link_pending_footer" = "This link hasn't been activated yet."; "lng_gift_link_pending_footer" = "This link hasn't been activated yet.";
"lng_gift_stars_title#one" = "{count} Star";
"lng_gift_stars_title#other" = "{count} Stars";
"lng_gift_stars_outgoing" = "With Stars, {user} will be able to unlock content and services on Telegram.";
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
"lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
@ -2920,6 +2949,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_has_been_archived" = "Mask pack has been archived.";
"lng_masks_installed" = "Mask pack has been installed."; "lng_masks_installed" = "Mask pack has been installed.";
"lng_emoji_nothing_found" = "No emoji found"; "lng_emoji_nothing_found" = "No emoji found";
"lng_stickers_context_reorder" = "Reorder";
"lng_stickers_context_edit_name" = "Edit name";
"lng_stickers_context_delete" = "Delete sticker";
"lng_stickers_context_delete_sure" = "Are you sure you want to delete the sticker from your sticker set?";
"lng_stickers_box_edit_name_title" = "Edit Sticker Set Name";
"lng_stickers_box_edit_name_about" = "Choose a name for your set.";
"lng_stickers_creator_badge" = "edit";
"lng_in_dlg_photo" = "Photo"; "lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album"; "lng_in_dlg_album" = "Album";
@ -3151,6 +3187,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_close_warning_sure" = "Close anyway"; "lng_bot_close_warning_sure" = "Close anyway";
"lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time."; "lng_bot_add_to_side_menu" = "{bot} asks your permission to be added as an option to your main menu so you can access it any time.";
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu."; "lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_click_to_start" = "Click here to use this bot.";
"lng_bot_status_users#one" = "{count} user";
"lng_bot_status_users#other" = "{count} users";
"lng_typing" = "typing"; "lng_typing" = "typing";
"lng_user_typing" = "{user} is typing"; "lng_user_typing" = "{user} is typing";
@ -3186,6 +3226,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_unread_bar_some" = "Unread messages"; "lng_unread_bar_some" = "Unread messages";
"lng_maps_point" = "Location"; "lng_maps_point" = "Location";
"lng_maps_select_on_map" = "Select on the Map";
"lng_maps_point_send" = "Send This Location";
"lng_maps_point_set" = "Set This Location";
"lng_maps_or_choose" = "Or choose a venue";
"lng_maps_places_in_area" = "Places in this area";
"lng_maps_no_places" = "No places found";
"lng_maps_choose_to_search" = "Choose location to see places nearby.";
"lng_maps_venues_source" = "Powered by Foursquare";
"lng_live_location" = "Live Location"; "lng_live_location" = "Live Location";
"lng_live_location_now" = "updated just now"; "lng_live_location_now" = "updated just now";
"lng_live_location_minutes#one" = "updated {count} minute ago"; "lng_live_location_minutes#one" = "updated {count} minute ago";
@ -3740,6 +3788,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_card_declined" = "Your card was declined."; "lng_payments_card_declined" = "Your card was declined.";
"lng_payments_payment_failed" = "Payment failed. Your card has not been billed."; "lng_payments_payment_failed" = "Payment failed. Your card has not been billed.";
"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed."; "lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed.";
"lng_payments_precheckout_timeout" = "The bot didn't respond in time. Your card has not been billed.";
"lng_payments_precheckout_stars_failed" = "The bot couldn't process your payment.";
"lng_payments_precheckout_stars_timeout" = "The bot didn't respond in time.";
"lng_payments_already_paid" = "You have already paid for this item."; "lng_payments_already_paid" = "You have already paid for this item.";
"lng_payments_terms_title" = "Terms of Service"; "lng_payments_terms_title" = "Terms of Service";
@ -5267,6 +5318,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_iv_join_channel" = "Join"; "lng_iv_join_channel" = "Join";
"lng_iv_window_title" = "Instant View"; "lng_iv_window_title" = "Instant View";
"lng_iv_wrong_layout" = "Wrong layout?"; "lng_iv_wrong_layout" = "Wrong layout?";
"lng_iv_not_supported" = "This link appears to be invalid.";
"lng_limit_download_title" = "Download speed limited"; "lng_limit_download_title" = "Download speed limited";
"lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}."; "lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
@ -5295,12 +5347,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recent_none" = "Recent search results\nwill appear here."; "lng_recent_none" = "Recent search results\nwill appear here.";
"lng_recent_chats" = "Chats"; "lng_recent_chats" = "Chats";
"lng_recent_channels" = "Channels"; "lng_recent_channels" = "Channels";
"lng_recent_apps" = "Apps";
"lng_channels_none_title" = "No channels yet..."; "lng_channels_none_title" = "No channels yet...";
"lng_channels_none_about" = "You are not currently subscribed to any channels."; "lng_channels_none_about" = "You are not currently subscribed to any channels.";
"lng_channels_your_title" = "Channels you joined"; "lng_channels_your_title" = "Channels you joined";
"lng_channels_your_more" = "Show more"; "lng_channels_your_more" = "Show more";
"lng_channels_your_less" = "Show less"; "lng_channels_your_less" = "Show less";
"lng_channels_recommended" = "Recommended channels"; "lng_channels_recommended" = "Recommended channels";
"lng_bot_apps_your" = "Apps you use";
"lng_bot_apps_popular" = "Popular apps";
"lng_font_box_title" = "Choose font family"; "lng_font_box_title" = "Choose font family";
"lng_font_default" = "Default"; "lng_font_default" = "Default";

View file

@ -0,0 +1,120 @@
:root {
--font-sans: -apple-system, BlinkMacSystemFont, avenir next, avenir, Segoe UI Variable Text, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, tahoma, arial, sans-serif;
}
html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
body {
font-family: var(--font-sans);
width: 100%;
height: 100%;
padding: 0;
margin: 0;
background-color: var(--td-window-bg);
color: var(--td-window-fg);
}
html.custom_scroll ::-webkit-scrollbar {
border-radius: 5px !important;
border: 3px solid transparent !important;
background-color: var(--td-scroll-bg) !important;
background-clip: content-box !important;
width: 10px !important;
}
html.custom_scroll ::-webkit-scrollbar:hover {
background-color: var(--td-scroll-bg-over) !important;
}
html.custom_scroll ::-webkit-scrollbar-thumb {
border-radius: 5px !important;
border: 3px solid transparent !important;
background-color: var(--td-scroll-bar-bg) !important;
background-clip: content-box !important;
}
html.custom_scroll ::-webkit-scrollbar-thumb:hover {
background-color: var(--td-scroll-bar-bg-over) !important;
}
#map {
position: relative;
width: 100%;
height: 100%;
}
#marker {
pointer-events: none;
display: none;
z-index: 2;
position: absolute;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
#marker_drop {
margin-bottom: 0px;
transition: margin 160ms ease-in-out;
}
#marker_drop.moving {
margin-bottom: 24px;
}
#marker_shadow {
position: absolute;
}
#search_venues {
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 2;
top: -30px;
transition: top 200ms ease-in-out;
}
#search_venues.shown {
top: 6px;
}
#search_venues_inner {
position: relative;
overflow: hidden;
font-size: 13px;
font-weight: 500;
background: var(--td-window-bg);
color: var(--td-window-active-text-fg);
cursor: pointer;
border-radius: 14px;
padding: 5px 12px 6px;
box-shadow: 0 0 3px 0px var(--td-history-to-down-shadow);
}
#search_venues_inner:hover {
background: var(--td-window-bg-over);
}
#search_venues_content {
position: relative;
z-index: 2;
}
#search_venues_content:before {
content: var(--td-lng-maps-places-in-area);
}
#search_venues_inner .ripple .inner {
position: absolute;
border-radius: 50%;
transform: scale(0);
opacity: 1;
animation: ripple 650ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
background-color: var(--td-window-bg-ripple);
}
#search_venues_inner .ripple.hiding {
animation: fadeOut 200ms linear forwards;
}
@keyframes ripple {
to {
transform: scale(2);
}
}
@keyframes fadeOut {
to {
opacity: 0;
}
}

View file

@ -0,0 +1,199 @@
var LocationPicker = {
startZoom: 14,
flySpeed: 2.4,
notify: function(message) {
if (window.external && window.external.invoke) {
window.external.invoke(JSON.stringify(message));
}
},
frameKeyDown: function (e) {
const keyW = (e.key === 'w')
|| (e.code === 'KeyW')
|| (e.keyCode === 87);
const keyQ = (e.key === 'q')
|| (e.code === 'KeyQ')
|| (e.keyCode === 81);
const keyM = (e.key === 'm')
|| (e.code === 'KeyM')
|| (e.keyCode === 77);
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
e.preventDefault();
LocationPicker.notify({
event: 'keydown',
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
key: keyW ? 'w' : keyQ ? 'q' : 'm',
});
} else if (e.key === 'Escape' || e.keyCode === 27) {
e.preventDefault();
LocationPicker.notify({
event: 'keydown',
key: 'escape',
});
}
},
isNight: function() {
var html = document.getElementsByTagName('html')[0];
return html.style.getPropertyValue('--td-night') == '1';
},
lightPreset: function() {
return LocationPicker.isNight() ? 'night' : 'day';
},
updateStyles: function (styles) {
if (LocationPicker.styles !== styles) {
LocationPicker.styles = styles;
document.getElementsByTagName('html')[0].style = styles;
LocationPicker.map.setConfigProperty(
'basemap',
'lightPreset',
LocationPicker.lightPreset());
}
},
init: function (params) {
mapboxgl.accessToken = params.token;
if (params.protocol) {
mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
}
var options = { container: 'map', config: {
basemap: { lightPreset: LocationPicker.lightPreset() }
} };
var center = params.center;
if (center) {
center = [center[1], center[0]];
options.center = center;
options.zoom = LocationPicker.startZoom;
} else if (params.bounds) {
options.bounds = params.bounds;
center = new mapboxgl.LngLatBounds(params.bounds).getCenter();
} else {
center = [0, 0];
}
LocationPicker.map = new mapboxgl.Map(options);
LocationPicker.createMarker(center);
LocationPicker.trackMovement();
LocationPicker.initSearchVenueRipple();
},
marker: function() {
return document.getElementById('marker_drop');
},
createMarker: function(center) {
document.getElementById('marker').style.display = 'flex';
},
clearMovingTimer: function() {
if (LocationPicker.clearMovingTimeoutId) {
clearTimeout(LocationPicker.clearMovingTimeoutId);
LocationPicker.clearMovingTimeoutId = 0;
}
},
startMovingTimer: function(done) {
LocationPicker.clearMovingTimer();
LocationPicker.clearMovingTimeoutId = setTimeout(done, 500);
},
trackMovement: function() {
LocationPicker.map.on('movestart', function() {
LocationPicker.marker().classList.add('moving');
LocationPicker.clearMovingTimer();
LocationPicker.toggleSearchVenues(false);
LocationPicker.notify({ event: 'move_start' });
});
LocationPicker.map.on('moveend', function() {
LocationPicker.startMovingTimer(function() {
LocationPicker.marker().classList.remove('moving');
LocationPicker.notify({
event: 'move_end',
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
});
});
},
narrowTo: function (point) {
LocationPicker.map.flyTo({
center: [point[1], point[0]],
zoom: LocationPicker.startZoom,
speed: LocationPicker.flySpeed,
});
},
send: function () {
LocationPicker.notify({
event: 'send',
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
},
addRipple: function (button, x, y) {
const ripple = document.createElement('span');
ripple.classList.add('ripple');
const inner = document.createElement('span');
inner.classList.add('inner');
var rect = button.getBoundingClientRect();
x -= rect.x;
y -= rect.y;
const mx = button.clientWidth - x;
const my = button.clientHeight - y;
const sq1 = x * x + y * y;
const sq2 = mx * mx + y * y;
const sq3 = x * x + my * my;
const sq4 = mx * mx + my * my;
const radius = Math.sqrt(Math.max(sq1, sq2, sq3, sq4));
inner.style.width = inner.style.height = `${2 * radius}px`;
inner.style.left = `${x - radius}px`;
inner.style.top = `${y - radius}px`;
inner.classList.add('inner');
ripple.addEventListener('animationend', function (e) {
if (e.animationName === 'fadeOut') {
ripple.remove();
}
});
ripple.appendChild(inner);
button.appendChild(ripple);
},
stopRipples: function (button) {
const id = button.id ? button.id : button;
button = document.getElementById(id);
const ripples = button.getElementsByClassName('ripple');
for (var i = 0; i < ripples.length; ++i) {
const ripple = ripples[i];
if (!ripple.classList.contains('hiding')) {
ripple.classList.add('hiding');
}
}
},
initSearchVenueRipple: function() {
var button = document.getElementById('search_venues_inner');
button.addEventListener('mousedown', function (e) {
LocationPicker.addRipple(e.currentTarget, e.clientX, e.clientY);
LocationPicker.searchVenuesPressed = true;
});
button.addEventListener('mouseup', function (e) {
const id = e.currentTarget.id;
setTimeout(function () {
LocationPicker.stopRipples(id);
}, 0);
if (LocationPicker.searchVenuesPressed) {
LocationPicker.searchVenuesPressed = false;
LocationPicker.toggleSearchVenues(false);
LocationPicker.notify({
event: 'search_venues',
latitude: LocationPicker.map.getCenter().lat,
longitude: LocationPicker.map.getCenter().lng
});
}
});
button.addEventListener('mouseleave', function (e) {
LocationPicker.stopRipples(e.currentTarget);
LocationPicker.searchVenuesPressed = false;
});
},
toggleSearchVenues: function(shown) {
var button = document.getElementById('search_venues');
button.classList.toggle('shown', shown);
},
};

View file

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/picker">
<file alias="picker.css">../../picker_html/picker.css</file>
<file alias="picker.js">../../picker_html/picker.js</file>
</qresource>
</RCC>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.2.2.0" /> Version="5.3.2.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
@ -37,6 +37,9 @@
<Extensions> <Extensions>
<uap3:Extension Category="windows.protocol"> <uap3:Extension Category="windows.protocol">
<uap3:Protocol Name="tg" Parameters="-- &quot;%1&quot;" /> <uap3:Protocol Name="tg" Parameters="-- &quot;%1&quot;" />
<uap3:Extension Category="windows.protocol">
</uap3:Extension>
<uap3:Protocol Name="tonsite" Parameters="-- &quot;%1&quot;" />
</uap3:Extension> </uap3:Extension>
<desktop:Extension <desktop:Extension
Category="windows.startupTask" Category="windows.startupTask"

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,2,2,0 FILEVERSION 5,3,2,0
PRODUCTVERSION 5,2,2,0 PRODUCTVERSION 5,3,2,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.2.2.0" VALUE "FileVersion", "5.3.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.2.2.0" VALUE "ProductVersion", "5.3.2.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,2,2,0 FILEVERSION 5,3,2,0
PRODUCTVERSION 5,2,2,0 PRODUCTVERSION 5,3,2,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.2.2.0" VALUE "FileVersion", "5.3.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.2.2.0" VALUE "ProductVersion", "5.3.2.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -155,6 +155,7 @@ int main(int argc, char *argv[])
QString remove; QString remove;
int version = 0; int version = 0;
[[maybe_unused]] bool targetwin64 = false; [[maybe_unused]] bool targetwin64 = false;
[[maybe_unused]] bool targetwinarm = false;
[[maybe_unused]] bool targetarmac = false; [[maybe_unused]] bool targetarmac = false;
QFileInfoList files; QFileInfoList files;
for (int i = 0; i < argc; ++i) { for (int i = 0; i < argc; ++i) {
@ -165,6 +166,7 @@ int main(int argc, char *argv[])
if (remove.isEmpty()) remove = info.canonicalPath() + "/"; if (remove.isEmpty()) remove = info.canonicalPath() + "/";
} else if (string("-target") == argv[i] && i + 1 < argc) { } else if (string("-target") == argv[i] && i + 1 < argc) {
targetwin64 = (string("win64") == argv[i + 1]); targetwin64 = (string("win64") == argv[i + 1]);
targetwinarm = (string("winarm") == argv[i + 1]);
} else if (string("-arch") == argv[i] && i + 1 < argc) { } else if (string("-arch") == argv[i] && i + 1 < argc) {
targetarmac = (string("arm64") == argv[i + 1]); targetarmac = (string("arm64") == argv[i + 1]);
if (!targetarmac && string("x86_64") != argv[i + 1]) { if (!targetarmac && string("x86_64") != argv[i + 1]) {
@ -493,7 +495,7 @@ int main(int argc, char *argv[])
cout << "Signature verified!\n"; cout << "Signature verified!\n";
RSA_free(pbKey); RSA_free(pbKey);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString outName((targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version)); QString outName((targetwinarm ? QString("tarm64upd%1") : targetwin64 ? QString("tx64upd%1") : QString("tupdate%1")).arg(AlphaVersion ? AlphaVersion : version));
#elif defined Q_OS_MAC #elif defined Q_OS_MAC
QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version)); QString outName((targetarmac ? QString("tarmacupd%1") : QString("tmacupd%1")).arg(AlphaVersion ? AlphaVersion : version));
#else #else

View file

@ -571,8 +571,8 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
} }
if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) { if (!hDumpFile || hDumpFile == INVALID_HANDLE_VALUE) {
WCHAR wstrPath[maxFileLen]; WCHAR wstrPath[maxFileLen];
DWORD wstrPathLen; DWORD wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen);
if (wstrPathLen = GetEnvironmentVariable(L"APPDATA", wstrPath, maxFileLen)) { if (wstrPathLen) {
wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName); wsprintf(wstrPath + wstrPathLen, L"\\%s\\", _programName);
hDumpFile = _generateDumpFileAtPath(wstrPath); hDumpFile = _generateDumpFileAtPath(wstrPath);
} }

View file

@ -127,11 +127,7 @@ void SendBotCallbackData(
UrlClickHandler::Open(link); UrlClickHandler::Open(link);
return; return;
} }
const auto scoreLink = AppendShareGameScoreUrl( BotGameUrlClickHandler(bot, link).onClick({
session,
link,
item->fullId());
BotGameUrlClickHandler(bot, scoreLink).onClick({
Qt::LeftButton, Qt::LeftButton,
QVariant::fromValue(ClickHandlerContext{ QVariant::fromValue(ClickHandlerContext{
.itemId = item->fullId(), .itemId = item->fullId(),
@ -492,20 +488,23 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::WebView: { case ButtonType::WebView: {
if (const auto bot = item->getMessageBot()) { if (const auto bot = item->getMessageBot()) {
bot->session().attachWebView().request( bot->session().attachWebView().open({
controller, .bot = bot,
Api::SendAction(bot->owner().history(bot)), .context = { .controller = controller },
bot, .button = { .text = button->text, .url = button->data },
{ .text = button->text, .url = button->data }); .source = InlineBots::WebViewSourceButton{ .simple = false },
});
} }
} break; } break;
case ButtonType::SimpleWebView: { case ButtonType::SimpleWebView: {
if (const auto bot = item->getMessageBot()) { if (const auto bot = item->getMessageBot()) {
bot->session().attachWebView().requestSimple( bot->session().attachWebView().open({
controller, .bot = bot,
bot, .context = { .controller = controller },
{ .text = button->text, .url = button->data }); .button = {.text = button->text, .url = button->data },
.source = InlineBots::WebViewSourceButton{ .simple = true },
});
} }
} break; } break;
} }

View file

@ -30,6 +30,10 @@ struct SendOptions {
bool invertCaption = false; bool invertCaption = false;
bool hideViaBot = false; bool hideViaBot = false;
crl::time ttlSeconds = 0; crl::time ttlSeconds = 0;
friend inline bool operator==(
const SendOptions &,
const SendOptions &) = default;
}; };
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions(); [[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@ -52,6 +56,10 @@ struct SendAction {
MsgId replaceMediaOf = 0; MsgId replaceMediaOf = 0;
[[nodiscard]] MTPInputReplyTo mtpReplyTo() const; [[nodiscard]] MTPInputReplyTo mtpReplyTo() const;
friend inline bool operator==(
const SendAction &,
const SendAction &) = default;
}; };
struct MessageToSend { struct MessageToSend {

View file

@ -102,6 +102,7 @@ constexpr auto kTransactionsLimit = 100;
: QDateTime(), : QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()), .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.in = (int64(tl.data().vstars().v) >= 0), .in = (int64(tl.data().vstars().v) >= 0),
.gift = tl.data().is_gift(),
}; };
} }
@ -133,12 +134,12 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
using TLOption = MTPStarsTopupOption; const auto giftBarePeerId = !_peer->isSelf() ? _peer->id.value : 0;
_api.request(MTPpayments_GetStarsTopupOptions(
)).done([=](const MTPVector<TLOption> &result) { const auto optionsFromTL = [giftBarePeerId](const auto &options) {
_options = ranges::views::all( return ranges::views::all(
result.v options
) | ranges::views::transform([](const TLOption &option) { ) | ranges::views::transform([=](const auto &option) {
return Data::CreditTopupOption{ return Data::CreditTopupOption{
.credits = option.data().vstars().v, .credits = option.data().vstars().v,
.product = qs( .product = qs(
@ -146,12 +147,31 @@ rpl::producer<rpl::no_value, QString> CreditsTopupOptions::request() {
.currency = qs(option.data().vcurrency()), .currency = qs(option.data().vcurrency()),
.amount = option.data().vamount().v, .amount = option.data().vamount().v,
.extended = option.data().is_extended(), .extended = option.data().is_extended(),
.giftBarePeerId = giftBarePeerId,
}; };
}) | ranges::to_vector; }) | ranges::to_vector;
consumer.put_done(); };
}).fail([=](const MTP::Error &error) { const auto fail = [=](const MTP::Error &error) {
consumer.put_error_copy(error.type()); consumer.put_error_copy(error.type());
}).send(); };
if (_peer->isSelf()) {
using TLOption = MTPStarsTopupOption;
_api.request(MTPpayments_GetStarsTopupOptions(
)).done([=](const MTPVector<TLOption> &result) {
_options = optionsFromTL(result.v);
consumer.put_done();
}).fail(fail).send();
} else if (const auto user = _peer->asUser()) {
using TLOption = MTPStarsGiftOption;
_api.request(MTPpayments_GetStarsGiftOptions(
MTP_flags(MTPpayments_GetStarsGiftOptions::Flag::f_user_id),
user->inputUser
)).done([=](const MTPVector<TLOption> &result) {
_options = optionsFromTL(result.v);
consumer.put_done();
}).fail(fail).send();
}
return lifetime; return lifetime;
}; };

View file

@ -128,7 +128,7 @@ mtpRequestId EditMessage(
} }
if (updateRecentStickers) { if (updateRecentStickers) {
api->requestRecentStickersForce(true); api->requestSpecialStickersForce(false, false, true);
} }
}).fail([=](const MTP::Error &error, mtpRequestId requestId) { }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
if constexpr (ErrorWithId<FailCallback>) { if constexpr (ErrorWithId<FailCallback>) {
@ -153,9 +153,7 @@ mtpRequestId EditMessage(
const auto &text = item->originalText(); const auto &text = item->originalText();
const auto webpage = (!item->media() || !item->media()->webpage()) const auto webpage = (!item->media() || !item->media()->webpage())
? Data::WebPageDraft{ .removed = true } ? Data::WebPageDraft{ .removed = true }
: Data::WebPageDraft{ : Data::WebPageDraft::FromItem(item);
.id = item->media()->webpage()->id,
};
return EditMessage( return EditMessage(
item, item,
text, text,

View file

@ -36,7 +36,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
MTP_double(document->duration() / 1000.), MTP_double(document->duration() / 1000.),
MTP_int(dimensions.width()), MTP_int(dimensions.width()),
MTP_int(dimensions.height()), MTP_int(dimensions.height()),
MTPint())); // preload_prefix_size MTPint(), // preload_prefix_size
MTPdouble())); // video_start_ts
} else { } else {
attributes.push_back(MTP_documentAttributeImageSize( attributes.push_back(MTP_documentAttributeImageSize(
MTP_int(dimensions.width()), MTP_int(dimensions.width()),

View file

@ -62,6 +62,79 @@ void InnerFillMessagePostFlags(
} }
} }
void SendSimpleMedia(SendAction action, MTPInputMedia inputMedia) {
const auto history = action.history;
const auto peer = history->peer;
const auto session = &history->session();
const auto api = &session->api();
action.clearDraft = false;
action.generateLocal = false;
api->sendAction(action);
const auto randomId = base::RandomValue<uint64>();
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
const auto silentPost = ShouldSendSilent(peer, action.options);
InnerFillMessagePostFlags(action.options, peer, flags);
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sendAs = action.options.sendAs;
if (sendAs) {
sendFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? session->user()->name()
: QString();
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
if (action.options.shortcutId) {
flags |= MessageFlag::ShortcutMessage;
sendFlags |= MTPmessages_SendMedia::Flag::f_quick_reply_shortcut;
}
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
auto &histories = history->owner().histories();
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
std::move(inputMedia),
MTPstring(),
MTP_long(randomId),
MTPReplyMarkup(),
MTPvector<MTPMessageEntity>(),
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(session, action.options.shortcutId),
MTP_long(action.options.effectId)
), [=](const MTPUpdates &result, const MTP::Response &response) {
}, [=](const MTP::Error &error, const MTP::Response &response) {
api->sendMessageFail(error, peer, randomId);
});
api->finishForwarding(action);
}
template <typename MediaData> template <typename MediaData>
void SendExistingMedia( void SendExistingMedia(
MessageToSend &&message, MessageToSend &&message,
@ -362,6 +435,33 @@ bool SendDice(MessageToSend &message) {
return true; return true;
} }
void SendLocation(SendAction action, float64 lat, float64 lon) {
SendSimpleMedia(
action,
MTP_inputMediaGeoPoint(
MTP_inputGeoPoint(
MTP_flags(0),
MTP_double(lat),
MTP_double(lon),
MTPint()))); // accuracy_radius
}
void SendVenue(SendAction action, Data::InputVenue venue) {
SendSimpleMedia(
action,
MTP_inputMediaVenue(
MTP_inputGeoPoint(
MTP_flags(0),
MTP_double(venue.lat),
MTP_double(venue.lon),
MTPint()), // accuracy_radius
MTP_string(venue.title),
MTP_string(venue.address),
MTP_string(venue.provider),
MTP_string(venue.id),
MTP_string(venue.venueType)));
}
void FillMessagePostFlags( void FillMessagePostFlags(
const SendAction &action, const SendAction &action,
not_null<PeerData*> peer, not_null<PeerData*> peer,

View file

@ -7,15 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace Main {
class Session;
} // namespace Main
class History; class History;
class PhotoData; class PhotoData;
class DocumentData; class DocumentData;
struct FilePrepareResult; struct FilePrepareResult;
namespace Data {
struct InputVenue;
} // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Api { namespace Api {
struct MessageToSend; struct MessageToSend;
@ -33,6 +37,13 @@ void SendExistingPhoto(
bool SendDice(MessageToSend &message); bool SendDice(MessageToSend &message);
// We can't create Data::LocationPoint() and use it
// for a local sending message, because we can't request
// map thumbnail in messages history without access hash.
void SendLocation(SendAction action, float64 lat, float64 lon);
void SendVenue(SendAction action, Data::InputVenue venue);
void FillMessagePostFlags( void FillMessagePostFlags(
const SendAction &action, const SendAction &action,
not_null<PeerData*> peer, not_null<PeerData*> peer,

View file

@ -2629,7 +2629,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
void ApiWrap::updateStickers() { void ApiWrap::updateStickers() {
const auto now = crl::now(); const auto now = crl::now();
requestStickers(now); requestStickers(now);
requestRecentStickers(now); requestRecentStickers(now, false);
requestFavedStickers(now); requestFavedStickers(now);
requestFeaturedStickers(now); requestFeaturedStickers(now);
} }
@ -2651,8 +2651,15 @@ void ApiWrap::updateCustomEmoji() {
requestFeaturedEmoji(now); requestFeaturedEmoji(now);
} }
void ApiWrap::requestRecentStickersForce(bool attached) { void ApiWrap::requestSpecialStickersForce(
requestRecentStickersWithHash(0, attached); bool faved,
bool recent,
bool attached) {
if (faved) {
requestFavedStickers(std::nullopt);
} else if (recent || attached) {
requestRecentStickers(std::nullopt, attached);
}
} }
void ApiWrap::setGroupStickerSet( void ApiWrap::setGroupStickerSet(
@ -2805,18 +2812,17 @@ void ApiWrap::requestCustomEmoji(TimeId now) {
}).send(); }).send();
} }
void ApiWrap::requestRecentStickers(TimeId now, bool attached) { void ApiWrap::requestRecentStickers(
const auto needed = attached std::optional<TimeId> now,
? _session->data().stickers().recentAttachedUpdateNeeded(now) bool attached) {
: _session->data().stickers().recentUpdateNeeded(now); const auto needed = !now
? true
: attached
? _session->data().stickers().recentAttachedUpdateNeeded(*now)
: _session->data().stickers().recentUpdateNeeded(*now);
if (!needed) { if (!needed) {
return; return;
} }
requestRecentStickersWithHash(
Api::CountRecentStickersHash(_session, attached), attached);
}
void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
const auto requestId = [=]() -> mtpRequestId & { const auto requestId = [=]() -> mtpRequestId & {
return attached return attached
? _recentAttachedStickersUpdateRequest ? _recentAttachedStickersUpdateRequest
@ -2839,7 +2845,7 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
: MTPmessages_getRecentStickers::Flags(0); : MTPmessages_getRecentStickers::Flags(0);
requestId() = request(MTPmessages_GetRecentStickers( requestId() = request(MTPmessages_GetRecentStickers(
MTP_flags(flags), MTP_flags(flags),
MTP_long(hash) MTP_long(now ? Api::CountRecentStickersHash(_session, attached) : 0)
)).done([=](const MTPmessages_RecentStickers &result) { )).done([=](const MTPmessages_RecentStickers &result) {
finish(); finish();
@ -2866,13 +2872,15 @@ void ApiWrap::requestRecentStickersWithHash(uint64 hash, bool attached) {
}).send(); }).send();
} }
void ApiWrap::requestFavedStickers(TimeId now) { void ApiWrap::requestFavedStickers(std::optional<TimeId> now) {
if (!_session->data().stickers().favedUpdateNeeded(now) if (now) {
|| _favedStickersUpdateRequest) { if (!_session->data().stickers().favedUpdateNeeded(*now)
return; || _favedStickersUpdateRequest) {
return;
}
} }
_favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers( _favedStickersUpdateRequest = request(MTPmessages_GetFavedStickers(
MTP_long(Api::CountFavedStickersHash(_session)) MTP_long(now ? Api::CountFavedStickersHash(_session) : 0)
)).done([=](const MTPmessages_FavedStickers &result) { )).done([=](const MTPmessages_FavedStickers &result) {
_session->data().stickers().setLastFavedUpdate(crl::now()); _session->data().stickers().setLastFavedUpdate(crl::now());
_favedStickersUpdateRequest = 0; _favedStickersUpdateRequest = 0;
@ -4281,7 +4289,7 @@ void ApiWrap::sendMediaWithRandomId(
), [=](const MTPUpdates &result, const MTP::Response &response) { ), [=](const MTPUpdates &result, const MTP::Response &response) {
if (done) done(true); if (done) done(true);
if (updateRecentStickers) { if (updateRecentStickers) {
requestRecentStickersForce(true); requestRecentStickers(std::nullopt, true);
} }
AyuWorker::markAsOnline(_session); AyuWorker::markAsOnline(_session);

View file

@ -244,7 +244,10 @@ public:
void updateSavedGifs(); void updateSavedGifs();
void updateMasks(); void updateMasks();
void updateCustomEmoji(); void updateCustomEmoji();
void requestRecentStickersForce(bool attached = false); void requestSpecialStickersForce(
bool faved,
bool recent,
bool attached);
void setGroupStickerSet( void setGroupStickerSet(
not_null<ChannelData*> megagroup, not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set); const StickerSetIdentifier &set);
@ -477,9 +480,10 @@ private:
void requestStickers(TimeId now); void requestStickers(TimeId now);
void requestMasks(TimeId now); void requestMasks(TimeId now);
void requestCustomEmoji(TimeId now); void requestCustomEmoji(TimeId now);
void requestRecentStickers(TimeId now, bool attached = false); void requestRecentStickers(
void requestRecentStickersWithHash(uint64 hash, bool attached = false); std::optional<TimeId> now,
void requestFavedStickers(TimeId now); bool attached);
void requestFavedStickers(std::optional<TimeId> now);
void requestFeaturedStickers(TimeId now); void requestFeaturedStickers(TimeId now);
void requestFeaturedEmoji(TimeId now); void requestFeaturedEmoji(TimeId now);
void requestSavedGifs(TimeId now); void requestSavedGifs(TimeId now);

View file

@ -100,6 +100,8 @@ void AboutBox::showVersionHistory() {
url += u"win/%1.zip"_q; url += u"win/%1.zip"_q;
} else if (Platform::IsWindows64Bit()) { } else if (Platform::IsWindows64Bit()) {
url += u"win64/%1.zip"_q; url += u"win64/%1.zip"_q;
} else if (Platform::IsWindowsARM64()) {
url += u"winarm/%1.zip"_q;
} else if (Platform::IsMac()) { } else if (Platform::IsMac()) {
url += u"mac/%1.zip"_q; url += u"mac/%1.zip"_q;
} else if (Platform::IsLinux()) { } else if (Platform::IsLinux()) {
@ -155,6 +157,8 @@ QString currentVersionText() {
} }
if (Platform::IsWindows64Bit()) { if (Platform::IsWindows64Bit()) {
result += " x64"; result += " x64";
} else if (Platform::IsWindowsARM64()) {
result += " arm64";
} }
return result; return result;
} }

View file

@ -39,6 +39,7 @@ Data::ChatFilter ChangedFilter(
filter.id(), filter.id(),
filter.title(), filter.title(),
filter.iconEmoji(), filter.iconEmoji(),
filter.colorIndex(),
filter.flags(), filter.flags(),
std::move(always), std::move(always),
filter.pinned(), filter.pinned(),
@ -58,6 +59,7 @@ Data::ChatFilter ChangedFilter(
filter.id(), filter.id(),
filter.title(), filter.title(),
filter.iconEmoji(), filter.iconEmoji(),
filter.colorIndex(),
filter.flags(), filter.flags(),
std::move(always), std::move(always),
filter.pinned(), filter.pinned(),

View file

@ -83,6 +83,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
rules.id(), rules.id(),
rules.title(), rules.title(),
rules.iconEmoji(), rules.iconEmoji(),
rules.colorIndex(),
(rules.flags() & ~flag), (rules.flags() & ~flag),
rules.always(), rules.always(),
rules.pinned(), rules.pinned(),
@ -104,6 +105,7 @@ not_null<FilterChatsPreview*> SetupChatsPreview(
rules.id(), rules.id(),
rules.title(), rules.title(),
rules.iconEmoji(), rules.iconEmoji(),
rules.colorIndex(),
rules.flags(), rules.flags(),
std::move(always), std::move(always),
std::move(pinned), std::move(pinned),
@ -170,6 +172,7 @@ void EditExceptions(
rules.id(), rules.id(),
rules.title(), rules.title(),
rules.iconEmoji(), rules.iconEmoji(),
rules.colorIndex(),
((rules.flags() & ~options) ((rules.flags() & ~options)
| rawController->chosenOptions()), | rawController->chosenOptions()),
include ? std::move(changed) : std::move(removeFrom), include ? std::move(changed) : std::move(removeFrom),
@ -240,6 +243,7 @@ void CreateIconSelector(
rules.id(), rules.id(),
rules.title(), rules.title(),
Ui::LookupFilterIcon(icon).emoji, Ui::LookupFilterIcon(icon).emoji,
rules.colorIndex(),
rules.flags(), rules.flags(),
rules.always(), rules.always(),
rules.pinned(), rules.pinned(),

View file

@ -0,0 +1,180 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/gift_credits_box.h"
#include "api/api_credits.h"
#include "boxes/peer_list_controllers.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "lang/lang_keys.h"
#include "main/session/session_show.h"
#include "settings/settings_credits_graphics.h"
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/label_with_custom_emoji.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
#include "styles/style_chat.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
#include "styles/style_layers.h"
#include "styles/style_premium.h"
namespace Ui {
void GiftCreditsBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer,
Fn<void()> gifted) {
box->setStyle(st::creditsGiftBox);
box->setNoContentMargin(true);
box->addButton(tr::lng_create_group_back(), [=] { box->closeBox(); });
const auto content = box->setPinnedToTopContent(
object_ptr<Ui::VerticalLayout>(box));
Ui::AddSkip(content);
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
const auto userpicWrap = content->add(
object_ptr<Ui::CenterWrap<>>(
content,
object_ptr<Ui::UserpicButton>(content, peer, stUser)));
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
Ui::AddSkip(content);
{
const auto widget = Ui::CreateChild<Ui::RpWidget>(content);
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = widget->lifetime().make_state<ColoredMiniStars>(
widget,
false,
Ui::Premium::MiniStars::Type::BiStars);
stars->setColorOverride(Ui::Premium::CreditsIconGradientStops());
widget->resize(
st::boxWidth - stUser.photoSize,
stUser.photoSize * 2);
content->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
widget->moveToLeft(stUser.photoSize / 2, 0);
const auto starsRect = Rect(widget->size());
stars->setPosition(starsRect.topLeft());
stars->setSize(starsRect.size());
widget->lower();
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(widget);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, widget->lifetime());
}
{
Ui::AddSkip(content);
const auto arrow = Ui::Text::SingleCustomEmoji(
peer->owner().customEmojiManager().registerInternalEmoji(
st::topicButtonArrow,
st::channelEarnLearnArrowMargins,
false));
auto link = tr::lng_credits_box_history_entry_gift_about_link(
lt_emoji,
rpl::single(arrow),
Ui::Text::RichLangValue
) | rpl::map([](TextWithEntities text) {
return Ui::Text::Link(
std::move(text),
tr::lng_credits_box_history_entry_gift_about_url(tr::now));
});
content->add(
object_ptr<Ui::CenterWrap<>>(
content,
Ui::CreateLabelWithCustomEmoji(
content,
tr::lng_credits_box_history_entry_gift_out_about(
lt_user,
rpl::single(TextWithEntities{ peer->shortName() }),
lt_link,
std::move(link),
Ui::Text::RichLangValue),
{ .session = &peer->session() },
st::creditsBoxAbout)),
st::boxRowPadding);
}
Ui::AddSkip(content);
Ui::AddSkip(box->verticalLayout());
Settings::FillCreditOptions(
Main::MakeSessionShow(box->uiShow(), &peer->session()),
box->verticalLayout(),
peer,
0,
[=] { gifted(); box->uiShow()->hideLayer(); });
box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));
}
void ShowGiftCreditsBox(
not_null<Window::SessionController*> controller,
Fn<void()> gifted) {
class Controller final : public ContactsBoxController {
public:
Controller(
not_null<Main::Session*> session,
Fn<void(not_null<PeerData*>)> choose)
: ContactsBoxController(session)
, _choose(std::move(choose)) {
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
return ContactsBoxController::createRow(user);
}
void rowClicked(not_null<PeerListRow*> row) override {
_choose(row->peer());
}
private:
const Fn<void(not_null<PeerData*>)> _choose;
};
auto initBox = [=](not_null<PeerListBox*> peersBox) {
peersBox->setTitle(tr::lng_credits_gift_title());
peersBox->addButton(tr::lng_cancel(), [=] { peersBox->closeBox(); });
};
const auto show = controller->uiShow();
auto listController = std::make_unique<Controller>(
&controller->session(),
[=](not_null<PeerData*> peer) {
show->showBox(Box(GiftCreditsBox, peer, gifted));
});
show->showBox(
Box<PeerListBox>(std::move(listController), std::move(initBox)),
Ui::LayerOption::KeepOther);
}
} // namespace Ui

View file

@ -0,0 +1,20 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
void ShowGiftCreditsBox(
not_null<Window::SessionController*> controller,
Fn<void()> gifted);
} // namespace Ui

View file

@ -1014,6 +1014,7 @@ void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
if (users.empty()) { if (users.empty()) {
show->showToast( show->showToast(
tr::lng_settings_gift_premium_choose(tr::now)); tr::lng_settings_gift_premium_choose(tr::now));
return;
} }
const auto giftBox = show->show( const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api, ref)); Box(GiftsBox, _controller, users, api, ref));
@ -1648,7 +1649,9 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTableMargin); st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId); const auto peerId = PeerId(entry.barePeerId);
if (peerId) { if (peerId) {
auto text = tr::lng_credits_box_history_entry_peer(); auto text = entry.in
? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer();
AddTableRow(table, std::move(text), controller, peerId); AddTableRow(table, std::move(text), controller, peerId);
} }
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) { if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
@ -1692,14 +1695,24 @@ void AddCreditsHistoryEntryTable(
} else if (entry.peerType == Type::Fragment) { } else if (entry.peerType == Type::Fragment) {
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_box_history_entry_via(), (entry.gift
tr::lng_credits_box_history_entry_fragment( ? tr::lng_credits_box_history_entry_peer_in
Ui::Text::RichLangValue)); : tr::lng_credits_box_history_entry_via)(),
(entry.gift
? tr::lng_credits_box_history_entry_anonymous
: tr::lng_credits_box_history_entry_fragment)(
Ui::Text::RichLangValue));
} else if (entry.peerType == Type::Ads) { } else if (entry.peerType == Type::Ads) {
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_box_history_entry_via(), tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue)); tr::lng_credits_box_history_entry_ads(Ui::Text::RichLangValue));
} else if (entry.peerType == Type::PremiumBot) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_via(),
tr::lng_credits_box_history_entry_via_premium_bot(
Ui::Text::RichLangValue));
} }
if (!entry.id.isEmpty()) { if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18; constexpr auto kOneLineCount = 18;

View file

@ -459,6 +459,7 @@ Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
.customWallpaperLevel = group .customWallpaperLevel = group
? levelLimits.groupCustomWallpaperLevelMin() ? levelLimits.groupCustomWallpaperLevelMin()
: levelLimits.channelCustomWallpaperLevelMin(), : levelLimits.channelCustomWallpaperLevelMin(),
.sponsoredLevel = levelLimits.channelRestrictSponsoredLevelMin(),
}; };
} }

View file

@ -418,7 +418,9 @@ void SimpleLimitBox(
BoxShowFinishes(box), BoxShowFinishes(box),
0, 0,
descriptor.current, descriptor.current,
descriptor.premiumLimit, (descriptor.complexRatio
? descriptor.premiumLimit
: 2 * descriptor.current),
premiumPossible, premiumPossible,
descriptor.phrase, descriptor.phrase,
descriptor.icon); descriptor.icon);
@ -769,7 +771,7 @@ void FilterLinksLimitBox(
premiumLimit, premiumLimit,
&st::premiumIconChats, &st::premiumIconChats,
std::nullopt, std::nullopt,
true }); /*true */}); // Don't use real ratio, "Free" doesn't fit.
} }
@ -856,7 +858,7 @@ void ShareableFiltersLimitBox(
premiumLimit, premiumLimit,
&st::premiumIconFolders, &st::premiumIconFolders,
std::nullopt, std::nullopt,
true }); /*true*/ }); // Don't use real ratio, "Free" doesn't fit.
} }
void FilterPinsLimitBox( void FilterPinsLimitBox(

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h" #include "payments/payments_checkout_process.h"
#include "payments/payments_form.h" #include "payments/payments_form.h"
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg. #include "ui/effects/premium_top_bar.h" // Ui::Premium::ColorizedSvg.
@ -257,6 +258,8 @@ void SendCreditsBox(
if (state->confirmButtonBusy.current()) { if (state->confirmButtonBusy.current()) {
return; return;
} }
const auto show = box->uiShow();
const auto weak = MakeWeak(box.get());
state->confirmButtonBusy = true; state->confirmButtonBusy = true;
session->api().request( session->api().request(
MTPpayments_SendStarsForm( MTPpayments_SendStarsForm(
@ -264,12 +267,31 @@ void SendCreditsBox(
MTP_long(form->formId), MTP_long(form->formId),
form->inputInvoice) form->inputInvoice)
).done([=](auto result) { ).done([=](auto result) {
state->confirmButtonBusy = false; if (weak) {
box->closeBox(); state->confirmButtonBusy = false;
box->closeBox();
}
sent(); sent();
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
state->confirmButtonBusy = false; if (weak) {
box->uiShow()->showToast(error.type()); state->confirmButtonBusy = false;
}
const auto id = error.type();
if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
auto error = ::Ui::MakeInformBox(
tr::lng_payments_precheckout_stars_failed(tr::now));
error->boxClosing() | rpl::start_with_next([=] {
if (const auto paybox = weak.data()) {
paybox->closeBox();
}
}, error->lifetime());
show->showBox(std::move(error));
} else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
show->showToast(
tr::lng_payments_precheckout_stars_timeout(tr::now));
} else {
show->showToast(id);
}
}).send(); }).send();
}); });
{ {

View file

@ -1414,55 +1414,6 @@ std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const {
return result; return result;
} }
QString AppendShareGameScoreUrl(
not_null<Main::Session*> session,
const QString &url,
const FullMsgId &fullId) {
auto shareHashData = QByteArray(0x20, Qt::Uninitialized);
auto shareHashDataInts = reinterpret_cast<uint64*>(shareHashData.data());
const auto peer = fullId.peer
? session->data().peerLoaded(fullId.peer)
: static_cast<PeerData*>(nullptr);
const auto channelAccessHash = uint64((peer && peer->isChannel())
? peer->asChannel()->access
: 0);
shareHashDataInts[0] = session->userId().bare;
shareHashDataInts[1] = fullId.peer.value;
shareHashDataInts[2] = uint64(fullId.msg.bare);
shareHashDataInts[3] = channelAccessHash;
// Count SHA1() of data.
auto key128Size = 0x10;
auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized);
hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data());
//// Mix in channel access hash to the first 64 bits of SHA1 of data.
//*reinterpret_cast<uint64*>(shareHashEncrypted.data()) ^= channelAccessHash;
// Encrypt data.
if (!session->local().encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) {
return url;
}
auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
auto shareUrl = u"tg://share_game_score?hash="_q + QString::fromLatin1(shareHash);
auto shareComponent = u"tgShareScoreUrl="_q + qthelp::url_encode(shareUrl);
auto hashPosition = url.indexOf('#');
if (hashPosition < 0) {
return url + '#' + shareComponent;
}
auto hash = url.mid(hashPosition + 1);
if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) {
return url + '&' + shareComponent;
}
if (!hash.isEmpty()) {
return url + '?' + shareComponent;
}
return url + shareComponent;
}
ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs( ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
const std::vector<not_null<Data::Thread*>> &result, const std::vector<not_null<Data::Thread*>> &result,
const MessageIdsList &msgIds) { const MessageIdsList &msgIds) {
@ -1624,9 +1575,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
} }
void FastShareMessage( void FastShareMessage(
not_null<Window::SessionController*> controller, std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) { not_null<HistoryItem*> item) {
const auto show = controller->uiShow();
const auto history = item->history(); const auto history = item->history();
const auto owner = &history->owner(); const auto owner = &history->owner();
const auto session = &history->session(); const auto session = &history->session();
@ -1655,7 +1605,7 @@ void FastShareMessage(
} }
if (item->hasDirectLink()) { if (item->hasDirectLink()) {
using namespace HistoryView; using namespace HistoryView;
CopyPostLink(controller, item->fullId(), Context::History); CopyPostLink(show, item->fullId(), Context::History);
} else if (const auto bot = item->getMessageBot()) { } else if (const auto bot = item->getMessageBot()) {
if (const auto media = item->media()) { if (const auto media = item->media()) {
if (const auto game = media->game()) { if (const auto game = media->game()) {
@ -1687,23 +1637,27 @@ void FastShareMessage(
auto copyLinkCallback = canCopyLink auto copyLinkCallback = canCopyLink
? Fn<void()>(std::move(copyCallback)) ? Fn<void()>(std::move(copyCallback))
: Fn<void()>(); : Fn<void()>();
controller->show( show->show(Box<ShareBox>(ShareBox::Descriptor{
Box<ShareBox>(ShareBox::Descriptor{ .session = session,
.session = session, .copyCallback = std::move(copyLinkCallback),
.copyCallback = std::move(copyLinkCallback), .submitCallback = ShareBox::DefaultForwardCallback(
.submitCallback = ShareBox::DefaultForwardCallback( show,
show, history,
history, msgIds),
msgIds), .filterCallback = std::move(filterCallback),
.filterCallback = std::move(filterCallback), .forwardOptions = {
.forwardOptions = { .sendersCount = ItemsForwardSendersCount(items),
.sendersCount = ItemsForwardSendersCount(items), .captionsCount = ItemsForwardCaptionsCount(items),
.captionsCount = ItemsForwardCaptionsCount(items), .show = !hasOnlyForcedForwardedInfo,
.show = !hasOnlyForcedForwardedInfo, },
}, .premiumRequiredError = SharePremiumRequiredError(),
.premiumRequiredError = SharePremiumRequiredError(), }), Ui::LayerOption::CloseOther);
}), }
Ui::LayerOption::CloseOther);
void FastShareMessage(
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) {
FastShareMessage(controller->uiShow(), item);
} }
void FastShareLink( void FastShareLink(
@ -1805,111 +1759,3 @@ auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> { -> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError; return WritePremiumRequiredError;
} }
void ShareGameScoreByHash(
not_null<Window::SessionController*> controller,
const QString &hash) {
auto &session = controller->session();
auto key128Size = 0x10;
auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() != key128Size + 0x20)) {
controller->show(
Ui::MakeInformBox(tr::lng_confirm_phone_link_invalid()),
Ui::LayerOption::CloseOther);
return;
}
// Decrypt data.
auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized);
if (!session.local().decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) {
return;
}
// Count SHA1() of data.
char dataSha1[20] = { 0 };
hashSha1(hashData.constData(), hashData.size(), dataSha1);
//// Mix out channel access hash from the first 64 bits of SHA1 of data.
//auto channelAccessHash = *reinterpret_cast<uint64*>(hashEncrypted.data()) ^ *reinterpret_cast<uint64*>(dataSha1);
//// Check next 64 bits of SHA1() of data.
//auto skipSha1Part = sizeof(channelAccessHash);
//if (memcmp(dataSha1 + skipSha1Part, hashEncrypted.constData() + skipSha1Part, key128Size - skipSha1Part) != 0) {
// Ui::show(Box<Ui::InformBox>(tr::lng_share_wrong_user(tr::now)));
// return;
//}
// Check 128 bits of SHA1() of data.
if (memcmp(dataSha1, hashEncrypted.constData(), key128Size) != 0) {
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
auto hashDataInts = reinterpret_cast<uint64*>(hashData.data());
if (hashDataInts[0] != session.userId().bare) {
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
const auto peerId = PeerId(hashDataInts[1]);
const auto channelAccessHash = hashDataInts[3];
if (!peerIsChannel(peerId) && channelAccessHash) {
// If there is no channel id, there should be no channel access_hash.
controller->show(
Ui::MakeInformBox(tr::lng_share_wrong_user()),
Ui::LayerOption::CloseOther);
return;
}
const auto msgId = MsgId(int64(hashDataInts[2]));
if (const auto item = session.data().message(peerId, msgId)) {
FastShareMessage(controller, item);
} else {
const auto weak = base::make_weak(controller);
const auto resolveMessageAndShareScore = crl::guard(weak, [=](
PeerData *peer) {
auto done = crl::guard(weak, [=] {
const auto item = weak->session().data().message(
peerId,
msgId);
if (item) {
FastShareMessage(weak.get(), item);
} else {
weak->show(
Ui::MakeInformBox(tr::lng_edit_deleted()),
Ui::LayerOption::CloseOther);
}
});
auto &api = weak->session().api();
api.requestMessageData(peer, msgId, std::move(done));
});
const auto peer = peerIsChannel(peerId)
? controller->session().data().peerLoaded(peerId)
: nullptr;
if (peer || !peerIsChannel(peerId)) {
resolveMessageAndShareScore(peer);
} else {
const auto owner = &controller->session().data();
controller->session().api().request(MTPchannels_GetChannels(
MTP_vector<MTPInputChannel>(
1,
MTP_inputChannel(
MTP_long(peerToChannel(peerId).bare),
MTP_long(channelAccessHash)))
)).done([=](const MTPmessages_Chats &result) {
result.match([&](const auto &data) {
owner->processChats(data.vchats());
});
if (const auto peer = owner->peerLoaded(peerId)) {
resolveMessageAndShareScore(peer);
}
}).send();
}
}
}

View file

@ -59,13 +59,11 @@ class SlideWrap;
class PopupMenu; class PopupMenu;
} // namespace Ui } // namespace Ui
QString AppendShareGameScoreUrl( class ShareBox;
not_null<Main::Session*> session,
const QString &url, void FastShareMessage(
const FullMsgId &fullId); std::shared_ptr<Main::SessionShow> show,
void ShareGameScoreByHash( not_null<HistoryItem*> item);
not_null<Window::SessionController*> controller,
const QString &hash);
void FastShareMessage( void FastShareMessage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item); not_null<HistoryItem*> item);

View file

@ -7,50 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "data/data_document.h" #include "api/api_common.h"
#include "data/data_session.h" #include "api/api_toggling_media.h"
#include "data/data_file_origin.h" #include "apiwrap.h"
#include "data/data_document_media.h" #include "base/unixtime.h"
#include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "menu/menu_send.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/stickers_list_widget.h"
#include "chat_helpers/stickers_lottie.h"
#include "core/application.h" #include "core/application.h"
#include "mtproto/sender.h" #include "data/data_document.h"
#include "storage/storage_account.h" #include "data/data_document_media.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "ui/widgets/buttons.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "ui/widgets/scroll_area.h" #include "lang/lang_keys.h"
#include "ui/widgets/gradient_round_button.h" #include "lottie/lottie_animation.h"
#include "ui/image/image.h" #include "lottie/lottie_multi_player.h"
#include "ui/image/image_location_factory.h" #include "main/main_session.h"
#include "ui/text/text_utilities.h" #include "mainwindow.h"
#include "ui/text/custom_emoji_instance.h" #include "media/clip/media_clip_reader.h"
#include "menu/menu_send.h"
#include "mtproto/sender.h"
#include "settings/settings_premium.h"
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "ui/cached_round_corners.h"
#include "ui/effects/animation_value_f.h"
#include "ui/effects/path_shift_gradient.h" #include "ui/effects/path_shift_gradient.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
#include "ui/image/image.h"
#include "ui/image/image_location_factory.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/text/custom_emoji_instance.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/cached_round_corners.h" #include "ui/widgets/scroll_area.h"
#include "lottie/lottie_multi_player.h"
#include "lottie/lottie_animation.h"
#include "chat_helpers/compose/compose_show.h"
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/stickers_list_widget.h"
#include "media/clip/media_clip_reader.h"
#include "window/window_controller.h"
#include "settings/settings_premium.h"
#include "base/unixtime.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_toggling_media.h"
#include "api/api_common.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_info.h" #include "styles/style_info.h"
@ -75,10 +80,12 @@ constexpr auto kEmojiPerRow = 8;
constexpr auto kMinRepaintDelay = crl::time(33); constexpr auto kMinRepaintDelay = crl::time(33);
constexpr auto kMinAfterScrollDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33);
constexpr auto kGrayLockOpacity = 0.3; constexpr auto kGrayLockOpacity = 0.3;
constexpr auto kStickerMoveDuration = crl::time(200);
using Data::StickersSet; using Data::StickersSet;
using Data::StickersPack; using Data::StickersPack;
using SetFlag = Data::StickersSetFlag; using SetFlag = Data::StickersSetFlag;
using TLStickerSet = MTPmessages_StickerSet;
[[nodiscard]] std::optional<QColor> ComputeImageColor( [[nodiscard]] std::optional<QColor> ComputeImageColor(
const style::icon &lockIcon, const style::icon &lockIcon,
@ -266,6 +273,20 @@ public:
[[nodiscard]] rpl::producer<uint64> setArchived() const; [[nodiscard]] rpl::producer<uint64> setArchived() const;
[[nodiscard]] rpl::producer<> updateControls() const; [[nodiscard]] rpl::producer<> updateControls() const;
void setReorderState(bool enabled) {
_dragging.enabled = enabled;
if (enabled) {
_shakeAnimation.init([=] { update(); });
_shakeAnimation.start();
} else {
_shakeAnimation.stop();
update();
}
}
[[nodiscard]] bool reorderState() const {
return _dragging.enabled;
}
[[nodiscard]] rpl::producer<Error> errors() const; [[nodiscard]] rpl::producer<Error> errors() const;
void archiveStickers(); void archiveStickers();
@ -278,6 +299,12 @@ public:
: Data::StickersType::Stickers; : Data::StickersType::Stickers;
} }
[[nodiscard]] bool amSetCreator() const {
return _amSetCreator;
}
void applySet(const TLStickerSet &set);
~Inner(); ~Inner();
protected: protected:
@ -313,6 +340,11 @@ private:
QPoint position, QPoint position,
bool paused, bool paused,
crl::time now) const; crl::time now) const;
void shakeTransform(
QPainter &p,
int index,
QPoint position,
crl::time now) const;
void setupLottie(int index); void setupLottie(int index);
void setupWebm(int index); void setupWebm(int index);
void clipCallback( void clipCallback(
@ -329,14 +361,19 @@ private:
void startOverAnimation(int index, float64 from, float64 to); void startOverAnimation(int index, float64 from, float64 to);
int stickerFromGlobalPos(const QPoint &p) const; int stickerFromGlobalPos(const QPoint &p) const;
void gotSet(const MTPmessages_StickerSet &set);
void installDone(const MTPmessages_StickerSetInstallResult &result); void installDone(const MTPmessages_StickerSetInstallResult &result);
void requestReorder(not_null<DocumentData*> document, int index);
void fillDeleteStickerBox(not_null<Ui::GenericBox*> box, int index);
void chosen( void chosen(
int index, int index,
not_null<DocumentData*> sticker, not_null<DocumentData*> sticker,
Api::SendOptions options); Api::SendOptions options);
[[nodiscard]] QPoint posFromIndex(int index) const;
[[nodiscard]] bool isDraggedAnimating() const;
not_null<Lottie::MultiPlayer*> getLottiePlayer(); not_null<Lottie::MultiPlayer*> getLottiePlayer();
void showPreview(); void showPreview();
@ -373,6 +410,24 @@ private:
TimeId _setInstallDate = TimeId(0); TimeId _setInstallDate = TimeId(0);
StickerType _setThumbnailType = StickerType::Webp; StickerType _setThumbnailType = StickerType::Webp;
ImageWithLocation _setThumbnail; ImageWithLocation _setThumbnail;
bool _amSetCreator = false;
struct {
bool enabled = false;
int index = -1;
int lastSelected = -1;
QPoint point;
} _dragging;
Ui::Animations::Basic _shakeAnimation;
std::deque<Fn<void()>> _reorderRequests;
std::optional<MTP::Sender> _apiReorder;
struct ShiftAnimation final {
Ui::Animations::Simple animation;
Ui::Animations::Simple yAnimation;
int shift = 0;
};
base::flat_map<int, ShiftAnimation> _shiftAnimations;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
mutable StickerPremiumMark _premiumMark; mutable StickerPremiumMark _premiumMark;
@ -545,9 +600,112 @@ void StickerSetBox::updateTitleAndButtons() {
updateButtons(); updateButtons();
} }
void ChangeSetNameBox(
not_null<Ui::GenericBox*> box,
not_null<Data::Session*> data,
const StickerSetIdentifier &input,
Fn<void(TLStickerSet)> done) {
struct State final {
rpl::variable<mtpRequestId> requestId = 0;
Ui::RpWidget* saveButton = nullptr;
};
box->setTitle(tr::lng_stickers_box_edit_name_title());
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_stickers_box_edit_name_about(),
st::boxLabel));
const auto state = box->lifetime().make_state<State>();
const auto wasName = [&] {
const auto &sets = data->stickers().sets();
const auto it = sets.find(input.id);
return (it == sets.end()) ? QString() : it->second->title;
}();
const auto wrap = box->addRow(object_ptr<Ui::FixedHeightWidget>(
box,
st::editStickerSetNameField.heightMin));
auto owned = object_ptr<Ui::InputField>(
wrap,
st::editStickerSetNameField,
tr::lng_stickers_context_edit_name(),
wasName);
const auto field = owned.data();
wrap->widthValue() | rpl::start_with_next([=](int width) {
field->move(0, 0);
field->resize(width, field->height());
wrap->resize(width, field->height());
}, wrap->lifetime());
field->selectAll();
constexpr auto kMaxSetNameLength = 50;
field->setMaxLength(kMaxSetNameLength);
Ui::AddLengthLimitLabel(field, kMaxSetNameLength, kMaxSetNameLength + 1);
box->setFocusCallback([=] { field->setFocusFast(); });
const auto close = crl::guard(box, [=] { box->closeBox(); });
const auto save = [=, show = box->uiShow()] {
if (state->requestId.current()) {
return;
}
const auto text = field->getLastText().trimmed();
if ((Ui::ComputeRealUnicodeCharactersCount(text) > kMaxSetNameLength)
|| text.isEmpty()) {
field->showError();
return;
}
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
state->requestId = data->session().api().request(
MTPstickers_RenameStickerSet(
Data::InputStickerSet(input),
MTP_string(text))
).done([=](const TLStickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &d) {
data->stickers().feedSetFull(d);
data->stickers().notifyUpdated(Data::StickersType::Stickers);
}, [](const auto &) {
});
done(result);
close();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
close();
}).send();
if (state->saveButton) {
state->saveButton->resizeToWidth(buttonWidth);
}
};
state->saveButton = box->addButton(
rpl::conditional(
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
rpl::single(QString()),
tr::lng_box_done()),
save);
if (const auto saveButton = state->saveButton) {
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
saveButton,
saveButton->height() / 2,
&st::editStickerSetNameLoading);
AddChildToWidgetCenter(saveButton, loadingAnimation);
loadingAnimation->showOn(
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
}
box->addButton(tr::lng_cancel(), [=] {
data->session().api().request(state->requestId.current()).cancel();
close();
});
}
void StickerSetBox::updateButtons() { void StickerSetBox::updateButtons() {
clearButtons(); clearButtons();
if (_inner->loaded()) { if (_inner->reorderState()) {
addButton(tr::lng_box_done(), [=] {
_inner->setReorderState(false);
updateButtons();
});
} else if (_inner->loaded()) {
const auto type = _inner->setType(); const auto type = _inner->setType();
const auto share = [=] { const auto share = [=] {
copyStickersLink(); copyStickersLink();
@ -555,6 +713,34 @@ void StickerSetBox::updateButtons() {
? tr::lng_stickers_copied_emoji(tr::now) ? tr::lng_stickers_copied_emoji(tr::now)
: tr::lng_stickers_copied(tr::now)); : tr::lng_stickers_copied(tr::now));
}; };
const auto fillSetCreatorMenu = [&] {
using Filler = Fn<void(not_null<Ui::PopupMenu*>)>;
if (!_inner->amSetCreator()) {
return Filler(nullptr);
}
const auto data = &_session->data();
return Filler([=, show = _show, set = _set](
not_null<Ui::PopupMenu*> menu) {
const auto done = [inner = _inner](const TLStickerSet &set) {
if (const auto raw = inner.data()) {
raw->applySet(set);
}
};
menu->addAction(
tr::lng_stickers_context_edit_name(tr::now),
[=] {
show->showBox(Box(ChangeSetNameBox, data, set, done));
},
&st::menuIconEdit);
menu->addAction(
tr::lng_stickers_context_reorder(tr::now),
[=] {
_inner->setReorderState(true);
updateButtons();
},
&st::menuIconManage);
});
}();
const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu) const auto addPackOwner = [=](const std::shared_ptr<base::unique_qptr<Ui::PopupMenu>> &menu)
{ {
if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) { if (type == Data::StickersType::Stickers || type == Data::StickersType::Emoji) {
@ -629,6 +815,9 @@ void StickerSetBox::updateButtons() {
*menu = base::make_unique_q<Ui::PopupMenu>( *menu = base::make_unique_q<Ui::PopupMenu>(
top, top,
st::popupMenuWithIcons); st::popupMenuWithIcons);
if (fillSetCreatorMenu) {
fillSetCreatorMenu(*menu);
}
(*menu)->addAction( (*menu)->addAction(
((type == Data::StickersType::Emoji) ((type == Data::StickersType::Emoji)
? tr::lng_stickers_share_emoji ? tr::lng_stickers_share_emoji
@ -680,6 +869,9 @@ void StickerSetBox::updateButtons() {
remove, remove,
&st::menuIconRemove); &st::menuIconRemove);
} else { } else {
if (fillSetCreatorMenu) {
fillSetCreatorMenu(*menu);
}
(*menu)->addAction( (*menu)->addAction(
(type == Data::StickersType::Masks (type == Data::StickersType::Masks
? tr::lng_masks_archive_pack(tr::now) ? tr::lng_masks_archive_pack(tr::now)
@ -732,8 +924,8 @@ StickerSetBox::Inner::Inner(
_api.request(MTPmessages_GetStickerSet( _api.request(MTPmessages_GetStickerSet(
Data::InputStickerSet(_input), Data::InputStickerSet(_input),
MTP_int(0) // hash MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) { )).done([=](const TLStickerSet &result) {
gotSet(result); applySet(result);
}).fail([=] { }).fail([=] {
_loaded = true; _loaded = true;
_errors.fire(Error::NotFound); _errors.fire(Error::NotFound);
@ -749,7 +941,7 @@ StickerSetBox::Inner::Inner(
setMouseTracking(true); setMouseTracking(true);
} }
void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) { void StickerSetBox::Inner::applySet(const TLStickerSet &set) {
_pack.clear(); _pack.clear();
_emoji.clear(); _emoji.clear();
_elements.clear(); _elements.clear();
@ -793,7 +985,9 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
} }
}); });
} }
data.vset().match([&](const MTPDstickerSet &set) {
{
const auto &set = data.vset().data();
_setTitle = _session->data().stickers().getSetTitle( _setTitle = _session->data().stickers().getSetTitle(
set); set);
_setShortName = qs(set.vshort_name()); _setShortName = qs(set.vshort_name());
@ -804,6 +998,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
_setFlags = Data::ParseStickersSetFlags(set); _setFlags = Data::ParseStickersSetFlags(set);
_setInstallDate = set.vinstalled_date().value_or(0); _setInstallDate = set.vinstalled_date().value_or(0);
_setThumbnailDocumentId = set.vthumb_document_id().value_or_empty(); _setThumbnailDocumentId = set.vthumb_document_id().value_or_empty();
_amSetCreator = set.is_creator();
_setThumbnail = [&] { _setThumbnail = [&] {
if (const auto thumbs = set.vthumbs()) { if (const auto thumbs = set.vthumbs()) {
for (const auto &thumb : thumbs->v) { for (const auto &thumb : thumbs->v) {
@ -836,7 +1031,7 @@ void StickerSetBox::Inner::gotSet(const MTPmessages_StickerSet &set) {
set->emoji = _emoji; set->emoji = _emoji;
set->setThumbnail(_setThumbnail, _setThumbnailType); set->setThumbnail(_setThumbnail, _setThumbnailType);
} }
}); };
}, [&](const MTPDmessages_stickerSetNotModified &data) { }, [&](const MTPDmessages_stickerSetNotModified &data) {
LOG(("API Error: Unexpected messages.stickerSetNotModified.")); LOG(("API Error: Unexpected messages.stickerSetNotModified."));
}); });
@ -977,11 +1172,100 @@ void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
if (index < 0 || index >= _pack.size()) { if (index < 0 || index >= _pack.size()) {
return; return;
} }
if (_dragging.enabled) {
_previewTimer.cancel();
if (isDraggedAnimating()) {
return;
}
_dragging.index = index;
_dragging.point = mapFromGlobal(QCursor::pos()) - posFromIndex(index);
return;
}
_previewTimer.callOnce(QApplication::startDragTime()); _previewTimer.callOnce(QApplication::startDragTime());
} }
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) { void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
updateSelected(); updateSelected();
const auto draggedAnimating = isDraggedAnimating();
if (_selected >= 0 && !draggedAnimating) {
_dragging.lastSelected = _selected;
}
if (_dragging.index >= 0
&& _dragging.index < _pack.size()
&& _dragging.lastSelected >= 0
&& !draggedAnimating) {
for (auto i = 0; i < _pack.size(); i++) {
if (i == _dragging.index) {
continue;
}
auto &entry = _shiftAnimations[i];
const auto wasShift = entry.shift;
if ((i >= _dragging.index) && (i <= _dragging.lastSelected)) {
if (entry.shift == 0) {
entry.shift = -1;
} else if (entry.shift == 1) {
entry.shift = 0;
}
} else if ((i < _dragging.index)
&& (i >= _dragging.lastSelected)) {
if (entry.shift == 0) {
entry.shift = 1;
} else if (entry.shift == -1) {
entry.shift = 0;
}
}
if ((i < std::min(_dragging.index, _dragging.lastSelected))
|| (i > std::max(_dragging.index, _dragging.lastSelected))) {
entry.shift = 0;
}
if (wasShift != entry.shift) {
const auto fromPoint = posFromIndex(i + wasShift);
const auto toPoint = posFromIndex(i + entry.shift);
const auto toX = float64(toPoint.x());
const auto toY = float64(toPoint.y());
const auto ratio = [&] {
const auto fromX = entry.animation.value(toX);
const auto ratioX = std::min(toX, fromX)
/ std::max(toX, fromX);
const auto fromY = entry.yAnimation.value(toY);
const auto ratioY = std::min(toY, fromY)
/ std::max(toY, fromY);
return (ratioX == 1.)
? ratioY
: (ratioY == 1.)
? ratioX
: std::max(ratioX, ratioY);
}();
if (!entry.animation.animating()) {
entry.animation.stop();
entry.animation.start(
[=] { update(); },
fromPoint.x(),
toX,
kStickerMoveDuration);
} else {
entry.animation.change(
toX,
kStickerMoveDuration * (1. - ratio),
anim::linear);
}
if (!entry.yAnimation.animating()) {
entry.yAnimation.stop();
entry.yAnimation.start(
[=] { update(); },
fromPoint.y(),
toY,
kStickerMoveDuration);
} else {
entry.yAnimation.change(
toY,
kStickerMoveDuration * (1. - ratio),
anim::linear);
}
}
}
update();
}
if (_previewShown >= 0) { if (_previewShown >= 0) {
showPreviewAt(e->globalPos()); showPreviewAt(e->globalPos());
} }
@ -1003,7 +1287,86 @@ void StickerSetBox::Inner::leaveEventHook(QEvent *e) {
setSelected(-1); setSelected(-1);
} }
void StickerSetBox::Inner::requestReorder(
not_null<DocumentData*> document,
int index) {
if (!_apiReorder) {
_apiReorder.emplace(&_session->mtp());
}
_reorderRequests.emplace_back([document, index, this] {
_apiReorder->request(
MTPstickers_ChangeStickerPosition(
document->mtpInput(),
MTP_int(index))
).done([this, document](const TLStickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &d) {
document->owner().stickers().feedSetFull(d);
document->owner().stickers().notifyUpdated(
Data::StickersType::Stickers);
}, [](const auto &) {
});
if (!_reorderRequests.empty()) {
_reorderRequests.pop_front();
}
if (_reorderRequests.empty()) {
// applySet(result); // Causes stickers blink.
} else {
_reorderRequests.front()();
}
}).fail([show = _show](const MTP::Error &error) {
show->showToast(error.type());
}).send();
});
if (_reorderRequests.size() == 1) {
_reorderRequests.front()();
}
}
void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) { void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
if (_dragging.index >= 0 && !isDraggedAnimating()) {
const auto fromPos = mapFromGlobal(e->globalPos()) - _dragging.point;
const auto toPos = posFromIndex(_dragging.lastSelected);
const auto document = _pack[_dragging.index];
const auto wasPosition = _dragging.index;
const auto nowPosition = _dragging.lastSelected;
const auto finish = [=, this] {
requestReorder(document, nowPosition);
base::reorder(_pack, wasPosition, nowPosition);
base::reorder(_elements, wasPosition, nowPosition);
_dragging = {};
_dragging.enabled = true;
_shiftAnimations.clear();
};
auto &entry = _shiftAnimations[_dragging.index];
entry.animation.stop();
entry.yAnimation.stop();
entry.animation.start(
[finish, toPos, this](float64 value) {
const auto index = _dragging.index;
if (value >= toPos.x()
&& index >= 0
&& !_shiftAnimations[index].yAnimation.animating()) {
finish();
}
update();
},
fromPos.x(),
toPos.x(),
kStickerMoveDuration);
entry.yAnimation.start(
[finish, toPos, this](float64 value) {
const auto index = _dragging.index;
if (value >= toPos.y()
&& index >= 0
&& !_shiftAnimations[index].animation.animating()) {
finish();
}
update();
},
fromPos.y(),
toPos.y(),
kStickerMoveDuration);
}
if (_previewShown >= 0) { if (_previewShown >= 0) {
_previewShown = -1; _previewShown = -1;
return; return;
@ -1106,6 +1469,20 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
(isFaved (isFaved
? &st::menuIconUnfave ? &st::menuIconUnfave
: &st::menuIconFave)); : &st::menuIconFave));
if (amSetCreator()) {
const auto addAction = Ui::Menu::CreateAddActionCallback(
_menu.get());
addAction({
.text = tr::lng_stickers_context_delete(tr::now),
.handler = [index, this, show = _show] {
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
fillDeleteStickerBox(box, index);
}));
},
.icon = &st::menuIconDeleteAttention,
.isAttention = true,
});
}
} }
if (_menu->empty()) { if (_menu->empty()) {
_menu = nullptr; _menu = nullptr;
@ -1114,6 +1491,129 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
} }
} }
void StickerSetBox::Inner::fillDeleteStickerBox(
not_null<Ui::GenericBox*> box,
int index) {
Expects(index >= 0 || index < _pack.size());
const auto document = _pack[index];
const auto weak = Ui::MakeWeak(this);
const auto show = _show;
const auto container = box->verticalLayout();
Ui::AddSkip(container);
Ui::AddSkip(container);
const auto line = container->add(object_ptr<Ui::RpWidget>(container));
line->resize(line->width(), _singleSize.height());
const auto sticker = Ui::CreateChild<Ui::RpWidget>(line);
auto &lifetime = sticker->lifetime();
struct State final {
rpl::variable<mtpRequestId> requestId = 0;
Ui::RpWidget* saveButton = nullptr;
};
const auto state = lifetime.make_state<State>();
sticker->resize(_singleSize);
{
const auto animation = lifetime.make_state<Ui::Animations::Basic>();
animation->init([=] { sticker->update(); });
animation->start();
}
sticker->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(sticker);
if (const auto strong = weak.data()) {
const auto paused = On(PowerSaving::kStickersPanel)
|| show->paused(ChatHelpers::PauseReason::Layer);
paintSticker(p, index, QPoint(), paused, crl::now());
if (_lottiePlayer && !paused) {
_lottiePlayer->markFrameShown();
}
}
}, sticker->lifetime());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
line,
tr::lng_stickers_context_delete(),
box->getDelegate()->style().title);
line->widthValue(
) | rpl::start_with_next([=](int width) {
sticker->moveToLeft(st::boxRowPadding.left(), 0);
const auto skip = st::defaultBoxCheckbox.textPosition.x();
label->resizeToWidth(width
- rect::right(sticker)
- skip
- st::boxRowPadding.right());
label->moveToLeft(
rect::right(sticker) + skip,
((sticker->height() - label->height()) / 2));
}, label->lifetime());
sticker->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(container);
Ui::AddSkip(container);
box->addRow(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_stickers_context_delete_sure(),
st::boxLabel));
const auto save = [=] {
if (state->requestId.current()) {
return;
}
const auto weakBox = Ui::MakeWeak(box);
const auto buttonWidth = state->saveButton
? state->saveButton->width()
: 0;
state->requestId = document->owner().session().api().request(
MTPstickers_RemoveStickerFromSet(document->mtpInput()
)).done([=](const TLStickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &d) {
document->owner().stickers().feedSetFull(d);
document->owner().stickers().notifyUpdated(
Data::StickersType::Stickers);
}, [](const auto &) {
});
if (const auto strong = weak.data()) {
applySet(result);
}
if (const auto strongBox = weakBox.data()) {
strongBox->closeBox();
}
}).fail([=](const MTP::Error &error) {
if (const auto strongBox = weakBox.data()) {
strongBox->uiShow()->showToast(error.type());
}
}).send();
if (state->saveButton) {
state->saveButton->resizeToWidth(buttonWidth);
}
};
state->saveButton = box->addButton(
rpl::conditional(
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0),
rpl::single(QString()),
tr::lng_selected_delete()),
save,
st::attentionBoxButton);
if (const auto saveButton = state->saveButton) {
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
saveButton,
saveButton->height() / 2,
&st::editStickerSetNameLoading);
AddChildToWidgetCenter(saveButton, loadingAnimation);
loadingAnimation->showOn(
state->requestId.value() | rpl::map(rpl::mappers::_1 > 0));
}
box->addButton(tr::lng_close(), [=] {
document->owner().session().api().request(
state->requestId.current()).cancel();
box->closeBox();
});
}
void StickerSetBox::Inner::updateSelected() { void StickerSetBox::Inner::updateSelected() {
auto selected = stickerFromGlobalPos(QCursor::pos()); auto selected = stickerFromGlobalPos(QCursor::pos());
setSelected(setType() == Data::StickersType::Masks ? -1 : selected); setSelected(setType() == Data::StickersType::Masks ? -1 : selected);
@ -1124,7 +1624,11 @@ void StickerSetBox::Inner::setSelected(int selected) {
startOverAnimation(_selected, 1., 0.); startOverAnimation(_selected, 1., 0.);
_selected = selected; _selected = selected;
startOverAnimation(_selected, 0., 1.); startOverAnimation(_selected, 0., 1.);
setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default); setCursor((_selected < 0)
? style::cur_default
: _dragging.enabled
? style::cur_sizeall
: style::cur_pointer);
} }
} }
@ -1146,6 +1650,24 @@ void StickerSetBox::Inner::showPreview() {
showPreviewAt(QCursor::pos()); showPreviewAt(QCursor::pos());
} }
QPoint StickerSetBox::Inner::posFromIndex(int index) const {
return {
_padding.left() + (index % _perRow) * _singleSize.width(),
_padding.top() + (index / _perRow) * _singleSize.height(),
};
}
bool StickerSetBox::Inner::isDraggedAnimating() const {
if (_dragging.index < 0) {
return false;
}
const auto it = _shiftAnimations.find(_dragging.index);
return (it == _shiftAnimations.end())
? false
: (it->second.animation.animating()
|| it->second.yAnimation.animating());
}
not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() { not_null<Lottie::MultiPlayer*> StickerSetBox::Inner::getLottiePlayer() {
if (!_lottiePlayer) { if (!_lottiePlayer) {
_lottiePlayer = std::make_unique<Lottie::MultiPlayer>( _lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
@ -1185,12 +1707,36 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
_pathGradient->startFrame(0, width(), width() / 2); _pathGradient->startFrame(0, width(), width() / 2);
const auto indexUnderCursor = (_dragging.index >= 0
&& _dragging.index < _elements.size())
? stickerFromGlobalPos(QCursor::pos())
: -2;
const auto lastIndex = indexUnderCursor >= 0
? indexUnderCursor
: _dragging.lastSelected;
const auto now = crl::now(); const auto now = crl::now();
const auto paused = On(PowerSaving::kStickersPanel) const auto paused = On(PowerSaving::kStickersPanel)
|| _show->paused(ChatHelpers::PauseReason::Layer); || _show->paused(ChatHelpers::PauseReason::Layer);
for (int32 i = from; i < to; ++i) { for (int32 i = from; i < to; ++i) {
for (int32 j = 0; j < _perRow; ++j) { for (int32 j = 0; j < _perRow; ++j) {
int32 index = i * _perRow + j; int32 index = i * _perRow + j;
if (lastIndex >= 0) {
if (_dragging.index == index) {
continue;
}
const auto it = _shiftAnimations.find(index);
if (it != _shiftAnimations.end()) {
const auto &entry = it->second;
const auto toPos = posFromIndex(index + entry.shift);
const auto pos = QPoint(
entry.animation.value(toPos.x()),
entry.yAnimation.value(toPos.y()));
paintSticker(p, index, pos, paused, now);
continue;
}
}
if (index >= _elements.size()) { if (index >= _elements.size()) {
break; break;
} }
@ -1200,6 +1746,14 @@ void StickerSetBox::Inner::paintEvent(QPaintEvent *e) {
paintSticker(p, index, pos, paused, now); paintSticker(p, index, pos, paused, now);
} }
} }
if (_dragging.index >= 0 && _dragging.index < _elements.size()) {
const auto pos = isDraggedAnimating()
? QPoint(
_shiftAnimations[_dragging.index].animation.value(0),
_shiftAnimations[_dragging.index].yAnimation.value(0))
: (mapFromGlobal(QCursor::pos()) - _dragging.point);
paintSticker(p, _dragging.index, pos, paused, now);
}
if (_lottiePlayer && !paused) { if (_lottiePlayer && !paused) {
_lottiePlayer->markFrameShown(); _lottiePlayer->markFrameShown();
@ -1355,18 +1909,99 @@ void StickerSetBox::Inner::customEmojiRepaint() {
update(); update();
} }
void StickerSetBox::Inner::shakeTransform(
QPainter &p,
int index,
QPoint position,
crl::time now) const {
constexpr auto kShakeADuration = crl::time(400);
constexpr auto kShakeXDuration = crl::time(kShakeADuration * 1.2);
constexpr auto kShakeYDuration = kShakeADuration;
const auto diff = ((index % 2) ? 0 : kShakeYDuration / 2)
+ (now - _shakeAnimation.started());
const auto pX = (diff % kShakeXDuration)
/ float64(kShakeXDuration);
const auto pY = (diff % kShakeYDuration)
/ float64(kShakeYDuration);
const auto pA = (diff % kShakeADuration)
/ float64(kShakeADuration);
constexpr auto kMaxA = 2.;
constexpr auto kMaxTranslation = .5;
constexpr auto kAStep = 1. / 5;
constexpr auto kXStep = 1. / 5;
constexpr auto kYStep = 1. / 4;
// 0, -kMaxA, 0, kMaxA, 0.
const auto angle = (pA < kAStep)
? anim::interpolateF(0., -kMaxA, pA / kAStep)
: (pA < kAStep * 2.)
? anim::interpolateF(-kMaxA, 0, (pA - kAStep) / kAStep)
: (pA < kAStep * 3.)
? anim::interpolateF(0, kMaxA, (pA - kAStep * 2.) / kAStep)
: (pA < kAStep * 4.)
? anim::interpolateF(kMaxA, 0, (pA - kAStep * 3.) / kAStep)
: anim::interpolateF(0, 0., (pA - kAStep * 4.) / kAStep);
// 0, kMaxTranslation, 0, -kMaxTranslation, 0.
const auto x = (pX < kXStep)
? anim::interpolateF(0., kMaxTranslation, pX / kXStep)
: (pX < kXStep * 2.)
? anim::interpolateF(kMaxTranslation, 0, (pX - kXStep) / kXStep)
: (pX < kXStep * 3.)
? anim::interpolateF(0, -kMaxTranslation, (pX - kXStep * 2.) / kXStep)
: (pX < kXStep * 4.)
? anim::interpolateF(-kMaxTranslation, 0, (pX - kXStep * 3.) / kXStep)
: anim::interpolateF(0, 0., (pX - kXStep * 4.) / kXStep);
// 0, kMaxTranslation, -kMaxTranslation, 0.
const auto y = (pY < kYStep)
? anim::interpolateF(0., kMaxTranslation, pY / kYStep)
: (pY < kYStep * 2.)
? anim::interpolateF(kMaxTranslation, 0, (pY - kYStep) / kYStep)
: (pY < kYStep * 3.)
? anim::interpolateF(0, -kMaxTranslation, (pY - kYStep * 2.) / kYStep)
: anim::interpolateF(-kMaxTranslation, 0, (pY - kYStep * 3) / kYStep);
const auto center = position + QPoint(
_singleSize.width() / 2,
_singleSize.height() / 2);
p.translate(center);
p.rotate(angle);
p.translate(-center);
p.translate(x, y);
}
void StickerSetBox::Inner::paintSticker( void StickerSetBox::Inner::paintSticker(
Painter &p, Painter &p,
int index, int index,
QPoint position, QPoint position,
bool paused, bool paused,
crl::time now) const { crl::time now) const {
if (const auto over = _elements[index].overAnimation.value((index == _selected) ? 1. : 0.)) { if (_dragging.index != index) {
p.setOpacity(over); const auto over = _elements[index].overAnimation.value(
auto tl = position; (index == _selected) ? 1. : 0.);
if (rtl()) tl.setX(width() - tl.x() - _singleSize.width()); if (over) {
Ui::FillRoundRect(p, QRect(tl, _singleSize), st::emojiPanHover, Ui::StickerHoverCorners); p.setOpacity(over);
p.setOpacity(1); Ui::FillRoundRect(
p,
QRect(
rtl()
? QPoint(
width() - position.x() - _singleSize.width(),
position.y())
: position,
_singleSize),
st::emojiPanHover,
Ui::StickerHoverCorners);
p.setOpacity(1);
}
}
const auto hasShake = _shakeAnimation.animating();
if (hasShake) {
shakeTransform(p, index, position, now);
} }
const auto &element = _elements[index]; const auto &element = _elements[index];
@ -1452,6 +2087,9 @@ void StickerSetBox::Inner::paintSticker(
_singleSize, _singleSize,
width()); width());
} }
if (hasShake) {
p.resetTransform();
}
} }
bool StickerSetBox::Inner::loaded() const { bool StickerSetBox::Inner::loaded() const {

View file

@ -39,7 +39,6 @@ namespace tgcalls {
class InstanceImpl; class InstanceImpl;
class InstanceV2Impl; class InstanceV2Impl;
class InstanceV2ReferenceImpl; class InstanceV2ReferenceImpl;
class InstanceV2_4_0_0Impl;
class InstanceImplLegacy; class InstanceImplLegacy;
void SetLegacyGlobalServerConfig(const std::string &serverConfig); void SetLegacyGlobalServerConfig(const std::string &serverConfig);
} // namespace tgcalls } // namespace tgcalls
@ -56,7 +55,6 @@ const auto kDefaultVersion = "2.4.4"_q;
const auto Register = tgcalls::Register<tgcalls::InstanceImpl>(); const auto Register = tgcalls::Register<tgcalls::InstanceImpl>();
const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>(); const auto RegisterV2 = tgcalls::Register<tgcalls::InstanceV2Impl>();
const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>(); const auto RegV2Ref = tgcalls::Register<tgcalls::InstanceV2ReferenceImpl>();
const auto RegisterV240 = tgcalls::Register<tgcalls::InstanceV2_4_0_0Impl>();
const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>(); const auto RegisterLegacy = tgcalls::Register<tgcalls::InstanceImplLegacy>();
[[nodiscard]] base::flat_set<int64> CollectEndpointIds( [[nodiscard]] base::flat_set<int64> CollectEndpointIds(

View file

@ -215,7 +215,7 @@ void Panel::initWindow() {
} }
const auto shown = _layerBg->topShownLayer(); const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint)) return (!shown || !shown->geometry().contains(widgetPoint))
? (Flag::Move | Flag::FullScreen) ? (Flag::Move | Flag::Menu | Flag::FullScreen)
: Flag::None; : Flag::None;
}); });
@ -276,8 +276,8 @@ void Panel::initControls() {
_layerBg->showBox(std::move(box)); _layerBg->showBox(std::move(box));
} }
} else if (const auto source = env->uniqueDesktopCaptureSource()) { } else if (const auto source = env->uniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) { if (!chooseSourceActiveDeviceId().isEmpty()) {
_call->toggleScreenSharing(std::nullopt); chooseSourceStop();
} else { } else {
chooseSourceAccepted(*source, false); chooseSourceAccepted(*source, false);
} }

View file

@ -1195,24 +1195,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
const auto addVolumeItem = (!muted || isMe(participantPeer)); const auto addVolumeItem = (!muted || isMe(participantPeer));
const auto admin = IsGroupCallAdmin(_peer, participantPeer); const auto admin = IsGroupCallAdmin(_peer, participantPeer);
const auto session = &_peer->session(); const auto session = &_peer->session();
const auto getCurrentWindow = [=]() -> Window::SessionController* { const auto account = &session->account();
if (const auto window = Core::App().windowFor(participantPeer)) {
if (const auto controller = window->sessionController()) {
if (&controller->session() == session) {
return controller;
}
}
}
return nullptr;
};
const auto getWindow = [=] {
if (const auto current = getCurrentWindow()) {
return current;
} else if (&Core::App().domain().active() != &session->account()) {
Core::App().domain().activate(&session->account());
}
return getCurrentWindow();
};
auto result = base::make_unique_q<Ui::PopupMenu>( auto result = base::make_unique_q<Ui::PopupMenu>(
parent, parent,
@ -1223,7 +1206,7 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
: st::groupCallPopupMenu)); : st::groupCallPopupMenu));
const auto weakMenu = Ui::MakeWeak(result.get()); const auto weakMenu = Ui::MakeWeak(result.get());
const auto withActiveWindow = [=](auto callback) { const auto withActiveWindow = [=](auto callback) {
if (const auto window = getWindow()) { if (const auto window = Core::App().activePrimaryWindow()) {
if (const auto menu = weakMenu.data()) { if (const auto menu = weakMenu.data()) {
menu->discardParentReActivate(); menu->discardParentReActivate();
@ -1232,8 +1215,13 @@ base::unique_qptr<Ui::PopupMenu> Members::Controller::createRowContextMenu(
// PopupMenu::hide activates back the group call panel :( // PopupMenu::hide activates back the group call panel :(
delete weakMenu; delete weakMenu;
} }
callback(window); window->invokeForSessionController(
window->widget()->activate(); account,
participantPeer,
[&](not_null<Window::SessionController*> newController) {
callback(newController);
newController->widget()->activate();
});
} }
}; };
const auto showProfile = [=] { const auto showProfile = [=] {

View file

@ -410,7 +410,7 @@ void Panel::initWindow() {
} }
const auto shown = _layerBg->topShownLayer(); const auto shown = _layerBg->topShownLayer();
return (!shown || !shown->geometry().contains(widgetPoint)) return (!shown || !shown->geometry().contains(widgetPoint))
? (Flag::Move | Flag::Maximize) ? (Flag::Move | Flag::Menu | Flag::Maximize)
: Flag::None; : Flag::None;
}); });

View file

@ -284,6 +284,10 @@ stickersTrendingSubheaderFont: normalFont;
stickersTrendingSubheaderFg: windowSubTextFg; stickersTrendingSubheaderFg: windowSubTextFg;
stickersTrendingSubheaderTop: 31px; stickersTrendingSubheaderTop: 31px;
stickersHeaderBadgeFont: font(10px);
stickersHeaderBadgeFontTop: 12px;
stickersHeaderBadgeFontSkip: 12px;
emojiPanButtonRight: 7px; emojiPanButtonRight: 7px;
emojiPanButtonTop: 8px; emojiPanButtonTop: 8px;
emojiPanButton: RoundButton(defaultActiveButton) { emojiPanButton: RoundButton(defaultActiveButton) {
@ -1409,6 +1413,22 @@ editTagLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
} }
editStickerSetNameField: InputField(defaultInputField) {
textMargins: margins(0px, 8px, 26px, 4px);
heightMin: 36px;
heightMax: 36px;
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
placeholderFont: normalFont;
}
editStickerSetNameLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: lightButtonFg;
thickness: 2px;
}
paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }}; paidStarIcon: icon {{ "settings/premium/star", creditsBg1 }};
paidStarIconTop: 7px; paidStarIconTop: 7px;
paidAmountAbout: FlatLabel(defaultFlatLabel) { paidAmountAbout: FlatLabel(defaultFlatLabel) {
@ -1423,3 +1443,61 @@ paidTagLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle; style: semiboldTextStyle;
} }
paidTagPadding: margins(16px, 6px, 16px, 6px); paidTagPadding: margins(16px, 6px, 16px, 6px);
pickLocationWindow: size(364px, 680px);
pickLocationMapHeight: 220px;
pickLocationCollapsedHeight: 92px;
pickLocationRowHeight: 52px;
pickLocationButton: FlatButton {
height: pickLocationRowHeight;
bgColor: contactsBg;
overBgColor: contactsBgOver;
ripple: defaultRippleAnimation;
}
pickLocationButtonText: FlatLabel(defaultFlatLabel) {
minWidth: 128px;
maxHeight: 20px;
style: semiboldTextStyle;
textFg: windowBoldFg;
}
pickLocationButtonStatus: FlatLabel(defaultFlatLabel) {
minWidth: 128px;
maxHeight: 20px;
textFg: windowSubTextFg;
}
pickLocationButtonSkip: 6px;
pickLocationSendIcon: icon{{ "chat/filled_location", windowFgActive }};
pickLocationVenueItem: PeerListItem(defaultPeerListItem) {
height: pickLocationRowHeight;
photoSize: 42px;
photoPosition: point(18px, 5px);
namePosition: point(70px, 7px);
statusPosition: point(70px, 27px);
button: OutlineButton(defaultPeerListButton) {
textBg: contactsBg;
textBgOver: contactsBgOver;
font: normalFont;
padding: margins(11px, 5px, 11px, 5px);
ripple: defaultRippleAnimation;
}
statusFg: contactsStatusFg;
statusFgOver: contactsStatusFgOver;
statusFgActive: contactsStatusFgOnline;
}
pickLocationVenueList: PeerList(defaultPeerList) {
item: pickLocationVenueItem;
padding: margins(0px, 0px, 0px, 0px);
}
pickLocationIconSkip: 6px;
pickLocationLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
size: size(56px, 56px);
color: windowSubTextFg;
thickness: 4px;
}
pickLocationPromoHeight: 32px;
pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
height: 44px;
textTop: 11px;
width: -96px;
font: font(15px semibold);
}

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "menu/menu_send.h" // SendMenu::FillSendMenu #include "menu/menu_send.h" // SendMenu::FillSendMenu
#include "mtproto/mtproto_config.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "ui/controls/tabbed_search.h" #include "ui/controls/tabbed_search.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -55,7 +56,6 @@ namespace ChatHelpers {
namespace { namespace {
constexpr auto kSearchRequestDelay = 400; constexpr auto kSearchRequestDelay = 400;
constexpr auto kSearchBotUsername = "gif"_cs;
constexpr auto kMinRepaintDelay = crl::time(33); constexpr auto kMinRepaintDelay = crl::time(33);
constexpr auto kMinAfterScrollDelay = crl::time(33); constexpr auto kMinAfterScrollDelay = crl::time(33);
@ -893,13 +893,11 @@ void GifsListWidget::searchForGifs(const QString &query) {
} }
if (!_searchBot && !_searchBotRequestId) { if (!_searchBot && !_searchBotRequestId) {
auto username = kSearchBotUsername.utf16(); const auto username = session().serverConfig().gifSearchUsername;
_searchBotRequestId = _api.request(MTPcontacts_ResolveUsername( _searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username) MTP_string(username)
)).done([=](const MTPcontacts_ResolvedPeer &result) { )).done([=](const MTPcontacts_ResolvedPeer &result) {
Expects(result.type() == mtpc_contacts_resolvedPeer); auto &data = result.data();
auto &data = result.c_contacts_resolvedPeer();
session().data().processUsers(data.vusers()); session().data().processUsers(data.vusers());
session().data().processChats(data.vchats()); session().data().processChats(data.vchats());
const auto peer = session().data().peerLoaded( const auto peer = session().data().peerLoaded(

View file

@ -392,6 +392,9 @@ void InitMessageFieldHandlers(
Fn<bool()> customEmojiPaused, Fn<bool()> customEmojiPaused,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji, Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji,
const style::InputField *fieldStyle) { const style::InputField *fieldStyle) {
const auto paused = [customEmojiPaused] {
return customEmojiPaused && customEmojiPaused();
};
field->setTagMimeProcessor( field->setTagMimeProcessor(
FieldTagMimeProcessor(session, allowPremiumEmoji)); FieldTagMimeProcessor(session, allowPremiumEmoji));
field->setCustomTextContext([=](Fn<void()> repaint) { field->setCustomTextContext([=](Fn<void()> repaint) {
@ -399,10 +402,10 @@ void InitMessageFieldHandlers(
.session = session, .session = session,
.customEmojiRepaint = std::move(repaint), .customEmojiRepaint = std::move(repaint),
}); });
}, [customEmojiPaused] { }, [paused] {
return On(PowerSaving::kEmojiChat) || customEmojiPaused(); return On(PowerSaving::kEmojiChat) || paused();
}, [customEmojiPaused] { }, [paused] {
return On(PowerSaving::kChatSpoiler) || customEmojiPaused(); return On(PowerSaving::kChatSpoiler) || paused();
}); });
field->setInstantReplaces(Ui::InstantReplaces::Default()); field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled( field->setInstantReplacesEnabled(
@ -525,7 +528,7 @@ void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
st::historySendSize.height() - 2 * st::historySendPadding); st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight); field->setMaxHeight(st::historyComposeFieldMaxHeight);
field->document()->setDocumentMargin(4.); field->setDocumentMargin(4.);
field->setAdditionalMargin(style::ConvertScale(4) - 4); field->setAdditionalMargin(style::ConvertScale(4) - 4);
} }

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -21,6 +22,16 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
GiftBoxPack::~GiftBoxPack() = default; GiftBoxPack::~GiftBoxPack() = default;
int GiftBoxPack::monthsForStars(int stars) const {
if (stars <= 1000) {
return 3;
} else if (stars < 2500) {
return 6;
} else {
return 12;
}
}
DocumentData *GiftBoxPack::lookup(int months) const { DocumentData *GiftBoxPack::lookup(int months) const {
const auto it = ranges::lower_bound(_localMonths, months); const auto it = ranges::lower_bound(_localMonths, months);
const auto fallback = _documents.empty() ? nullptr : _documents[0]; const auto fallback = _documents.empty() ? nullptr : _documents[0];
@ -38,6 +49,10 @@ DocumentData *GiftBoxPack::lookup(int months) const {
return (index >= _documents.size()) ? fallback : _documents[index]; return (index >= _documents.size()) ? fallback : _documents[index];
} }
Data::FileOrigin GiftBoxPack::origin() const {
return Data::FileOriginStickerSet(_setId, _accessHash);
}
void GiftBoxPack::load() { void GiftBoxPack::load() {
if (_requestId || !_documents.empty()) { if (_requestId || !_documents.empty()) {
return; return;
@ -59,6 +74,7 @@ void GiftBoxPack::load() {
void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) { void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
_setId = data.vset().data().vid().v; _setId = data.vset().data().vid().v;
_accessHash = data.vset().data().vaccess_hash().v;
auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>(); auto documents = base::flat_map<DocumentId, not_null<DocumentData*>>();
for (const auto &sticker : data.vdocuments().v) { for (const auto &sticker : data.vdocuments().v) {
const auto document = _session->data().processDocument(sticker); const auto document = _session->data().processDocument(sticker);

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class DocumentData; class DocumentData;
namespace Data {
struct FileOrigin;
} // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -21,7 +25,9 @@ public:
~GiftBoxPack(); ~GiftBoxPack();
void load(); void load();
[[nodiscard]] int monthsForStars(int stars) const;
[[nodiscard]] DocumentData *lookup(int months) const; [[nodiscard]] DocumentData *lookup(int months) const;
[[nodiscard]] Data::FileOrigin origin() const;
private: private:
using SetId = uint64; using SetId = uint64;
@ -32,6 +38,7 @@ private:
std::vector<DocumentData*> _documents; std::vector<DocumentData*> _documents;
SetId _setId = 0; SetId _setId = 0;
uint64 _accessHash = 0;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
}; };

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "chat_helpers/stickers_list_widget.h" #include "chat_helpers/stickers_list_widget.h"
#include "base/timer_rpl.h"
#include "core/application.h" #include "core/application.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
@ -939,6 +940,9 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
if (sets.empty() && _section == Section::Search) { if (sets.empty() && _section == Section::Search) {
paintEmptySearchResults(p); paintEmptySearchResults(p);
} }
const auto badgeText = tr::lng_stickers_creator_badge(tr::now);
const auto &badgeFont = st::stickersHeaderBadgeFont;
const auto badgeWidth = badgeFont->width(badgeText);
enumerateSections([&](const SectionInfo &info) { enumerateSections([&](const SectionInfo &info) {
if (clip.top() >= info.rowsBottom) { if (clip.top() >= info.rowsBottom) {
return true; return true;
@ -1057,6 +1061,12 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
widthForTitle -= remove.width(); widthForTitle -= remove.width();
} }
const auto amCreator = (set.flags & Data::StickersSetFlag::AmCreator);
if (amCreator) {
widthForTitle -= badgeWidth
+ st::stickersFeaturedUnreadSkip
+ st::stickersHeaderBadgeFontSkip;
}
if (titleWidth > widthForTitle) { if (titleWidth > widthForTitle) {
titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle); titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle);
titleWidth = st::stickersTrendingHeaderFont->width(titleText); titleWidth = st::stickersTrendingHeaderFont->width(titleText);
@ -1064,6 +1074,39 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) {
p.setFont(st::emojiPanHeaderFont); p.setFont(st::emojiPanHeaderFont);
p.setPen(st().headerFg); p.setPen(st().headerFg);
p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth); p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth);
if (amCreator) {
const auto badgeLeft = st().headerLeft
- st().margin.left()
+ titleWidth
+ st::stickersFeaturedUnreadSkip;
{
auto color = st().headerFg->c;
color.setAlphaF(st().headerFg->c.alphaF() * 0.15);
p.setPen(Qt::NoPen);
p.setBrush(color);
auto hq = PainterHighQualityEnabler(p);
p.drawRoundedRect(
style::rtlrect(
badgeLeft,
info.top + st::stickersHeaderBadgeFontTop,
badgeWidth + badgeFont->height,
badgeFont->height,
width()),
badgeFont->height / 2.,
badgeFont->height / 2.);
}
p.setPen(st().headerFg);
p.setBrush(Qt::NoBrush);
p.setFont(badgeFont);
p.drawText(
QRect(
badgeLeft + badgeFont->height / 2,
info.top + st::stickersHeaderBadgeFontTop,
badgeWidth,
badgeFont->height),
badgeText,
style::al_center);
}
} }
if (clip.top() + clip.height() <= info.rowsTop) { if (clip.top() + clip.height() <= info.rowsTop) {
return true; return true;
@ -1694,12 +1737,32 @@ QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
+ st().removeSet.rippleAreaPosition; + st().removeSet.rippleAreaPosition;
} }
void StickersListWidget::showStickerSetBox(not_null<DocumentData*> document) { void StickersListWidget::showStickerSetBox(
not_null<DocumentData*> document,
uint64 setId) {
if (document->sticker() && document->sticker()->set) { if (document->sticker() && document->sticker()->set) {
checkHideWithBox(Box<StickerSetBox>( checkHideWithBox(Box<StickerSetBox>(
_show, _show,
document->sticker()->set, document->sticker()->set,
document->sticker()->setType)); document->sticker()->setType));
} else if ((setId == Data::Stickers::FavedSetId)
|| (setId == Data::Stickers::RecentSetId)) {
const auto lifetime = std::make_shared<rpl::lifetime>();
constexpr auto kTimeout = 10000;
rpl::merge(
base::timer_once(kTimeout),
document->owner().stickers().updated(
Data::StickersType::Stickers)
) | rpl::start_with_next([=, weak = Ui::MakeWeak(this)] {
if (weak.data()) {
showStickerSetBox(document, setId);
}
lifetime->destroy();
}, *lifetime);
document->owner().session().api().requestSpecialStickersForce(
setId == Data::Stickers::FavedSetId,
setId == Data::Stickers::RecentSetId,
false);
} }
} }
@ -1756,8 +1819,8 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
isFaved ? &icons->menuUnfave : &icons->menuFave); isFaved ? &icons->menuUnfave : &icons->menuFave);
if (_features.openStickerSets) { if (_features.openStickerSets) {
menu->addAction(tr::lng_context_pack_info(tr::now), [=] { menu->addAction(tr::lng_context_pack_info(tr::now), [=, id = set.id] {
showStickerSetBox(document); showStickerSetBox(document, id);
}, &icons->menuStickerSet); }, &icons->menuStickerSet);
} }
@ -1827,7 +1890,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
const auto document = set.stickers[sticker->index].document; const auto document = set.stickers[sticker->index].document;
if (_features.openStickerSets if (_features.openStickerSets
&& (e->modifiers() & Qt::ControlModifier)) { && (e->modifiers() & Qt::ControlModifier)) {
showStickerSetBox(document); showStickerSetBox(document, set.id);
} else { } else {
auto settings = &AyuSettings::getInstance(); auto settings = &AyuSettings::getInstance();
auto from = messageSentAnimationInfo( auto from = messageSentAnimationInfo(

View file

@ -350,7 +350,9 @@ private:
void refreshFooterIcons(); void refreshFooterIcons();
void refreshIcons(ValidateIconAnimations animations); void refreshIcons(ValidateIconAnimations animations);
void showStickerSetBox(not_null<DocumentData*> document); void showStickerSetBox(
not_null<DocumentData*> document,
uint64 setId);
void cancelSetsSearch(); void cancelSetsSearch();
void showSearchResults(); void showSearchResults();

View file

@ -192,8 +192,11 @@ Application::Application()
_platformIntegration->init(); _platformIntegration->init();
passcodeLockChanges( passcodeLockChanges(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=](bool locked) {
_shouldLockAt = 0; _shouldLockAt = 0;
if (locked) {
closeAdditionalWindows();
}
}, _lifetime); }, _lifetime);
passcodeLockChanges( passcodeLockChanges(
@ -215,6 +218,16 @@ Application::Application()
}, _lifetime); }, _lifetime);
} }
void Application::closeAdditionalWindows() {
Payments::CheckoutProcess::ClearAll();
for (const auto &[index, account] : _domain->accounts()) {
if (account->sessionExists()) {
account->session().attachWebView().closeAll();
}
}
_iv->closeAll();
}
Application::~Application() { Application::~Application() {
if (_saveSettingsTimer && _saveSettingsTimer->isActive()) { if (_saveSettingsTimer && _saveSettingsTimer->isActive()) {
Local::writeSettings(); Local::writeSettings();
@ -234,9 +247,7 @@ Application::~Application() {
// //
// For example Domain::removeRedundantAccounts() is called from // For example Domain::removeRedundantAccounts() is called from
// Domain::finish() and there is a violation on Ensures(started()). // Domain::finish() and there is a violation on Ensures(started()).
Payments::CheckoutProcess::ClearAll(); closeAdditionalWindows();
InlineBots::AttachWebView::ClearAll();
_iv->closeAll();
_domain->finish(); _domain->finish();
@ -274,14 +285,9 @@ void Application::run() {
refreshGlobalProxy(); // Depends on app settings being read. refreshGlobalProxy(); // Depends on app settings being read.
if (const auto old = Local::oldSettingsVersion()) { if (const auto old = Local::oldSettingsVersion(); old < AppVersion) {
if (old < AppVersion) {
autoRegisterUrlScheme();
Platform::NewVersionLaunched(old);
}
} else {
// Initial launch.
autoRegisterUrlScheme(); autoRegisterUrlScheme();
Platform::NewVersionLaunched(old);
} }
if (cAutoStart() && !Platform::AutostartSupported()) { if (cAutoStart() && !Platform::AutostartSupported()) {
@ -692,7 +698,8 @@ bool Application::eventFilter(QObject *object, QEvent *e) {
if (const auto file = event->file(); !file.isEmpty()) { if (const auto file = event->file(); !file.isEmpty()) {
_filesToOpen.append(file); _filesToOpen.append(file);
_fileOpenTimer.callOnce(kFileOpenTimeoutMs); _fileOpenTimer.callOnce(kFileOpenTimeoutMs);
} else if (event->url().scheme() == u"tg"_q) { } else if (event->url().scheme() == u"tg"_q
|| event->url().scheme() == u"tonsite"_q) {
const auto url = QString::fromUtf8( const auto url = QString::fromUtf8(
event->url().toEncoded().trimmed()); event->url().toEncoded().trimmed());
cSetStartUrl(url.mid(0, 8192)); cSetStartUrl(url.mid(0, 8192));
@ -1093,13 +1100,18 @@ void Application::checkSendPaths() {
} }
void Application::checkStartUrl() { void Application::checkStartUrl() {
if (!cStartUrl().isEmpty() if (!cStartUrl().isEmpty()) {
&& _lastActivePrimaryWindow
&& !_lastActivePrimaryWindow->locked()) {
const auto url = cStartUrl(); const auto url = cStartUrl();
cSetStartUrl(QString()); if (!Core::App().passcodeLocked()) {
if (!openLocalUrl(url, {})) { if (url.startsWith("tonsite://", Qt::CaseInsensitive)) {
cSetStartUrl(url); cSetStartUrl(QString());
iv().showTonSite(url, {});
} else if (_lastActivePrimaryWindow) {
cSetStartUrl(QString());
if (!openLocalUrl(url, {})) {
cSetStartUrl(url);
}
}
} }
} }
} }
@ -1350,7 +1362,7 @@ Window::Controller *Application::ensureSeparateWindowFor(
Window::Controller *Application::windowFor(Window::SeparateId id) const { Window::Controller *Application::windowFor(Window::SeparateId id) const {
if (const auto separate = separateWindowFor(id)) { if (const auto separate = separateWindowFor(id)) {
return separate; return separate;
} else if (id && id.primary()) { } else if (id && !id.primary()) {
return windowFor(not_null(id.account)); return windowFor(not_null(id.account));
} }
return activePrimaryWindow(); return activePrimaryWindow();
@ -1807,11 +1819,13 @@ void Application::startShortcuts() {
} }
void Application::RegisterUrlScheme() { void Application::RegisterUrlScheme() {
const auto arguments = Launcher::Instance().customWorkingDir()
? u"-workdir \"%1\""_q.arg(cWorkingDir())
: QString();
base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{ base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
.executable = Platform::ExecutablePathForShortcuts(), .executable = Platform::ExecutablePathForShortcuts(),
.arguments = Launcher::Instance().customWorkingDir() .arguments = arguments,
? u"-workdir \"%1\""_q.arg(cWorkingDir())
: QString(),
.protocol = u"tg"_q, .protocol = u"tg"_q,
.protocolName = u"Telegram Link"_q, .protocolName = u"Telegram Link"_q,
.shortAppName = u"AyuGram"_q, .shortAppName = u"AyuGram"_q,
@ -1819,6 +1833,17 @@ void Application::RegisterUrlScheme() {
.displayAppName = AppName.utf16(), .displayAppName = AppName.utf16(),
.displayAppDescription = AppName.utf16(), .displayAppDescription = AppName.utf16(),
}); });
base::Platform::RegisterUrlScheme(base::Platform::UrlSchemeDescriptor{
.executable = Platform::ExecutablePathForShortcuts(),
.arguments = arguments,
.protocol = u"tonsite"_q,
.protocolName = u"TonSite Link"_q,
.shortAppName = u"tdesktop"_q,
.longAppName = QCoreApplication::applicationName(),
.displayAppName = AppName.utf16(),
.displayAppDescription = AppName.utf16(),
});
} }
bool IsAppLaunched() { bool IsAppLaunched() {

View file

@ -379,6 +379,7 @@ private:
void showOpenGLCrashNotification(); void showOpenGLCrashNotification();
void clearPasscodeLock(); void clearPasscodeLock();
void closeAdditionalWindows();
bool openCustomUrl( bool openCustomUrl(
const QString &protocol, const QString &protocol,

View file

@ -20,6 +20,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" #include "history/history.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "inline_bots/bot_attach_web_view.h"
#include "data/data_game.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "window/window_controller.h" #include "window/window_controller.h"
@ -120,7 +122,9 @@ void HiddenUrlClickHandler::Open(QString url, QVariant context) {
return result; return result;
}())); }()));
} else { } else {
const auto parsedUrl = QUrl::fromUserInput(url); const auto parsedUrl = url.startsWith(u"tonsite://"_q)
? QUrl(url)
: QUrl::fromUserInput(url);
if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) { if (UrlRequiresConfirmation(parsedUrl) && !base::IsCtrlPressed()) {
const auto my = context.value<ClickHandlerContext>(); const auto my = context.value<ClickHandlerContext>();
if (!my.show) { if (!my.show) {
@ -171,23 +175,42 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
if (Core::InternalPassportLink(url)) { if (Core::InternalPassportLink(url)) {
return; return;
} }
const auto openLink = [=] {
const auto open = [=] {
UrlClickHandler::Open(url, context.other); UrlClickHandler::Open(url, context.other);
}; };
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { const auto my = context.other.value<ClickHandlerContext>();
open(); const auto weakController = my.sessionWindow;
} else if (!_bot const auto controller = weakController.get();
|| _bot->isVerified() const auto item = controller
? controller->session().data().message(my.itemId)
: nullptr;
const auto media = item ? item->media() : nullptr;
const auto game = media ? media->game() : nullptr;
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
openLink();
}
const auto bot = _bot;
const auto title = game->title;
const auto itemId = my.itemId;
const auto openGame = [=] {
bot->session().attachWebView().open({
.bot = bot,
.button = {.url = url.toUtf8() },
.source = InlineBots::WebViewSourceGame{
.messageId = itemId,
.title = title,
},
});
};
if (_bot->isVerified()
|| _bot->session().local().isBotTrustedOpenGame(_bot->id)) { || _bot->session().local().isBotTrustedOpenGame(_bot->id)) {
open(); openGame();
} else { } else {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
const auto callback = [=, bot = _bot](Fn<void()> close) { const auto callback = [=, bot = _bot](Fn<void()> close) {
close(); close();
bot->session().local().markBotTrustedOpenGame(bot->id); bot->session().local().markBotTrustedOpenGame(bot->id);
open(); openGame();
}; };
controller->show(Ui::MakeConfirmBox({ controller->show(Ui::MakeConfirmBox({
.text = tr::lng_allow_bot_pass( .text = tr::lng_allow_bot_pass(

View file

@ -21,6 +21,10 @@ namespace Ui {
class Show; class Show;
} // namespace Ui } // namespace Ui
namespace InlineBots {
struct WebViewContext;
} // namespace InlineBots
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -38,10 +42,10 @@ class SessionController;
class PeerData; class PeerData;
struct ClickHandlerContext { struct ClickHandlerContext {
FullMsgId itemId; FullMsgId itemId;
QString attachBotWebviewUrl;
// Is filled from sections. // Is filled from sections.
Fn<HistoryView::ElementDelegate*()> elementDelegate; Fn<HistoryView::ElementDelegate*()> elementDelegate;
base::weak_ptr<Window::SessionController> sessionWindow; base::weak_ptr<Window::SessionController> sessionWindow;
std::shared_ptr<InlineBots::WebViewContext> botWebviewContext;
std::shared_ptr<Ui::Show> show; std::shared_ptr<Ui::Show> show;
bool mayShowConfirmation = false; bool mayShowConfirmation = false;
bool skipBotAutoLogin = false; bool skipBotAutoLogin = false;

View file

@ -224,7 +224,8 @@ QByteArray Settings::serialize() const {
+ Serialize::bytearraySize(ivPosition) + Serialize::bytearraySize(ivPosition)
+ Serialize::stringSize(noWarningExtensions) + Serialize::stringSize(noWarningExtensions)
+ Serialize::stringSize(_customFontFamily) + Serialize::stringSize(_customFontFamily)
+ sizeof(qint32) * 2; + sizeof(qint32) * 3
+ Serialize::bytearraySize(_tonsiteStorageToken);
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -376,7 +377,9 @@ QByteArray Settings::serialize() const {
qRound(_dialogsNoChatWidthRatio.current() * 1000000), qRound(_dialogsNoChatWidthRatio.current() * 1000000),
0, 0,
1000000)) 1000000))
<< qint32(_systemUnlockEnabled ? 1 : 0); << qint32(_systemUnlockEnabled ? 1 : 0)
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
<< _tonsiteStorageToken;
} }
Ensures(result.size() == size); Ensures(result.size() == size);
@ -499,6 +502,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
QByteArray ivPosition; QByteArray ivPosition;
QString customFontFamily = _customFontFamily; QString customFontFamily = _customFontFamily;
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0; qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
stream >> themesAccentColors; stream >> themesAccentColors;
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -799,6 +804,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> systemUnlockEnabled; stream >> systemUnlockEnabled;
} }
if (!stream.atEnd()) {
stream >> weatherInCelsius;
}
if (!stream.atEnd()) {
stream >> tonsiteStorageToken;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -1008,6 +1019,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
} }
_customFontFamily = customFontFamily; _customFontFamily = customFontFamily;
_systemUnlockEnabled = (systemUnlockEnabled == 1); _systemUnlockEnabled = (systemUnlockEnabled == 1);
_weatherInCelsius = !weatherInCelsius
? std::optional<bool>()
: (weatherInCelsius == 1);
_tonsiteStorageToken = tonsiteStorageToken;
} }
QString Settings::getSoundPath(const QString &key) const { QString Settings::getSoundPath(const QString &key) const {

View file

@ -897,6 +897,20 @@ public:
_systemUnlockEnabled = enabled; _systemUnlockEnabled = enabled;
} }
[[nodiscard]] std::optional<bool> weatherInCelsius() const {
return _weatherInCelsius;
}
void setWeatherInCelsius(bool value) {
_weatherInCelsius = value;
}
[[nodiscard]] QByteArray tonsiteStorageToken() const {
return _tonsiteStorageToken;
}
void setTonsiteStorageToken(const QByteArray &value) {
_tonsiteStorageToken = value;
}
[[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio(); [[nodiscard]] static float64 DefaultDialogsWidthRatio();
@ -1028,6 +1042,8 @@ private:
WindowPosition _ivPosition; WindowPosition _ivPosition;
QString _customFontFamily; QString _customFontFamily;
bool _systemUnlockEnabled = false; bool _systemUnlockEnabled = false;
std::optional<bool> _weatherInCelsius;
QByteArray _tonsiteStorageToken;
bool _tabbedReplacedWithInfo = false; // per-window bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window

View file

@ -296,13 +296,17 @@ bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context,
QString PlatformString() { QString PlatformString() {
if (Platform::IsWindowsStoreBuild()) { if (Platform::IsWindowsStoreBuild()) {
return Platform::IsWindows64Bit() return Platform::IsWindowsARM64()
? u"WinStoreARM64"_q
: Platform::IsWindows64Bit()
? u"WinStore64Bit"_q ? u"WinStore64Bit"_q
: u"WinStore32Bit"_q; : u"WinStore32Bit"_q;
} else if (Platform::IsWindows32Bit()) { } else if (Platform::IsWindows32Bit()) {
return u"Windows32Bit"_q; return u"Windows32Bit"_q;
} else if (Platform::IsWindows64Bit()) { } else if (Platform::IsWindows64Bit()) {
return u"Windows64Bit"_q; return u"Windows64Bit"_q;
} else if (Platform::IsWindowsARM64()) {
return u"WindowsARM64"_q;
} else if (Platform::IsMacStoreBuild()) { } else if (Platform::IsMacStoreBuild()) {
return u"MacAppStore"_q; return u"MacAppStore"_q;
} else if (Platform::IsMac()) { } else if (Platform::IsMac()) {

View file

@ -0,0 +1,243 @@
/*
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 "core/current_geo_location.h"
#include "base/platform/base_platform_info.h"
#include "base/invoke_queued.h"
#include "base/timer.h"
#include "data/raw/raw_countries_bounds.h"
#include "platform/platform_current_geo_location.h"
#include "ui/ui_utility.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtCore/QCoreApplication>
#include <QtCore/QPointer>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
namespace Core {
namespace {
constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000);
[[nodiscard]] QString ChooseLanguage(const QString &language) {
// https://docs.mapbox.com/api/search/geocoding#language-coverage
auto result = language.toLower().replace('-', '_');
static const auto kGood = std::array{
// Global coverage.
u"de"_q, u"en"_q, u"es"_q, u"fr"_q, u"it"_q, u"nl"_q, u"pl"_q,
// Local coverage.
u"az"_q, u"bn"_q, u"ca"_q, u"cs"_q, u"da"_q, u"el"_q, u"fa"_q,
u"fi"_q, u"ga"_q, u"hu"_q, u"id"_q, u"is"_q, u"ja"_q, u"ka"_q,
u"km"_q, u"ko"_q, u"lt"_q, u"lv"_q, u"mn"_q, u"pt"_q, u"ro"_q,
u"sk"_q, u"sq"_q, u"sv"_q, u"th"_q, u"tl"_q, u"uk"_q, u"vi"_q,
u"zh"_q, u"zh_Hans"_q, u"zh_TW"_q,
// Limited coverage.
u"ar"_q, u"bs"_q, u"gu"_q, u"he"_q, u"hi"_q, u"kk"_q, u"lo"_q,
u"my"_q, u"nb"_q, u"ru"_q, u"sr"_q, u"te"_q, u"tk"_q, u"tr"_q,
u"zh_Hant"_q,
};
for (const auto &known : kGood) {
if (known.toLower() == result) {
return known;
}
}
if (const auto delimeter = result.indexOf('_'); delimeter > 0) {
result = result.mid(0, delimeter);
for (const auto &known : kGood) {
if (known == result) {
return known;
}
}
}
return u"en"_q;
}
void ResolveLocationAddressGeneric(
const GeoLocation &location,
const QString &language,
const QString &token,
Fn<void(GeoAddress)> callback) {
const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6"
"/reverse?longitude=%1&latitude=%2&language=%3&access_token=%4"_q
.arg(location.point.y())
.arg(location.point.x())
.arg(ChooseLanguage(language));
static auto Cache = base::flat_map<QString, GeoAddress>();
const auto i = Cache.find(partialUrl);
if (i != end(Cache)) {
callback(i->second);
return;
}
const auto finishWith = [=](GeoAddress result) {
Cache[partialUrl] = result;
callback(result);
};
struct State final : QObject {
explicit State(QObject *parent)
: QObject(parent)
, manager(this)
, destroyer([=] { if (sent.empty()) delete this; }) {
}
QNetworkAccessManager manager;
std::vector<QPointer<QNetworkReply>> sent;
base::Timer destroyer;
};
static auto state = QPointer<State>();
if (!state) {
state = Ui::CreateChild<State>(qApp);
}
const auto destroyReplyDelayed = [](QNetworkReply *reply) {
InvokeQueued(reply, [=] {
for (auto i = begin(state->sent); i != end(state->sent);) {
if (!*i || *i == reply) {
i = state->sent.erase(i);
} else {
++i;
}
}
delete reply;
if (state->sent.empty()) {
state->destroyer.callOnce(kDestroyManagerTimeout);
}
});
};
auto request = QNetworkRequest(partialUrl.arg(token));
request.setRawHeader("Referer", "http://desktop-app-resource/");
const auto reply = state->manager.get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
destroyReplyDelayed(reply);
const auto json = QJsonDocument::fromJson(reply->readAll());
if (!json.isObject()) {
finishWith({});
return;
}
const auto features = json["features"].toArray();
if (features.isEmpty()) {
finishWith({});
return;
}
const auto feature = features.at(0).toObject();
const auto properties = feature["properties"].toObject();
const auto context = properties["context"].toObject();
auto names = QStringList();
auto add = [&](std::vector<QString> keys) {
for (const auto &key : keys) {
const auto value = context[key];
if (value.isObject()) {
const auto name = value.toObject()["name"].toString();
if (!name.isEmpty()) {
names.push_back(name);
break;
}
}
}
};
add({ /*u"address"_q, u"street"_q, */u"neighborhood"_q });
add({ u"place"_q, u"region"_q });
add({ u"country"_q });
finishWith({ .name = names.join(", ") });
});
QObject::connect(reply, &QNetworkReply::errorOccurred, [=] {
destroyReplyDelayed(reply);
finishWith({});
});
}
} // namespace
GeoLocation ResolveCurrentCountryLocation() {
const auto iso2 = Platform::SystemCountry().toUpper();
const auto &bounds = Raw::CountryBounds();
const auto i = bounds.find(iso2);
if (i == end(bounds)) {
return {
.accuracy = GeoLocationAccuracy::Failed,
};
}
return {
.point = {
(i->second.minLat + i->second.maxLat) / 2.,
(i->second.minLon + i->second.maxLon) / 2.,
},
.bounds = {
i->second.minLat,
i->second.minLon,
i->second.maxLat - i->second.minLat,
i->second.maxLon - i->second.minLon,
},
.accuracy = GeoLocationAccuracy::Country,
};
}
void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) {
using namespace Platform;
return ResolveCurrentExactLocation([done = std::move(callback)](
GeoLocation result) {
done(result.accuracy != GeoLocationAccuracy::Failed
? result
: ResolveCurrentCountryLocation());
});
}
void ResolveLocationAddress(
const GeoLocation &location,
const QString &language,
const QString &token,
Fn<void(GeoAddress)> callback) {
auto done = [=, done = std::move(callback)](GeoAddress result) mutable {
if (!result && !token.isEmpty()) {
ResolveLocationAddressGeneric(
location,
language,
token,
std::move(done));
} else {
done(result);
}
};
Platform::ResolveLocationAddress(location, language, std::move(done));
}
bool AreTheSame(const GeoLocation &a, const GeoLocation &b) {
if (a.accuracy != GeoLocationAccuracy::Exact
|| b.accuracy != GeoLocationAccuracy::Exact) {
return false;
}
const auto normalize = [](float64 value) {
value = std::fmod(value + 180., 360.);
return (value + (value < 0. ? 360. : 0.)) - 180.;
};
constexpr auto kEpsilon = 0.0001;
const auto lon1 = normalize(a.point.y());
const auto lon2 = normalize(b.point.y());
const auto diffLat = std::abs(a.point.x() - b.point.x());
if (std::abs(a.point.x()) >= (90. - kEpsilon)
|| std::abs(b.point.x()) >= (90. - kEpsilon)) {
return diffLat <= kEpsilon;
}
auto diffLon = std::abs(lon1 - lon2);
if (diffLon > 180.) {
diffLon = 360. - diffLon;
}
return diffLat <= kEpsilon && diffLon <= kEpsilon;
}
} // namespace Core

View file

@ -0,0 +1,60 @@
/*
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 Core {
enum class GeoLocationAccuracy : uchar {
Exact,
Country,
Failed,
};
struct GeoLocation {
QPointF point;
QRectF bounds;
GeoLocationAccuracy accuracy = GeoLocationAccuracy::Failed;
[[nodiscard]] bool exact() const {
return accuracy == GeoLocationAccuracy::Exact;
}
[[nodiscard]] bool country() const {
return accuracy == GeoLocationAccuracy::Country;
}
[[nodiscard]] bool failed() const {
return accuracy == GeoLocationAccuracy::Failed;
}
explicit operator bool() const {
return !failed();
}
};
[[nodiscard]] bool AreTheSame(const GeoLocation &a, const GeoLocation &b);
struct GeoAddress {
QString name;
[[nodiscard]] bool empty() const {
return name.isEmpty();
}
explicit operator bool() const {
return !empty();
}
};
[[nodiscard]] GeoLocation ResolveCurrentCountryLocation();
void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback);
void ResolveLocationAddress(
const GeoLocation &location,
const QString &language,
const QString &token,
Fn<void(GeoAddress)> callback);
} // namespace Core

View file

@ -331,21 +331,6 @@ bool ConfirmPhone(
return true; return true;
} }
bool ShareGameScore(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto params = url_parse_params(
match->captured(1),
qthelp::UrlParamNameTransform::ToLower);
ShareGameScoreByHash(controller, params.value(u"hash"_q));
controller->window().activate();
return true;
}
bool ApplySocksProxy( bool ApplySocksProxy(
Window::SessionController *controller, Window::SessionController *controller,
const Match &match, const Match &match,
@ -522,7 +507,9 @@ bool ResolveUsernameOrPhone(
return false; return false;
} }
using ResolveType = Window::ResolveType; using ResolveType = Window::ResolveType;
auto resolveType = ResolveType::Default; auto resolveType = params.contains(u"profile"_q)
? ResolveType::Profile
: ResolveType::Default;
auto startToken = params.value(u"start"_q); auto startToken = params.value(u"start"_q);
if (!startToken.isEmpty()) { if (!startToken.isEmpty()) {
resolveType = ResolveType::BotStart; resolveType = ResolveType::BotStart;
@ -592,8 +579,11 @@ bool ResolveUsernameOrPhone(
: (appname.isEmpty() && params.contains(u"startapp"_q)) : (appname.isEmpty() && params.contains(u"startapp"_q))
? params.value(u"startapp"_q) ? params.value(u"startapp"_q)
: std::optional<QString>()), : std::optional<QString>()),
.attachBotMenuOpen = (appname.isEmpty() .attachBotMainOpen = (appname.isEmpty()
&& params.contains(u"startapp"_q)), && params.contains(u"startapp"_q)),
.attachBotMainCompact = (appname.isEmpty()
&& params.contains(u"startapp"_q)
&& (params.value(u"mode"_q) == u"compact"_q)),
.attachBotChooseTypes = InlineBots::ParseChooseTypes( .attachBotChooseTypes = InlineBots::ParseChooseTypes(
params.value(u"choose"_q)), params.value(u"choose"_q)),
.voicechatHash = (params.contains(u"livestream"_q) .voicechatHash = (params.contains(u"livestream"_q)
@ -604,7 +594,7 @@ bool ResolveUsernameOrPhone(
? std::make_optional(params.value(u"voicechat"_q)) ? std::make_optional(params.value(u"voicechat"_q))
: std::nullopt), : std::nullopt),
.clickFromMessageId = myContext.itemId, .clickFromMessageId = myContext.itemId,
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl, .clickFromBotWebviewContext = myContext.botWebviewContext,
}); });
return true; return true;
} }
@ -645,7 +635,7 @@ bool ResolvePrivatePost(
} }
: Window::RepliesByLinkInfo{ v::null }, : Window::RepliesByLinkInfo{ v::null },
.clickFromMessageId = my.itemId, .clickFromMessageId = my.itemId,
.clickFromAttachBotWebviewUrl = my.attachBotWebviewUrl, .clickFromBotWebviewContext = my.botWebviewContext,
}); });
controller->window().activate(); controller->window().activate();
return true; return true;
@ -1197,7 +1187,7 @@ bool ResolveChatLink(
controller->showPeerByLink(Window::PeerByLinkInfo{ controller->showPeerByLink(Window::PeerByLinkInfo{
.chatLinkSlug = match->captured(1), .chatLinkSlug = match->captured(1),
.clickFromMessageId = myContext.itemId, .clickFromMessageId = myContext.itemId,
.clickFromAttachBotWebviewUrl = myContext.attachBotWebviewUrl, .clickFromBotWebviewContext = myContext.botWebviewContext,
}); });
return true; return true;
} }
@ -1234,10 +1224,6 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"^confirmphone/?\\?(.+)(#|$)"_q, u"^confirmphone/?\\?(.+)(#|$)"_q,
ConfirmPhone ConfirmPhone
}, },
{
u"^share_game_score/?\\?(.+)(#|$)"_q,
ShareGameScore
},
{ {
u"^socks/?\\?(.+)(#|$)"_q, u"^socks/?\\?(.+)(#|$)"_q,
ApplySocksProxy ApplySocksProxy
@ -1291,7 +1277,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolveBoost, ResolveBoost,
}, },
{ {
u"^message/?\\?slug=([a-zA-Z0-9\\.\\_]+)(&|$)"_q, u"^message/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
ResolveChatLink ResolveChatLink
}, },
{ {
@ -1363,6 +1349,13 @@ QString TryConvertUrlToLocal(QString url) {
using namespace qthelp; using namespace qthelp;
auto matchOptions = RegExOption::CaseInsensitive; auto matchOptions = RegExOption::CaseInsensitive;
auto tonsiteMatch = (url.indexOf(u".ton") >= 0)
? regex_match(u"^(https?://)?[^/@:]+\\.ton($|/)"_q, url, matchOptions)
: RegularExpressionMatch(QRegularExpressionMatch());
if (tonsiteMatch) {
const auto protocol = tonsiteMatch->captured(1);
return u"tonsite://"_q + url.mid(protocol.size());
}
auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions); auto subdomainMatch = regex_match(u"^(https?://)?([a-zA-Z0-9\\_]+)\\.t\\.me(/\\d+)?/?(\\?.+)?"_q, url, matchOptions);
if (subdomainMatch) { if (subdomainMatch) {
const auto name = subdomainMatch->captured(2); const auto name = subdomainMatch->captured(2);

View file

@ -242,6 +242,9 @@ bool UiIntegration::handleUrlClick(
} else if (local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) { } else if (local.startsWith(u"tg://"_q, Qt::CaseInsensitive)) {
Core::App().openLocalUrl(local, context); Core::App().openLocalUrl(local, context);
return true; return true;
} else if (local.startsWith(u"tonsite://"_q, Qt::CaseInsensitive)) {
Core::App().iv().showTonSite(local, context);
return true;
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) { } else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context); Core::App().openInternalUrl(local, context);
return true; return true;

View file

@ -245,6 +245,7 @@ QString FindUpdateFile() {
"^(" "^("
"tupdate|" "tupdate|"
"tx64upd|" "tx64upd|"
"tarm64upd|"
"tmacupd|" "tmacupd|"
"tarmacupd|" "tarmacupd|"
"tlinuxupd|" "tlinuxupd|"

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5002002; constexpr auto AppVersion = 5003002;
constexpr auto AppVersionStr = "5.2.2"; constexpr auto AppVersionStr = "5.3.2";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -130,6 +130,42 @@ void BusinessInfo::saveChatIntro(ChatIntro data, Fn<void(QString)> fail) {
session->user()->setBusinessDetails(std::move(details)); session->user()->setBusinessDetails(std::move(details));
} }
void BusinessInfo::saveLocation(
BusinessLocation data,
Fn<void(QString)> fail) {
const auto session = &_owner->session();
auto details = session->user()->businessDetails();
const auto &was = details.location;
if (was == data) {
return;
} else {
const auto session = &_owner->session();
using Flag = MTPaccount_UpdateBusinessLocation::Flag;
session->api().request(MTPaccount_UpdateBusinessLocation(
MTP_flags((data.point ? Flag::f_geo_point : Flag())
| (data.address.isEmpty() ? Flag() : Flag::f_address)),
(data.point
? MTP_inputGeoPoint(
MTP_flags(0),
MTP_double(data.point->lat()),
MTP_double(data.point->lon()),
MTPint()) // accuracy_radius
: MTP_inputGeoPointEmpty()),
MTP_string(data.address)
)).fail([=](const MTP::Error &error) {
auto details = session->user()->businessDetails();
details.location = was;
session->user()->setBusinessDetails(std::move(details));
if (fail) {
fail(error.type());
}
}).send();
}
details.location = std::move(data);
session->user()->setBusinessDetails(std::move(details));
}
void BusinessInfo::applyAwaySettings(AwaySettings data) { void BusinessInfo::applyAwaySettings(AwaySettings data) {
if (_awaySettings == data) { if (_awaySettings == data) {
return; return;

View file

@ -22,6 +22,7 @@ public:
void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail); void saveWorkingHours(WorkingHours data, Fn<void(QString)> fail);
void saveChatIntro(ChatIntro data, Fn<void(QString)> fail); void saveChatIntro(ChatIntro data, Fn<void(QString)> fail);
void saveLocation(BusinessLocation data, Fn<void(QString)> fail);
void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail); void saveAwaySettings(AwaySettings data, Fn<void(QString)> fail);
void applyAwaySettings(AwaySettings data); void applyAwaySettings(AwaySettings data);

View file

@ -0,0 +1,44 @@
/*
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 "data/components/location_pickers.h"
#include "api/api_common.h"
#include "ui/controls/location_picker.h"
namespace Data {
struct LocationPickers::Entry {
Api::SendAction action;
base::weak_ptr<Ui::LocationPicker> picker;
};
LocationPickers::LocationPickers() = default;
LocationPickers::~LocationPickers() = default;
Ui::LocationPicker *LocationPickers::lookup(const Api::SendAction &action) {
for (auto i = begin(_pickers); i != end(_pickers);) {
if (const auto strong = i->picker.get()) {
if (i->action == action) {
return i->picker.get();
}
++i;
} else {
i = _pickers.erase(i);
}
}
return nullptr;
}
void LocationPickers::emplace(
const Api::SendAction &action,
not_null<Ui::LocationPicker*> picker) {
_pickers.push_back({ action, picker });
}
} // namespace Data

View file

@ -0,0 +1,39 @@
/*
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/weak_ptr.h"
namespace Api {
struct SendAction;
} // namespace Api
namespace Ui {
class LocationPicker;
} // namespace Ui
namespace Data {
class LocationPickers final {
public:
LocationPickers();
~LocationPickers();
Ui::LocationPicker *lookup(const Api::SendAction &action);
void emplace(
const Api::SendAction &action,
not_null<Ui::LocationPicker*> picker);
private:
struct Entry;
std::vector<Entry> _pickers;
};
} // namespace Data

View file

@ -41,12 +41,36 @@ constexpr auto kRequestTimeLimit = 10 * crl::time(1000);
) / 1'000'000.; ) / 1'000'000.;
} }
[[nodiscard]] MTPTopPeerCategory TypeToCategory(TopPeerType type) {
switch (type) {
case TopPeerType::Chat: return MTP_topPeerCategoryCorrespondents();
case TopPeerType::BotApp: return MTP_topPeerCategoryBotsApp();
}
Unexpected("Type in TypeToCategory.");
}
[[nodiscard]] auto TypeToGetFlags(TopPeerType type) {
using Flag = MTPcontacts_GetTopPeers::Flag;
switch (type) {
case TopPeerType::Chat: return Flag::f_correspondents;
case TopPeerType::BotApp: return Flag::f_bots_app;
}
Unexpected("Type in TypeToGetFlags.");
}
} // namespace } // namespace
TopPeers::TopPeers(not_null<Main::Session*> session) TopPeers::TopPeers(not_null<Main::Session*> session, TopPeerType type)
: _session(session) { : _session(session)
, _type(type) {
if (_type == TopPeerType::Chat) {
loadAfterChats();
}
}
void TopPeers::loadAfterChats() {
using namespace rpl::mappers; using namespace rpl::mappers;
crl::on_main(session, [=] { crl::on_main(_session, [=] {
_session->data().chatsListLoadedEvents( _session->data().chatsListLoadedEvents(
) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] { ) | rpl::filter(_1 == nullptr) | rpl::start_with_next([=] {
crl::on_main(_session, [=] { crl::on_main(_session, [=] {
@ -84,7 +108,7 @@ void TopPeers::remove(not_null<PeerData*> peer) {
} }
_requestId = _session->api().request(MTPcontacts_ResetTopPeerRating( _requestId = _session->api().request(MTPcontacts_ResetTopPeerRating(
MTP_topPeerCategoryCorrespondents(), TypeToCategory(_type),
peer->input peer->input
)).send(); )).send();
} }
@ -160,11 +184,13 @@ void TopPeers::request() {
} }
_requestId = _session->api().request(MTPcontacts_GetTopPeers( _requestId = _session->api().request(MTPcontacts_GetTopPeers(
MTP_flags(MTPcontacts_GetTopPeers::Flag::f_correspondents), MTP_flags(TypeToGetFlags(_type)),
MTP_int(0), MTP_int(0),
MTP_int(kLimit), MTP_int(kLimit),
MTP_long(countHash()) MTP_long(countHash())
)).done([=](const MTPcontacts_TopPeers &result, const MTP::Response &response) { )).done([=](
const MTPcontacts_TopPeers &result,
const MTP::Response &response) {
_lastReceivedDate = TimeId(response.outerMsgId >> 32); _lastReceivedDate = TimeId(response.outerMsgId >> 32);
_lastReceived = crl::now(); _lastReceived = crl::now();
_requestId = 0; _requestId = 0;
@ -176,19 +202,22 @@ void TopPeers::request() {
owner->processChats(data.vchats()); owner->processChats(data.vchats());
for (const auto &category : data.vcategories().v) { for (const auto &category : data.vcategories().v) {
const auto &data = category.data(); const auto &data = category.data();
data.vcategory().match( const auto cons = (_type == TopPeerType::Chat)
[&](const MTPDtopPeerCategoryCorrespondents &) { ? mtpc_topPeerCategoryCorrespondents
_list = ranges::views::all( : mtpc_topPeerCategoryBotsApp;
data.vpeers().v if (data.vcategory().type() != cons) {
) | ranges::views::transform([&](const MTPTopPeer &top) {
return TopPeer{
owner->peer(peerFromMTP(top.data().vpeer())),
top.data().vrating().v,
};
}) | ranges::to_vector;
}, [](const auto &) {
LOG(("API Error: Unexpected top peer category.")); LOG(("API Error: Unexpected top peer category."));
}); continue;
}
_list = ranges::views::all(
data.vpeers().v
) | ranges::views::transform([&](
const MTPTopPeer &top) {
return TopPeer{
owner->peer(peerFromMTP(top.data().vpeer())),
top.data().vrating().v,
};
}) | ranges::to_vector;
} }
updated(); updated();
}, [&](const MTPDcontacts_topPeersDisabled &) { }, [&](const MTPDcontacts_topPeersDisabled &) {

View file

@ -13,9 +13,14 @@ class Session;
namespace Data { namespace Data {
enum class TopPeerType {
Chat,
BotApp,
};
class TopPeers final { class TopPeers final {
public: public:
explicit TopPeers(not_null<Main::Session*> session); TopPeers(not_null<Main::Session*> session, TopPeerType type);
~TopPeers(); ~TopPeers();
[[nodiscard]] std::vector<not_null<PeerData*>> list() const; [[nodiscard]] std::vector<not_null<PeerData*>> list() const;
@ -36,11 +41,13 @@ private:
float64 rating = 0.; float64 rating = 0.;
}; };
void loadAfterChats();
void request(); void request();
[[nodiscard]] uint64 countHash() const; [[nodiscard]] uint64 countHash() const;
void updated(); void updated();
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
const TopPeerType _type = {};
std::vector<TopPeer> _list; std::vector<TopPeer> _list;
rpl::event_stream<> _updates; rpl::event_stream<> _updates;

View file

@ -1089,7 +1089,8 @@ void ApplyChannelUpdate(
| Flag::CanGetStatistics | Flag::CanGetStatistics
| Flag::ViewAsMessages | Flag::ViewAsMessages
| Flag::CanViewRevenue | Flag::CanViewRevenue
| Flag::PaidMediaAllowed; | Flag::PaidMediaAllowed
| Flag::CanViewCreditsRevenue;
channel->setFlags((channel->flags() & ~mask) channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants() | (update.is_can_view_participants()
@ -1107,7 +1108,10 @@ void ApplyChannelUpdate(
? Flag::ViewAsMessages ? Flag::ViewAsMessages
: Flag()) : Flag())
| (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag()) | (update.is_paid_media_allowed() ? Flag::PaidMediaAllowed : Flag())
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())); | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())
| (update.is_can_view_stars_revenue()
? Flag::CanViewCreditsRevenue
: Flag()));
channel->setUserpicPhoto(update.vchat_photo()); channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) { if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
channel->addFlags(Flag::Megagroup); channel->addFlags(Flag::Megagroup);

View file

@ -67,6 +67,7 @@ enum class ChannelDataFlag : uint64 {
SimilarExpanded = (1ULL << 31), SimilarExpanded = (1ULL << 31),
CanViewRevenue = (1ULL << 32), CanViewRevenue = (1ULL << 32),
PaidMediaAllowed = (1ULL << 33), PaidMediaAllowed = (1ULL << 33),
CanViewCreditsRevenue = (1ULL << 34),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;

View file

@ -47,6 +47,7 @@ ChatFilter::ChatFilter(
FilterId id, FilterId id,
const QString &title, const QString &title,
const QString &iconEmoji, const QString &iconEmoji,
std::optional<uint8> colorIndex,
Flags flags, Flags flags,
base::flat_set<not_null<History*>> always, base::flat_set<not_null<History*>> always,
std::vector<not_null<History*>> pinned, std::vector<not_null<History*>> pinned,
@ -54,6 +55,7 @@ ChatFilter::ChatFilter(
: _id(id) : _id(id)
, _title(title) , _title(title)
, _iconEmoji(iconEmoji) , _iconEmoji(iconEmoji)
, _colorIndex(colorIndex)
, _always(std::move(always)) , _always(std::move(always))
, _pinned(std::move(pinned)) , _pinned(std::move(pinned))
, _never(std::move(never)) , _never(std::move(never))
@ -99,6 +101,9 @@ ChatFilter ChatFilter::FromTL(
data.vid().v, data.vid().v,
qs(data.vtitle()), qs(data.vtitle()),
qs(data.vemoticon().value_or_empty()), qs(data.vemoticon().value_or_empty()),
data.vcolor()
? std::make_optional(data.vcolor()->v)
: std::nullopt,
flags, flags,
std::move(list), std::move(list),
std::move(pinned), std::move(pinned),
@ -144,6 +149,9 @@ ChatFilter ChatFilter::FromTL(
data.vid().v, data.vid().v,
qs(data.vtitle()), qs(data.vtitle()),
qs(data.vemoticon().value_or_empty()), qs(data.vemoticon().value_or_empty()),
data.vcolor()
? std::make_optional(data.vcolor()->v)
: std::nullopt,
(Flag::Chatlist (Flag::Chatlist
| (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())), | (data.is_has_my_invites() ? Flag::HasMyLinks : Flag())),
std::move(list), std::move(list),
@ -193,18 +201,20 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
} }
if (_flags & Flag::Chatlist) { if (_flags & Flag::Chatlist) {
using TLFlag = MTPDdialogFilterChatlist::Flag; using TLFlag = MTPDdialogFilterChatlist::Flag;
const auto flags = TLFlag::f_emoticon; const auto flags = TLFlag::f_emoticon
| (_colorIndex ? TLFlag::f_color : TLFlag(0));
return MTP_dialogFilterChatlist( return MTP_dialogFilterChatlist(
MTP_flags(flags), MTP_flags(flags),
MTP_int(replaceId ? replaceId : _id), MTP_int(replaceId ? replaceId : _id),
MTP_string(_title), MTP_string(_title),
MTP_string(_iconEmoji), MTP_string(_iconEmoji),
MTPint(), // color MTP_int(_colorIndex.value_or(0)),
MTP_vector<MTPInputPeer>(pinned), MTP_vector<MTPInputPeer>(pinned),
MTP_vector<MTPInputPeer>(include)); MTP_vector<MTPInputPeer>(include));
} }
using TLFlag = MTPDdialogFilter::Flag; using TLFlag = MTPDdialogFilter::Flag;
const auto flags = TLFlag::f_emoticon const auto flags = TLFlag::f_emoticon
| (_colorIndex ? TLFlag::f_color : TLFlag(0))
| ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0)) | ((_flags & Flag::Contacts) ? TLFlag::f_contacts : TLFlag(0))
| ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0)) | ((_flags & Flag::NonContacts) ? TLFlag::f_non_contacts : TLFlag(0))
| ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0)) | ((_flags & Flag::Groups) ? TLFlag::f_groups : TLFlag(0))
@ -225,7 +235,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const {
MTP_int(replaceId ? replaceId : _id), MTP_int(replaceId ? replaceId : _id),
MTP_string(_title), MTP_string(_title),
MTP_string(_iconEmoji), MTP_string(_iconEmoji),
MTPint(), // color MTP_int(_colorIndex.value_or(0)),
MTP_vector<MTPInputPeer>(pinned), MTP_vector<MTPInputPeer>(pinned),
MTP_vector<MTPInputPeer>(include), MTP_vector<MTPInputPeer>(include),
MTP_vector<MTPInputPeer>(never)); MTP_vector<MTPInputPeer>(never));
@ -243,6 +253,10 @@ QString ChatFilter::iconEmoji() const {
return _iconEmoji; return _iconEmoji;
} }
std::optional<uint8> ChatFilter::colorIndex() const {
return _colorIndex;
}
ChatFilter::Flags ChatFilter::flags() const { ChatFilter::Flags ChatFilter::flags() const {
return _flags; return _flags;
} }
@ -572,7 +586,7 @@ void ChatFilters::applyInsert(ChatFilter filter, int position) {
_list.insert( _list.insert(
begin(_list) + position, begin(_list) + position,
ChatFilter(filter.id(), {}, {}, {}, {}, {}, {})); ChatFilter(filter.id(), {}, {}, {}, {}, {}, {}, {}));
applyChange(*(begin(_list) + position), std::move(filter)); applyChange(*(begin(_list) + position), std::move(filter));
} }
@ -599,7 +613,7 @@ void ChatFilters::applyRemove(int position) {
Expects(position >= 0 && position < _list.size()); Expects(position >= 0 && position < _list.size());
const auto i = begin(_list) + position; const auto i = begin(_list) + position;
applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {})); applyChange(*i, ChatFilter(i->id(), {}, {}, {}, {}, {}, {}, {}));
_list.erase(i); _list.erase(i);
} }
@ -728,6 +742,7 @@ const ChatFilter &ChatFilters::applyUpdatedPinned(
id, id,
i->title(), i->title(),
i->iconEmoji(), i->iconEmoji(),
i->colorIndex(),
i->flags(), i->flags(),
std::move(always), std::move(always),
std::move(pinned), std::move(pinned),

View file

@ -52,6 +52,7 @@ public:
FilterId id, FilterId id,
const QString &title, const QString &title,
const QString &iconEmoji, const QString &iconEmoji,
std::optional<uint8> colorIndex,
Flags flags, Flags flags,
base::flat_set<not_null<History*>> always, base::flat_set<not_null<History*>> always,
std::vector<not_null<History*>> pinned, std::vector<not_null<History*>> pinned,
@ -71,6 +72,7 @@ public:
[[nodiscard]] FilterId id() const; [[nodiscard]] FilterId id() const;
[[nodiscard]] QString title() const; [[nodiscard]] QString title() const;
[[nodiscard]] QString iconEmoji() const; [[nodiscard]] QString iconEmoji() const;
[[nodiscard]] std::optional<uint8> colorIndex() const;
[[nodiscard]] Flags flags() const; [[nodiscard]] Flags flags() const;
[[nodiscard]] bool chatlist() const; [[nodiscard]] bool chatlist() const;
[[nodiscard]] bool hasMyLinks() const; [[nodiscard]] bool hasMyLinks() const;
@ -84,6 +86,7 @@ private:
FilterId _id = 0; FilterId _id = 0;
QString _title; QString _title;
QString _iconEmoji; QString _iconEmoji;
std::optional<uint8> _colorIndex;
base::flat_set<not_null<History*>> _always; base::flat_set<not_null<History*>> _always;
std::vector<not_null<History*>> _pinned; std::vector<not_null<History*>> _pinned;
base::flat_set<not_null<History*>> _never; base::flat_set<not_null<History*>> _never;
@ -94,6 +97,7 @@ private:
inline bool operator==(const ChatFilter &a, const ChatFilter &b) { inline bool operator==(const ChatFilter &a, const ChatFilter &b) {
return (a.title() == b.title()) return (a.title() == b.title())
&& (a.iconEmoji() == b.iconEmoji()) && (a.iconEmoji() == b.iconEmoji())
&& (a.colorIndex() == b.colorIndex())
&& (a.flags() == b.flags()) && (a.flags() == b.flags())
&& (a.always() == b.always()) && (a.always() == b.always())
&& (a.never() == b.never()); && (a.never() == b.never());

View file

@ -15,6 +15,7 @@ struct CreditTopupOption final {
QString currency; QString currency;
uint64 amount = 0; uint64 amount = 0;
bool extended = false; bool extended = false;
uint64 giftBarePeerId = 0;
}; };
using CreditTopupOptions = std::vector<CreditTopupOption>; using CreditTopupOptions = std::vector<CreditTopupOption>;
@ -57,7 +58,7 @@ struct CreditsHistoryEntry final {
QDateTime successDate; QDateTime successDate;
QString successLink; QString successLink;
bool in = false; bool in = false;
bool gift = false;
}; };
struct CreditsStatusSlice final { struct CreditsStatusSlice final {

View file

@ -26,6 +26,11 @@ LocationPoint::LocationPoint(const MTPDgeoPoint &point)
, _access(point.vaccess_hash().v) { , _access(point.vaccess_hash().v) {
} }
LocationPoint::LocationPoint(float64 lat, float64 lon, IgnoreAccessHash)
: _lat(lat)
, _lon(lon) {
}
QString LocationPoint::latAsString() const { QString LocationPoint::latAsString() const {
return AsString(_lat); return AsString(_lat);
} }

View file

@ -16,6 +16,11 @@ public:
LocationPoint() = default; LocationPoint() = default;
explicit LocationPoint(const MTPDgeoPoint &point); explicit LocationPoint(const MTPDgeoPoint &point);
enum IgnoreAccessHash {
NoAccessHash,
};
LocationPoint(float64 lat, float64 lon, IgnoreAccessHash);
[[nodiscard]] QString latAsString() const; [[nodiscard]] QString latAsString() const;
[[nodiscard]] QString lonAsString() const; [[nodiscard]] QString lonAsString() const;
[[nodiscard]] MTPGeoPoint toMTP() const; [[nodiscard]] MTPGeoPoint toMTP() const;
@ -45,6 +50,24 @@ private:
}; };
struct InputVenue {
float64 lat = 0.;
float64 lon = 0.;
QString title;
QString address;
QString provider;
QString id;
QString venueType;
[[nodiscard]] bool justLocation() const {
return id.isEmpty();
}
friend inline bool operator==(
const InputVenue &,
const InputVenue &) = default;
};
[[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point); [[nodiscard]] GeoPointLocation ComputeLocation(const LocationPoint &point);
} // namespace Data } // namespace Data

View file

@ -121,8 +121,8 @@ struct AlbumCounts {
ImageRoundRadius radius, ImageRoundRadius radius,
bool spoiler) { bool spoiler) {
const auto original = image->original(); const auto original = image->original();
if (original.width() * 10 < original.height() if (original.width() * 20 < original.height()
|| original.height() * 10 < original.width()) { || original.height() * 20 < original.width()) {
return QImage(); return QImage();
} }
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
@ -2303,8 +2303,9 @@ ClickHandlerPtr MediaDice::MakeHandler(
MediaGiftBox::MediaGiftBox( MediaGiftBox::MediaGiftBox(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<PeerData*> from, not_null<PeerData*> from,
int months) GiftType type,
: MediaGiftBox(parent, from, GiftCode{ .months = months }) { int count)
: MediaGiftBox(parent, from, GiftCode{ .count = count, .type = type }) {
} }
MediaGiftBox::MediaGiftBox( MediaGiftBox::MediaGiftBox(
@ -2631,7 +2632,11 @@ const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
} }
TextWithEntities MediaGiveawayResults::notificationText() const { TextWithEntities MediaGiveawayResults::notificationText() const {
return Ui::Text::Colorized({ tr::lng_prizes_results_title(tr::now) }); return Ui::Text::Colorized({
((_data.winnersCount == 1)
? tr::lng_prizes_results_title_one
: tr::lng_prizes_results_title)(tr::now)
});
} }
QString MediaGiveawayResults::pinnedTextSubstring() const { QString MediaGiveawayResults::pinnedTextSubstring() const {

View file

@ -125,10 +125,16 @@ struct GiveawayResults {
bool all = false; bool all = false;
}; };
enum class GiftType : uchar {
Premium, // count - months
Stars, // count - stars
};
struct GiftCode { struct GiftCode {
QString slug; QString slug;
ChannelData *channel = nullptr; ChannelData *channel = nullptr;
int months = 0; int count = 0;
GiftType type = GiftType::Premium;
bool viaGiveaway = false; bool viaGiveaway = false;
bool unclaimed = false; bool unclaimed = false;
}; };
@ -591,7 +597,8 @@ public:
MediaGiftBox( MediaGiftBox(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<PeerData*> from, not_null<PeerData*> from,
int months); GiftType type,
int count);
MediaGiftBox( MediaGiftBox(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<PeerData*> from, not_null<PeerData*> from,

View file

@ -57,6 +57,12 @@ std::optional<QString> OnlineTextSpecial(not_null<UserData*> user) {
} else if (user->isSupport()) { } else if (user->isSupport()) {
return tr::lng_status_support(tr::now); return tr::lng_status_support(tr::now);
} else if (user->isBot()) { } else if (user->isBot()) {
if (const auto count = user->botInfo->activeUsers) {
return tr::lng_bot_status_users(
tr::now,
lt_count_decimal,
count);
}
return tr::lng_status_bot(tr::now); return tr::lng_status_bot(tr::now);
} else if (user->isServiceUser()) { } else if (user->isServiceUser()) {
return tr::lng_status_support(tr::now); return tr::lng_status_support(tr::now);
@ -69,12 +75,14 @@ std::optional<QString> OnlineTextCommon(LastseenStatus status, TimeId now) {
return tr::lng_status_online(tr::now); return tr::lng_status_online(tr::now);
} else if (status.isLongAgo()) { } else if (status.isLongAgo()) {
return tr::lng_status_offline(tr::now); return tr::lng_status_offline(tr::now);
} else if (status.isRecently() || status.isHidden()) { } else if (status.isRecently()) {
return tr::lng_status_recently(tr::now); return tr::lng_status_recently(tr::now);
} else if (status.isWithinWeek()) { } else if (status.isWithinWeek()) {
return tr::lng_status_last_week(tr::now); return tr::lng_status_last_week(tr::now);
} else if (status.isWithinMonth()) { } else if (status.isWithinMonth()) {
return tr::lng_status_last_month(tr::now); return tr::lng_status_last_month(tr::now);
} else if (status.isHidden()) {
return tr::lng_status_recently(tr::now);
} }
return std::nullopt; return std::nullopt;
} }

View file

@ -731,6 +731,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
result->botInfo->supportsAttachMenu = data.is_bot_attach_menu(); result->botInfo->supportsAttachMenu = data.is_bot_attach_menu();
result->botInfo->supportsBusiness = data.is_bot_business(); result->botInfo->supportsBusiness = data.is_bot_business();
result->botInfo->canEditInformation = data.is_bot_can_edit(); result->botInfo->canEditInformation = data.is_bot_can_edit();
result->botInfo->activeUsers = data.vbot_active_users().value_or_empty();
result->botInfo->hasMainApp = data.is_bot_has_main_app();
} else { } else {
result->setBotInfoVersion(-1); result->setBotInfoVersion(-1);
} }
@ -3390,6 +3392,22 @@ void Session::documentApplyFields(
} }
} }
not_null<DocumentData*> Session::venueIconDocument(const QString &icon) {
const auto i = _venueIcons.find(icon);
if (i != end(_venueIcons)) {
return i->second;
}
const auto result = documentFromWeb(MTP_webDocumentNoProxy(
MTP_string(u"https://ss3.4sqi.net/img/categories_v2/"_q
+ icon
+ u"_64.png"_q),
MTP_int(0),
MTP_string("image/png"),
MTP_vector<MTPDocumentAttribute>()), {}, {});
_venueIcons.emplace(icon, result);
return result;
}
not_null<WebPageData*> Session::webpage(WebPageId id) { not_null<WebPageData*> Session::webpage(WebPageId id) {
auto i = _webpages.find(id); auto i = _webpages.find(id);
if (i == _webpages.cend()) { if (i == _webpages.cend()) {
@ -4582,7 +4600,8 @@ void Session::serviceNotification(
MTPVector<MTPUsername>(), MTPVector<MTPUsername>(),
MTPint(), // stories_max_id MTPint(), // stories_max_id
MTPPeerColor(), // color MTPPeerColor(), // color
MTPPeerColor())); // profile_color MTPPeerColor(), // profile_color
MTPint())); // bot_active_users
} }
const auto history = this->history(PeerData::kServiceNotificationsId); const auto history = this->history(PeerData::kServiceNotificationsId);
const auto insert = [=] { const auto insert = [=] {

View file

@ -559,6 +559,8 @@ public:
const MTPWebDocument &data, const MTPWebDocument &data,
const ImageLocation &thumbnailLocation, const ImageLocation &thumbnailLocation,
const ImageLocation &videoThumbnailLocation); const ImageLocation &videoThumbnailLocation);
[[nodiscard]] not_null<DocumentData*> venueIconDocument(
const QString &icon);
[[nodiscard]] not_null<WebPageData*> webpage(WebPageId id); [[nodiscard]] not_null<WebPageData*> webpage(WebPageId id);
not_null<WebPageData*> processWebpage(const MTPWebPage &data); not_null<WebPageData*> processWebpage(const MTPWebPage &data);
@ -1002,6 +1004,7 @@ private:
FullStoryId, FullStoryId,
base::flat_set<not_null<HistoryItem*>>> _storyItems; base::flat_set<not_null<HistoryItem*>>> _storyItems;
base::flat_map<uint64, not_null<HistoryItem*>> _highlightings; base::flat_map<uint64, not_null<HistoryItem*>> _highlightings;
base::flat_map<QString, not_null<DocumentData*>> _venueIcons;
base::flat_set<not_null<WebPageData*>> _webpagesUpdated; base::flat_set<not_null<WebPageData*>> _webpagesUpdated;
base::flat_set<not_null<GameData*>> _gamesUpdated; base::flat_set<not_null<GameData*>> _gamesUpdated;

View file

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/download_manager_mtproto.h" #include "storage/download_manager_mtproto.h"
#include "storage/file_download.h" // kMaxFileInMemory #include "storage/file_download.h" // kMaxFileInMemory
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/color_int_conversion.h"
namespace Data { namespace Data {
namespace { namespace {
@ -40,6 +41,7 @@ using UpdateFlag = StoryUpdate::Flag;
return { return {
.geometry = { corner / 100., size / 100. }, .geometry = { corner / 100., size / 100. },
.rotation = data.vrotation().v, .rotation = data.vrotation().v,
.radius = data.vradius().value_or_empty(),
}; };
} }
@ -83,6 +85,7 @@ using UpdateFlag = StoryUpdate::Flag;
}, [&](const MTPDmediaAreaSuggestedReaction &data) { }, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -105,6 +108,7 @@ using UpdateFlag = StoryUpdate::Flag;
}); });
}, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -127,6 +131,7 @@ using UpdateFlag = StoryUpdate::Flag;
data.vmsg_id().v), data.vmsg_id().v),
}); });
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -147,6 +152,33 @@ using UpdateFlag = StoryUpdate::Flag;
.area = ParseArea(data.vcoordinates()), .area = ParseArea(data.vcoordinates()),
.url = qs(data.vurl()), .url = qs(data.vurl()),
}); });
}, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
});
return result;
}
[[nodiscard]] auto ParseWeatherArea(const MTPMediaArea &area)
-> std::optional<WeatherArea> {
auto result = std::optional<WeatherArea>();
area.match([&](const MTPDmediaAreaVenue &data) {
}, [&](const MTPDmediaAreaGeoPoint &data) {
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) {
result.emplace(WeatherArea{
.area = ParseArea(data.vcoordinates()),
.emoji = qs(data.vemoji()),
.color = Ui::Color32FromSerialized(data.vcolor().v),
.millicelsius = int(1000. * std::clamp(
data.vtemperature_c().v,
-274.,
1'000'000.)),
});
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -689,6 +721,10 @@ const std::vector<UrlArea> &Story::urlAreas() const {
return _urlAreas; return _urlAreas;
} }
const std::vector<WeatherArea> &Story::weatherAreas() const {
return _weatherAreas;
}
void Story::applyChanges( void Story::applyChanges(
StoryMedia media, StoryMedia media,
const MTPDstoryItem &data, const MTPDstoryItem &data,
@ -793,6 +829,7 @@ void Story::applyFields(
auto suggestedReactions = std::vector<SuggestedReaction>(); auto suggestedReactions = std::vector<SuggestedReaction>();
auto channelPosts = std::vector<ChannelPost>(); auto channelPosts = std::vector<ChannelPost>();
auto urlAreas = std::vector<UrlArea>(); auto urlAreas = std::vector<UrlArea>();
auto weatherAreas = std::vector<WeatherArea>();
if (const auto areas = data.vmedia_areas()) { if (const auto areas = data.vmedia_areas()) {
for (const auto &area : areas->v) { for (const auto &area : areas->v) {
if (const auto location = ParseLocation(area)) { if (const auto location = ParseLocation(area)) {
@ -808,6 +845,8 @@ void Story::applyFields(
channelPosts.push_back(*post); channelPosts.push_back(*post);
} else if (auto url = ParseUrlArea(area)) { } else if (auto url = ParseUrlArea(area)) {
urlAreas.push_back(*url); urlAreas.push_back(*url);
} else if (auto weather = ParseWeatherArea(area)) {
weatherAreas.push_back(*weather);
} }
} }
} }
@ -821,6 +860,7 @@ void Story::applyFields(
= (_suggestedReactions != suggestedReactions); = (_suggestedReactions != suggestedReactions);
const auto channelPostsChanged = (_channelPosts != channelPosts); const auto channelPostsChanged = (_channelPosts != channelPosts);
const auto urlAreasChanged = (_urlAreas != urlAreas); const auto urlAreasChanged = (_urlAreas != urlAreas);
const auto weatherAreasChanged = (_weatherAreas != weatherAreas);
const auto reactionChanged = (_sentReactionId != reaction); const auto reactionChanged = (_sentReactionId != reaction);
_out = out; _out = out;
@ -849,6 +889,9 @@ void Story::applyFields(
if (urlAreasChanged) { if (urlAreasChanged) {
_urlAreas = std::move(urlAreas); _urlAreas = std::move(urlAreas);
} }
if (weatherAreasChanged) {
_weatherAreas = std::move(weatherAreas);
}
if (reactionChanged) { if (reactionChanged) {
_sentReactionId = reaction; _sentReactionId = reaction;
} }
@ -859,7 +902,8 @@ void Story::applyFields(
|| mediaChanged || mediaChanged
|| locationsChanged || locationsChanged
|| channelPostsChanged || channelPostsChanged
|| urlAreasChanged; || urlAreasChanged
|| weatherAreasChanged;
const auto reactionsChanged = reactionChanged const auto reactionsChanged = reactionChanged
|| suggestedReactionsChanged; || suggestedReactionsChanged;
if (!initial && (changed || reactionsChanged)) { if (!initial && (changed || reactionsChanged)) {

View file

@ -81,6 +81,7 @@ struct StoryViews {
struct StoryArea { struct StoryArea {
QRectF geometry; QRectF geometry;
float64 rotation = 0; float64 rotation = 0;
float64 radius = 0;
friend inline bool operator==( friend inline bool operator==(
const StoryArea &, const StoryArea &,
@ -131,6 +132,17 @@ struct UrlArea {
const UrlArea &) = default; const UrlArea &) = default;
}; };
struct WeatherArea {
StoryArea area;
QString emoji;
QColor color;
int millicelsius = 0;
friend inline bool operator==(
const WeatherArea &,
const WeatherArea &) = default;
};
class Story final { class Story final {
public: public:
Story( Story(
@ -208,6 +220,8 @@ public:
-> const std::vector<ChannelPost> &; -> const std::vector<ChannelPost> &;
[[nodiscard]] auto urlAreas() const [[nodiscard]] auto urlAreas() const
-> const std::vector<UrlArea> &; -> const std::vector<UrlArea> &;
[[nodiscard]] auto weatherAreas() const
-> const std::vector<WeatherArea> &;
void applyChanges( void applyChanges(
StoryMedia media, StoryMedia media,
@ -259,6 +273,7 @@ private:
std::vector<SuggestedReaction> _suggestedReactions; std::vector<SuggestedReaction> _suggestedReactions;
std::vector<ChannelPost> _channelPosts; std::vector<ChannelPost> _channelPosts;
std::vector<UrlArea> _urlAreas; std::vector<UrlArea> _urlAreas;
std::vector<WeatherArea> _weatherAreas;
StoryViews _views; StoryViews _views;
StoryViews _channelReactions; StoryViews _channelReactions;
const TimeId _date = 0; const TimeId _date = 0;

View file

@ -40,12 +40,14 @@ struct BotInfo {
int version = 0; int version = 0;
int descriptionVersion = 0; int descriptionVersion = 0;
int activeUsers = 0;
bool inited : 1 = false; bool inited : 1 = false;
bool readsAllHistory : 1 = false; bool readsAllHistory : 1 = false;
bool cantJoinGroups : 1 = false; bool cantJoinGroups : 1 = false;
bool supportsAttachMenu : 1 = false; bool supportsAttachMenu : 1 = false;
bool canEditInformation : 1 = false; bool canEditInformation : 1 = false;
bool supportsBusiness : 1 = false; bool supportsBusiness : 1 = false;
bool hasMainApp : 1 = false;
}; };
enum class UserDataFlag : uint32 { enum class UserDataFlag : uint32 {

View file

@ -0,0 +1,193 @@
/*
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 "data/raw/raw_countries_bounds.h"
// Source: https://github.com/sandstrom/country-bounding-boxes
namespace Raw {
const base::flat_map<QString, GeoBounds> &CountryBounds() {
static const auto result = base::flat_map<QString, GeoBounds>{
{ u"AF"_q, GeoBounds{ 60.53, 29.32, 75.16, 38.49 } },
{ u"AO"_q, GeoBounds{ 11.64, -17.93, 24.08, -4.44 } },
{ u"AL"_q, GeoBounds{ 19.3, 39.62, 21.02, 42.69 } },
{ u"AE"_q, GeoBounds{ 51.58, 22.5, 56.4, 26.06 } },
{ u"AR"_q, GeoBounds{ -73.42, -55.25, -53.63, -21.83 } },
{ u"AM"_q, GeoBounds{ 43.58, 38.74, 46.51, 41.25 } },
{ u"AQ"_q, GeoBounds{ -180.0, -90.0, 180.0, -63.27 } },
{ u"TF"_q, GeoBounds{ 68.72, -49.78, 70.56, -48.63 } },
{ u"AU"_q, GeoBounds{ 113.34, -43.63, 153.57, -10.67 } },
{ u"AT"_q, GeoBounds{ 9.48, 46.43, 16.98, 49.04 } },
{ u"AZ"_q, GeoBounds{ 44.79, 38.27, 50.39, 41.86 } },
{ u"BI"_q, GeoBounds{ 29.02, -4.5, 30.75, -2.35 } },
{ u"BE"_q, GeoBounds{ 2.51, 49.53, 6.16, 51.48 } },
{ u"BJ"_q, GeoBounds{ 0.77, 6.14, 3.8, 12.24 } },
{ u"BF"_q, GeoBounds{ -5.47, 9.61, 2.18, 15.12 } },
{ u"BD"_q, GeoBounds{ 88.08, 20.67, 92.67, 26.45 } },
{ u"BG"_q, GeoBounds{ 22.38, 41.23, 28.56, 44.23 } },
{ u"BS"_q, GeoBounds{ -78.98, 23.71, -77.0, 27.04 } },
{ u"BA"_q, GeoBounds{ 15.75, 42.65, 19.6, 45.23 } },
{ u"BY"_q, GeoBounds{ 23.2, 51.32, 32.69, 56.17 } },
{ u"BZ"_q, GeoBounds{ -89.23, 15.89, -88.11, 18.5 } },
{ u"BO"_q, GeoBounds{ -69.59, -22.87, -57.5, -9.76 } },
{ u"BR"_q, GeoBounds{ -73.99, -33.77, -34.73, 5.24 } },
{ u"BN"_q, GeoBounds{ 114.2, 4.01, 115.45, 5.45 } },
{ u"BT"_q, GeoBounds{ 88.81, 26.72, 92.1, 28.3 } },
{ u"BW"_q, GeoBounds{ 19.9, -26.83, 29.43, -17.66 } },
{ u"CF"_q, GeoBounds{ 14.46, 2.27, 27.37, 11.14 } },
{ u"CA"_q, GeoBounds{ -141.0, 41.68, -52.65, 73.23 } },
{ u"CH"_q, GeoBounds{ 6.02, 45.78, 10.44, 47.83 } },
{ u"CL"_q, GeoBounds{ -75.64, -55.61, -66.96, -17.58 } },
{ u"CN"_q, GeoBounds{ 73.68, 18.2, 135.03, 53.46 } },
{ u"CI"_q, GeoBounds{ -8.6, 4.34, -2.56, 10.52 } },
{ u"CM"_q, GeoBounds{ 8.49, 1.73, 16.01, 12.86 } },
{ u"CD"_q, GeoBounds{ 12.18, -13.26, 31.17, 5.26 } },
{ u"CG"_q, GeoBounds{ 11.09, -5.04, 18.45, 3.73 } },
{ u"CO"_q, GeoBounds{ -78.99, -4.3, -66.88, 12.44 } },
{ u"CR"_q, GeoBounds{ -85.94, 8.23, -82.55, 11.22 } },
{ u"CU"_q, GeoBounds{ -84.97, 19.86, -74.18, 23.19 } },
{ u"CY"_q, GeoBounds{ 32.26, 34.57, 34.0, 35.17 } },
{ u"CZ"_q, GeoBounds{ 12.24, 48.56, 18.85, 51.12 } },
{ u"DE"_q, GeoBounds{ 5.99, 47.3, 15.02, 54.98 } },
{ u"DJ"_q, GeoBounds{ 41.66, 10.93, 43.32, 12.7 } },
{ u"DK"_q, GeoBounds{ 8.09, 54.8, 12.69, 57.73 } },
{ u"DO"_q, GeoBounds{ -71.95, 17.6, -68.32, 19.88 } },
{ u"DZ"_q, GeoBounds{ -8.68, 19.06, 12.0, 37.12 } },
{ u"EC"_q, GeoBounds{ -80.97, -4.96, -75.23, 1.38 } },
{ u"EG"_q, GeoBounds{ 24.7, 22.0, 36.87, 31.59 } },
{ u"ER"_q, GeoBounds{ 36.32, 12.46, 43.08, 18.0 } },
{ u"ES"_q, GeoBounds{ -9.39, 35.95, 3.04, 43.75 } },
{ u"EE"_q, GeoBounds{ 23.34, 57.47, 28.13, 59.61 } },
{ u"ET"_q, GeoBounds{ 32.95, 3.42, 47.79, 14.96 } },
{ u"FI"_q, GeoBounds{ 20.65, 59.85, 31.52, 70.16 } },
{ u"FJ"_q, GeoBounds{ -180.0, -18.29, 180.0, -16.02 } },
{ u"FK"_q, GeoBounds{ -61.2, -52.3, -57.75, -51.1 } },
{ u"FR"_q, GeoBounds{ -5.0, 42.5, 9.56, 51.15 } },
{ u"GA"_q, GeoBounds{ 8.8, -3.98, 14.43, 2.33 } },
{ u"GB"_q, GeoBounds{ -7.57, 49.96, 1.68, 58.64 } },
{ u"GE"_q, GeoBounds{ 39.96, 41.06, 46.64, 43.55 } },
{ u"GH"_q, GeoBounds{ -3.24, 4.71, 1.06, 11.1 } },
{ u"GN"_q, GeoBounds{ -15.13, 7.31, -7.83, 12.59 } },
{ u"GM"_q, GeoBounds{ -16.84, 13.13, -13.84, 13.88 } },
{ u"GW"_q, GeoBounds{ -16.68, 11.04, -13.7, 12.63 } },
{ u"GQ"_q, GeoBounds{ 9.31, 1.01, 11.29, 2.28 } },
{ u"GR"_q, GeoBounds{ 20.15, 34.92, 26.6, 41.83 } },
{ u"GL"_q, GeoBounds{ -73.3, 60.04, -12.21, 83.65 } },
{ u"GT"_q, GeoBounds{ -92.23, 13.74, -88.23, 17.82 } },
{ u"GY"_q, GeoBounds{ -61.41, 1.27, -56.54, 8.37 } },
{ u"HN"_q, GeoBounds{ -89.35, 12.98, -83.15, 16.01 } },
{ u"HR"_q, GeoBounds{ 13.66, 42.48, 19.39, 46.5 } },
{ u"HT"_q, GeoBounds{ -74.46, 18.03, -71.62, 19.92 } },
{ u"HU"_q, GeoBounds{ 16.2, 45.76, 22.71, 48.62 } },
{ u"ID"_q, GeoBounds{ 95.29, -10.36, 141.03, 5.48 } },
{ u"IN"_q, GeoBounds{ 68.18, 7.97, 97.4, 35.49 } },
{ u"IE"_q, GeoBounds{ -9.98, 51.67, -6.03, 55.13 } },
{ u"IR"_q, GeoBounds{ 44.11, 25.08, 63.32, 39.71 } },
{ u"IQ"_q, GeoBounds{ 38.79, 29.1, 48.57, 37.39 } },
{ u"IS"_q, GeoBounds{ -24.33, 63.5, -13.61, 66.53 } },
{ u"IL"_q, GeoBounds{ 34.27, 29.5, 35.84, 33.28 } },
{ u"IT"_q, GeoBounds{ 6.75, 36.62, 18.48, 47.12 } },
{ u"JM"_q, GeoBounds{ -78.34, 17.7, -76.2, 18.52 } },
{ u"JO"_q, GeoBounds{ 34.92, 29.2, 39.2, 33.38 } },
{ u"JP"_q, GeoBounds{ 129.41, 31.03, 145.54, 45.55 } },
{ u"KZ"_q, GeoBounds{ 46.47, 40.66, 87.36, 55.39 } },
{ u"KE"_q, GeoBounds{ 33.89, -4.68, 41.86, 5.51 } },
{ u"KG"_q, GeoBounds{ 69.46, 39.28, 80.26, 43.3 } },
{ u"KH"_q, GeoBounds{ 102.35, 10.49, 107.61, 14.57 } },
{ u"KR"_q, GeoBounds{ 126.12, 34.39, 129.47, 38.61 } },
{ u"KW"_q, GeoBounds{ 46.57, 28.53, 48.42, 30.06 } },
{ u"LA"_q, GeoBounds{ 100.12, 13.88, 107.56, 22.46 } },
{ u"LB"_q, GeoBounds{ 35.13, 33.09, 36.61, 34.64 } },
{ u"LR"_q, GeoBounds{ -11.44, 4.36, -7.54, 8.54 } },
{ u"LY"_q, GeoBounds{ 9.32, 19.58, 25.16, 33.14 } },
{ u"LK"_q, GeoBounds{ 79.7, 5.97, 81.79, 9.82 } },
{ u"LS"_q, GeoBounds{ 27.0, -30.65, 29.33, -28.65 } },
{ u"LT"_q, GeoBounds{ 21.06, 53.91, 26.59, 56.37 } },
{ u"LU"_q, GeoBounds{ 5.67, 49.44, 6.24, 50.13 } },
{ u"LV"_q, GeoBounds{ 21.06, 55.62, 28.18, 57.97 } },
{ u"MA"_q, GeoBounds{ -17.02, 21.42, -1.12, 35.76 } },
{ u"MD"_q, GeoBounds{ 26.62, 45.49, 30.02, 48.47 } },
{ u"MG"_q, GeoBounds{ 43.25, -25.6, 50.48, -12.04 } },
{ u"MX"_q, GeoBounds{ -117.13, 14.54, -86.81, 32.72 } },
{ u"MK"_q, GeoBounds{ 20.46, 40.84, 22.95, 42.32 } },
{ u"ML"_q, GeoBounds{ -12.17, 10.1, 4.27, 24.97 } },
{ u"MM"_q, GeoBounds{ 92.3, 9.93, 101.18, 28.34 } },
{ u"ME"_q, GeoBounds{ 18.45, 41.88, 20.34, 43.52 } },
{ u"MN"_q, GeoBounds{ 87.75, 41.6, 119.77, 52.05 } },
{ u"MZ"_q, GeoBounds{ 30.18, -26.74, 40.78, -10.32 } },
{ u"MR"_q, GeoBounds{ -17.06, 14.62, -4.92, 27.4 } },
{ u"MW"_q, GeoBounds{ 32.69, -16.8, 35.77, -9.23 } },
{ u"MY"_q, GeoBounds{ 100.09, 0.77, 119.18, 6.93 } },
{ u"NA"_q, GeoBounds{ 11.73, -29.05, 25.08, -16.94 } },
{ u"NC"_q, GeoBounds{ 164.03, -22.4, 167.12, -20.11 } },
{ u"NE"_q, GeoBounds{ 0.3, 11.66, 15.9, 23.47 } },
{ u"NG"_q, GeoBounds{ 2.69, 4.24, 14.58, 13.87 } },
{ u"NI"_q, GeoBounds{ -87.67, 10.73, -83.15, 15.02 } },
{ u"NL"_q, GeoBounds{ 3.31, 50.8, 7.09, 53.51 } },
{ u"NO"_q, GeoBounds{ 4.99, 58.08, 31.29, 70.92 } },
{ u"NP"_q, GeoBounds{ 80.09, 26.4, 88.17, 30.42 } },
{ u"NZ"_q, GeoBounds{ 166.51, -46.64, 178.52, -34.45 } },
{ u"OM"_q, GeoBounds{ 52.0, 16.65, 59.81, 26.4 } },
{ u"PK"_q, GeoBounds{ 60.87, 23.69, 77.84, 37.13 } },
{ u"PA"_q, GeoBounds{ -82.97, 7.22, -77.24, 9.61 } },
{ u"PE"_q, GeoBounds{ -81.41, -18.35, -68.67, -0.06 } },
{ u"PH"_q, GeoBounds{ 117.17, 5.58, 126.54, 18.51 } },
{ u"PG"_q, GeoBounds{ 141.0, -10.65, 156.02, -2.5 } },
{ u"PL"_q, GeoBounds{ 14.07, 49.03, 24.03, 54.85 } },
{ u"PR"_q, GeoBounds{ -67.24, 17.95, -65.59, 18.52 } },
{ u"KP"_q, GeoBounds{ 124.27, 37.67, 130.78, 42.99 } },
{ u"PT"_q, GeoBounds{ -9.53, 36.84, -6.39, 42.28 } },
{ u"PY"_q, GeoBounds{ -62.69, -27.55, -54.29, -19.34 } },
{ u"QA"_q, GeoBounds{ 50.74, 24.56, 51.61, 26.11 } },
{ u"RO"_q, GeoBounds{ 20.22, 43.69, 29.63, 48.22 } },
{ u"RU"_q, GeoBounds{ -180.0, 41.15, 180.0, 81.25 } },
{ u"RW"_q, GeoBounds{ 29.02, -2.92, 30.82, -1.13 } },
{ u"SA"_q, GeoBounds{ 34.63, 16.35, 55.67, 32.16 } },
{ u"SD"_q, GeoBounds{ 21.94, 8.62, 38.41, 22.0 } },
{ u"SS"_q, GeoBounds{ 23.89, 3.51, 35.3, 12.25 } },
{ u"SN"_q, GeoBounds{ -17.63, 12.33, -11.47, 16.6 } },
{ u"SB"_q, GeoBounds{ 156.49, -10.83, 162.4, -6.6 } },
{ u"SL"_q, GeoBounds{ -13.25, 6.79, -10.23, 10.05 } },
{ u"SV"_q, GeoBounds{ -90.1, 13.15, -87.72, 14.42 } },
{ u"SO"_q, GeoBounds{ 40.98, -1.68, 51.13, 12.02 } },
{ u"RS"_q, GeoBounds{ 18.83, 42.25, 22.99, 46.17 } },
{ u"SR"_q, GeoBounds{ -58.04, 1.82, -53.96, 6.03 } },
{ u"SK"_q, GeoBounds{ 16.88, 47.76, 22.56, 49.57 } },
{ u"SI"_q, GeoBounds{ 13.7, 45.45, 16.56, 46.85 } },
{ u"SE"_q, GeoBounds{ 11.03, 55.36, 23.9, 69.11 } },
{ u"SZ"_q, GeoBounds{ 30.68, -27.29, 32.07, -25.66 } },
{ u"SY"_q, GeoBounds{ 35.7, 32.31, 42.35, 37.23 } },
{ u"TD"_q, GeoBounds{ 13.54, 7.42, 23.89, 23.41 } },
{ u"TG"_q, GeoBounds{ -0.05, 5.93, 1.87, 11.02 } },
{ u"TH"_q, GeoBounds{ 97.38, 5.69, 105.59, 20.42 } },
{ u"TJ"_q, GeoBounds{ 67.44, 36.74, 74.98, 40.96 } },
{ u"TM"_q, GeoBounds{ 52.5, 35.27, 66.55, 42.75 } },
{ u"TL"_q, GeoBounds{ 124.97, -9.39, 127.34, -8.27 } },
{ u"TT"_q, GeoBounds{ -61.95, 10.0, -60.9, 10.89 } },
{ u"TN"_q, GeoBounds{ 7.52, 30.31, 11.49, 37.35 } },
{ u"TR"_q, GeoBounds{ 26.04, 35.82, 44.79, 42.14 } },
{ u"TW"_q, GeoBounds{ 120.11, 21.97, 121.95, 25.3 } },
{ u"TZ"_q, GeoBounds{ 29.34, -11.72, 40.32, -0.95 } },
{ u"UG"_q, GeoBounds{ 29.58, -1.44, 35.04, 4.25 } },
{ u"UA"_q, GeoBounds{ 22.09, 44.36, 40.08, 52.34 } },
{ u"UY"_q, GeoBounds{ -58.43, -34.95, -53.21, -30.11 } },
{ u"US"_q, GeoBounds{ -125.0, 25.0, -66.96, 49.5 } },
{ u"UZ"_q, GeoBounds{ 55.93, 37.14, 73.06, 45.59 } },
{ u"VE"_q, GeoBounds{ -73.3, 0.72, -59.76, 12.16 } },
{ u"VN"_q, GeoBounds{ 102.17, 8.6, 109.34, 23.35 } },
{ u"VU"_q, GeoBounds{ 166.63, -16.6, 167.84, -14.63 } },
{ u"PS"_q, GeoBounds{ 34.93, 31.35, 35.55, 32.53 } },
{ u"YE"_q, GeoBounds{ 42.6, 12.59, 53.11, 19.0 } },
{ u"ZA"_q, GeoBounds{ 16.34, -34.82, 32.83, -22.09 } },
{ u"ZM"_q, GeoBounds{ 21.89, -17.96, 33.49, -8.24 } },
{ u"ZW"_q, GeoBounds{ 25.26, -22.27, 32.85, -15.51 } }
};
return result;
}
} // namespace Raw

View file

@ -0,0 +1,23 @@
/*
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 <QtCore/QString>
namespace Raw {
struct GeoBounds {
double minLat = 0.;
double minLon = 0.;
double maxLat = 0.;
double maxLon = 0.;
};
[[nodiscard]] const base::flat_map<QString, GeoBounds> &CountryBounds();
} // namespace Raw

View file

@ -228,7 +228,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
auto index = set->stickers.indexOf(document); auto index = set->stickers.indexOf(document);
if (index > 0) { if (index > 0) {
if (set->dates.empty()) { if (set->dates.empty()) {
session().api().requestRecentStickersForce(); session().api().requestSpecialStickersForce(false, true, false);
} else { } else {
Assert(set->dates.size() == set->stickers.size()); Assert(set->dates.size() == set->stickers.size());
set->dates.erase(set->dates.begin() + index); set->dates.erase(set->dates.begin() + index);
@ -260,7 +260,7 @@ void Stickers::incrementSticker(not_null<DocumentData*> document) {
set->emoji[emoji].push_front(document); set->emoji[emoji].push_front(document);
} }
} else { } else {
session().api().requestRecentStickersForce(); session().api().requestSpecialStickersForce(false, true, false);
} }
writeRecentStickers = true; writeRecentStickers = true;

View file

@ -55,7 +55,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
| (data.vinstalled_date() ? Flag::Installed : Flag()) | (data.vinstalled_date() ? Flag::Installed : Flag())
//| (data.is_videos() ? Flag::Webm : Flag()) //| (data.is_videos() ? Flag::Webm : Flag())
| (data.is_text_color() ? Flag::TextColor : Flag()) | (data.is_text_color() ? Flag::TextColor : Flag())
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag()); | (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag())
| (data.is_creator() ? Flag::AmCreator : Flag());
} }
StickersSet::StickersSet( StickersSet::StickersSet(

View file

@ -59,6 +59,7 @@ enum class StickersSetFlag : ushort {
Emoji = (1 << 9), Emoji = (1 << 9),
TextColor = (1 << 10), TextColor = (1 << 10),
ChannelStatus = (1 << 11), ChannelStatus = (1 << 11),
AmCreator = (1 << 12),
}; };
inline constexpr bool is_flag_type(StickersSetFlag) { return true; }; inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
using StickersSetFlags = base::flags<StickersSetFlag>; using StickersSetFlags = base::flags<StickersSetFlag>;

View file

@ -3814,7 +3814,9 @@ ChosenRow InnerWidget::computeChosenRow() const {
bool InnerWidget::isUserpicPress() const { bool InnerWidget::isUserpicPress() const {
return (_lastRowLocalMouseX >= 0) return (_lastRowLocalMouseX >= 0)
&& (_lastRowLocalMouseX < _st->nameLeft); && (_lastRowLocalMouseX < _st->nameLeft)
&& (_collapsedSelected < 0
|| _collapsedSelected >= _collapsedRows.size());
} }
bool InnerWidget::isUserpicPressOnWide() const { bool InnerWidget::isUserpicPressOnWide() const {

View file

@ -78,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_stories.h" #include "data/data_stories.h"
#include "info/downloads/info_downloads_widget.h" #include "info/downloads/info_downloads_widget.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "inline_bots/bot_attach_web_view.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
@ -1282,6 +1283,27 @@ void Widget::updateSuggestions(anim::type animated) {
} }
}, _suggestions->lifetime()); }, _suggestions->lifetime());
_suggestions->recentAppChosen(
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
if (const auto info = user->botInfo.get()) {
if (info->hasMainApp) {
openBotMainApp(user);
return;
}
}
}
chosenRow({
.key = peer->owner().history(peer),
.newWindow = base::IsCtrlPressed(),
});
}, _suggestions->lifetime());
_suggestions->popularAppChosen(
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
controller()->showPeerInfo(peer);
}, _suggestions->lifetime());
updateControlsGeometry(); updateControlsGeometry();
_suggestions->show(animated, [=] { _suggestions->show(animated, [=] {
@ -1293,6 +1315,17 @@ void Widget::updateSuggestions(anim::type animated) {
} }
} }
void Widget::openBotMainApp(not_null<UserData*> bot) {
session().attachWebView().open({
.bot = bot,
.context = {
.controller = controller(),
.maySkipConfirmation = true,
},
.source = InlineBots::WebViewSourceBotProfile(),
});
}
void Widget::changeOpenedSubsection( void Widget::changeOpenedSubsection(
FnMut<void()> change, FnMut<void()> change,
bool fromRight, bool fromRight,

View file

@ -214,6 +214,7 @@ private:
void refreshTopBars(); void refreshTopBars();
void showSearchInTopBar(anim::type animated); void showSearchInTopBar(anim::type animated);
void checkUpdateStatus(); void checkUpdateStatus();
void openBotMainApp(not_null<UserData*> bot);
void changeOpenedSubsection( void changeOpenedSubsection(
FnMut<void()> change, FnMut<void()> change,
bool fromRight, bool fromRight,

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
class PeerListContent;
namespace Data { namespace Data {
class Thread; class Thread;
} // namespace Data } // namespace Data
@ -67,21 +69,32 @@ public:
} }
[[nodiscard]] auto recentPeerChosen() const [[nodiscard]] auto recentPeerChosen() const
-> rpl::producer<not_null<PeerData*>> { -> rpl::producer<not_null<PeerData*>> {
return _recentPeerChosen.events(); return _recent->chosen.events();
} }
[[nodiscard]] auto myChannelChosen() const [[nodiscard]] auto myChannelChosen() const
-> rpl::producer<not_null<PeerData*>> { -> rpl::producer<not_null<PeerData*>> {
return _myChannelChosen.events(); return _myChannels->chosen.events();
} }
[[nodiscard]] auto recommendationChosen() const [[nodiscard]] auto recommendationChosen() const
-> rpl::producer<not_null<PeerData*>> { -> rpl::producer<not_null<PeerData*>> {
return _recommendationChosen.events(); return _recommendations->chosen.events();
} }
[[nodiscard]] auto recentAppChosen() const
-> rpl::producer<not_null<PeerData*>> {
return _recentApps->chosen.events();
}
[[nodiscard]] auto popularAppChosen() const
-> rpl::producer<not_null<PeerData*>> {
return _popularApps->chosen.events();
}
class ObjectListController;
private: private:
enum class Tab : uchar { enum class Tab : uchar {
Chats, Chats,
Channels, Channels,
Apps,
}; };
enum class JumpResult : uchar { enum class JumpResult : uchar {
NotApplied, NotApplied,
@ -89,29 +102,54 @@ private:
AppliedAndOut, AppliedAndOut,
}; };
struct ObjectList {
not_null<Ui::SlideWrap<PeerListContent>*> wrap;
rpl::variable<int> count;
Fn<bool()> choose;
Fn<JumpResult(Qt::Key, int)> selectJump;
Fn<uint64(QPoint)> updateFromParentDrag;
Fn<void()> dragLeft;
Fn<bool(not_null<QTouchEvent*>)> processTouch;
rpl::event_stream<not_null<PeerData*>> chosen;
};
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void setupTabs(); void setupTabs();
void setupChats(); void setupChats();
void setupChannels(); void setupChannels();
void setupApps();
void selectJumpChats(Qt::Key direction, int pageSize); void selectJumpChats(Qt::Key direction, int pageSize);
void selectJumpChannels(Qt::Key direction, int pageSize); void selectJumpChannels(Qt::Key direction, int pageSize);
void selectJumpApps(Qt::Key direction, int pageSize);
[[nodiscard]] Data::Thread *updateFromChatsDrag(QPoint globalPosition); [[nodiscard]] Data::Thread *updateFromChatsDrag(QPoint globalPosition);
[[nodiscard]] Data::Thread *updateFromChannelsDrag( [[nodiscard]] Data::Thread *updateFromChannelsDrag(
QPoint globalPosition); QPoint globalPosition);
[[nodiscard]] Data::Thread *updateFromAppsDrag(QPoint globalPosition);
[[nodiscard]] Data::Thread *fromListId(uint64 peerListRowId); [[nodiscard]] Data::Thread *fromListId(uint64 peerListRowId);
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers( [[nodiscard]] std::unique_ptr<ObjectList> setupRecentPeers(
RecentPeersList recentPeers); RecentPeersList recentPeers);
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent(); [[nodiscard]] auto setupEmptyRecent()
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupMyChannels();
[[nodiscard]] auto setupRecommendations()
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>; -> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
[[nodiscard]] std::unique_ptr<ObjectList> setupMyChannels();
[[nodiscard]] std::unique_ptr<ObjectList> setupRecommendations();
[[nodiscard]] auto setupEmptyChannels() [[nodiscard]] auto setupEmptyChannels()
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>; -> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
[[nodiscard]] std::unique_ptr<ObjectList> setupRecentApps();
[[nodiscard]] std::unique_ptr<ObjectList> setupPopularApps();
[[nodiscard]] std::unique_ptr<ObjectList> setupObjectList(
not_null<Ui::ElasticScroll*> scroll,
not_null<Ui::VerticalLayout*> parent,
not_null<ObjectListController*> controller,
Fn<int()> addToScroll = nullptr);
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmpty( [[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmpty(
not_null<QWidget*> parent, not_null<QWidget*> parent,
SearchEmptyIcon icon, SearchEmptyIcon icon,
@ -119,7 +157,7 @@ private:
void switchTab(Tab tab); void switchTab(Tab tab);
void startShownAnimation(bool shown, Fn<void()> finish); void startShownAnimation(bool shown, Fn<void()> finish);
void startSlideAnimation(); void startSlideAnimation(Tab was, Tab now);
void finishShow(); void finishShow();
void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback); void handlePressForChatPreview(PeerId id, Fn<void(bool)> callback);
@ -131,43 +169,30 @@ private:
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll; const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
const not_null<Ui::VerticalLayout*> _chatsContent; const not_null<Ui::VerticalLayout*> _chatsContent;
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap; const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
const not_null<TopPeersStrip*> _topPeers; const not_null<TopPeersStrip*> _topPeers;
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
const std::unique_ptr<ObjectList> _recent;
rpl::variable<int> _recentCount;
Fn<bool()> _recentPeersChoose;
Fn<JumpResult(Qt::Key, int)> _recentSelectJump;
Fn<uint64(QPoint)> _recentUpdateFromParentDrag;
Fn<void()> _recentDragLeft;
Fn<bool(not_null<QTouchEvent*>)> _recentProcessTouch;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent; const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
const std::unique_ptr<Ui::ElasticScroll> _channelsScroll; const std::unique_ptr<Ui::ElasticScroll> _channelsScroll;
const not_null<Ui::VerticalLayout*> _channelsContent; const not_null<Ui::VerticalLayout*> _channelsContent;
rpl::variable<int> _myChannelsCount; const std::unique_ptr<ObjectList> _myChannels;
Fn<bool()> _myChannelsChoose; const std::unique_ptr<ObjectList> _recommendations;
Fn<JumpResult(Qt::Key, int)> _myChannelsSelectJump;
Fn<uint64(QPoint)> _myChannelsUpdateFromParentDrag;
Fn<void()> _myChannelsDragLeft;
Fn<bool(not_null<QTouchEvent*>)> _myChannelsProcessTouch;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _myChannels;
rpl::variable<int> _recommendationsCount;
Fn<bool()> _recommendationsChoose;
Fn<JumpResult(Qt::Key, int)> _recommendationsSelectJump;
Fn<uint64(QPoint)> _recommendationsUpdateFromParentDrag;
Fn<void()> _recommendationsDragLeft;
Fn<bool(not_null<QTouchEvent*>)> _recommendationsProcessTouch;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recommendations;
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels; const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels;
rpl::event_stream<not_null<PeerData*>> _topPeerChosen; const std::unique_ptr<Ui::ElasticScroll> _appsScroll;
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen; const not_null<Ui::VerticalLayout*> _appsContent;
rpl::event_stream<not_null<PeerData*>> _myChannelChosen;
rpl::event_stream<not_null<PeerData*>> _recommendationChosen; rpl::producer<> _recentAppsRefreshed;
Fn<bool(not_null<PeerData*>)> _recentAppsShows;
const std::unique_ptr<ObjectList> _recentApps;
const std::unique_ptr<ObjectList> _popularApps;
Ui::Animations::Simple _shownAnimation; Ui::Animations::Simple _shownAnimation;
Fn<void()> _showFinished; Fn<void()> _showFinished;
@ -179,6 +204,9 @@ private:
QPixmap _slideLeft; QPixmap _slideLeft;
QPixmap _slideRight; QPixmap _slideRight;
int _slideLeftTop = 0;
int _slideRightTop = 0;
}; };
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent( [[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(

View file

@ -1515,6 +1515,20 @@ ServiceAction ParseServiceAction(
result.content = content; result.content = content;
}, [&](const MTPDmessageActionRequestedPeerSentMe &data) { }, [&](const MTPDmessageActionRequestedPeerSentMe &data) {
// Should not be in user inbox. // Should not be in user inbox.
}, [&](const MTPDmessageActionPaymentRefunded &data) {
auto content = ActionPaymentRefunded();
content.currency = ParseString(data.vcurrency());
content.amount = data.vtotal_amount().v;
content.peerId = ParsePeerId(data.vpeer());
content.transactionId = data.vcharge().data().vid().v;
result.content = content;
}, [&](const MTPDmessageActionGiftStars &data) {
auto content = ActionGiftStars();
content.cost = Ui::FillAmountAndCurrency(
data.vamount().v,
qs(data.vcurrency())).toUtf8();
content.stars = data.vstars().v;
result.content = content;
}, [](const MTPDmessageActionEmpty &data) {}); }, [](const MTPDmessageActionEmpty &data) {});
return result; return result;
} }

View file

@ -529,7 +529,7 @@ struct ActionWebViewDataSent {
struct ActionGiftPremium { struct ActionGiftPremium {
Utf8String cost; Utf8String cost;
int months; int months = 0;
}; };
struct ActionTopicCreate { struct ActionTopicCreate {
@ -576,6 +576,18 @@ struct ActionBoostApply {
int boosts = 0; int boosts = 0;
}; };
struct ActionPaymentRefunded {
PeerId peerId = 0;
Utf8String currency;
uint64 amount = 0;
Utf8String transactionId;
};
struct ActionGiftStars {
Utf8String cost;
int stars = 0;
};
struct ServiceAction { struct ServiceAction {
std::variant< std::variant<
v::null_t, v::null_t,
@ -617,7 +629,9 @@ struct ServiceAction {
ActionGiftCode, ActionGiftCode,
ActionGiveawayLaunch, ActionGiveawayLaunch,
ActionGiveawayResults, ActionGiveawayResults,
ActionBoostApply> content; ActionBoostApply,
ActionPaymentRefunded,
ActionGiftStars> content;
}; };
ServiceAction ParseServiceAction( ServiceAction ParseServiceAction(

View file

@ -1315,6 +1315,22 @@ auto HtmlWriter::Wrap::pushMessage(
+ " boosted the group " + " boosted the group "
+ QByteArray::number(data.boosts) + QByteArray::number(data.boosts)
+ (data.boosts > 1 ? " times" : " time"); + (data.boosts > 1 ? " times" : " time");
}, [&](const ActionPaymentRefunded &data) {
const auto amount = FormatMoneyAmount(data.amount, data.currency);
auto result = peers.wrapPeerName(data.peerId)
+ " refunded back "
+ amount;
return result;
}, [&](const ActionGiftStars &data) {
if (!data.stars || data.cost.isEmpty()) {
return serviceFrom + " sent you a gift.";
}
return serviceFrom
+ " sent you a gift for "
+ data.cost
+ ": "
+ QString::number(data.stars).toUtf8()
+ " Telegram Stars.";
}, [](v::null_t) { return QByteArray(); }); }, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) { if (!serviceText.isEmpty()) {

View file

@ -625,6 +625,22 @@ QByteArray SerializeMessage(
pushActor(); pushActor();
pushAction("boost_apply"); pushAction("boost_apply");
push("boosts", data.boosts); push("boosts", data.boosts);
}, [&](const ActionPaymentRefunded &data) {
pushAction("refunded_payment");
push("amount", data.amount);
push("currency", data.currency);
pushBare("peer_name", wrapPeerName(data.peerId));
push("peer_id", data.peerId);
push("charge_id", data.transactionId);
}, [&](const ActionGiftStars &data) {
pushActor();
pushAction("send_stars_gift");
if (!data.cost.isEmpty()) {
push("cost", data.cost);
}
if (data.stars) {
push("stars", data.stars);
}
}, [](v::null_t) {}); }, [](v::null_t) {});
if (v::is_null(message.action.content)) { if (v::is_null(message.action.content)) {

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/history_translation.h" #include "history/history_translation.h"
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "core/ui_integration.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "data/business/data_shortcut_messages.h" #include "data/business/data_shortcut_messages.h"
#include "data/components/scheduled_messages.h" #include "data/components/scheduled_messages.h"
@ -1136,14 +1137,23 @@ void History::applyServiceChanges(
} }
if (paid) { if (paid) {
// Toast on a current active window. // Toast on a current active window.
const auto context = [=](not_null<QWidget*> toast) {
return Core::MarkedTextContext{
.session = &session(),
.customEmojiRepaint = [=] { toast->update(); },
};
};
Ui::Toast::Show({ Ui::Toast::Show({
.text = tr::lng_payments_success( .text = tr::lng_payments_success(
tr::now, tr::now,
lt_amount, lt_amount,
Ui::Text::Bold(payment->amount), Ui::Text::Wrapped(
payment->amount,
EntityType::Bold),
lt_title, lt_title,
Ui::Text::Bold(paid->title), Ui::Text::Bold(paid->title),
Ui::Text::WithEntities), Ui::Text::WithEntities),
.textContext = context,
}); });
} }
} }

View file

@ -2232,8 +2232,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (!item->isService() if (!item->isService()
&& peerIsChannel(itemId.peer) && peerIsChannel(itemId.peer)
&& !_peer->isMegagroup()) { && !_peer->isMegagroup()) {
constexpr auto kMinViewsCount = 10;
if (const auto channel = _peer->asChannel()) { if (const auto channel = _peer->asChannel()) {
if (channel->flags() & ChannelDataFlag::CanGetStatistics) { if ((channel->flags() & ChannelDataFlag::CanGetStatistics)
|| (channel->canPostMessages()
&& item->viewsCount() >= kMinViewsCount)) {
auto callback = crl::guard(controller, [=] { auto callback = crl::guard(controller, [=] {
controller->showSection( controller->showSection(
Info::Statistics::Make(channel, itemId, {})); Info::Statistics::Make(channel, itemId, {}));

Some files were not shown because too many files have changed in this diff Show more