Merge tag 'v5.13.1' into dev

This commit is contained in:
AlexeyZavar 2025-04-25 02:03:14 +03:00
commit 1447b62999
200 changed files with 6117 additions and 1350 deletions

View file

@ -231,6 +231,8 @@ PRIVATE
api/api_peer_colors.h api/api_peer_colors.h
api/api_peer_photo.cpp api/api_peer_photo.cpp
api/api_peer_photo.h api/api_peer_photo.h
api/api_peer_search.cpp
api/api_peer_search.h
api/api_polls.cpp api/api_polls.cpp
api/api_polls.h api/api_polls.h
api/api_premium.cpp api/api_premium.cpp
@ -764,6 +766,8 @@ PRIVATE
dialogs/dialogs_main_list.h dialogs/dialogs_main_list.h
dialogs/dialogs_pinned_list.cpp dialogs/dialogs_pinned_list.cpp
dialogs/dialogs_pinned_list.h dialogs/dialogs_pinned_list.h
dialogs/dialogs_quick_action.cpp
dialogs/dialogs_quick_action.h
dialogs/dialogs_row.cpp dialogs/dialogs_row.cpp
dialogs/dialogs_row.h dialogs/dialogs_row.h
dialogs/dialogs_search_from_controllers.cpp dialogs/dialogs_search_from_controllers.cpp
@ -1001,6 +1005,8 @@ PRIVATE
history/history_unread_things.h history/history_unread_things.h
history/history_view_highlight_manager.cpp history/history_view_highlight_manager.cpp
history/history_view_highlight_manager.h history/history_view_highlight_manager.h
history/history_view_swipe_back_session.cpp
history/history_view_swipe_back_session.h
history/history_widget.cpp history/history_widget.cpp
history/history_widget.h history/history_widget.h
info/bot/earn/info_bot_earn_list.cpp info/bot/earn/info_bot_earn_list.cpp
@ -2143,14 +2149,16 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
configure_file("../lib/xdg/com.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" @ONLY) configure_file("../lib/xdg/com.ayugram.desktop.metainfo.xml" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" @ONLY)
generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml") generate_appdata_changelog(Telegram "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml")
install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS Telegram RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon64.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/64x64/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "ayugram.png") install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "com.ayugram.desktop.png")
install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "ayugram-symbolic.svg") install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "com.ayugram.desktop-symbolic.svg")
install(FILES "Resources/icons/tray_monochrome_attention.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "com.ayugram.desktop-attention-symbolic.svg")
install(FILES "Resources/icons/tray_monochrome_mute.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "com.ayugram.desktop-mute-symbolic.svg")
install(FILES "../lib/xdg/com.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications") install(FILES "../lib/xdg/com.ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
</g>
<circle class="error" fill="#f23c34" cx="3.9" cy="12.7" r="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="plane" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M1.3311718,6.36592184 C5.3576954,4.67244493 8.04267511,3.5560013 9.38611094,3.01659096 C13.2218932,1.47646481 14.0189359,1.2089284 14.5384372,1.2 C14.6526967,1.19815119 14.9081723,1.22548649 15.0736587,1.35511219 C15.2133922,1.4645656 15.2518384,1.61242159 15.2702362,1.71619544 C15.288634,1.81996929 15.3115436,2.05636876 15.2933322,2.24108442 C15.0854698,4.34939964 14.1860526,9.46572464 13.7284802,11.8270738 C13.5348641,12.8262491 13.1536281,13.1612675 12.7845475,13.1940535 C11.9824498,13.265305 11.3733733,12.6823476 10.5965026,12.190753 C9.3808532,11.4215044 8.69408865,10.9426448 7.51409044,10.1920004 C6.15039834,9.32450079 7.03442319,8.84770795 7.81158733,8.06849502 C8.01497489,7.86457129 11.5490353,4.7615061 11.6174372,4.48000946 C11.625992,4.44480359 11.6339313,4.31357282 11.5531696,4.24427815 C11.472408,4.17498349 11.3532107,4.19867957 11.2671947,4.21752527 C11.1452695,4.24423848 9.20325394,5.48334063 5.44114787,7.93483171 C4.88991321,8.30022994 4.39062196,8.47826423 3.94327414,8.46893456 C3.45010907,8.45864936 2.50145729,8.19975808 1.79623221,7.97846422 C0.931244952,7.70703829 0.243770289,7.56353344 0.303633888,7.10256824 C0.334814555,6.86246904 0.677327192,6.61692024 1.3311718,6.36592184 Z" id="Path-3" fill="#FFFFFF"></path>
</g>
<circle fill="#888888" cx="3.9" cy="12.7" r="2.2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -681,6 +681,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_shortcuts_archive_chat" = "Archive chat"; "lng_shortcuts_archive_chat" = "Archive chat";
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen"; "lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
"lng_shortcuts_show_chat_menu" = "Show chat menu"; "lng_shortcuts_show_chat_menu" = "Show chat menu";
"lng_shortcuts_show_chat_preview" = "Show chat preview";
"lng_settings_chat_reactions_title" = "Quick Reaction"; "lng_settings_chat_reactions_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
@ -1179,6 +1180,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_restart_now" = "Restart"; "lng_settings_restart_now" = "Restart";
"lng_settings_restart_later" = "Later"; "lng_settings_restart_later" = "Later";
"lng_settings_quick_dialog_action_title" = "Chat list quick action";
"lng_settings_quick_dialog_action_about" = "Choose the action you want to perform when you middle-click or swipe left in the chat list.";
"lng_settings_quick_dialog_action_both" = "Swipe left and Middle-click";
"lng_settings_quick_dialog_action_swipe" = "Swipe left";
"lng_settings_quick_dialog_action_mute" = "Mute";
"lng_settings_quick_dialog_action_unmute" = "Unmute";
"lng_settings_quick_dialog_action_pin" = "Pin";
"lng_settings_quick_dialog_action_unpin" = "Unpin";
"lng_settings_quick_dialog_action_read" = "Read";
"lng_settings_quick_dialog_action_unread" = "Unread";
"lng_settings_quick_dialog_action_archive" = "Archive";
"lng_settings_quick_dialog_action_unarchive" = "Unarchive";
"lng_settings_quick_dialog_action_delete" = "Delete";
"lng_settings_quick_dialog_action_disabled" = "Change folder";
"lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting.";
"lng_settings_generic_subscribe_link" = "Telegram Premium";
"lng_sessions_header" = "This device"; "lng_sessions_header" = "This device";
"lng_sessions_other_header" = "Active Devices"; "lng_sessions_other_header" = "Active Devices";
"lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized.";
@ -1294,6 +1313,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_gifts_always_title" = "Always allow"; "lng_edit_privacy_gifts_always_title" = "Always allow";
"lng_edit_privacy_gifts_never_title" = "Never allow"; "lng_edit_privacy_gifts_never_title" = "Never allow";
"lng_edit_privacy_gifts_types" = "Accepted Gift Types";
"lng_edit_privacy_gifts_premium" = "Premium Subscriptions";
"lng_edit_privacy_gifts_unlimited" = "Unlimited";
"lng_edit_privacy_gifts_limited" = "Limited-Edition";
"lng_edit_privacy_gifts_unique" = "Unique";
"lng_edit_privacy_gifts_types_about" = "Choose the types of gifts that you accept.";
"lng_edit_privacy_gifts_show_icon" = "Show Gift Icon in Chats";
"lng_edit_privacy_gifts_show_icon_about" = "Display the {emoji}Gift icon in the message input field for both participants in all chats.";
"lng_edit_privacy_gifts_restricted" = "This user doesn't accept gifts.";
"lng_edit_privacy_calls_title" = "Calls"; "lng_edit_privacy_calls_title" = "Calls";
"lng_edit_privacy_calls_header" = "Who can call me"; "lng_edit_privacy_calls_header" = "Who can call me";
"lng_edit_privacy_calls_always_empty" = "Always allow"; "lng_edit_privacy_calls_always_empty" = "Always allow";
@ -2173,6 +2202,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_paid_message_some#other" = "send {count} messages"; "lng_action_paid_message_some#other" = "send {count} messages";
"lng_action_paid_message_got#one" = "You received {count} Star from {name}"; "lng_action_paid_message_got#one" = "You received {count} Star from {name}";
"lng_action_paid_message_got#other" = "You received {count} Stars from {name}"; "lng_action_paid_message_got#other" = "You received {count} Stars from {name}";
"lng_action_paid_message_refund#one" = "{from} refunded {count} Star to you";
"lng_action_paid_message_refund#other" = "{from} refunded {count} Stars to you";
"lng_action_paid_message_refund_self#one" = "You refunded {count} Star to {name}";
"lng_action_paid_message_refund_self#other" = "You refunded {count} Stars to {name}";
"lng_action_message_price_free" = "Messages are now free in this group.";
"lng_action_message_price_paid#one" = "Messages now cost {count} Star each in this group.";
"lng_action_message_price_paid#other" = "Messages now cost {count} Stars each in this group.";
"lng_you_paid_stars#one" = "You paid {count} Star."; "lng_you_paid_stars#one" = "You paid {count} Star.";
"lng_you_paid_stars#other" = "You paid {count} Stars."; "lng_you_paid_stars#other" = "You paid {count} Stars.";
@ -2906,8 +2942,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_chatbots_include_button" = "Select Chats"; "lng_chatbots_include_button" = "Select Chats";
"lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to."; "lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to.";
"lng_chatbots_permissions_title" = "Bot permissions"; "lng_chatbots_permissions_title" = "Bot permissions";
"lng_chatbots_manage_messages" = "Manage Messages";
"lng_chatbots_read" = "Read Messages";
"lng_chatbots_reply" = "Reply to Messages"; "lng_chatbots_reply" = "Reply to Messages";
"lng_chatbots_reply_about" = "The bot can only reply on your behalf in chats that were active during the last 24h."; "lng_chatbots_mark_as_read" = "Mark Messages as Read";
"lng_chatbots_delete_sent" = "Delete Sent Messages";
"lng_chatbots_delete_received" = "Delete Received Messages";
"lng_chatbots_manage_profile" = "Manage Profile";
"lng_chatbots_edit_name" = "Edit Name";
"lng_chatbots_edit_bio" = "Edit Bio";
"lng_chatbots_edit_userpic" = "Edit Profile Picture";
"lng_chatbots_edit_username" = "Edit Username";
"lng_chatbots_manage_gifts" = "Manage Gifts and Stars";
"lng_chatbots_view_gifts" = "View Gifts";
"lng_chatbots_sell_gifts" = "Sell Gifts";
"lng_chatbots_gift_settings" = "Change Gift Settings";
"lng_chatbots_transfer_gifts" = "Transfer and Upgrade Gifts";
"lng_chatbots_transfer_stars" = "Transfer Stars";
"lng_chatbots_manage_stories" = "Manage Stories";
"lng_chatbots_remove" = "Remove Bot"; "lng_chatbots_remove" = "Remove Bot";
"lng_chatbots_not_found" = "Chatbot not found."; "lng_chatbots_not_found" = "Chatbot not found.";
"lng_chatbots_not_supported" = "This bot doesn't support Telegram Business yet."; "lng_chatbots_not_supported" = "This bot doesn't support Telegram Business yet.";
@ -3342,6 +3399,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_limited" = "limited"; "lng_gift_stars_limited" = "limited";
"lng_gift_stars_sold_out" = "sold out"; "lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_tabs_all" = "All Gifts"; "lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_my" = "My Gifts";
"lng_gift_stars_tabs_limited" = "Limited"; "lng_gift_stars_tabs_limited" = "Limited";
"lng_gift_stars_tabs_in_stock" = "In Stock"; "lng_gift_stars_tabs_in_stock" = "In Stock";
"lng_gift_send_title" = "Send a Gift"; "lng_gift_send_title" = "Send a Gift";
@ -3371,7 +3429,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_limited_of_one" = "unique"; "lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}"; "lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift"; "lng_gift_collectible_tag" = "gift";
"lng_gift_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack"; "lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name."; "lng_gift_anonymous_hint" = "Only you can see the sender's name.";
"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name."; "lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name.";
@ -3428,7 +3485,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts."; "lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page."; "lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts."; "lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
"lng_gift_pinned_done_title" = "{gift} pinned";
"lng_gift_pinned_done" = "The gift will always be shown on top."; "lng_gift_pinned_done" = "The gift will always be shown on top.";
"lng_gift_pinned_done_replaced" = "replacing {gift}";
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift."; "lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift."; "lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift."; "lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift.";
@ -3501,6 +3560,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles."; "lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
"lng_gift_wear_start_toast" = "You put on {name}"; "lng_gift_wear_start_toast" = "You put on {name}";
"lng_gift_wear_end_toast" = "You took off {name}"; "lng_gift_wear_end_toast" = "You took off {name}";
"lng_gift_many_pinned_title" = "Too Many Pinned Gifts";
"lng_gift_many_pinned_choose" = "Select a gift to unpin below";
"lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
@ -4331,6 +4392,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_filter_private" = "Private chats"; "lng_search_filter_private" = "Private chats";
"lng_search_filter_group" = "Group chats"; "lng_search_filter_group" = "Group chats";
"lng_search_filter_channel" = "Channels"; "lng_search_filter_channel" = "Channels";
"lng_search_sponsored_button" = "Ad ⋮";
"lng_media_save_progress" = "{ready} of {total} {mb}"; "lng_media_save_progress" = "{ready} of {total} {mb}";
"lng_mediaview_save_as" = "Save As..."; "lng_mediaview_save_as" = "Save As...";
@ -5745,6 +5807,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy"; "lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them."; "lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them."; "lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
"lng_sponsored_revenued_info1_search_description" = "Ads on Telegram do not use your personal information and are based on the search query you entered.";
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator"; "lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer"; "lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed."; "lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
@ -5753,9 +5816,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers."; "lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers."; "lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}."; "lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
"lng_sponsored_revenued_info3_search_description" = "You can turn off ads by subscribing to Telegram Premium. {link}";
"lng_sponsored_revenued_info3_search_link" = "Subscribe {arrow}";
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?"; "lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}"; "lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}"; "lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_revenued_footer_search_description" = "Anyone can create an ad to display in search results for any query. Check out the **Telegram Ad Platform** for details. {link}";
"lng_sponsored_top_bar_hide" = "remove"; "lng_sponsored_top_bar_hide" = "remove";
"lng_telegram_features_url" = "https://t.me/TelegramTips"; "lng_telegram_features_url" = "https://t.me/TelegramTips";
@ -6241,6 +6307,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_qr_box_transparent_background" = "Transparent Background"; "lng_qr_box_transparent_background" = "Transparent Background";
"lng_qr_box_font_size" = "Font size"; "lng_qr_box_font_size" = "Font size";
"lng_frozen_bar_title" = "Your account is frozen!";
"lng_frozen_bar_text" = "Click to view details {arrow}";
"lng_frozen_restrict_title" = "Your account is frozen";
"lng_frozen_restrict_text" = "Click to view details";
"lng_frozen_title" = "Your Account is Frozen";
"lng_frozen_subtitle1" = "Violation of Terms";
"lng_frozen_text1" = "Your account was frozen for breaking Telegram's Terms and Conditions.";
"lng_frozen_subtitle2" = "Read-Only Mode";
"lng_frozen_text2" = "You can access your account but can't send messages or take actions.";
"lng_frozen_subtitle3" = "Appeal Before Deactivation";
"lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted.";
"lng_frozen_appeal_button" = "Submit an Appeal";
// Wnd specific // Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program..."; "lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

@ -30,6 +30,7 @@
<file alias="noresults.tgs">../../animations/noresults.tgs</file> <file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file> <file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="starref_link.tgs">../../animations/starref_link.tgs</file> <file alias="starref_link.tgs">../../animations/starref_link.tgs</file>
<file alias="media_forbidden.tgs">../../animations/media_forbidden.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file> <file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file> <file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
@ -49,5 +50,16 @@
<file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file> <file alias="star_reaction_effect1.tgs">../../animations/star_reaction/effect1.tgs</file>
<file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file> <file alias="star_reaction_effect2.tgs">../../animations/star_reaction/effect2.tgs</file>
<file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file> <file alias="star_reaction_effect3.tgs">../../animations/star_reaction/effect3.tgs</file>
<file alias="swipe_archive.tgs">../../animations/swipe_action/archive.tgs</file>
<file alias="swipe_unarchive.tgs">../../animations/swipe_action/unarchive.tgs</file>
<file alias="swipe_delete.tgs">../../animations/swipe_action/delete.tgs</file>
<file alias="swipe_disabled.tgs">../../animations/swipe_action/disabled.tgs</file>
<file alias="swipe_mute.tgs">../../animations/swipe_action/mute.tgs</file>
<file alias="swipe_unmute.tgs">../../animations/swipe_action/unmute.tgs</file>
<file alias="swipe_pin.tgs">../../animations/swipe_action/pin.tgs</file>
<file alias="swipe_unpin.tgs">../../animations/swipe_action/unpin.tgs</file>
<file alias="swipe_read.tgs">../../animations/swipe_action/read.tgs</file>
<file alias="swipe_unread.tgs">../../animations/swipe_action/unread.tgs</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -24,6 +24,8 @@
<file alias="icons/settings/star.svg">../../icons/settings/star.svg</file> <file alias="icons/settings/star.svg">../../icons/settings/star.svg</file>
<file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file> <file alias="icons/settings/starmini.svg">../../icons/settings/starmini.svg</file>
<file alias="icons/tray/monochrome.svg">../../icons/tray_monochrome.svg</file> <file alias="icons/tray/monochrome.svg">../../icons/tray_monochrome.svg</file>
<file alias="icons/tray/monochrome_attention.svg">../../icons/tray_monochrome_attention.svg</file>
<file alias="icons/tray/monochrome_mute.svg">../../icons/tray_monochrome_mute.svg</file>
<file alias="topic_icons/blue.svg">../../art/topic_icons/blue.svg</file> <file alias="topic_icons/blue.svg">../../art/topic_icons/blue.svg</file>
<file alias="topic_icons/yellow.svg">../../art/topic_icons/yellow.svg</file> <file alias="topic_icons/yellow.svg">../../art/topic_icons/yellow.svg</file>
<file alias="topic_icons/violet.svg">../../art/topic_icons/violet.svg</file> <file alias="topic_icons/violet.svg">../../art/topic_icons/violet.svg</file>

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.12.3.0" /> Version="5.13.1.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,12,3,0 FILEVERSION 5,13,1,0
PRODUCTVERSION 5,12,3,0 PRODUCTVERSION 5,13,1,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.12.3.0" VALUE "FileVersion", "5.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.12.3.0" VALUE "ProductVersion", "5.13.1.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,12,3,0 FILEVERSION 5,13,1,0
PRODUCTVERSION 5,12,3,0 PRODUCTVERSION 5,13,1,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.12.3.0" VALUE "FileVersion", "5.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.12.3.0" VALUE "ProductVersion", "5.13.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -150,6 +150,9 @@ void ConfirmPhone::resolve(
}, [](const MTPDauth_sentCodeSuccess &) { }, [](const MTPDauth_sentCodeSuccess &) {
LOG(("API Error: Unexpected auth.sentCodeSuccess " LOG(("API Error: Unexpected auth.sentCodeSuccess "
"(Api::ConfirmPhone).")); "(Api::ConfirmPhone)."));
}, [](const MTPDauth_sentCodePaymentRequired &) {
LOG(("API Error: Unexpected auth.sentCodePaymentRequired "
"(Api::ConfirmPhone)."));
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_sendRequestId = 0; _sendRequestId = 0;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_global_privacy.h" #include "api/api_global_privacy.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_user.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
@ -115,7 +116,8 @@ void GlobalPrivacy::updateHideReadTime(bool hide) {
unarchiveOnNewMessageCurrent(), unarchiveOnNewMessageCurrent(),
hide, hide,
newRequirePremiumCurrent(), newRequirePremiumCurrent(),
newChargeStarsCurrent()); newChargeStarsCurrent(),
disallowedGiftTypesCurrent());
} }
bool GlobalPrivacy::hideReadTimeCurrent() const { bool GlobalPrivacy::hideReadTimeCurrent() const {
@ -150,7 +152,27 @@ void GlobalPrivacy::updateMessagesPrivacy(
unarchiveOnNewMessageCurrent(), unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(), hideReadTimeCurrent(),
requirePremium, requirePremium,
chargeStars); chargeStars,
disallowedGiftTypesCurrent());
}
DisallowedGiftTypes GlobalPrivacy::disallowedGiftTypesCurrent() const {
return _disallowedGiftTypes.current();
}
auto GlobalPrivacy::disallowedGiftTypes() const
-> rpl::producer<DisallowedGiftTypes> {
return _disallowedGiftTypes.value();
}
void GlobalPrivacy::updateDisallowedGiftTypes(DisallowedGiftTypes types) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
newRequirePremiumCurrent(),
newChargeStarsCurrent(),
types);
} }
void GlobalPrivacy::loadPaidReactionShownPeer() { void GlobalPrivacy::loadPaidReactionShownPeer() {
@ -182,7 +204,8 @@ void GlobalPrivacy::updateArchiveAndMute(bool value) {
unarchiveOnNewMessageCurrent(), unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(), hideReadTimeCurrent(),
newRequirePremiumCurrent(), newRequirePremiumCurrent(),
newChargeStarsCurrent()); newChargeStarsCurrent(),
disallowedGiftTypesCurrent());
} }
void GlobalPrivacy::updateUnarchiveOnNewMessage( void GlobalPrivacy::updateUnarchiveOnNewMessage(
@ -192,7 +215,8 @@ void GlobalPrivacy::updateUnarchiveOnNewMessage(
value, value,
hideReadTimeCurrent(), hideReadTimeCurrent(),
newRequirePremiumCurrent(), newRequirePremiumCurrent(),
newChargeStarsCurrent()); newChargeStarsCurrent(),
disallowedGiftTypesCurrent());
} }
void GlobalPrivacy::update( void GlobalPrivacy::update(
@ -200,12 +224,16 @@ void GlobalPrivacy::update(
UnarchiveOnNewMessage unarchiveOnNewMessage, UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime, bool hideReadTime,
bool newRequirePremium, bool newRequirePremium,
int newChargeStars) { int newChargeStars,
DisallowedGiftTypes disallowedGiftTypes) {
using Flag = MTPDglobalPrivacySettings::Flag; using Flag = MTPDglobalPrivacySettings::Flag;
using DisallowedFlag = MTPDdisallowedGiftsSettings::Flag;
_api.request(_requestId).cancel(); _api.request(_requestId).cancel();
const auto newRequirePremiumAllowed = _session->premium() const auto newRequirePremiumAllowed = _session->premium()
|| _session->appConfig().newRequirePremiumFree(); || _session->appConfig().newRequirePremiumFree();
const auto showGiftIcon
= (disallowedGiftTypes & DisallowedGiftType::SendHide);
const auto flags = Flag() const auto flags = Flag()
| (archiveAndMute | (archiveAndMute
? Flag::f_archive_and_mute_new_noncontact_peers ? Flag::f_archive_and_mute_new_noncontact_peers
@ -220,14 +248,35 @@ void GlobalPrivacy::update(
| ((newRequirePremium && newRequirePremiumAllowed) | ((newRequirePremium && newRequirePremiumAllowed)
? Flag::f_new_noncontact_peers_require_premium ? Flag::f_new_noncontact_peers_require_premium
: Flag()) : Flag())
| Flag::f_noncontact_peers_paid_stars; | Flag::f_noncontact_peers_paid_stars
| (showGiftIcon ? Flag::f_display_gifts_button : Flag())
| Flag::f_disallowed_gifts;
const auto disallowedFlags = DisallowedFlag()
| ((disallowedGiftTypes & DisallowedGiftType::Premium)
? DisallowedFlag::f_disallow_premium_gifts
: DisallowedFlag())
| ((disallowedGiftTypes & DisallowedGiftType::Unlimited)
? DisallowedFlag::f_disallow_unlimited_stargifts
: DisallowedFlag())
| ((disallowedGiftTypes & DisallowedGiftType::Limited)
? DisallowedFlag::f_disallow_limited_stargifts
: DisallowedFlag())
| ((disallowedGiftTypes & DisallowedGiftType::Unique)
? DisallowedFlag::f_disallow_unique_stargifts
: DisallowedFlag());
const auto typesWas = _disallowedGiftTypes.current();
const auto typesChanged = (typesWas != disallowedGiftTypes);
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
MTP_globalPrivacySettings( MTP_globalPrivacySettings(
MTP_flags(flags), MTP_flags(flags),
MTP_long(newChargeStars)) MTP_long(newChargeStars),
MTP_disallowedGiftsSettings(MTP_flags(disallowedFlags)))
)).done([=](const MTPGlobalPrivacySettings &result) { )).done([=](const MTPGlobalPrivacySettings &result) {
_requestId = 0; _requestId = 0;
apply(result); apply(result);
if (typesChanged) {
_session->user()->updateFullForced();
}
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_requestId = 0; _requestId = 0;
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
@ -236,7 +285,8 @@ void GlobalPrivacy::update(
unarchiveOnNewMessage, unarchiveOnNewMessage,
hideReadTime, hideReadTime,
false, false,
0); 0,
DisallowedGiftTypes());
} }
}).send(); }).send();
_archiveAndMute = archiveAndMute; _archiveAndMute = archiveAndMute;
@ -244,6 +294,7 @@ void GlobalPrivacy::update(
_hideReadTime = hideReadTime; _hideReadTime = hideReadTime;
_newRequirePremium = newRequirePremium; _newRequirePremium = newRequirePremium;
_newChargeStars = newChargeStars; _newChargeStars = newChargeStars;
_disallowedGiftTypes = disallowedGiftTypes;
} }
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) { void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
@ -257,6 +308,29 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &settings) {
_hideReadTime = data.is_hide_read_marks(); _hideReadTime = data.is_hide_read_marks();
_newRequirePremium = data.is_new_noncontact_peers_require_premium(); _newRequirePremium = data.is_new_noncontact_peers_require_premium();
_newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty(); _newChargeStars = data.vnoncontact_peers_paid_stars().value_or_empty();
if (const auto gifts = data.vdisallowed_gifts()) {
const auto &disallow = gifts->data();
_disallowedGiftTypes = DisallowedGiftType()
| (disallow.is_disallow_unlimited_stargifts()
? DisallowedGiftType::Unlimited
: DisallowedGiftType())
| (disallow.is_disallow_limited_stargifts()
? DisallowedGiftType::Limited
: DisallowedGiftType())
| (disallow.is_disallow_unique_stargifts()
? DisallowedGiftType::Unique
: DisallowedGiftType())
| (disallow.is_disallow_premium_gifts()
? DisallowedGiftType::Premium
: DisallowedGiftType())
| (data.is_display_gifts_button()
? DisallowedGiftType::SendHide
: DisallowedGiftType());
} else {
_disallowedGiftTypes = data.is_display_gifts_button()
? DisallowedGiftType::SendHide
: DisallowedGiftType();
}
} }
} // namespace Api } // namespace Api

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/flags.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
class ApiWrap; class ApiWrap;
@ -23,6 +24,17 @@ enum class UnarchiveOnNewMessage {
AnyUnmuted, AnyUnmuted,
}; };
enum class DisallowedGiftType : uchar {
Limited = 0x01,
Unlimited = 0x02,
Unique = 0x04,
Premium = 0x08,
SendHide = 0x10,
};
inline constexpr bool is_flag_type(DisallowedGiftType) { return true; }
using DisallowedGiftTypes = base::flags<DisallowedGiftType>;
[[nodiscard]] PeerId ParsePaidReactionShownPeer( [[nodiscard]] PeerId ParsePaidReactionShownPeer(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPPaidReactionPrivacy &value); const MTPPaidReactionPrivacy &value);
@ -57,6 +69,11 @@ public:
void updateMessagesPrivacy(bool requirePremium, int chargeStars); void updateMessagesPrivacy(bool requirePremium, int chargeStars);
[[nodiscard]] DisallowedGiftTypes disallowedGiftTypesCurrent() const;
[[nodiscard]] auto disallowedGiftTypes() const
-> rpl::producer<DisallowedGiftTypes>;
void updateDisallowedGiftTypes(DisallowedGiftTypes types);
void loadPaidReactionShownPeer(); void loadPaidReactionShownPeer();
void updatePaidReactionShownPeer(PeerId shownPeer); void updatePaidReactionShownPeer(PeerId shownPeer);
[[nodiscard]] PeerId paidReactionShownPeerCurrent() const; [[nodiscard]] PeerId paidReactionShownPeerCurrent() const;
@ -70,7 +87,8 @@ private:
UnarchiveOnNewMessage unarchiveOnNewMessage, UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime, bool hideReadTime,
bool newRequirePremium, bool newRequirePremium,
int newChargeStars); int newChargeStars,
DisallowedGiftTypes disallowedGiftTypes);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -82,6 +100,7 @@ private:
rpl::variable<bool> _hideReadTime = false; rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false; rpl::variable<bool> _newRequirePremium = false;
rpl::variable<int> _newChargeStars = 0; rpl::variable<int> _newChargeStars = 0;
rpl::variable<DisallowedGiftTypes> _disallowedGiftTypes;
rpl::variable<PeerId> _paidReactionShownPeer = false; rpl::variable<PeerId> _paidReactionShownPeer = false;
std::vector<Fn<void()>> _callbacks; std::vector<Fn<void()>> _callbacks;
bool _paidReactionShownPeerLoaded = false; bool _paidReactionShownPeerLoaded = false;

View file

@ -0,0 +1,170 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "api/api_peer_search.h"
#include "api/api_single_message_search.h"
#include "apiwrap.h"
#include "data/data_session.h"
#include "dialogs/ui/chat_search_in.h" // IsHashOrCashtagSearchQuery
#include "main/main_session.h"
namespace Api {
namespace {
constexpr auto kMinSponsoredQueryLength = 4;
} // namespace
PeerSearch::PeerSearch(not_null<Main::Session*> session, Type type)
: _session(session)
, _type(type) {
}
PeerSearch::~PeerSearch() {
clear();
}
void PeerSearch::request(
const QString &query,
Fn<void(PeerSearchResult)> callback,
RequestType type) {
using namespace Dialogs;
_query = Api::ConvertPeerSearchQuery(query);
_callback = callback;
if (_query.isEmpty()
|| IsHashOrCashtagSearchQuery(_query) != HashOrCashtag::None) {
finish(PeerSearchResult{});
return;
}
auto &cache = _cache[_query];
if (cache.peersReady && cache.sponsoredReady) {
finish(cache.result);
return;
} else if (type == RequestType::CacheOnly) {
_callback = nullptr;
return;
} else if (cache.requested) {
return;
}
cache.requested = true;
cache.result.query = _query;
if (_query.size() < kMinSponsoredQueryLength) {
cache.sponsoredReady = true;
} else if (_type == Type::WithSponsored) {
requestSponsored();
}
requestPeers();
}
void PeerSearch::requestPeers() {
const auto requestId = _session->api().request(MTPcontacts_Search(
MTP_string(_query),
MTP_int(SearchPeopleLimit)
)).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
const auto &data = result.data();
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
auto parsed = PeerSearchResult();
parsed.my.reserve(data.vmy_results().v.size());
for (const auto &id : data.vmy_results().v) {
const auto peerId = peerFromMTP(id);
parsed.my.push_back(_session->data().peer(peerId));
}
parsed.peers.reserve(data.vresults().v.size());
for (const auto &id : data.vresults().v) {
const auto peerId = peerFromMTP(id);
parsed.peers.push_back(_session->data().peer(peerId));
}
finishPeers(requestId, std::move(parsed));
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finishPeers(requestId, PeerSearchResult{});
}).send();
_peerRequests.emplace(requestId, _query);
}
void PeerSearch::requestSponsored() {
const auto requestId = _session->api().request(
MTPcontacts_GetSponsoredPeers(MTP_string(_query))
).done([=](
const MTPcontacts_SponsoredPeers &result,
mtpRequestId requestId) {
result.match([&](const MTPDcontacts_sponsoredPeersEmpty &) {
finishSponsored(requestId, PeerSearchResult{});
}, [&](const MTPDcontacts_sponsoredPeers &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
auto parsed = PeerSearchResult();
parsed.sponsored.reserve(data.vpeers().v.size());
for (const auto &peer : data.vpeers().v) {
const auto &data = peer.data();
const auto peerId = peerFromMTP(data.vpeer());
parsed.sponsored.push_back({
.peer = _session->data().peer(peerId),
.randomId = data.vrandom_id().v,
.sponsorInfo = TextWithEntities::Simple(
qs(data.vsponsor_info().value_or_empty())),
.additionalInfo = TextWithEntities::Simple(
qs(data.vadditional_info().value_or_empty())),
});
}
finishSponsored(requestId, std::move(parsed));
});
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
finishSponsored(requestId, PeerSearchResult{});
}).send();
_sponsoredRequests.emplace(requestId, _query);
}
void PeerSearch::finishPeers(
mtpRequestId requestId,
PeerSearchResult result) {
const auto query = _peerRequests.take(requestId);
Assert(query.has_value());
auto &cache = _cache[*query];
cache.peersReady = true;
cache.result.my = std::move(result.my);
cache.result.peers = std::move(result.peers);
if (cache.sponsoredReady && _query == *query) {
finish(cache.result);
}
}
void PeerSearch::finishSponsored(
mtpRequestId requestId,
PeerSearchResult result) {
const auto query = _sponsoredRequests.take(requestId);
Assert(query.has_value());
auto &cache = _cache[*query];
cache.sponsoredReady = true;
cache.result.sponsored = std::move(result.sponsored);
if (cache.peersReady && _query == *query) {
finish(cache.result);
}
}
void PeerSearch::finish(PeerSearchResult result) {
if (const auto onstack = base::take(_callback)) {
onstack(std::move(result));
}
}
void PeerSearch::clear() {
_query = QString();
_callback = nullptr;
_cache.clear();
for (const auto &[requestId, query] : base::take(_peerRequests)) {
_session->api().request(requestId).cancel();
}
for (const auto &[requestId, query] : base::take(_sponsoredRequests)) {
_session->api().request(requestId).cancel();
}
}
} // namespace Api

View file

@ -0,0 +1,76 @@
/*
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 Main {
class Session;
} // namespace Main
namespace Api {
struct SponsoredSearchResult {
not_null<PeerData*> peer;
QByteArray randomId;
TextWithEntities sponsorInfo;
TextWithEntities additionalInfo;
};
struct PeerSearchResult {
QString query;
std::vector<not_null<PeerData*>> my;
std::vector<not_null<PeerData*>> peers;
std::vector<SponsoredSearchResult> sponsored;
};
class PeerSearch final {
public:
enum class Type {
WithSponsored,
JustPeers,
};
PeerSearch(not_null<Main::Session*> session, Type type);
~PeerSearch();
enum class RequestType {
CacheOnly,
CacheOrRemote,
};
void request(
const QString &query,
Fn<void(PeerSearchResult)> callback,
RequestType type = RequestType::CacheOrRemote);
void clear();
private:
struct CacheEntry {
PeerSearchResult result;
bool requested = false;
bool peersReady = false;
bool sponsoredReady = false;
};
void requestPeers();
void requestSponsored();
void finish(PeerSearchResult result);
void finishPeers(mtpRequestId requestId, PeerSearchResult result);
void finishSponsored(mtpRequestId requestId, PeerSearchResult result);
const not_null<Main::Session*> _session;
const Type _type;
QString _query;
Fn<void(PeerSearchResult)> _callback;
base::flat_map<QString, CacheEntry> _cache;
base::flat_map<mtpRequestId, QString> _peerRequests;
base::flat_map<mtpRequestId, QString> _sponsoredRequests;
};
} // namespace Api

View file

@ -816,6 +816,7 @@ std::optional<Data::StarGift> FromTL(
.lastSaleDate = data.vlast_sale_date().value_or_empty(), .lastSaleDate = data.vlast_sale_date().value_or_empty(),
.upgradable = data.vupgrade_stars().has_value(), .upgradable = data.vupgrade_stars().has_value(),
.birthday = data.is_birthday(), .birthday = data.is_birthday(),
.soldOut = data.is_sold_out(),
}); });
}, [&](const MTPDstarGiftUnique &data) { }, [&](const MTPDstarGiftUnique &data) {
const auto total = data.vavailability_total().v; const auto total = data.vavailability_total().v;
@ -882,6 +883,7 @@ std::optional<Data::SavedStarGift> FromTL(
unique->exportAt = data.vcan_export_at().value_or_empty(); unique->exportAt = data.vcan_export_at().value_or_empty();
} }
using Id = Data::SavedStarGiftId; using Id = Data::SavedStarGiftId;
const auto hasUnique = parsed->unique != nullptr;
return Data::SavedStarGift{ return Data::SavedStarGift{
.info = std::move(*parsed), .info = std::move(*parsed),
.manageId = (to->isUser() .manageId = (to->isUser()
@ -904,7 +906,7 @@ std::optional<Data::SavedStarGift> FromTL(
.date = data.vdate().v, .date = data.vdate().v,
.upgradable = data.is_can_upgrade(), .upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top(), .pinned = data.is_pinned_to_top() && hasUnique,
.hidden = data.is_unsaved(), .hidden = data.is_unsaved(),
.mine = to->isSelf(), .mine = to->isSelf(),
}; };

View file

@ -295,6 +295,7 @@ void FillChooseFilterMenu(
contains ? &st::mediaPlayerMenuCheck : nullptr); contains ? &st::mediaPlayerMenuCheck : nullptr);
item->setMarkedText(title.text, QString(), Core::TextContext({ item->setMarkedText(title.text, QString(), Core::TextContext({
.session = &history->session(), .session = &history->session(),
.repaint = [raw = item.get()] { raw->update(); },
.customEmojiLoopLimit = title.isStatic ? -1 : 0, .customEmojiLoopLimit = title.isStatic ? -1 : 0,
})); }));

View file

@ -271,7 +271,8 @@ void DeleteMessagesBox::prepare() {
appendDetails({ appendDetails({
tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count) tr::lng_delete_for_me_chat_hint(tr::now, lt_count, count)
}); });
} else if (!peer->isSelf()) { } else if (!peer->isSelf()
&& (!peer->isUser() || !peer->asUser()->isInaccessible())) {
if (const auto user = peer->asUser(); user && user->isBot()) { if (const auto user = peer->asUser(); user && user->isBot()) {
_revokeForBot = true; _revokeForBot = true;
} }

View file

@ -1054,7 +1054,7 @@ void EditMessagesPrivacyBox(
tr::now, tr::now,
lt_count, lt_count,
always) always)
: QString(); : tr::lng_edit_privacy_exceptions_add(tr::now);
}); });
const auto exceptions = Settings::AddButtonWithLabel( const auto exceptions = Settings::AddButtonWithLabel(

View file

@ -111,6 +111,9 @@ void AddBotToGroupBoxController::Start(
Scope scope, Scope scope,
const QString &token, const QString &token,
ChatAdminRights requestedRights) { ChatAdminRights requestedRights) {
if (controller->showFrozenError()) {
return;
}
auto initBox = [=](not_null<PeerListBox*> box) { auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
}; };

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_filter.h" #include "history/admin_log/history_admin_log_filter.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/business/data_business_chatbots.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -63,6 +64,11 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
return std::vector<std::pair<Flag, Flag>>{}; return std::vector<std::pair<Flag, Flag>>{};
} }
[[nodiscard]] auto Dependencies(Data::ChatbotsPermissions) {
using Flag = Data::ChatbotsPermission;
return std::vector<std::pair<Flag, Flag>>{};
}
[[nodiscard]] auto NestedRestrictionLabelsList( [[nodiscard]] auto NestedRestrictionLabelsList(
Data::RestrictionsSetOptions options) Data::RestrictionsSetOptions options)
-> std::vector<NestedEditFlagsLabels<ChatRestrictions>> { -> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
@ -680,7 +686,7 @@ template <typename Flags>
checkView->setChecked(false, anim::type::instant); checkView->setChecked(false, anim::type::instant);
} else if (locked.has_value()) { } else if (locked.has_value()) {
if (checked != toggled) { if (checked != toggled) {
if (!state->toast) { if (!state->toast && !locked->isEmpty()) {
state->toast = Ui::Toast::Show(container, { state->toast = Ui::Toast::Show(container, {
.text = { *locked }, .text = { *locked },
.duration = kForceDisableTooltipDuration, .duration = kForceDisableTooltipDuration,
@ -1490,3 +1496,20 @@ EditFlagsControl<AdminLog::FilterValue::Flags> CreateEditAdminLogFilter(
return result; return result;
} }
EditFlagsControl<Data::ChatbotsPermissions> CreateEditChatbotPermissions(
QWidget *parent,
Data::ChatbotsPermissions flags) {
auto widget = object_ptr<Ui::VerticalLayout>(parent);
auto descriptor = Data::ChatbotsPermissionsLabels();
descriptor.disabledMessages.emplace(
Data::ChatbotsPermission::ViewMessages,
QString());
auto result = CreateEditFlags(
widget.data(),
flags | Data::ChatbotsPermission::ViewMessages,
std::move(descriptor));
result.widget = std::move(widget);
return result;
}

View file

@ -27,6 +27,11 @@ enum Flag : uint32;
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
} // namespace PowerSaving } // namespace PowerSaving
namespace Data {
enum class ChatbotsPermission;
using ChatbotsPermissions = base::flags<ChatbotsPermission>;
} // namespace Data
template <typename Object> template <typename Object>
class object_ptr; class object_ptr;
@ -120,3 +125,8 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
AdminLog::FilterValue::Flags flags, AdminLog::FilterValue::Flags flags,
bool isChannel bool isChannel
) -> EditFlagsControl<AdminLog::FilterValue::Flags>; ) -> EditFlagsControl<AdminLog::FilterValue::Flags>;
[[nodiscard]] auto CreateEditChatbotPermissions(
QWidget *parent,
Data::ChatbotsPermissions flags
) -> EditFlagsControl<Data::ChatbotsPermissions>;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "api/api_global_privacy.h"
#include "api/api_premium.h" #include "api/api_premium.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/random.h" #include "base/random.h"
@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h" #include "boxes/send_credits_box.h"
#include "boxes/transfer_gift_box.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
#include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_gift_box_pack.h"
@ -101,8 +103,10 @@ namespace Ui {
namespace { namespace {
constexpr auto kPriceTabAll = 0; constexpr auto kPriceTabAll = 0;
constexpr auto kPriceTabLimited = -2;
constexpr auto kPriceTabInStock = -1; constexpr auto kPriceTabInStock = -1;
constexpr auto kPriceTabLimited = -2;
constexpr auto kPriceTabMy = -3;
constexpr auto kMyGiftsPerPage = 50;
constexpr auto kGiftMessageLimit = 255; constexpr auto kGiftMessageLimit = 255;
constexpr auto kSentToastDuration = 3 * crl::time(1000); constexpr auto kSentToastDuration = 3 * crl::time(1000);
constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000); constexpr auto kSwitchUpgradeCoverInterval = 3 * crl::time(1000);
@ -118,6 +122,11 @@ struct PremiumGiftsDescriptor {
std::shared_ptr<Api::PremiumGiftCodeOptions> api; std::shared_ptr<Api::PremiumGiftCodeOptions> api;
}; };
struct MyGiftsDescriptor {
std::vector<Data::SavedStarGift> list;
QString offset;
};
struct GiftsDescriptor { struct GiftsDescriptor {
std::vector<GiftDescriptor> list; std::vector<GiftDescriptor> list;
std::shared_ptr<Api::PremiumGiftCodeOptions> api; std::shared_ptr<Api::PremiumGiftCodeOptions> api;
@ -360,6 +369,30 @@ auto GenerateGiftMedia(
return Images::Round(std::move(result), mask, RectPart::FullTop); return Images::Round(std::move(result), mask, RectPart::FullTop);
} }
struct VisibleRange {
int top = 0;
int bottom = 0;
friend inline bool operator==(VisibleRange, VisibleRange) = default;
};
class WidgetWithRange final : public RpWidget {
public:
using RpWidget::RpWidget;
[[nodiscard]] rpl::producer<VisibleRange> visibleRange() const {
return _visibleRange.value();
}
private:
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override {
_visibleRange = VisibleRange{ visibleTop, visibleBottom };
}
rpl::variable<VisibleRange> _visibleRange;
};
void PrepareImage( void PrepareImage(
QImage &image, QImage &image,
not_null<Text::CustomEmoji*> emoji, not_null<Text::CustomEmoji*> emoji,
@ -673,7 +706,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
} }
ranges::sort(list, ranges::less(), &GiftTypePremium::months); ranges::sort(list, ranges::less(), &GiftTypePremium::months);
auto &map = Map[session]; auto &map = Map[session];
if (map.last.list != list) { if (map.last.list != list || list.empty()) {
map.last = PremiumGiftsDescriptor{ map.last = PremiumGiftsDescriptor{
std::move(list), std::move(list),
api, api,
@ -686,6 +719,24 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
} }
[[nodiscard]] bool AllowedToSend(
const GiftTypeStars &gift,
not_null<PeerData*> peer) {
using Type = Api::DisallowedGiftType;
const auto user = peer->asUser();
if (!user || user->isSelf()) {
return true;
}
const auto disallowedTypes = user ? user->disallowedGiftTypes() : Type();
const auto allowLimited = !(disallowedTypes & Type::Limited);
const auto allowUnlimited = !(disallowedTypes & Type::Unlimited);
const auto allowUnique = !(disallowedTypes & Type::Unique);
if (!gift.info.limitedCount) {
return allowUnlimited;
}
return allowLimited || (gift.info.starsToUpgrade && allowUnique);
}
[[nodiscard]] rpl::producer<std::vector<GiftTypeStars>> GiftsStars( [[nodiscard]] rpl::producer<std::vector<GiftTypeStars>> GiftsStars(
not_null<Main::Session*> session, not_null<Main::Session*> session,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
@ -694,6 +745,12 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
static auto Map = base::flat_map<not_null<Main::Session*>, Session>(); static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
const auto filtered = [=](std::vector<GiftTypeStars> list) {
list.erase(ranges::remove_if(list, [&](const GiftTypeStars &gift) {
return !AllowedToSend(gift, peer);
}), end(list));
return list;
};
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
@ -703,7 +760,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
session->lifetime().add([=] { Map.remove(session); }); session->lifetime().add([=] { Map.remove(session); });
} }
if (!i->second.last.empty()) { if (!i->second.last.empty()) {
consumer.put_next_copy(i->second.last); consumer.put_next(filtered(i->second.last));
} }
using namespace Api; using namespace Api;
@ -718,25 +775,14 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
for (auto &gift : gifts) { for (auto &gift : gifts) {
list.push_back({ .info = gift }); list.push_back({ .info = gift });
} }
ranges::sort(list, []( ranges::stable_sort(list, [](const auto &a, const auto &b) {
const GiftTypeStars &a, return a.info.soldOut < b.info.soldOut;
const GiftTypeStars &b) {
if (!a.info.limitedCount && !b.info.limitedCount) {
return a.info.stars <= b.info.stars;
} else if (!a.info.limitedCount) {
return true;
} else if (!b.info.limitedCount) {
return false;
} else if (a.info.limitedLeft != b.info.limitedLeft) {
return a.info.limitedLeft > b.info.limitedLeft;
}
return a.info.stars <= b.info.stars;
}); });
auto &map = Map[session]; auto &map = Map[session];
if (map.last != list) { if (map.last != list || list.empty()) {
map.last = list; map.last = list;
consumer.put_next_copy(list); consumer.put_next(filtered(std::move(list)));
} }
}, lifetime); }, lifetime);
@ -744,6 +790,48 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
} }
[[nodiscard]] rpl::producer<MyGiftsDescriptor> UniqueGiftsSlice(
not_null<Main::Session*> session,
QString offset = QString()) {
return [=](auto consumer) {
using Flag = MTPpayments_GetSavedStarGifts::Flag;
const auto user = session->user();
const auto requestId = session->api().request(
MTPpayments_GetSavedStarGifts(
MTP_flags(Flag::f_exclude_limited | Flag::f_exclude_unlimited),
user->input,
MTP_string(offset),
MTP_int(kMyGiftsPerPage)
)).done([=](const MTPpayments_SavedStarGifts &result) {
auto gifts = MyGiftsDescriptor();
const auto &data = result.data();
if (const auto next = data.vnext_offset()) {
gifts.offset = qs(*next);
}
const auto owner = &session->data();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
gifts.list.reserve(data.vgifts().v.size());
for (const auto &gift : data.vgifts().v) {
if (auto parsed = Api::FromTL(user, gift)) {
gifts.list.push_back(std::move(*parsed));
}
}
consumer.put_next(std::move(gifts));
consumer.put_done();
}).fail([=] {
consumer.put_next({});
consumer.put_done();
}).send();
auto lifetime = rpl::lifetime();
lifetime.add([=] { session->api().request(requestId).cancel(); });
return lifetime;
};
}
[[nodiscard]] Text::String TabTextForPrice( [[nodiscard]] Text::String TabTextForPrice(
not_null<Main::Session*> session, not_null<Main::Session*> session,
int price) { int price) {
@ -752,6 +840,8 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
}; };
if (price == kPriceTabAll) { if (price == kPriceTabAll) {
return simple(tr::lng_gift_stars_tabs_all(tr::now)); return simple(tr::lng_gift_stars_tabs_all(tr::now));
} else if (price == kPriceTabMy) {
return simple(tr::lng_gift_stars_tabs_my(tr::now));
} else if (price == kPriceTabLimited) { } else if (price == kPriceTabLimited) {
return simple(tr::lng_gift_stars_tabs_limited(tr::now)); return simple(tr::lng_gift_stars_tabs_limited(tr::now));
} else if (price == kPriceTabInStock) { } else if (price == kPriceTabInStock) {
@ -774,7 +864,8 @@ struct GiftPriceTabs {
[[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs( [[nodiscard]] GiftPriceTabs MakeGiftsPriceTabs(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<std::vector<GiftTypeStars>> gifts) { rpl::producer<std::vector<GiftTypeStars>> gifts,
bool hasMyUnique) {
auto widget = object_ptr<RpWidget>((QWidget*)nullptr); auto widget = object_ptr<RpWidget>((QWidget*)nullptr);
const auto raw = widget.data(); const auto raw = widget.data();
@ -798,6 +889,13 @@ struct GiftPriceTabs {
int pressed = -1; int pressed = -1;
int active = -1; int active = -1;
}; };
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftType();
if (disallowed & Api::DisallowedGiftType::Unique) {
hasMyUnique = false;
}
const auto state = raw->lifetime().make_state<State>(); const auto state = raw->lifetime().make_state<State>();
const auto scroll = [=] { const auto scroll = [=] {
return QPoint(int(base::SafeRound(state->scroll)), 0); return QPoint(int(base::SafeRound(state->scroll)), 0);
@ -805,25 +903,14 @@ struct GiftPriceTabs {
state->prices = std::move( state->prices = std::move(
gifts gifts
) | rpl::map([](const std::vector<GiftTypeStars> &gifts) { ) | rpl::map([=](const std::vector<GiftTypeStars> &gifts) {
auto result = std::vector<int>(); auto result = std::vector<int>();
result.push_back(kPriceTabAll); result.push_back(kPriceTabAll);
auto special = 1;
auto same = true;
auto sameKey = 0;
auto hasNonSoldOut = false; auto hasNonSoldOut = false;
auto hasSoldOut = false; auto hasSoldOut = false;
auto hasLimited = false; auto hasLimited = false;
auto hasNonLimited = false;
for (const auto &gift : gifts) { for (const auto &gift : gifts) {
if (same) {
const auto key = gift.info.stars
* (gift.info.limitedCount ? -1 : 1);
if (!sameKey) {
sameKey = key;
} else if (sameKey != key) {
same = false;
}
}
if (IsSoldOut(gift.info)) { if (IsSoldOut(gift.info)) {
hasSoldOut = true; hasSoldOut = true;
} else { } else {
@ -831,19 +918,21 @@ struct GiftPriceTabs {
} }
if (gift.info.limitedCount) { if (gift.info.limitedCount) {
hasLimited = true; hasLimited = true;
} else {
hasNonLimited = true;
} }
if (!ranges::contains(result, gift.info.stars)) { if (!ranges::contains(result, gift.info.stars)) {
result.push_back(gift.info.stars); result.push_back(gift.info.stars);
} }
} }
if (same) { if (hasMyUnique && !gifts.empty()) {
return std::vector<int>(); result.push_back(kPriceTabMy);
} }
if (hasSoldOut && hasNonSoldOut) { if (hasSoldOut && hasNonSoldOut) {
result.insert(begin(result) + (special++), kPriceTabInStock); result.push_back(kPriceTabInStock);
} }
if (hasLimited) { if (hasLimited && hasNonLimited) {
result.insert(begin(result) + (special++), kPriceTabLimited); result.push_back(kPriceTabLimited);
} }
ranges::sort(begin(result) + 1, end(result)); ranges::sort(begin(result) + 1, end(result));
return result; return result;
@ -1446,6 +1535,13 @@ void SendGiftBox(
const auto limited = stars const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft) && (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0); && (stars->info.limitedLeft > 0);
const auto costToUpgrade = stars ? stars->info.starsToUpgrade : 0;
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftTypes();
const auto disallowLimited = !peer->isSelf()
&& (disallowed & Api::DisallowedGiftType::Limited);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox); box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title()); box->setTitle(tr::lng_gift_send_title());
@ -1465,6 +1561,7 @@ void SendGiftBox(
state->details = GiftDetails{ state->details = GiftDetails{
.descriptor = descriptor, .descriptor = descriptor,
.randomId = base::RandomValue<uint64>(), .randomId = base::RandomValue<uint64>(),
.upgraded = disallowLimited && (costToUpgrade > 0),
}; };
peer->updateFull(); peer->updateFull();
state->messageAllowed = peer->session().changes().peerFlagsValue( state->messageAllowed = peer->session().changes().peerFlagsValue(
@ -1563,13 +1660,13 @@ void SendGiftBox(
session, session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (stars) { if (stars) {
const auto cost = stars->info.starsToUpgrade; if (costToUpgrade > 0 && !peer->isSelf() && !disallowLimited) {
if (cost > 0 && !peer->isSelf()) {
const auto id = stars->info.id; const auto id = stars->info.id;
const auto showing = std::make_shared<bool>(); const auto showing = std::make_shared<bool>();
AddDivider(container); AddDivider(container);
AddSkip(container); AddSkip(container);
AddUpgradeButton(container, session, cost, peer, [=](bool on) { AddUpgradeButton(container, session, costToUpgrade, peer, [=](
bool on) {
auto now = state->details.current(); auto now = state->details.current();
now.upgraded = on; now.upgraded = on;
state->details = std::move(now); state->details = std::move(now);
@ -1583,7 +1680,7 @@ void SendGiftBox(
.stargiftId = id, .stargiftId = id,
.ready = [=](bool) { *showing = false; }, .ready = [=](bool) { *showing = false; },
.peer = peer, .peer = peer,
.cost = int(cost), .cost = int(costToUpgrade),
}); });
}); });
} else { } else {
@ -1726,17 +1823,24 @@ void SendGiftBox(
[[nodiscard]] object_ptr<RpWidget> MakeGiftsList( [[nodiscard]] object_ptr<RpWidget> MakeGiftsList(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<GiftsDescriptor> gifts) { rpl::producer<GiftsDescriptor> gifts,
auto result = object_ptr<RpWidget>((QWidget*)nullptr); Fn<void()> loadMore) {
auto result = object_ptr<WidgetWithRange>((QWidget*)nullptr);
const auto raw = result.data(); const auto raw = result.data();
struct State { struct State {
Delegate delegate; Delegate delegate;
std::vector<int> order;
std::vector<bool> validated;
std::vector<GiftDescriptor> list;
std::vector<std::unique_ptr<GiftButton>> buttons; std::vector<std::unique_ptr<GiftButton>> buttons;
std::shared_ptr<Api::PremiumGiftCodeOptions> api;
rpl::variable<VisibleRange> visibleRange;
bool sending = false; bool sending = false;
int perRow = 1;
}; };
const auto state = raw->lifetime().make_state<State>(State{ const auto state = raw->lifetime().make_state<State>(State{
.delegate = Delegate(window, GiftButtonMode::Full), .delegate = Delegate(&window->session(), GiftButtonMode::Full),
}); });
const auto single = state->delegate.buttonSize(); const auto single = state->delegate.buttonSize();
const auto shadow = st::defaultDropdownMenu.wrap.shadow; const auto shadow = st::defaultDropdownMenu.wrap.shadow;
@ -1745,74 +1849,165 @@ void SendGiftBox(
auto &packs = window->session().giftBoxStickersPacks(); auto &packs = window->session().giftBoxStickersPacks();
packs.updated() | rpl::start_with_next([=] { packs.updated() | rpl::start_with_next([=] {
for (const auto &button : state->buttons) { for (const auto &button : state->buttons) {
button->update(); if (const auto raw = button.get()) {
raw->update();
}
} }
}, raw->lifetime()); }, raw->lifetime());
const auto rebuild = [=] {
const auto width = st::boxWideWidth;
const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right();
const auto range = state->visibleRange.current();
const auto count = int(state->list.size());
auto &buttons = state->buttons;
if (buttons.size() < count) {
buttons.resize(count);
}
auto &validated = state->validated;
validated.resize(count);
auto x = padding.left();
auto y = padding.top();
const auto perRow = state->perRow;
const auto singlew = single.width() + st::giftBoxGiftSkip.x();
const auto singleh = single.height() + st::giftBoxGiftSkip.y();
const auto rowFrom = std::max(range.top - y, 0) / singleh;
const auto rowTill = (std::max(range.bottom - y + st::giftBoxGiftSkip.y(), 0) + singleh - 1)
/ singleh;
Assert(rowTill >= rowFrom);
const auto first = rowFrom * perRow;
const auto last = std::min(rowTill * perRow, count);
auto checkedFrom = 0;
auto checkedTill = int(buttons.size());
const auto ensureButton = [&](int index) {
auto &button = buttons[index];
if (!button) {
validated[index] = false;
for (; checkedFrom != first; ++checkedFrom) {
if (buttons[checkedFrom]) {
button = std::move(buttons[checkedFrom]);
break;
}
}
}
if (!button) {
for (; checkedTill != last; ) {
--checkedTill;
if (buttons[checkedTill]) {
button = std::move(buttons[checkedTill]);
break;
}
}
}
if (!button) {
button = std::make_unique<GiftButton>(
raw,
&state->delegate);
}
if (validated[index]) {
return;
}
button->show();
validated[index] = true;
const auto &descriptor = state->list[state->order[index]];
button->setDescriptor(descriptor, GiftButton::Mode::Full);
button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star && star->info.unique) {
const auto done = [=] {
window->session().credits().load(true);
window->showPeerHistory(peer);
};
ShowTransferToBox(
window,
peer,
star->info.unique,
star->transferId,
done);
} else if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star));
} else {
window->show(Box(
SendGiftBox,
window,
peer,
state->api,
descriptor));
}
});
button->setGeometry(QRect(QPoint(x, y), single), extend);
};
y += rowFrom * singleh;
for (auto row = rowFrom; row != rowTill; ++row) {
for (auto col = 0; col != perRow; ++col) {
const auto index = row * perRow + col;
if (index >= count) {
break;
}
const auto last = !((col + 1) % perRow);
if (last) {
x = padding.left() + available - single.width();
}
ensureButton(index);
if (last) {
x = padding.left();
y += singleh;
} else {
x += singlew;
}
}
}
const auto till = std::min(int(buttons.size()), rowTill * perRow);
for (auto i = count; i < till; ++i) {
if (const auto button = buttons[i].get()) {
button->hide();
}
}
const auto page = range.bottom - range.top;
if (loadMore && page > 0 && range.bottom + page > raw->height()) {
loadMore();
}
};
state->visibleRange = raw->visibleRange();
state->visibleRange.value(
) | rpl::start_with_next(rebuild, raw->lifetime());
std::move( std::move(
gifts gifts
) | rpl::start_with_next([=](const GiftsDescriptor &gifts) { ) | rpl::start_with_next([=](const GiftsDescriptor &gifts) {
const auto width = st::boxWideWidth; const auto width = st::boxWideWidth;
const auto padding = st::giftBoxPadding; const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right(); const auto available = width - padding.left() - padding.right();
const auto perRow = available / single.width(); state->perRow = available / single.width();
const auto count = int(gifts.list.size()); state->list = std::move(gifts.list);
state->api = gifts.api;
auto order = ranges::views::ints const auto count = int(state->list.size());
state->order = ranges::views::ints
| ranges::views::take(count) | ranges::views::take(count)
| ranges::to_vector; | ranges::to_vector;
state->validated.clear();
if (SortForBirthday(peer)) { if (SortForBirthday(peer)) {
ranges::stable_partition(order, [&](int i) { ranges::stable_partition(state->order, [&](int i) {
const auto &gift = gifts.list[i]; const auto &gift = state->list[i];
const auto stars = std::get_if<GiftTypeStars>(&gift); const auto stars = std::get_if<GiftTypeStars>(&gift);
return stars && stars->info.birthday; return stars && stars->info.birthday && !stars->info.unique;
}); });
} }
auto x = padding.left(); const auto rows = (count + state->perRow - 1) / state->perRow;
auto y = padding.top(); const auto height = padding.top()
state->buttons.resize(count); + (rows * single.height())
for (auto &button : state->buttons) { + ((rows - 1) * st::giftBoxGiftSkip.y())
if (!button) { + padding.bottom();
button = std::make_unique<GiftButton>(raw, &state->delegate); raw->resize(raw->width(), height);
button->show(); rebuild();
}
}
const auto api = gifts.api;
for (auto i = 0; i != count; ++i) {
const auto button = state->buttons[i].get();
const auto &descriptor = gifts.list[order[i]];
button->setDescriptor(descriptor, GiftButton::Mode::Full);
const auto last = !((i + 1) % perRow);
if (last) {
x = padding.left() + available - single.width();
}
button->setGeometry(QRect(QPoint(x, y), single), extend);
if (last) {
x = padding.left();
y += single.height() + st::giftBoxGiftSkip.y();
} else {
x += single.width() + st::giftBoxGiftSkip.x();
}
button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star && IsSoldOut(star->info)) {
window->show(Box(SoldOutBox, window, *star));
} else {
window->show(
Box(SendGiftBox, window, peer, api, descriptor));
}
});
}
if (count % perRow) {
y += padding.bottom() + single.height();
} else {
y += padding.bottom() - st::giftBoxGiftSkip.y();
}
raw->resize(raw->width(), count ? y : 0);
}, raw->lifetime()); }, raw->lifetime());
return result; return result;
@ -1876,42 +2071,83 @@ void AddBlock(
gifts.list | ranges::to<std::vector<GiftDescriptor>>, gifts.list | ranges::to<std::vector<GiftDescriptor>>,
gifts.api, gifts.api,
}; };
})); }), nullptr);
result->lifetime().add([state = std::move(state)] {}); result->lifetime().add([state = std::move(state)] {});
return result; return result;
} }
[[nodiscard]] object_ptr<RpWidget> MakeStarsGifts( [[nodiscard]] object_ptr<RpWidget> MakeStarsGifts(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
MyGiftsDescriptor my) {
auto result = object_ptr<VerticalLayout>((QWidget*)nullptr); auto result = object_ptr<VerticalLayout>((QWidget*)nullptr);
struct State { struct State {
rpl::variable<std::vector<GiftTypeStars>> gifts; rpl::variable<std::vector<GiftTypeStars>> gifts;
rpl::variable<int> priceTab = kPriceTabAll; rpl::variable<int> priceTab = kPriceTabAll;
rpl::event_stream<> myUpdated;
MyGiftsDescriptor my;
rpl::lifetime myLoading;
}; };
const auto state = result->lifetime().make_state<State>(); const auto state = result->lifetime().make_state<State>();
state->my = std::move(my);
state->gifts = GiftsStars(&window->session(), peer); state->gifts = GiftsStars(&window->session(), peer);
auto tabs = MakeGiftsPriceTabs(window, peer, state->gifts.value()); auto tabs = MakeGiftsPriceTabs(
window,
peer,
state->gifts.value(),
!state->my.list.empty());
state->priceTab = std::move(tabs.priceTab); state->priceTab = std::move(tabs.priceTab);
result->add(std::move(tabs.widget)); result->add(std::move(tabs.widget));
result->add(MakeGiftsList(window, peer, rpl::combine( result->add(MakeGiftsList(window, peer, rpl::combine(
state->gifts.value(), state->gifts.value(),
state->priceTab.value() state->priceTab.value(),
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) { rpl::single(rpl::empty) | rpl::then(state->myUpdated.events())
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) { ) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price, auto) {
return (price == kPriceTabLimited) if (price == kPriceTabMy) {
? (!gift.info.limitedCount) gifts.clear();
: (price == kPriceTabInStock) for (const auto &gift : state->my.list) {
? IsSoldOut(gift.info) gifts.push_back({
: (price && gift.info.stars != price); .transferId = gift.manageId,
}), end(gifts)); .info = gift.info,
.mine = true,
});
}
} else {
const auto pred = [&](const GiftTypeStars &gift) {
return (price == kPriceTabLimited)
? (!gift.info.limitedCount)
: (price == kPriceTabInStock)
? IsSoldOut(gift.info)
: (price && gift.info.stars != price);
};
gifts.erase(ranges::remove_if(gifts, pred), end(gifts));
}
return GiftsDescriptor{ return GiftsDescriptor{
gifts | ranges::to<std::vector<GiftDescriptor>>(), gifts | ranges::to<std::vector<GiftDescriptor>>(),
}; };
}))); }), [=] {
if (state->priceTab.current() == kPriceTabMy
&& !state->my.offset.isEmpty()
&& !state->myLoading) {
state->myLoading = UniqueGiftsSlice(
&peer->session(),
state->my.offset
) | rpl::start_with_next([=](MyGiftsDescriptor &&descriptor) {
state->myLoading.destroy();
state->my.offset = descriptor.list.empty()
? QString()
: descriptor.offset;
state->my.list.insert(
end(state->my.list),
std::make_move_iterator(begin(descriptor.list)),
std::make_move_iterator(end(descriptor.list)));
state->myUpdated.fire({});
});
}
}));
return result; return result;
} }
@ -1919,7 +2155,8 @@ void AddBlock(
void GiftBox( void GiftBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
MyGiftsDescriptor my) {
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setStyle(st::creditsGiftBox); box->setStyle(st::creditsGiftBox);
box->setNoContentMargin(true); box->setNoContentMargin(true);
@ -1935,6 +2172,24 @@ void GiftBox(
AddSkip(content, st::defaultVerticalListSkip * 5); AddSkip(content, st::defaultVerticalListSkip * 5);
// Check disallowed gift types
const auto user = peer->asUser();
using Type = Api::DisallowedGiftType;
const auto disallowedTypes = user
? user->disallowedGiftTypes()
: Type::Premium;
const auto premiumDisallowed = peer->isSelf()
|| (disallowedTypes & Type::Premium);
const auto limitedDisallowed = !peer->isSelf()
&& (disallowedTypes & Type::Limited);
const auto unlimitedDisallowed = !peer->isSelf()
&& (disallowedTypes & Type::Unlimited);
const auto uniqueDisallowed = !peer->isSelf()
&& (disallowedTypes & Type::Unique);
const auto allStarsDisallowed = limitedDisallowed
&& unlimitedDisallowed
&& uniqueDisallowed;
content->add( content->add(
object_ptr<CenterWrap<>>( object_ptr<CenterWrap<>>(
content, content,
@ -1956,11 +2211,12 @@ void GiftBox(
window->showSettings(Settings::CreditsId()); window->showSettings(Settings::CreditsId());
return false; return false;
}; };
if (peer->isUser() && !peer->isSelf()) { if (peer->isUser() && !peer->isSelf() && !premiumDisallowed) {
const auto premiumClickHandlerFilter = [=](const auto &...) { const auto premiumClickHandlerFilter = [=](const auto &...) {
Settings::ShowPremium(window, u"gift_send"_q); Settings::ShowPremium(window, u"gift_send"_q);
return false; return false;
}; };
AddBlock(content, window, { AddBlock(content, window, {
.subtitle = tr::lng_gift_premium_subtitle(), .subtitle = tr::lng_gift_premium_subtitle(),
.about = tr::lng_gift_premium_about( .about = tr::lng_gift_premium_about(
@ -1973,28 +2229,32 @@ void GiftBox(
.content = MakePremiumGifts(window, peer), .content = MakePremiumGifts(window, peer),
}); });
} }
AddBlock(content, window, {
.subtitle = (peer->isSelf() // Only add star gifts if at least one type is allowed
? tr::lng_gift_self_title() if (!allStarsDisallowed) {
: peer->isBroadcast() AddBlock(content, window, {
? tr::lng_gift_channel_title() .subtitle = (peer->isSelf()
: tr::lng_gift_stars_subtitle()), ? tr::lng_gift_self_title()
.about = (peer->isSelf() : peer->isBroadcast()
? tr::lng_gift_self_about(Text::WithEntities) ? tr::lng_gift_channel_title()
: peer->isBroadcast() : tr::lng_gift_stars_subtitle()),
? tr::lng_gift_channel_about( .about = (peer->isSelf()
lt_name, ? tr::lng_gift_self_about(Text::WithEntities)
rpl::single(Text::Bold(peer->name())), : peer->isBroadcast()
Text::WithEntities) ? tr::lng_gift_channel_about(
: tr::lng_gift_stars_about( lt_name,
lt_name, rpl::single(Text::Bold(peer->name())),
rpl::single(Text::Bold(peer->shortName())), Text::WithEntities)
lt_link, : tr::lng_gift_stars_about(
tr::lng_gift_stars_link() | Text::ToLink(), lt_name,
Text::WithEntities)), rpl::single(Text::Bold(peer->shortName())),
.aboutFilter = starsClickHandlerFilter, lt_link,
.content = MakeStarsGifts(window, peer), tr::lng_gift_stars_link() | Text::ToLink(),
}); Text::WithEntities)),
.aboutFilter = starsClickHandlerFilter,
.content = MakeStarsGifts(window, peer, std::move(my)),
});
}
} }
struct SelfOption { struct SelfOption {
@ -2184,11 +2444,31 @@ void ChooseStarGiftRecipient(
void ShowStarGiftBox( void ShowStarGiftBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
if (controller->showFrozenError()) {
return;
}
struct Session { struct Session {
PeerData *peer = nullptr; PeerData *peer = nullptr;
MyGiftsDescriptor my;
bool premiumGiftsReady = false; bool premiumGiftsReady = false;
bool starsGiftsReady = false; bool starsGiftsReady = false;
bool fullReady = false;
bool myReady = false;
bool hasPremium = false;
bool hasUpgradable = false;
bool hasLimited = false;
bool hasUnlimited = false;
rpl::lifetime lifetime; rpl::lifetime lifetime;
[[nodiscard]] bool ready() const {
return premiumGiftsReady
&& starsGiftsReady
&& fullReady
&& myReady;
}
}; };
static auto Map = base::flat_map<not_null<Main::Session*>, Session>(); static auto Map = base::flat_map<not_null<Main::Session*>, Session>();
@ -2203,46 +2483,91 @@ void ShowStarGiftBox(
i->second = Session{ .peer = peer }; i->second = Session{ .peer = peer };
const auto weak = base::make_weak(controller); const auto weak = base::make_weak(controller);
const auto show = [=] { const auto checkReady = [=] {
Map[session] = Session(); auto &entry = Map[session];
if (!entry.ready()) {
return;
}
auto was = std::move(entry);
entry = Session();
if (const auto strong = weak.get()) { if (const auto strong = weak.get()) {
strong->show(Box(GiftBox, strong, peer)); if (const auto user = peer->asUser()) {
using Type = Api::DisallowedGiftType;
const auto disallowedTypes = user->disallowedGiftTypes();
const auto premium = (disallowedTypes & Type::Premium)
|| peer->isSelf();
const auto limited = (disallowedTypes & Type::Limited);
const auto unlimited = (disallowedTypes & Type::Unlimited);
const auto unique = (disallowedTypes & Type::Unique);
if ((unique || (!was.hasUpgradable && was.my.list.empty()))
&& (premium || !was.hasPremium)
&& (limited || !was.hasLimited)
&& (unlimited || !was.hasUnlimited)) {
strong->showToast(
tr::lng_edit_privacy_gifts_restricted(tr::now));
return;
}
}
strong->show(Box(GiftBox, strong, peer, std::move(was.my)));
} }
}; };
base::timer_once(
kGiftsPreloadTimeout
) | rpl::start_with_next(show, i->second.lifetime);
const auto user = peer->asUser(); const auto user = peer->asUser();
if (user && !user->isSelf()) { if (user && !user->isSelf()) {
GiftsPremium( GiftsPremium(
session, session,
peer peer
) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) { ) | rpl::start_with_next([=](PremiumGiftsDescriptor &&gifts) {
if (!gifts.list.empty()) { auto &entry = Map[session];
auto &entry = Map[session]; entry.premiumGiftsReady = true;
entry.premiumGiftsReady = true; entry.hasPremium = !gifts.list.empty();
if (entry.starsGiftsReady) { checkReady();
show();
}
}
}, i->second.lifetime); }, i->second.lifetime);
} else { } else {
i->second.hasPremium = false;
i->second.premiumGiftsReady = true; i->second.premiumGiftsReady = true;
} }
if (peer->isFullLoaded()) {
i->second.fullReady = true;
} else {
peer->updateFull();
peer->session().changes().peerUpdates(
peer,
Data::PeerUpdate::Flag::FullInfo
) | rpl::take(1) | rpl::start_with_next([=] {
auto &entry = Map[session];
entry.fullReady = true;
checkReady();
}, i->second.lifetime);
}
GiftsStars( GiftsStars(
session, session,
peer peer
) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) { ) | rpl::start_with_next([=](std::vector<GiftTypeStars> &&gifts) {
if (!gifts.empty()) { auto &entry = Map[session];
auto &entry = Map[session]; entry.starsGiftsReady = true;
entry.starsGiftsReady = true; for (const auto &gift : gifts) {
if (entry.premiumGiftsReady) { if (gift.info.limitedCount) {
show(); entry.hasLimited = true;
if (gift.info.starsToUpgrade) {
entry.hasUpgradable = true;
}
} else {
entry.hasUnlimited = true;
} }
} }
checkReady();
}, i->second.lifetime);
UniqueGiftsSlice(
session
) | rpl::start_with_next([=](MyGiftsDescriptor &&gifts) {
auto &entry = Map[session];
entry.my = std::move(gifts);
entry.myReady = true;
checkReady();
}, i->second.lifetime); }, i->second.lifetime);
} }

View file

@ -463,6 +463,8 @@ void TransferGift(
std::move(formDone)); std::move(formDone));
} }
} // namespace
void ShowTransferToBox( void ShowTransferToBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -539,8 +541,6 @@ void ShowTransferToBox(
})); }));
} }
} // namespace
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,

View file

@ -16,6 +16,13 @@ struct UniqueGift;
class SavedStarGiftId; class SavedStarGiftId;
} // namespace Data } // namespace Data
void ShowTransferToBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId savedId,
Fn<void()> closeParentBox);
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,

View file

@ -715,18 +715,21 @@ void Panel::createRemoteLowBattery() {
}, _remoteLowBattery->lifetime()); }, _remoteLowBattery->lifetime());
constexpr auto kBatterySize = QSize(29, 13); constexpr auto kBatterySize = QSize(29, 13);
const auto scaledBatterySize = QSize(
style::ConvertScale(kBatterySize.width()),
style::ConvertScale(kBatterySize.height()));
const auto icon = [&] { const auto icon = [&] {
auto svg = QSvgRenderer( auto svg = QSvgRenderer(
BatterySvg(kBatterySize, st::videoPlayIconFg->c)); BatterySvg(kBatterySize, st::videoPlayIconFg->c));
auto image = QImage( auto image = QImage(
kBatterySize * style::DevicePixelRatio(), scaledBatterySize * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
image.setDevicePixelRatio(style::DevicePixelRatio()); image.setDevicePixelRatio(style::DevicePixelRatio());
image.fill(Qt::transparent); image.fill(Qt::transparent);
{ {
auto p = QPainter(&image); auto p = QPainter(&image);
svg.render(&p, Rect(kBatterySize)); svg.render(&p, Rect(scaledBatterySize));
} }
return image; return image;
}(); }();
@ -745,7 +748,7 @@ void Panel::createRemoteLowBattery() {
p.drawImage( p.drawImage(
st::callTooltipMutedIconPosition.x(), st::callTooltipMutedIconPosition.x(),
(r.height() - kBatterySize.height()) / 2, (r.height() - scaledBatterySize.height()) / 2,
icon); icon);
}, _remoteLowBattery->lifetime()); }, _remoteLowBattery->lifetime());

View file

@ -1131,6 +1131,10 @@ historyScheduledToggle: IconButton(historyAttach) {
{ "chat/input_scheduled_dot", attentionButtonFg } { "chat/input_scheduled_dot", attentionButtonFg }
}; };
} }
historyGiftToUser: IconButton(historyAttach) {
icon: icon {{ "chat/input_gift", historyComposeIconFg }};
iconOver: icon {{ "chat/input_gift", historyComposeIconFgOver }};
}
historyAttachEmojiInner: IconButton(historyAttach) { historyAttachEmojiInner: IconButton(historyAttach) {
icon: icon {{ "chat/input_smile_face", historyComposeIconFg }}; icon: icon {{ "chat/input_smile_face", historyComposeIconFg }};
@ -1566,3 +1570,27 @@ processingVideoView: RoundButton(defaultActiveButton) {
textBgOver: transparent; textBgOver: transparent;
ripple: emptyRippleAnimation; ripple: emptyRippleAnimation;
} }
frozenBarTitle: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;
textFg: attentionButtonFg;
}
frozenRestrictionTitle: FlatLabel(frozenBarTitle) {
align: align(top);
}
frozenBarSubtitle: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
frozenRestrictionSubtitle: FlatLabel(frozenBarSubtitle) {
align: align(top);
}
frozenInfoBox: Box(defaultBox) {
buttonPadding: margins(16px, 11px, 16px, 16px);
buttonHeight: 42px;
button: RoundButton(defaultActiveButton) {
height: 42px;
textTop: 12px;
style: semiboldTextStyle;
}
shadowIgnoreTopSkip: true;
}

View file

@ -1265,7 +1265,7 @@ void EmojiListWidget::fillEmojiStatusMenu(
int section, int section,
int index) { int index) {
const auto chosen = lookupCustomEmoji(index, section); const auto chosen = lookupCustomEmoji(index, section);
if (!chosen || chosen.collectible) { if (!chosen) {
return; return;
} }
const auto selectWith = [=](TimeId scheduled) { const auto selectWith = [=](TimeId scheduled) {

View file

@ -11,38 +11,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h" // History::session #include "history/history.h" // History::session
#include "history/history_item.h" // HistoryItem::originalText #include "history/history_item.h" // HistoryItem::originalText
#include "history/history_item_helpers.h" // DropDisallowedCustomEmoji #include "history/history_item_helpers.h" // DropDisallowedCustomEmoji
#include "base/unixtime.h"
#include "base/qthelp_regex.h" #include "base/qthelp_regex.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/basic_click_handlers.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "lottie/lottie_icon.h"
#include "info/profile/info_profile_icon.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "history/view/controls/compose_controls_common.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_credits.h" #include "styles/style_credits.h"
#include "styles/style_dialogs.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "base/qt/qt_common_adapters.h" #include "base/qt/qt_common_adapters.h"
@ -1187,10 +1197,10 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
return result; return result;
} }
base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction( std::unique_ptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent, QWidget *parent,
const QString &text) { const QString &text) {
auto result = base::make_unique_q<Ui::RpWidget>(parent); auto result = std::make_unique<Ui::RpWidget>(parent);
const auto raw = result.get(); const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>( const auto label = CreateChild<Ui::FlatLabel>(
result.get(), result.get(),
@ -1215,11 +1225,11 @@ base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
return result; return result;
} }
base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction( std::unique_ptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent, QWidget *parent,
not_null<UserData*> user, not_null<UserData*> user,
not_null<Window::SessionController*> controller) { not_null<Window::SessionController*> controller) {
auto result = base::make_unique_q<Ui::RpWidget>(parent); auto result = std::make_unique<Ui::RpWidget>(parent);
const auto raw = result.get(); const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>( const auto label = CreateChild<Ui::FlatLabel>(
result.get(), result.get(),
@ -1254,6 +1264,102 @@ base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
return result; return result;
} }
std::unique_ptr<Ui::AbstractButton> BoostsToLiftWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
int boosts) {
auto result = std::make_unique<Ui::FlatButton>(
parent,
tr::lng_restricted_boost_group(tr::now),
st::historyComposeButton);
result->setClickedCallback([=] {
const auto window = show->resolveWindow();
window->resolveBoostState(peer->asChannel(), boosts);
});
return result;
}
std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
FrozenWriteRestrictionType type,
FreezeInfoStyleOverride st) {
using namespace Ui;
auto result = std::make_unique<FlatButton>(
parent,
QString(),
st::historyComposeButton);
const auto raw = result.get();
const auto bar = (type == FrozenWriteRestrictionType::DialogsList);
const auto title = CreateChild<FlatLabel>(
raw,
(bar ? tr::lng_frozen_bar_title : tr::lng_frozen_restrict_title)(
tr::now),
bar ? st::frozenBarTitle : st::frozenRestrictionTitle);
title->setAttribute(Qt::WA_TransparentForMouseEvents);
title->show();
const auto subtitle = CreateChild<FlatLabel>(
raw,
(bar
? tr::lng_frozen_bar_text(
lt_arrow,
rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
Ui::Text::WithEntities)
: tr::lng_frozen_restrict_text(Ui::Text::WithEntities)),
bar ? st::frozenBarSubtitle : st::frozenRestrictionSubtitle);
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
subtitle->show();
const auto shadow = bar ? CreateChild<PlainShadow>(raw) : nullptr;
const auto icon = bar ? CreateChild<RpWidget>(raw) : nullptr;
if (icon) {
icon->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icon);
st::menuIconDisableAttention.paintInCenter(p, icon->rect());
}, icon->lifetime());
icon->show();
}
raw->sizeValue() | rpl::start_with_next([=](QSize size) {
if (bar) {
const auto toggle = [&](auto &&widget, bool shown) {
if (widget->isHidden() == shown) {
widget->setVisible(shown);
}
};
const auto small = 2 * st::defaultDialogRow.photoSize;
const auto shown = (size.width() > small);
toggle(icon, !shown);
toggle(title, shown);
toggle(subtitle, shown);
icon->setGeometry(0, 0, size.width(), size.height());
}
const auto skip = bar
? st::defaultDialogRow.padding.left()
: 2 * st::normalFont->spacew;
const auto available = size.width() - skip * 2;
title->resizeToWidth(available);
subtitle->resizeToWidth(available);
const auto height = title->height() + subtitle->height();
const auto top = (size.height() - height) / 2;
title->moveToLeft(skip, top, size.width());
subtitle->moveToLeft(skip, top + title->height(), size.width());
const auto line = st::lineWidth;
if (shadow) {
shadow->setGeometry(0, size.height() - line, size.width(), line);
}
}, title->lifetime());
raw->setClickedCallback([=] {
show->show(Box(FrozenInfoBox, &show->session(), st));
});
return result;
}
void SelectTextInFieldWithMargins( void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,
const TextSelection &selection) { const TextSelection &selection) {
@ -1301,3 +1407,101 @@ rpl::producer<TextWithEntities> PaidSendButtonText(
return PaidSendButtonText(tr::now, count); return PaidSendButtonText(tr::now, count);
}); });
} }
void FrozenInfoBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
FreezeInfoStyleOverride st) {
box->setWidth(st::boxWideWidth);
box->setStyle(st::frozenInfoBox);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
const auto info = session->frozen();
const auto content = box->verticalLayout();
auto icon = Settings::CreateLottieIcon(
content,
{
.name = u"media_forbidden"_q,
.sizeOverride = {
st::changePhoneIconSize,
st::changePhoneIconSize,
},
},
st::settingLocalPasscodeIconPadding);
content->add(std::move(icon.widget));
box->setShowFinishedCallback([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
});
Ui::AddSkip(content);
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<TextWithEntities> text,
not_null<const style::icon*> icon) {
auto raw = content->add(
object_ptr<Ui::VerticalLayout>(content));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st.infoAbout ? *st.infoAbout : st::upgradeGiftSubtext),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
};
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_frozen_title(),
st.title ? *st.title : st::uniqueGiftTitle),
st::settingsPremiumRowTitlePadding);
Ui::AddSkip(content, st::defaultVerticalListSkip * 3);
infoRow(
tr::lng_frozen_subtitle1(),
tr::lng_frozen_text1(Ui::Text::WithEntities),
st.violationIcon ? st.violationIcon : &st::menuIconBlock);
infoRow(
tr::lng_frozen_subtitle2(),
tr::lng_frozen_text2(Ui::Text::WithEntities),
st.readOnlyIcon ? st.readOnlyIcon : &st::menuIconLock);
infoRow(
tr::lng_frozen_subtitle3(),
tr::lng_frozen_text3(
lt_link,
rpl::single(Ui::Text::Link(u"@SpamBot"_q, info.appealUrl)),
lt_date,
rpl::single(TextWithEntities{
langDayOfMonthFull(
base::unixtime::parse(info.until).date()),
}),
Ui::Text::WithEntities),
st.appealIcon ? st.appealIcon : &st::menuIconHourglass);
const auto button = box->addButton(
tr::lng_frozen_appeal_button(),
[url = info.appealUrl] { UrlClickHandler::Open(url); });
const auto buttonPadding = st::frozenInfoBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
}

View file

@ -37,7 +37,12 @@ enum class PauseReason;
class Show; class Show;
} // namespace ChatHelpers } // namespace ChatHelpers
namespace HistoryView::Controls {
struct WriteRestriction;
} // namespace HistoryView::Controls
namespace Ui { namespace Ui {
class GenericBox;
class PopupMenu; class PopupMenu;
class Show; class Show;
} // namespace Ui } // namespace Ui
@ -162,13 +167,41 @@ private:
[[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView( [[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
QWidget *parent, QWidget *parent,
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction( [[nodiscard]] std::unique_ptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent, QWidget *parent,
const QString &text); const QString &text);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction( [[nodiscard]] std::unique_ptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent, QWidget *parent,
not_null<UserData*> user, not_null<UserData*> user,
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
[[nodiscard]] auto BoostsToLiftWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
int boosts)
-> std::unique_ptr<Ui::AbstractButton>;
struct FreezeInfoStyleOverride {
const style::Box *box = nullptr;
const style::FlatLabel *title = nullptr;
const style::FlatLabel *subtitle = nullptr;
const style::icon *violationIcon = nullptr;
const style::icon *readOnlyIcon = nullptr;
const style::icon *appealIcon = nullptr;
const style::FlatLabel *infoTitle = nullptr;
const style::FlatLabel *infoAbout = nullptr;
};
[[nodiscard]] FreezeInfoStyleOverride DarkFreezeInfoStyle();
enum class FrozenWriteRestrictionType {
MessageField,
DialogsList,
};
[[nodiscard]] std::unique_ptr<Ui::AbstractButton> FrozenWriteRestriction(
not_null<QWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
FrozenWriteRestrictionType type,
FreezeInfoStyleOverride st = {});
void SelectTextInFieldWithMargins( void SelectTextInFieldWithMargins(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,
@ -178,3 +211,8 @@ void SelectTextInFieldWithMargins(
[[nodiscard]] rpl::producer<TextWithEntities> PaidSendButtonText( [[nodiscard]] rpl::producer<TextWithEntities> PaidSendButtonText(
rpl::producer<int> stars, rpl::producer<int> stars,
rpl::producer<QString> fallback = nullptr); rpl::producer<QString> fallback = nullptr);
void FrozenInfoBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
FreezeInfoStyleOverride st);

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_list_widget.h" #include "chat_helpers/stickers_list_widget.h"
#include "chat_helpers/gifs_list_widget.h" #include "chat_helpers/gifs_list_widget.h"
#include "menu/menu_send.h" #include "menu/menu_send.h"
#include "ui/controls/swipe_handler.h"
#include "ui/controls/tabbed_search.h" #include "ui/controls/tabbed_search.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
@ -522,7 +523,6 @@ TabbedSelector::TabbedSelector(
if (hasEmojiTab()) { if (hasEmojiTab()) {
emoji()->refreshEmoji(); emoji()->refreshEmoji();
} }
//setAttribute(Qt::WA_AcceptTouchEvents);
setAttribute(Qt::WA_OpaquePaintEvent, false); setAttribute(Qt::WA_OpaquePaintEvent, false);
showAll(); showAll();
hide(); hide();
@ -530,6 +530,60 @@ TabbedSelector::TabbedSelector(
TabbedSelector::~TabbedSelector() = default; TabbedSelector::~TabbedSelector() = default;
void TabbedSelector::reinstallSwipe(not_null<Ui::RpWidget*> widget) {
_swipeLifetime.destroy();
auto update = [=](Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[=]() -> std::pair<QColor, QColor> {
return {
st::historyForwardChooseBg->c,
st::historyForwardChooseFg->c,
};
},
data.translation < 0);
}
_swipeBackData.callback(data);
return;
} else if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
};
auto init = [=](int, Qt::LayoutDirection direction) {
if (!_tabsSlider) {
return Ui::Controls::SwipeHandlerFinishData();
}
const auto activeSection = _tabsSlider->activeSection();
const auto isToLeft = direction == Qt::RightToLeft;
if ((isToLeft && activeSection > 0)
|| (!isToLeft && activeSection < _tabs.size() - 1)) {
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
if (_tabsSlider
&& _tabsSlider->activeSection() == activeSection) {
_swipeBackData = {};
_tabsSlider->setActiveSection(isToLeft
? activeSection - 1
: activeSection + 1);
}
});
}
return Ui::Controls::SwipeHandlerFinishData();
};
Ui::Controls::SetupSwipeHandler({
.widget = widget,
.scroll = _scroll.data(),
.update = std::move(update),
.init = std::move(init),
.dontStart = nullptr,
.onLifetime = &_swipeLifetime,
});
}
const style::EmojiPan &TabbedSelector::st() const { const style::EmojiPan &TabbedSelector::st() const {
return _st; return _st;
} }
@ -1302,6 +1356,10 @@ void TabbedSelector::setWidgetToScrollArea() {
inner->moveToLeft(0, 0); inner->moveToLeft(0, 0);
inner->show(); inner->show();
if (_tabs.size() > 1) {
reinstallSwipe(inner);
}
_scroll->disableScroll(false); _scroll->disableScroll(false);
scrollToY(currentTab()->getScrollTop()); scrollToY(currentTab()->getScrollTop());
handleScroll(); handleScroll();

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_common.h" #include "api/api_common.h"
#include "chat_helpers/compose/compose_features.h" #include "chat_helpers/compose/compose_features.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "ui/controls/swipe_handler_data.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/effects/message_sending_animation_common.h" #include "ui/effects/message_sending_animation_common.h"
#include "ui/effects/panel_animation.h" #include "ui/effects/panel_animation.h"
@ -287,12 +288,16 @@ private:
not_null<GifsListWidget*> gifs() const; not_null<GifsListWidget*> gifs() const;
not_null<StickersListWidget*> masks() const; not_null<StickersListWidget*> masks() const;
void reinstallSwipe(not_null<Ui::RpWidget*> widget);
const style::EmojiPan &_st; const style::EmojiPan &_st;
const ComposeFeatures _features; const ComposeFeatures _features;
const std::shared_ptr<Show> _show; const std::shared_ptr<Show> _show;
const PauseReason _level = {}; const PauseReason _level = {};
const Fn<QColor()> _customTextColor; const Fn<QColor()> _customTextColor;
Ui::Controls::SwipeBackResult _swipeBackData;
Mode _mode = Mode::Full; Mode _mode = Mode::Full;
int _roundRadius = 0; int _roundRadius = 0;
int _footerTop = 0; int _footerTop = 0;
@ -329,6 +334,8 @@ private:
rpl::event_stream<> _showRequests; rpl::event_stream<> _showRequests;
rpl::event_stream<> _slideFinished; rpl::event_stream<> _slideFinished;
rpl::lifetime _swipeLifetime;
}; };
class TabbedSelector::Inner : public Ui::RpWidget { class TabbedSelector::Inner : public Ui::RpWidget {

View file

@ -93,6 +93,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QScreen> #include <QtGui/QScreen>
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <ksandbox.h>
// AyuGram includes // AyuGram includes
#include "ayu/ayu_infra.h" #include "ayu/ayu_infra.h"
#include "ayu/features/streamer_mode/streamer_mode.h" #include "ayu/features/streamer_mode/streamer_mode.h"

View file

@ -244,7 +244,7 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(_customFontFamily) + Serialize::stringSize(_customFontFamily)
+ sizeof(qint32) * 3 + sizeof(qint32) * 3
+ Serialize::bytearraySize(_tonsiteStorageToken) + Serialize::bytearraySize(_tonsiteStorageToken)
+ sizeof(qint32) * 7; + sizeof(qint32) * 8;
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -405,7 +405,8 @@ QByteArray Settings::serialize() const {
<< qint32(_recordVideoMessages ? 1 : 0) << qint32(_recordVideoMessages ? 1 : 0)
<< SerializeVideoQuality(_videoQuality) << SerializeVideoQuality(_videoQuality)
<< qint32(_ivZoom.current()) << qint32(_ivZoom.current())
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0); << qint32(_systemDarkModeEnabled.current() ? 1 : 0)
<< qint32(_quickDialogAction);
} }
Ensures(result.size() == size); Ensures(result.size() == size);
@ -536,6 +537,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0; qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
quint32 videoQuality = SerializeVideoQuality(_videoQuality); quint32 videoQuality = SerializeVideoQuality(_videoQuality);
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0; quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
quint32 quickDialogAction = quint32(_quickDialogAction);
stream >> themesAccentColors; stream >> themesAccentColors;
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -864,6 +866,9 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> systemDarkModeEnabled; stream >> systemDarkModeEnabled;
} }
if (!stream.atEnd()) {
stream >> quickDialogAction;
}
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()"));
@ -1086,6 +1091,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_recordVideoMessages = (recordVideoMessages == 1); _recordVideoMessages = (recordVideoMessages == 1);
_videoQuality = DeserializeVideoQuality(videoQuality); _videoQuality = DeserializeVideoQuality(videoQuality);
_chatFiltersHorizontal = (chatFiltersHorizontal == 1); _chatFiltersHorizontal = (chatFiltersHorizontal == 1);
_quickDialogAction = Dialogs::Ui::QuickDialogAction(quickDialogAction);
} }
QString Settings::getSoundPath(const QString &key) const { QString Settings::getSoundPath(const QString &key) const {
@ -1477,6 +1483,7 @@ void Settings::resetOnLastLogout() {
_recordVideoMessages = false; _recordVideoMessages = false;
_videoQuality = {}; _videoQuality = {};
_chatFiltersHorizontal = false; _chatFiltersHorizontal = false;
_quickDialogAction = Dialogs::Ui::QuickDialogAction::Disabled;
_recentEmojiPreload.clear(); _recentEmojiPreload.clear();
_recentEmoji.clear(); _recentEmoji.clear();
@ -1664,4 +1671,12 @@ void Settings::setChatFiltersHorizontal(bool value) {
_chatFiltersHorizontal = value; _chatFiltersHorizontal = value;
} }
Dialogs::Ui::QuickDialogAction Settings::quickDialogAction() const {
return _quickDialogAction;
}
void Settings::setQuickDialogAction(Dialogs::Ui::QuickDialogAction action) {
_quickDialogAction = action;
}
} // namespace Core } // namespace Core

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/core_settings_proxy.h" #include "core/core_settings_proxy.h"
#include "media/media_common.h" #include "media/media_common.h"
#include "dialogs/ui/dialogs_quick_action.h"
#include "window/themes/window_themes_embedded.h" #include "window/themes/window_themes_embedded.h"
#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/attach/attach_send_files_way.h"
#include "base/flags.h" #include "base/flags.h"
@ -952,6 +953,9 @@ public:
[[nodiscard]] static PlaybackSpeed DeserializePlaybackSpeed( [[nodiscard]] static PlaybackSpeed DeserializePlaybackSpeed(
qint32 speed); qint32 speed);
[[nodiscard]] Dialogs::Ui::QuickDialogAction quickDialogAction() const;
void setQuickDialogAction(Dialogs::Ui::QuickDialogAction);
void resetOnLastLogout(); void resetOnLastLogout();
private: private:
@ -1092,6 +1096,9 @@ private:
bool _recordVideoMessages = false; bool _recordVideoMessages = false;
Dialogs::Ui::QuickDialogAction _quickDialogAction
= Dialogs::Ui::QuickDialogAction::Disabled;
QByteArray _photoEditorBrush; QByteArray _photoEditorBrush;
}; };

View file

@ -504,6 +504,22 @@ uint64 Launcher::installationTag() const {
return InstallationTag; return InstallationTag;
} }
QByteArray Launcher::instanceHash() const {
static const auto Result = [&] {
QByteArray h(32, 0);
if (customWorkingDir()) {
const auto d = QFile::encodeName(
QDir(cWorkingDir()).absolutePath());
hashMd5Hex(d.constData(), d.size(), h.data());
} else {
const auto f = QFile::encodeName(cExeDir() + cExeName());
hashMd5Hex(f.constData(), f.size(), h.data());
}
return h;
}();
return Result;
}
void Launcher::processArguments() { void Launcher::processArguments() {
enum class KeyFormat { enum class KeyFormat {
NoValues, NoValues,

View file

@ -33,6 +33,7 @@ public:
bool customWorkingDir() const; bool customWorkingDir() const;
uint64 installationTag() const; uint64 installationTag() const;
QByteArray instanceHash() const;
bool checkPortableVersionFolder(); bool checkPortableVersionFolder();
bool validateCustomWorkingDir(); bool validateCustomWorkingDir();

View file

@ -306,6 +306,42 @@ void ShowLanguagesBox(Window::SessionController *controller) {
Guard = LanguageBox::Show(controller); Guard = LanguageBox::Show(controller);
} }
void ShowPhonePrivacyBox(Window::SessionController *controller) {
static auto Guard = base::binary_guard();
auto guard = base::binary_guard();
using Privacy = Api::UserPrivacy;
const auto key = Privacy::Key::PhoneNumber;
controller->session().api().userPrivacy().reload(key);
const auto weak = base::make_weak(controller);
auto shared = std::make_shared<base::binary_guard>(
guard.make_guard());
auto lifetime = std::make_shared<rpl::lifetime>();
controller->session().api().userPrivacy().value(
key
) | rpl::take(
1
) | rpl::start_with_next([=](const Privacy::Rule &value) mutable {
using namespace ::Settings;
const auto show = shared->alive();
if (lifetime) {
base::take(lifetime)->destroy();
}
if (show) {
if (const auto controller = weak.get()) {
controller->show(Box<EditPrivacyBox>(
controller,
std::make_unique<PhoneNumberPrivacyController>(
controller),
value));
}
}
}, *lifetime);
Guard = std::move(guard);
}
bool SetLanguage( bool SetLanguage(
Window::SessionController *controller, Window::SessionController *controller,
const Match &match, const Match &match,
@ -722,6 +758,9 @@ bool ResolveSettings(
if (section == u"language"_q) { if (section == u"language"_q) {
ShowLanguagesBox(controller); ShowLanguagesBox(controller);
return {}; return {};
} else if (section == u"phone_privacy"_q) {
ShowPhonePrivacyBox(controller);
return {};
} else if (section == u"devices"_q) { } else if (section == u"devices"_q) {
return ::Settings::Sessions::Id(); return ::Settings::Sessions::Id();
} else if (section == u"folders"_q) { } else if (section == u"folders"_q) {
@ -876,6 +915,8 @@ bool ShowEditBirthday(
const QVariant &context) { const QVariant &context) {
if (!controller) { if (!controller) {
return false; return false;
} else if (controller->showFrozenError()) {
return true;
} }
const auto user = controller->session().user(); const auto user = controller->session().user();
const auto save = [=](Data::Birthday result) { const auto save = [=](Data::Birthday result) {
@ -932,6 +973,8 @@ bool ShowEditPersonalChannel(
const QVariant &context) { const QVariant &context) {
if (!controller) { if (!controller) {
return false; return false;
} else if (controller->showFrozenError()) {
return true;
} }
auto listController = std::make_unique<PersonalChannelController>( auto listController = std::make_unique<PersonalChannelController>(
@ -1466,7 +1509,7 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
ResolvePrivatePost ResolvePrivatePost
}, },
{ {
u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile)?$"_q, u"^settings(/language|/devices|/folders|/privacy|/themes|/change_number|/auto_delete|/information|/edit_profile|/phone_privacy)?$"_q,
ResolveSettings ResolveSettings
}, },
{ {

View file

@ -110,6 +110,7 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"read_chat"_q , Command::ReadChat }, { u"read_chat"_q , Command::ReadChat },
{ u"show_chat_menu"_q , Command::ShowChatMenu }, { u"show_chat_menu"_q , Command::ShowChatMenu },
{ u"show_chat_preview"_q , Command::ShowChatPreview },
// Shortcuts that have no default values. // Shortcuts that have no default values.
{ u"message"_q , Command::JustSendMessage }, { u"message"_q , Command::JustSendMessage },
@ -506,6 +507,7 @@ void Manager::fillDefaults() {
set(u"ctrl+r"_q, Command::ReadChat); set(u"ctrl+r"_q, Command::ReadChat);
set(u"ctrl+\\"_q, Command::ShowChatMenu); set(u"ctrl+\\"_q, Command::ShowChatMenu);
set(u"ctrl+]"_q, Command::ShowChatPreview);
_defaults = keysCurrents(); _defaults = keysCurrents();
} }

View file

@ -72,6 +72,7 @@ enum class Command {
MediaViewerFullscreen, MediaViewerFullscreen,
ShowChatMenu, ShowChatMenu,
ShowChatPreview,
SupportReloadTemplates, SupportReloadTemplates,
SupportToggleMuted, SupportToggleMuted,

View file

@ -21,10 +21,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <set> #include <set>
#include <filesystem> #include <filesystem>
#if __has_include(<ksandbox.h>)
#include <ksandbox.h>
#endif
#define qsl(s) QStringLiteral(s) #define qsl(s) QStringLiteral(s)
namespace base { namespace base {
@ -34,15 +30,6 @@ inline bool in_range(Value &&value, From &&from, Till &&till) {
return (value >= from) && (value < till); return (value >= from) && (value < till);
} }
#if __has_include(<ksandbox.h>)
inline QString IconName() {
static const auto Result = KSandbox::isFlatpak()
? qEnvironmentVariable("FLATPAK_ID")
: u"telegram"_q;
return Result;
}
#endif
inline bool CanReadDirectory(const QString &path) { inline bool CanReadDirectory(const QString &path) {
#ifndef Q_OS_MAC // directory_iterator since 10.15 #ifndef Q_OS_MAC // directory_iterator since 10.15
std::error_code error; std::error_code error;

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 = 5012003; constexpr auto AppVersion = 5013001;
constexpr auto AppVersionStr = "5.12.3"; constexpr auto AppVersionStr = "5.13.1";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -8,10 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/business/data_business_chatbots.h" #include "data/business/data_business_chatbots.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "data/business/data_business_common.h" #include "data/business/data_business_common.h"
#include "data/business/data_business_info.h" #include "data/business/data_business_info.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
namespace Data { namespace Data {
@ -41,7 +43,7 @@ void Chatbots::preload() {
_settings = ChatbotsSettings{ _settings = ChatbotsSettings{
.bot = _owner->session().data().user(botId), .bot = _owner->session().data().user(botId),
.recipients = FromMTP(_owner, bot.vrecipients()), .recipients = FromMTP(_owner, bot.vrecipients()),
.repliesAllowed = bot.is_can_reply(), .permissions = FromMTP(bot.vrights()),
}; };
} else { } else {
_settings.force_assign(ChatbotsSettings()); _settings.force_assign(ChatbotsSettings());
@ -81,11 +83,8 @@ void Chatbots::save(
using Flag = MTPaccount_UpdateConnectedBot::Flag; using Flag = MTPaccount_UpdateConnectedBot::Flag;
const auto api = &_owner->session().api(); const auto api = &_owner->session().api();
api->request(MTPaccount_UpdateConnectedBot( api->request(MTPaccount_UpdateConnectedBot(
MTP_flags(!settings.bot MTP_flags(!settings.bot ? Flag::f_deleted : Flag::f_rights),
? Flag::f_deleted ToMTP(settings.permissions),
: settings.repliesAllowed
? Flag::f_can_reply
: Flag()),
(settings.bot ? settings.bot : was.bot)->inputUser, (settings.bot ? settings.bot : was.bot)->inputUser,
ForBotsToMTP(settings.recipients) ForBotsToMTP(settings.recipients)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
@ -180,4 +179,39 @@ void Chatbots::reload() {
preload(); preload();
} }
EditFlagsDescriptor<ChatbotsPermissions> ChatbotsPermissionsLabels() {
using Flag = ChatbotsPermission;
using PermissionLabel = EditFlagsLabel<ChatbotsPermissions>;
auto messages = std::vector<PermissionLabel>{
{ Flag::ViewMessages, tr::lng_chatbots_read(tr::now) },
{ Flag::ReplyToMessages, tr::lng_chatbots_reply(tr::now) },
{ Flag::MarkAsRead, tr::lng_chatbots_mark_as_read(tr::now) },
{ Flag::DeleteSent, tr::lng_chatbots_delete_sent(tr::now) },
{ Flag::DeleteReceived, tr::lng_chatbots_delete_received(tr::now) },
};
auto manage = std::vector<PermissionLabel>{
{ Flag::EditName, tr::lng_chatbots_edit_name(tr::now) },
{ Flag::EditBio, tr::lng_chatbots_edit_bio(tr::now) },
{ Flag::EditUserpic, tr::lng_chatbots_edit_userpic(tr::now) },
{ Flag::EditUsername, tr::lng_chatbots_edit_username(tr::now) },
};
auto gifts = std::vector<PermissionLabel>{
{ Flag::ViewGifts, tr::lng_chatbots_view_gifts(tr::now) },
{ Flag::SellGifts, tr::lng_chatbots_sell_gifts(tr::now) },
{ Flag::GiftSettings, tr::lng_chatbots_gift_settings(tr::now) },
{ Flag::TransferGifts, tr::lng_chatbots_transfer_gifts(tr::now) },
{ Flag::TransferStars, tr::lng_chatbots_transfer_stars(tr::now) },
};
auto stories = std::vector<PermissionLabel>{
{ Flag::ManageStories, tr::lng_chatbots_manage_stories(tr::now) },
};
return { .labels = {
{ tr::lng_chatbots_manage_messages(), std::move(messages) },
{ tr::lng_chatbots_manage_profile(), std::move(manage) },
{ tr::lng_chatbots_manage_gifts(), std::move(gifts) },
{ std::nullopt, std::move(stories) },
}, .st = nullptr };
}
} // namespace Data } // namespace Data

View file

@ -11,6 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData; class UserData;
template <typename Flags>
struct EditFlagsDescriptor;
namespace Data { namespace Data {
class Session; class Session;
@ -18,7 +21,7 @@ class Session;
struct ChatbotsSettings { struct ChatbotsSettings {
UserData *bot = nullptr; UserData *bot = nullptr;
BusinessRecipients recipients; BusinessRecipients recipients;
bool repliesAllowed = false; ChatbotsPermissions permissions;
friend inline bool operator==( friend inline bool operator==(
const ChatbotsSettings &, const ChatbotsSettings &,
@ -67,4 +70,7 @@ private:
}; };
[[nodiscard]] auto ChatbotsPermissionsLabels()
-> EditFlagsDescriptor<ChatbotsPermissions>;
} // namespace Data } // namespace Data

View file

@ -159,6 +159,47 @@ BusinessRecipients FromMTP(
return result; return result;
} }
ChatbotsPermissions FromMTP(const MTPBusinessBotRights &rights) {
using Flag = ChatbotsPermission;
const auto &data = rights.data();
return Flag::ViewMessages
| (data.is_reply() ? Flag::ReplyToMessages : Flag())
| (data.is_read_messages() ? Flag::MarkAsRead : Flag())
| (data.is_delete_sent_messages() ? Flag::DeleteSent : Flag())
| (data.is_delete_received_messages() ? Flag::DeleteReceived : Flag())
| (data.is_edit_name() ? Flag::EditName : Flag())
| (data.is_edit_bio() ? Flag::EditBio : Flag())
| (data.is_edit_profile_photo() ? Flag::EditUserpic : Flag())
| (data.is_edit_username() ? Flag::EditUsername : Flag())
| (data.is_view_gifts() ? Flag::ViewGifts : Flag())
| (data.is_sell_gifts() ? Flag::SellGifts : Flag())
| (data.is_change_gift_settings() ? Flag::GiftSettings : Flag())
| (data.is_transfer_and_upgrade_gifts() ? Flag::TransferGifts : Flag())
| (data.is_transfer_stars() ? Flag::TransferStars : Flag())
| (data.is_manage_stories() ? Flag::ManageStories : Flag());
}
MTPBusinessBotRights ToMTP(ChatbotsPermissions rights) {
using Flag = MTPDbusinessBotRights::Flag;
using Right = ChatbotsPermission;
return MTP_businessBotRights(MTP_flags(Flag()
| ((rights & Right::ReplyToMessages) ? Flag::f_reply : Flag())
| ((rights & Right::MarkAsRead) ? Flag::f_read_messages : Flag())
| ((rights & Right::DeleteSent) ? Flag::f_delete_sent_messages : Flag())
| ((rights & Right::DeleteReceived) ? Flag::f_delete_received_messages : Flag())
| ((rights & Right::EditName) ? Flag::f_edit_name : Flag())
| ((rights & Right::EditBio) ? Flag::f_edit_bio : Flag())
| ((rights & Right::EditUserpic) ? Flag::f_edit_profile_photo : Flag())
| ((rights & Right::EditUsername) ? Flag::f_edit_username : Flag())
| ((rights & Right::ViewGifts) ? Flag::f_view_gifts : Flag())
| ((rights & Right::SellGifts) ? Flag::f_sell_gifts : Flag())
| ((rights & Right::GiftSettings) ? Flag::f_change_gift_settings : Flag())
| ((rights & Right::TransferGifts) ? Flag::f_transfer_and_upgrade_gifts : Flag())
| ((rights & Right::TransferStars) ? Flag::f_transfer_stars : Flag())
| ((rights & Right::ManageStories) ? Flag::f_manage_stories : Flag())));
}
BusinessDetails FromMTP( BusinessDetails FromMTP(
not_null<Session*> owner, not_null<Session*> owner,
const tl::conditional<MTPBusinessWorkHours> &hours, const tl::conditional<MTPBusinessWorkHours> &hours,

View file

@ -57,6 +57,26 @@ enum class BusinessRecipientsType : uchar {
Bots, Bots,
}; };
enum class ChatbotsPermission {
ViewMessages = 0x0001,
ReplyToMessages = 0x0002,
MarkAsRead = 0x0004,
DeleteSent = 0x0008,
DeleteReceived = 0x0010,
EditName = 0x0020,
EditBio = 0x0040,
EditUserpic = 0x0080,
EditUsername = 0x0100,
ViewGifts = 0x0200,
SellGifts = 0x0400,
GiftSettings = 0x0800,
TransferGifts = 0x1000,
TransferStars = 0x2000,
ManageStories = 0x4000,
};
inline constexpr bool is_flag_type(ChatbotsPermission) { return true; }
using ChatbotsPermissions = base::flags<ChatbotsPermission>;
[[nodiscard]] MTPInputBusinessRecipients ForMessagesToMTP( [[nodiscard]] MTPInputBusinessRecipients ForMessagesToMTP(
const BusinessRecipients &data); const BusinessRecipients &data);
[[nodiscard]] MTPInputBusinessBotRecipients ForBotsToMTP( [[nodiscard]] MTPInputBusinessBotRecipients ForBotsToMTP(
@ -67,6 +87,9 @@ enum class BusinessRecipientsType : uchar {
[[nodiscard]] BusinessRecipients FromMTP( [[nodiscard]] BusinessRecipients FromMTP(
not_null<Session*> owner, not_null<Session*> owner,
const MTPBusinessBotRecipients &recipients); const MTPBusinessBotRecipients &recipients);
[[nodiscard]] ChatbotsPermissions FromMTP(
const MTPBusinessBotRights &rights);
[[nodiscard]] MTPBusinessBotRights ToMTP(ChatbotsPermissions rights);
struct Timezone { struct Timezone {
QString id; QString id;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/components/sponsored_messages.h" #include "data/components/sponsored_messages.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "api/api_peer_search.h" // SponsoredSearchResult
#include "apiwrap.h" #include "apiwrap.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -37,6 +38,19 @@ constexpr auto kRequestTimeLimit = 5 * 60 * crl::time(1000);
return (received > 0) && (received + kRequestTimeLimit > crl::now()); return (received > 0) && (received + kRequestTimeLimit > crl::now());
} }
template <typename Fields>
[[nodiscard]] std::vector<TextWithEntities> Prepare(const Fields &fields) {
using InfoList = std::vector<TextWithEntities>;
return (!fields.sponsorInfo.text.isEmpty()
&& !fields.additionalInfo.text.isEmpty())
? InfoList{ fields.sponsorInfo, fields.additionalInfo }
: !fields.sponsorInfo.text.isEmpty()
? InfoList{ fields.sponsorInfo }
: !fields.additionalInfo.text.isEmpty()
? InfoList{ fields.additionalInfo }
: InfoList{};
}
} // namespace } // namespace
SponsoredMessages::SponsoredMessages(not_null<Main::Session*> session) SponsoredMessages::SponsoredMessages(not_null<Main::Session*> session)
@ -523,17 +537,16 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
if (!entryPtr) { if (!entryPtr) {
return; return;
} }
const auto randomId = entryPtr->sponsored.randomId; view(entryPtr->sponsored.randomId);
}
void SponsoredMessages::view(const QByteArray &randomId) {
auto &request = _viewRequests[randomId]; auto &request = _viewRequests[randomId];
if (request.requestId || TooEarlyForRequest(request.lastReceived)) { if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
return; return;
} }
request.requestId = _session->api().request( request.requestId = _session->api().request(
MTPmessages_ViewSponsoredMessage( MTPmessages_ViewSponsoredMessage(MTP_bytes(randomId))
entryPtr->item
? entryPtr->item->history()->peer->input
: _session->data().peer(fullId.peer)->input,
MTP_bytes(randomId))
).done([=] { ).done([=] {
auto &request = _viewRequests[randomId]; auto &request = _viewRequests[randomId];
request.lastReceived = crl::now(); request.lastReceived = crl::now();
@ -550,18 +563,8 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
return {}; return {};
} }
const auto &data = entryPtr->sponsored; const auto &data = entryPtr->sponsored;
using InfoList = std::vector<TextWithEntities>;
auto info = (!data.sponsorInfo.text.isEmpty()
&& !data.additionalInfo.text.isEmpty())
? InfoList{ data.sponsorInfo, data.additionalInfo }
: !data.sponsorInfo.text.isEmpty()
? InfoList{ data.sponsorInfo }
: !data.additionalInfo.text.isEmpty()
? InfoList{ data.additionalInfo }
: InfoList{};
return { return {
.info = std::move(info), .info = Prepare(data),
.link = data.link, .link = data.link,
.buttonText = data.from.buttonText, .buttonText = data.from.buttonText,
.photoId = data.from.photoId, .photoId = data.from.photoId,
@ -574,6 +577,14 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
}; };
} }
SponsoredMessages::Details SponsoredMessages::lookupDetails(
const Api::SponsoredSearchResult &data) const {
return {
.info = Prepare(data),
.canReport = true,
};
}
void SponsoredMessages::clicked( void SponsoredMessages::clicked(
const FullMsgId &fullId, const FullMsgId &fullId,
bool isMedia, bool isMedia,
@ -582,22 +593,45 @@ void SponsoredMessages::clicked(
if (!entryPtr) { if (!entryPtr) {
return; return;
} }
const auto randomId = entryPtr->sponsored.randomId; clicked(entryPtr->sponsored.randomId, isMedia, isFullscreen);
}
void SponsoredMessages::clicked(
const QByteArray &randomId,
bool isMedia,
bool isFullscreen) {
using Flag = MTPmessages_ClickSponsoredMessage::Flag; using Flag = MTPmessages_ClickSponsoredMessage::Flag;
_session->api().request(MTPmessages_ClickSponsoredMessage( _session->api().request(MTPmessages_ClickSponsoredMessage(
MTP_flags(Flag(0) MTP_flags(Flag(0)
| (isMedia ? Flag::f_media : Flag(0)) | (isMedia ? Flag::f_media : Flag(0))
| (isFullscreen ? Flag::f_fullscreen : Flag(0))), | (isFullscreen ? Flag::f_fullscreen : Flag(0))),
entryPtr->item
? entryPtr->item->history()->peer->input
: _session->data().peer(fullId.peer)->input,
MTP_bytes(randomId) MTP_bytes(randomId)
)).send(); )).send();
} }
SponsoredReportAction SponsoredMessages::createReportCallback(
const FullMsgId &fullId) {
const auto entry = find(fullId);
if (!entry) {
return { .callback = [=](const auto &...) {} };
}
const auto history = _session->data().history(fullId.peer);
const auto erase = [=] {
const auto it = _data.find(history);
if (it != end(_data)) {
auto &list = it->second.entries;
const auto proj = [&](const Entry &e) {
return e.itemFullId == fullId;
};
list.erase(ranges::remove_if(list, proj), end(list));
}
};
return createReportCallback(entry->sponsored.randomId, erase);
}
auto SponsoredMessages::createReportCallback(const FullMsgId &fullId) SponsoredReportAction SponsoredMessages::createReportCallback(
-> Fn<void(SponsoredReportResult::Id, Fn<void(SponsoredReportResult)>)> { const QByteArray &randomId,
Fn<void()> erase) {
using TLChoose = MTPDchannels_sponsoredMessageReportResultChooseOption; using TLChoose = MTPDchannels_sponsoredMessageReportResultChooseOption;
using TLAdsHidden = MTPDchannels_sponsoredMessageReportResultAdsHidden; using TLAdsHidden = MTPDchannels_sponsoredMessageReportResultAdsHidden;
using TLReported = MTPDchannels_sponsoredMessageReportResultReported; using TLReported = MTPDchannels_sponsoredMessageReportResultReported;
@ -613,25 +647,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
}; };
const auto state = std::make_shared<State>(); const auto state = std::make_shared<State>();
return [=](Result::Id optionId, Fn<void(Result)> done) { return { .callback = [=](Result::Id optionId, Fn<void(Result)> done) {
const auto entry = find(fullId);
if (!entry) {
return;
}
const auto history = _session->data().history(fullId.peer);
const auto erase = [=] {
const auto it = _data.find(history);
if (it != end(_data)) {
auto &list = it->second.entries;
const auto proj = [&](const Entry &e) {
return e.itemFullId == fullId;
};
list.erase(ranges::remove_if(list, proj), end(list));
}
};
if (optionId == Result::Id("-1")) { if (optionId == Result::Id("-1")) {
erase(); erase();
return; return;
@ -639,8 +655,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
state->requestId = _session->api().request( state->requestId = _session->api().request(
MTPmessages_ReportSponsoredMessage( MTPmessages_ReportSponsoredMessage(
history->peer->input, MTP_bytes(randomId),
MTP_bytes(entry->sponsored.randomId),
MTP_bytes(optionId)) MTP_bytes(optionId))
).done([=]( ).done([=](
const MTPchannels_SponsoredMessageReportResult &result, const MTPchannels_SponsoredMessageReportResult &result,
@ -677,7 +692,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
done({ .error = error.type() }); done({ .error = error.type() });
} }
}).send(); }).send();
}; } };
} }
SponsoredMessages::State SponsoredMessages::state( SponsoredMessages::State SponsoredMessages::state(

View file

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History; class History;
namespace Api {
struct SponsoredSearchResult;
} // namespace Api
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -69,6 +73,25 @@ struct SponsoredMessage {
TextWithEntities additionalInfo; TextWithEntities additionalInfo;
}; };
struct SponsoredMessageDetails {
std::vector<TextWithEntities> info;
QString link;
QString buttonText;
PhotoId photoId = PhotoId(0);
PhotoId mediaPhotoId = PhotoId(0);
DocumentId mediaDocumentId = DocumentId(0);
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
bool isLinkInternal = false;
bool canReport = false;
};
struct SponsoredReportAction {
Fn<void(
Data::SponsoredReportResult::Id,
Fn<void(Data::SponsoredReportResult)>)> callback;
};
class SponsoredMessages final { class SponsoredMessages final {
public: public:
enum class AppendResult { enum class AppendResult {
@ -82,18 +105,7 @@ public:
InjectToMiddle, InjectToMiddle,
AppendToTopBar, AppendToTopBar,
}; };
struct Details { using Details = SponsoredMessageDetails;
std::vector<TextWithEntities> info;
QString link;
QString buttonText;
PhotoId photoId = PhotoId(0);
PhotoId mediaPhotoId = PhotoId(0);
DocumentId mediaDocumentId = DocumentId(0);
uint64 backgroundEmojiId = 0;
uint8 colorIndex : 6 = 0;
bool isLinkInternal = false;
bool canReport = false;
};
using RandomId = QByteArray; using RandomId = QByteArray;
explicit SponsoredMessages(not_null<Main::Session*> session); explicit SponsoredMessages(not_null<Main::Session*> session);
~SponsoredMessages(); ~SponsoredMessages();
@ -103,7 +115,13 @@ public:
void request(not_null<History*> history, Fn<void()> done); void request(not_null<History*> history, Fn<void()> done);
void clearItems(not_null<History*> history); void clearItems(not_null<History*> history);
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const; [[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
[[nodiscard]] Details lookupDetails(
const Api::SponsoredSearchResult &data) const;
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen); void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
void clicked(
const QByteArray &randomId,
bool isMedia,
bool isFullscreen);
[[nodiscard]] FullMsgId fillTopBar( [[nodiscard]] FullMsgId fillTopBar(
not_null<History*> history, not_null<History*> history,
not_null<Ui::RpWidget*> widget); not_null<Ui::RpWidget*> widget);
@ -117,11 +135,15 @@ public:
int fallbackWidth); int fallbackWidth);
void view(const FullMsgId &fullId); void view(const FullMsgId &fullId);
void view(const QByteArray &randomId);
[[nodiscard]] State state(not_null<History*> history) const; [[nodiscard]] State state(not_null<History*> history) const;
[[nodiscard]] auto createReportCallback(const FullMsgId &fullId) [[nodiscard]] SponsoredReportAction createReportCallback(
-> Fn<void(SponsoredReportResult::Id, Fn<void(SponsoredReportResult)>)>; const FullMsgId &fullId);
[[nodiscard]] SponsoredReportAction createReportCallback(
const QByteArray &randomId,
Fn<void()> erase);
void clear(); void clear();

View file

@ -96,27 +96,28 @@ struct PeerUpdate {
PersonalChannel = (1ULL << 34), PersonalChannel = (1ULL << 34),
StarRefProgram = (1ULL << 35), StarRefProgram = (1ULL << 35),
PaysPerMessage = (1ULL << 36), PaysPerMessage = (1ULL << 36),
GiftSettings = (1ULL << 37),
// For chats and channels // For chats and channels
InviteLinks = (1ULL << 37), InviteLinks = (1ULL << 38),
Members = (1ULL << 38), Members = (1ULL << 39),
Admins = (1ULL << 39), Admins = (1ULL << 40),
BannedUsers = (1ULL << 40), BannedUsers = (1ULL << 41),
Rights = (1ULL << 41), Rights = (1ULL << 42),
PendingRequests = (1ULL << 42), PendingRequests = (1ULL << 43),
Reactions = (1ULL << 43), Reactions = (1ULL << 44),
// For channels // For channels
ChannelAmIn = (1ULL << 44), ChannelAmIn = (1ULL << 45),
StickersSet = (1ULL << 45), StickersSet = (1ULL << 46),
EmojiSet = (1ULL << 46), EmojiSet = (1ULL << 47),
ChannelLinkedChat = (1ULL << 47), ChannelLinkedChat = (1ULL << 48),
ChannelLocation = (1ULL << 48), ChannelLocation = (1ULL << 49),
Slowmode = (1ULL << 49), Slowmode = (1ULL << 50),
GroupCall = (1ULL << 50), GroupCall = (1ULL << 51),
// For iteration // For iteration
LastUsedBit = (1ULL << 50), LastUsedBit = (1ULL << 51),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -118,7 +118,10 @@ bool CanSendAnyOf(
not_null<const PeerData*> peer, not_null<const PeerData*> peer,
ChatRestrictions rights, ChatRestrictions rights,
bool forbidInForums) { bool forbidInForums) {
if (const auto user = peer->asUser()) { if (peer->session().frozen()
&& !peer->isFreezeAppealChat()) {
return false;
} else if (const auto user = peer->asUser()) {
if (user->isInaccessible() if (user->isInaccessible()
|| user->isRepliesChat() || user->isRepliesChat()
|| user->isVerifyCodes()) { || user->isVerifyCodes()) {
@ -178,7 +181,13 @@ SendError RestrictionError(
not_null<PeerData*> peer, not_null<PeerData*> peer,
ChatRestriction restriction) { ChatRestriction restriction) {
using Flag = ChatRestriction; using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) { if (peer->session().frozen()
&& !peer->isFreezeAppealChat()) {
return SendError({
.text = tr::lng_frozen_restrict_title(tr::now),
.frozen = true,
});
} else if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->requiresPremiumToWrite() if (user->requiresPremiumToWrite()
&& !user->session().premium()) { && !user->session().premium()) {

View file

@ -191,16 +191,19 @@ struct SendError {
QString text; QString text;
int boostsToLift = 0; int boostsToLift = 0;
bool premiumToLift = false; bool premiumToLift = false;
bool frozen = false;
}; };
SendError(Args &&args) SendError(Args &&args)
: text(std::move(args.text)) : text(std::move(args.text))
, boostsToLift(args.boostsToLift) , boostsToLift(args.boostsToLift)
, premiumToLift(args.premiumToLift) { , premiumToLift(args.premiumToLift)
, frozen(args.frozen) {
} }
QString text; QString text;
int boostsToLift = 0; int boostsToLift = 0;
bool premiumToLift = false; bool premiumToLift = false;
bool frozen = false;
[[nodiscard]] SendError value_or(SendError other) const { [[nodiscard]] SendError value_or(SendError other) const {
return *this ? *this : other; return *this ? *this : other;

View file

@ -171,8 +171,19 @@ struct AlbumCounts {
} }
template <typename MediaType> template <typename MediaType>
[[nodiscard]] uint64 CountCacheKey(not_null<MediaType*> data, bool spoiler) { [[nodiscard]] uint64 CountCacheKey(
return (reinterpret_cast<uint64>(data.get()) & ~1) | (spoiler ? 1 : 0); not_null<MediaType*> data,
ImageRoundRadius radius,
bool spoiler) {
return (reinterpret_cast<uint64>(data.get()) & ~3)
| ((radius == ImageRoundRadius::Ellipse) ? 2 : 0)
| (spoiler ? 1 : 0);
}
[[nodiscard]] uint64 SimpleCacheKey(ImageRoundRadius radius, bool spoiler) {
return uint64()
| ((radius == ImageRoundRadius::Ellipse) ? 2 : 0)
| (spoiler ? 1 : 0);
} }
[[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage( [[nodiscard]] ItemPreviewImage PreparePhotoPreviewImage(
@ -181,7 +192,7 @@ template <typename MediaType>
ImageRoundRadius radius, ImageRoundRadius radius,
bool spoiler) { bool spoiler) {
const auto photo = media->owner(); const auto photo = media->owner();
const auto counted = CountCacheKey(photo, spoiler); const auto counted = CountCacheKey(photo, radius, spoiler);
if (const auto small = media->image(PhotoSize::Small)) { if (const auto small = media->image(PhotoSize::Small)) {
return { PreparePreviewImage(small, radius, spoiler), counted }; return { PreparePreviewImage(small, radius, spoiler), counted };
} else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) { } else if (const auto thumbnail = media->image(PhotoSize::Thumbnail)) {
@ -191,15 +202,15 @@ template <typename MediaType>
} }
const auto allowedToDownload = media->autoLoadThumbnailAllowed( const auto allowedToDownload = media->autoLoadThumbnailAllowed(
item->history()->peer); item->history()->peer);
const auto spoilered = uint64(spoiler ? 1 : 0); const auto simple = SimpleCacheKey(radius, spoiler);
const auto cacheKey = allowedToDownload ? spoilered : counted; const auto cacheKey = allowedToDownload ? simple : counted;
if (allowedToDownload) { if (allowedToDownload) {
media->owner()->load(PhotoSize::Small, item->fullId()); media->owner()->load(PhotoSize::Small, item->fullId());
} }
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), cacheKey }; return { PreparePreviewImage(blurred, radius, spoiler), cacheKey };
} }
return { QImage(), allowedToDownload ? spoilered : cacheKey }; return { QImage(), allowedToDownload ? simple : cacheKey };
} }
[[nodiscard]] ItemPreviewImage PrepareFilePreviewImage( [[nodiscard]] ItemPreviewImage PrepareFilePreviewImage(
@ -210,7 +221,7 @@ template <typename MediaType>
Expects(media->owner()->hasThumbnail()); Expects(media->owner()->hasThumbnail());
const auto document = media->owner(); const auto document = media->owner();
const auto readyCacheKey = CountCacheKey(document, spoiler); const auto readyCacheKey = CountCacheKey(document, radius, spoiler);
if (const auto thumbnail = media->thumbnail()) { if (const auto thumbnail = media->thumbnail()) {
return { return {
PreparePreviewImage(thumbnail, radius, spoiler), PreparePreviewImage(thumbnail, radius, spoiler),
@ -218,11 +229,11 @@ template <typename MediaType>
}; };
} }
document->loadThumbnail(item->fullId()); document->loadThumbnail(item->fullId());
const auto spoilered = uint64(spoiler ? 1 : 0); const auto simple = SimpleCacheKey(radius, spoiler);
if (const auto blurred = media->thumbnailInline()) { if (const auto blurred = media->thumbnailInline()) {
return { PreparePreviewImage(blurred, radius, spoiler), spoilered }; return { PreparePreviewImage(blurred, radius, spoiler), simple };
} }
return { QImage(), spoilered }; return { QImage(), simple };
} }
[[nodiscard]] QImage PutPlayIcon(QImage preview) { [[nodiscard]] QImage PutPlayIcon(QImage preview) {
@ -274,13 +285,14 @@ template <typename MediaType>
[[nodiscard]] ItemPreviewImage FindCachedPreview( [[nodiscard]] ItemPreviewImage FindCachedPreview(
const std::vector<ItemPreviewImage> *existing, const std::vector<ItemPreviewImage> *existing,
not_null<MediaType*> data, not_null<MediaType*> data,
ImageRoundRadius radius,
bool spoiler) { bool spoiler) {
if (!existing) { if (!existing) {
return {}; return {};
} }
const auto i = ranges::find( const auto i = ranges::find(
*existing, *existing,
CountCacheKey(data, spoiler), CountCacheKey(data, radius, spoiler),
&ItemPreviewImage::cacheKey); &ItemPreviewImage::cacheKey);
return (i != end(*existing)) ? *i : ItemPreviewImage(); return (i != end(*existing)) ? *i : ItemPreviewImage();
} }
@ -856,13 +868,17 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
} }
auto images = std::vector<ItemPreviewImage>(); auto images = std::vector<ItemPreviewImage>();
auto context = std::any(); auto context = std::any();
if (auto found = FindCachedPreview(options.existing, _photo, _spoiler)) { const auto radius = _chat
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto found = FindCachedPreview(
options.existing,
_photo,
radius,
_spoiler)) {
images.push_back(std::move(found)); images.push_back(std::move(found));
} else { } else {
const auto media = _photo->createMediaView(); const auto media = _photo->createMediaView();
const auto radius = _chat
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto prepared = PreparePhotoPreview( if (auto prepared = PreparePhotoPreview(
parent(), parent(),
media, media,
@ -1101,18 +1117,24 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
auto images = std::vector<ItemPreviewImage>(); auto images = std::vector<ItemPreviewImage>();
auto context = std::any(); auto context = std::any();
const auto existing = options.existing; const auto existing = options.existing;
if (auto found = FindCachedPreview(existing, _document, _spoiler)) { const auto spoilered = _spoiler
|| (_document->isVideoMessage() && ttlSeconds());
const auto radius = _document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto found = FindCachedPreview(
existing,
_document,
radius,
spoilered)) {
images.push_back(std::move(found)); images.push_back(std::move(found));
} else if (TryFilePreview(_document)) { } else if (TryFilePreview(_document)) {
const auto media = _document->createMediaView(); const auto media = _document->createMediaView();
const auto radius = _document->isVideoMessage()
? ImageRoundRadius::Ellipse
: ImageRoundRadius::Small;
if (auto prepared = PrepareFilePreview( if (auto prepared = PrepareFilePreview(
parent(), parent(),
media, media,
radius, radius,
_spoiler) spoilered)
; prepared || !prepared.cacheKey) { ; prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared)); images.push_back(std::move(prepared));
if (!prepared.cacheKey) { if (!prepared.cacheKey) {
@ -1809,13 +1831,17 @@ ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|| _page->type == WebPageType::Video || _page->type == WebPageType::Video
|| _page->type == WebPageType::Document; || _page->type == WebPageType::Document;
if (pageTypeWithPreview || !_page->collage.items.empty()) { if (pageTypeWithPreview || !_page->collage.items.empty()) {
if (auto found = FindCachedPreview(options.existing, _page, false)) { const auto radius = ImageRoundRadius::Small;
if (auto found = FindCachedPreview(
options.existing,
_page,
radius,
false)) {
return { .text = caption, .images = { std::move(found) } }; return { .text = caption, .images = { std::move(found) } };
} }
auto context = std::any(); auto context = std::any();
auto images = std::vector<ItemPreviewImage>(); auto images = std::vector<ItemPreviewImage>();
auto prepared = ItemPreviewImage(); auto prepared = ItemPreviewImage();
const auto radius = ImageRoundRadius::Small;
if (const auto photo = MediaWebPage::photo()) { if (const auto photo = MediaWebPage::photo()) {
const auto media = photo->createMediaView(); const auto media = photo->createMediaView();
prepared = PreparePhotoPreview(parent(), media, radius, false); prepared = PreparePhotoPreview(parent(), media, radius, false);
@ -2063,10 +2089,18 @@ ItemPreview MediaInvoice::toPreview(ToPreviewOptions options) const {
if (!photo && !document) { if (!photo && !document) {
continue; continue;
} else if (images.size() < kMaxPreviewImages) { } else if (images.size() < kMaxPreviewImages) {
auto found = photo
? FindCachedPreview(existing, not_null(photo), spoiler)
: FindCachedPreview(existing, not_null(document), spoiler);
const auto radius = ImageRoundRadius::Small; const auto radius = ImageRoundRadius::Small;
auto found = photo
? FindCachedPreview(
existing,
not_null(photo),
radius,
spoiler)
: FindCachedPreview(
existing,
not_null(document),
radius,
spoiler);
if (found) { if (found) {
images.push_back(std::move(found)); images.push_back(std::move(found));
} else if (photo) { } else if (photo) {

View file

@ -1319,6 +1319,10 @@ bool PeerData::isVerifyCodes() const {
return (id == kVerifyCodesId); return (id == kVerifyCodesId);
} }
bool PeerData::isFreezeAppealChat() const {
return username().compare(u"spambot"_q, Qt::CaseInsensitive) == 0;
}
bool PeerData::sharedMediaInfo() const { bool PeerData::sharedMediaInfo() const {
return isSelf() || isRepliesChat(); return isSelf() || isRepliesChat();
} }
@ -1453,6 +1457,7 @@ bool PeerData::canRevokeFullHistory() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return !isSelf() return !isSelf()
&& (!user->isBot() || user->isSupport()) && (!user->isBot() || user->isSupport())
&& !user->isInaccessible()
&& session().serverConfig().revokePrivateInbox && session().serverConfig().revokePrivateInbox
&& (session().serverConfig().revokePrivateTimeLimit == 0x7FFFFFFF); && (session().serverConfig().revokePrivateTimeLimit == 0x7FFFFFFF);
} else if (const auto chat = asChat()) { } else if (const auto chat = asChat()) {

View file

@ -235,6 +235,7 @@ public:
[[nodiscard]] bool isGigagroup() const; [[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const; [[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool isVerifyCodes() const; [[nodiscard]] bool isVerifyCodes() const;
[[nodiscard]] bool isFreezeAppealChat() const;
[[nodiscard]] bool sharedMediaInfo() const; [[nodiscard]] bool sharedMediaInfo() const;
[[nodiscard]] bool savedSublistsInfo() const; [[nodiscard]] bool savedSublistsInfo() const;
[[nodiscard]] bool hasStoriesHidden() const; [[nodiscard]] bool hasStoriesHidden() const;

View file

@ -69,6 +69,7 @@ struct StarGift {
TimeId lastSaleDate = 0; TimeId lastSaleDate = 0;
bool upgradable = false; bool upgradable = false;
bool birthday = false; bool birthday = false;
bool soldOut = false;
friend inline bool operator==( friend inline bool operator==(
const StarGift &, const StarGift &,

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h" #include "data/data_user.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "api/api_global_privacy.h"
#include "api/api_sensitive_content.h" #include "api/api_sensitive_content.h"
#include "api/api_statistics.h" #include "api/api_statistics.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
@ -673,6 +674,13 @@ bool UserData::hasCalls() const {
&& (callsStatus() != CallsStatus::Unknown); && (callsStatus() != CallsStatus::Unknown);
} }
void UserData::setDisallowedGiftTypes(Api::DisallowedGiftTypes types) {
if (_disallowedGiftTypes != types) {
_disallowedGiftTypes = types;
session().changes().peerUpdated(this, UpdateFlag::GiftSettings);
}
}
namespace Data { namespace Data {
void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) { void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
@ -829,6 +837,31 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setBotVerifyDetails( user->setBotVerifyDetails(
ParseBotVerifyDetails(update.vbot_verification())); ParseBotVerifyDetails(update.vbot_verification()));
if (const auto gifts = update.vdisallowed_gifts()) {
const auto &data = gifts->data();
user->setDisallowedGiftTypes(Api::DisallowedGiftType()
| (data.is_disallow_unlimited_stargifts()
? Api::DisallowedGiftType::Unlimited
: Api::DisallowedGiftType())
| (data.is_disallow_limited_stargifts()
? Api::DisallowedGiftType::Limited
: Api::DisallowedGiftType())
| (data.is_disallow_unique_stargifts()
? Api::DisallowedGiftType::Unique
: Api::DisallowedGiftType())
| (data.is_disallow_premium_gifts()
? Api::DisallowedGiftType::Premium
: Api::DisallowedGiftType())
| (update.is_display_gifts_button()
? Api::DisallowedGiftType::SendHide
: Api::DisallowedGiftType()));
} else {
user->setDisallowedGiftTypes(Api::DisallowedGiftTypes()
| (update.is_display_gifts_button()
? Api::DisallowedGiftType::SendHide
: Api::DisallowedGiftType()));
}
user->owner().stories().apply(user, update.vstories()); user->owner().stories().apply(user, update.vstories());
user->fullUpdated(); user->fullUpdated();

View file

@ -15,12 +15,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_lastseen_status.h" #include "data/data_lastseen_status.h"
#include "data/data_user_names.h" #include "data/data_user_names.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "base/flags.h"
namespace Data { namespace Data {
struct BotCommand; struct BotCommand;
struct BusinessDetails; struct BusinessDetails;
} // namespace Data } // namespace Data
namespace Api {
enum class DisallowedGiftType : uchar;
using DisallowedGiftTypes = base::flags<DisallowedGiftType>;
} // namespace Api
struct StarRefProgram { struct StarRefProgram {
StarsAmount revenuePerUser; StarsAmount revenuePerUser;
TimeId endDate = 0; TimeId endDate = 0;
@ -262,6 +268,11 @@ public:
std::unique_ptr<BotInfo> botInfo; std::unique_ptr<BotInfo> botInfo;
[[nodiscard]] Api::DisallowedGiftTypes disallowedGiftTypes() const {
return _disallowedGiftTypes;
}
void setDisallowedGiftTypes(Api::DisallowedGiftTypes types);
private: private:
auto unavailableReasons() const auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & override; -> const std::vector<Data::UnavailableReason> & override;
@ -293,6 +304,8 @@ private:
static constexpr auto kInaccessibleAccessHashOld static constexpr auto kInaccessibleAccessHashOld
= 0xFFFFFFFFFFFFFFFFULL; = 0xFFFFFFFFFFFFFFFFULL;
Api::DisallowedGiftTypes _disallowedGiftTypes;
}; };
namespace Data { namespace Data {

View file

@ -24,6 +24,10 @@ DialogRow {
unreadMarkDiameter: pixels; unreadMarkDiameter: pixels;
tagTop: pixels; tagTop: pixels;
} }
DialogRightButton {
button: RoundButton;
margin: margins;
}
ThreeStateIcon { ThreeStateIcon {
icon: icon; icon: icon;
@ -115,11 +119,16 @@ dialogRowFilterTagSkip: 4px;
dialogRowFilterTagStyle: TextStyle(defaultTextStyle) { dialogRowFilterTagStyle: TextStyle(defaultTextStyle) {
font: font(10px); font: font(10px);
} }
dialogRowOpenBotTextStyle: semiboldTextStyle; dialogRowOpenBot: DialogRightButton {
dialogRowOpenBotHeight: 20px; button: RoundButton(defaultActiveButton) {
dialogRowOpenBotRight: 10px; height: 20px;
dialogRowOpenBotTop: 32px; textTop: 1px;
dialogRowOpenBotRecentTop: 28px; }
margin: margins(0px, 32px, 10px, 0px);
}
dialogRowOpenBotRecent: DialogRightButton(dialogRowOpenBot) {
margin: margins(0px, 32px, 28px, 0px);
}
forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }}; forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }};
forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }}; forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }};
@ -789,3 +798,18 @@ dialogsPopularAppsPadding: margins(10px, 8px, 10px, 12px);
dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) { dialogsPopularAppsAbout: FlatLabel(boxDividerLabel) {
minWidth: 128px; minWidth: 128px;
} }
dialogsQuickActionSize: 20px;
dialogsQuickActionRippleSize: 80px;
dialogsSponsoredButton: DialogRightButton(dialogRowOpenBot) {
button: RoundButton(defaultLightButton) {
textFg: windowActiveTextFg;
textFgOver: windowActiveTextFg;
textBg: lightButtonBgOver;
textBgOver: lightButtonBgOver;
height: 20px;
textTop: 1px;
}
margin: margins(0px, 9px, 10px, 0px);
}

View file

@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace style {
struct DialogRightButton;
} // namespace style
namespace Ui { namespace Ui {
class RippleAnimation; class RippleAnimation;
} // namespace Ui } // namespace Ui
@ -114,11 +118,16 @@ struct RowsByLetter {
}; };
struct RightButton final { struct RightButton final {
const style::DialogRightButton *st = nullptr;
QImage bg; QImage bg;
QImage selectedBg; QImage selectedBg;
QImage activeBg; QImage activeBg;
Ui::Text::String text; Ui::Text::String text;
std::unique_ptr<Ui::RippleAnimation> ripple; std::unique_ptr<Ui::RippleAnimation> ripple;
explicit operator bool() const {
return st != nullptr;
}
}; };
} // namespace Dialogs } // namespace Dialogs

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h" #include "base/object_ptr.h"
#include "base/timer.h" #include "base/timer.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "dialogs/ui/dialogs_quick_action_context.h"
#include "data/data_messages.h" #include "data/data_messages.h"
#include "ui/dragging_scroll_manager.h" #include "ui/dragging_scroll_manager.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
@ -19,12 +20,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace style { namespace style {
struct DialogRow; struct DialogRow;
struct DialogRightButton;
} // namespace style } // namespace style
namespace Api {
struct PeerSearchResult;
} // namespace Api
namespace MTP { namespace MTP {
class Error; class Error;
} // namespace MTP } // namespace MTP
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -34,6 +44,9 @@ class IconButton;
class PopupMenu; class PopupMenu;
class FlatLabel; class FlatLabel;
struct ScrollToRequest; struct ScrollToRequest;
namespace Controls {
enum class QuickDialogAction;
} // namespace Controls
} // namespace Ui } // namespace Ui
namespace Window { namespace Window {
@ -70,6 +83,7 @@ enum class ChatTypeFilter : uchar;
struct ChosenRow { struct ChosenRow {
Key key; Key key;
Data::MessagePosition message; Data::MessagePosition message;
QByteArray sponsoredRandomId;
bool userpicClick : 1 = false; bool userpicClick : 1 = false;
bool filteredRow : 1 = false; bool filteredRow : 1 = false;
bool newWindow : 1 = false; bool newWindow : 1 = false;
@ -118,10 +132,7 @@ public:
HistoryItem *inject, HistoryItem *inject,
SearchRequestType type, SearchRequestType type,
int fullCount); int fullCount);
void peerSearchReceived( void peerSearchReceived(Api::PeerSearchResult result);
const QString &query,
const QVector<MTPPeer> &my,
const QVector<MTPPeer> &result);
[[nodiscard]] FilterId filterId() const; [[nodiscard]] FilterId filterId() const;
@ -208,6 +219,13 @@ public:
[[nodiscard]] rpl::producer<UserId> openBotMainAppRequests() const; [[nodiscard]] rpl::producer<UserId> openBotMainAppRequests() const;
void setSwipeContextData(
int64 key,
std::optional<Ui::Controls::SwipeContextData> data);
[[nodiscard]] int64 calcSwipeKey(int top);
void prepareQuickAction(int64 key, Dialogs::Ui::QuickDialogAction);
void clearQuickActions();
protected: protected:
void visibleTopBottomUpdated( void visibleTopBottomUpdated(
int visibleTop, int visibleTop,
@ -225,6 +243,7 @@ protected:
private: private:
struct CollapsedRow; struct CollapsedRow;
struct HashtagResult; struct HashtagResult;
struct SponsoredSearchResult;
struct PeerSearchResult; struct PeerSearchResult;
struct TagCache; struct TagCache;
@ -282,6 +301,7 @@ private:
void repaintDialogRow(RowDescriptor row); void repaintDialogRow(RowDescriptor row);
void refreshDialogRow(RowDescriptor row); void refreshDialogRow(RowDescriptor row);
bool updateEntryHeight(not_null<Entry*> entry); bool updateEntryHeight(not_null<Entry*> entry);
void showSponsoredMenu(int peerSearchIndex, QPoint globalPos);
void clearMouseSelection(bool clearSelection = false); void clearMouseSelection(bool clearSelection = false);
void mousePressReleased( void mousePressReleased(
@ -295,14 +315,17 @@ private:
void scrollToItem(int top, int height); void scrollToItem(int top, int height);
void scrollToDefaultSelected(); void scrollToDefaultSelected();
void setCollapsedPressed(int pressed); void setCollapsedPressed(int pressed);
void setPressed(Row *pressed, bool pressedTopicJump, bool pressedBotApp); void setPressed(
Row *pressed,
bool pressedTopicJump,
bool pressedRightButton);
void clearPressed(); void clearPressed();
void setHashtagPressed(int pressed); void setHashtagPressed(int pressed);
void setFilteredPressed( void setFilteredPressed(
int pressed, int pressed,
bool pressedTopicJump, bool pressedTopicJump,
bool pressedBotApp); bool pressedRightButton);
void setPeerSearchPressed(int pressed); void setPeerSearchPressed(int pressed, bool pressedRightButton);
void setPreviewPressed(int pressed); void setPreviewPressed(int pressed);
void setSearchedPressed(int pressed); void setSearchedPressed(int pressed);
bool isPressed() const { bool isPressed() const {
@ -338,6 +361,9 @@ private:
void repaintDialogRowCornerStatus(not_null<History*> history); void repaintDialogRowCornerStatus(not_null<History*> history);
bool addBotAppRipple(QPoint origin, Fn<void()> updateCallback); bool addBotAppRipple(QPoint origin, Fn<void()> updateCallback);
bool addQuickActionRipple(not_null<Row*> row, Fn<void()> updateCallback);
bool addRightButtonRipple(QPoint origin, Fn<void()> updateCallback);
void setupShortcuts(); void setupShortcuts();
RowDescriptor computeJump( RowDescriptor computeJump(
@ -441,7 +467,8 @@ private:
Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history); Ui::VideoUserpic *validateVideoUserpic(not_null<History*> history);
Row *shownRowByKey(Key key); Row *shownRowByKey(Key key);
void clearSearchResults(bool clearPeerSearchResults = true); void clearSearchResults(bool alsoPeerSearchResults = true);
void clearPeerSearchResults();
void clearPreviewResults(); void clearPreviewResults();
void updateSelectedRow(Key key = Key()); void updateSelectedRow(Key key = Key());
void trackResultsHistory(not_null<History*> history); void trackResultsHistory(not_null<History*> history);
@ -468,10 +495,21 @@ private:
void saveChatsFilterScrollState(FilterId filterId); void saveChatsFilterScrollState(FilterId filterId);
void restoreChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId);
[[nodiscard]] not_null<Ui::QuickActionContext*> ensureQuickAction(
int64 key);
void deactivateQuickAction();
[[nodiscard]] bool lookupIsInBotAppButton( [[nodiscard]] bool lookupIsInBotAppButton(
Row *row, Row *row,
QPoint localPosition); QPoint localPosition);
[[nodiscard]] bool lookupIsInRightButton(
const RightButton &button,
QPoint localPosition);
[[nodiscard]] RightButton *maybeCacheRightButton(Row *row); [[nodiscard]] RightButton *maybeCacheRightButton(Row *row);
void fillRightButton(
RightButton &button,
const TextWithEntities &text,
const style::DialogRightButton &st);
[[nodiscard]] QImage *cacheChatsFilterTag( [[nodiscard]] QImage *cacheChatsFilterTag(
const Data::ChatFilter &filter, const Data::ChatFilter &filter,
@ -507,9 +545,10 @@ private:
bool _selectedTopicJump = false; bool _selectedTopicJump = false;
bool _pressedTopicJump = false; bool _pressedTopicJump = false;
RightButton *_pressedBotAppData = nullptr; RightButton *_pressedRightButtonData = nullptr;
bool _selectedBotApp = false; bool _pressedRightButtonSponsored = false;
bool _pressedBotApp = false; bool _selectedRightButton = false;
bool _pressedRightButton = false;
Row *_dragging = nullptr; Row *_dragging = nullptr;
int _draggingIndex = -1; int _draggingIndex = -1;
@ -544,9 +583,11 @@ private:
rpl::lifetime _trackedLifetime; rpl::lifetime _trackedLifetime;
QString _peerSearchQuery; QString _peerSearchQuery;
base::flat_set<not_null<PeerData*>> _sponsoredRemoved;
std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults; std::vector<std::unique_ptr<PeerSearchResult>> _peerSearchResults;
int _peerSearchSelected = -1; int _peerSearchSelected = -1;
int _peerSearchPressed = -1; int _peerSearchPressed = -1;
int _peerSearchMenu = -1;
std::vector<std::unique_ptr<FakeRow>> _previewResults; std::vector<std::unique_ptr<FakeRow>> _previewResults;
int _previewCount = 0; int _previewCount = 0;
@ -611,6 +652,10 @@ private:
rpl::event_stream<> _refreshHashtagsRequests; rpl::event_stream<> _refreshHashtagsRequests;
rpl::event_stream<UserId> _openBotMainAppRequests; rpl::event_stream<UserId> _openBotMainAppRequests;
using QuickActionPtr = std::unique_ptr<Ui::QuickActionContext>;
QuickActionPtr _activeQuickAction;
std::vector<QuickActionPtr> _inactiveQuickActions;
RowDescriptor _chatPreviewRow; RowDescriptor _chatPreviewRow;
bool _chatPreviewScheduled = false; bool _chatPreviewScheduled = false;
std::optional<QPoint> _chatPreviewTouchGlobal; std::optional<QPoint> _chatPreviewTouchGlobal;

View file

@ -198,7 +198,7 @@ Row *List::rowAtY(int y) const {
List::iterator List::findByY(int y) const { List::iterator List::findByY(int y) const {
return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) { return ranges::lower_bound(_rows, y, ranges::less(), [](const Row *row) {
return row->top() + row->height(); return row->top() + row->height() - 1;
}); });
} }

View file

@ -0,0 +1,245 @@
/*
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 "dialogs/dialogs_quick_action.h"
#include "dialogs/ui/dialogs_quick_action_context.h"
#include "apiwrap.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "dialogs/dialogs_entry.h"
#include "history/history.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "menu/menu_mute.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
#include "styles/style_dialogs.h"
namespace Dialogs {
namespace {
const style::font &SwipeActionFont(
Dialogs::Ui::QuickDialogActionLabel action,
int availableWidth) {
struct Entry final {
Dialogs::Ui::QuickDialogActionLabel action;
QString langId;
style::font font;
};
static auto Fonts = std::vector<Entry>();
for (auto &entry : Fonts) {
if (entry.action == action) {
if (entry.langId == Lang::GetInstance().id()) {
return entry.font;
}
}
}
constexpr auto kNormalFontSize = 13;
constexpr auto kMinFontSize = 5;
for (auto i = kNormalFontSize; i >= kMinFontSize; --i) {
auto font = style::font(
style::ConvertScale(i, style::Scale()),
st::semiboldFont->flags(),
st::semiboldFont->family());
if (font->width(ResolveQuickDialogLabel(action)) <= availableWidth
|| i == kMinFontSize) {
Fonts.emplace_back(Entry{
.action = action,
.langId = Lang::GetInstance().id(),
.font = std::move(font),
});
return Fonts.back().font;
}
}
Unexpected("SwipeActionFont: can't find font.");
}
} // namespace
void PerformQuickDialogAction(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
Ui::QuickDialogAction action,
FilterId filterId) {
const auto history = peer->owner().history(peer);
if (action == Dialogs::Ui::QuickDialogAction::Mute) {
const auto isMuted = rpl::variable<bool>(
MuteMenu::ThreadDescriptor(history).isMutedValue()).current();
MuteMenu::ThreadDescriptor(history).updateMutePeriod(isMuted
? 0
: std::numeric_limits<TimeId>::max());
} else if (action == Dialogs::Ui::QuickDialogAction::Pin) {
const auto entry = (Dialogs::Entry*)(history);
Window::TogglePinnedThread(controller, entry, filterId);
} else if (action == Dialogs::Ui::QuickDialogAction::Read) {
if (Window::IsUnreadThread(history)) {
Window::MarkAsReadThread(history);
} else if (history) {
peer->owner().histories().changeDialogUnreadMark(history, true);
}
} else if (action == Dialogs::Ui::QuickDialogAction::Archive) {
history->session().api().toggleHistoryArchived(
history,
!Window::IsArchived(history),
[] {});
} else if (action == Dialogs::Ui::QuickDialogAction::Delete) {
Window::DeleteAndLeaveHandler(controller, peer)();
}
}
QString ResolveQuickDialogLottieIconName(Ui::QuickDialogActionLabel action) {
switch (action) {
case Ui::QuickDialogActionLabel::Mute:
return u"swipe_mute"_q;
case Ui::QuickDialogActionLabel::Unmute:
return u"swipe_unmute"_q;
case Ui::QuickDialogActionLabel::Pin:
return u"swipe_pin"_q;
case Ui::QuickDialogActionLabel::Unpin:
return u"swipe_unpin"_q;
case Ui::QuickDialogActionLabel::Read:
return u"swipe_read"_q;
case Ui::QuickDialogActionLabel::Unread:
return u"swipe_unread"_q;
case Ui::QuickDialogActionLabel::Archive:
return u"swipe_archive"_q;
case Ui::QuickDialogActionLabel::Unarchive:
return u"swipe_unarchive"_q;
case Ui::QuickDialogActionLabel::Delete:
return u"swipe_delete"_q;
default:
return u"swipe_disabled"_q;
}
}
Ui::QuickDialogActionLabel ResolveQuickDialogLabel(
not_null<History*> history,
Ui::QuickDialogAction action,
FilterId filterId) {
if (action == Dialogs::Ui::QuickDialogAction::Mute) {
if (history->peer->isSelf()) {
return Ui::QuickDialogActionLabel::Disabled;
}
const auto isMuted = rpl::variable<bool>(
MuteMenu::ThreadDescriptor(history).isMutedValue()).current();
return isMuted
? Ui::QuickDialogActionLabel::Unmute
: Ui::QuickDialogActionLabel::Mute;
} else if (action == Dialogs::Ui::QuickDialogAction::Pin) {
const auto entry = (Dialogs::Entry*)(history);
return entry->isPinnedDialog(filterId)
? Ui::QuickDialogActionLabel::Unpin
: Ui::QuickDialogActionLabel::Pin;
} else if (action == Dialogs::Ui::QuickDialogAction::Read) {
const auto unread = Window::IsUnreadThread(history);
if (history->isForum() && !unread) {
return Ui::QuickDialogActionLabel::Disabled;
}
return unread
? Ui::QuickDialogActionLabel::Read
: Ui::QuickDialogActionLabel::Unread;
} else if (action == Dialogs::Ui::QuickDialogAction::Archive) {
if (!Window::CanArchive(history, history->peer)) {
return Ui::QuickDialogActionLabel::Disabled;
}
return Window::IsArchived(history)
? Ui::QuickDialogActionLabel::Unarchive
: Ui::QuickDialogActionLabel::Archive;
} else if (action == Dialogs::Ui::QuickDialogAction::Delete) {
return Ui::QuickDialogActionLabel::Delete;
}
return Ui::QuickDialogActionLabel::Disabled;
}
QString ResolveQuickDialogLabel(Ui::QuickDialogActionLabel action) {
switch (action) {
case Ui::QuickDialogActionLabel::Mute:
return tr::lng_settings_quick_dialog_action_mute(tr::now);
case Ui::QuickDialogActionLabel::Unmute:
return tr::lng_settings_quick_dialog_action_unmute(tr::now);
case Ui::QuickDialogActionLabel::Pin:
return tr::lng_settings_quick_dialog_action_pin(tr::now);
case Ui::QuickDialogActionLabel::Unpin:
return tr::lng_settings_quick_dialog_action_unpin(tr::now);
case Ui::QuickDialogActionLabel::Read:
return tr::lng_settings_quick_dialog_action_read(tr::now);
case Ui::QuickDialogActionLabel::Unread:
return tr::lng_settings_quick_dialog_action_unread(tr::now);
case Ui::QuickDialogActionLabel::Archive:
return tr::lng_settings_quick_dialog_action_archive(tr::now);
case Ui::QuickDialogActionLabel::Unarchive:
return tr::lng_settings_quick_dialog_action_unarchive(tr::now);
case Ui::QuickDialogActionLabel::Delete:
return tr::lng_settings_quick_dialog_action_delete(tr::now);
default:
return tr::lng_settings_quick_dialog_action_disabled(tr::now);
};
}
const style::color &ResolveQuickActionBg(
Ui::QuickDialogActionLabel action) {
switch (action) {
case Ui::QuickDialogActionLabel::Delete:
return st::attentionButtonFg;
case Ui::QuickDialogActionLabel::Disabled:
return st::windowSubTextFgOver;
case Ui::QuickDialogActionLabel::Mute:
case Ui::QuickDialogActionLabel::Unmute:
case Ui::QuickDialogActionLabel::Pin:
case Ui::QuickDialogActionLabel::Unpin:
case Ui::QuickDialogActionLabel::Read:
case Ui::QuickDialogActionLabel::Unread:
case Ui::QuickDialogActionLabel::Archive:
case Ui::QuickDialogActionLabel::Unarchive:
default:
return st::windowBgActive;
};
}
const style::color &ResolveQuickActionBgActive(
Ui::QuickDialogActionLabel action) {
return st::windowSubTextFgOver;
}
void DrawQuickAction(
QPainter &p,
const QRect &rect,
not_null<Lottie::Icon*> icon,
Ui::QuickDialogActionLabel label,
float64 iconRatio,
bool twoLines) {
const auto iconSize = st::dialogsQuickActionSize * iconRatio;
const auto innerHeight = iconSize * 2;
const auto top = (rect.height() - innerHeight) / 2;
icon->paint(p, rect.x() + (rect.width() - iconSize) / 2, top);
p.setPen(st::premiumButtonFg);
p.setBrush(Qt::NoBrush);
const auto availableWidth = rect.width();
p.setFont(SwipeActionFont(label, availableWidth));
if (twoLines) {
auto text = ResolveQuickDialogLabel(label);
const auto index = text.indexOf(' ');
if (index != -1) {
text = text.replace(index, 1, '\n');
}
p.drawText(
QRect(rect.x(), top, availableWidth, innerHeight),
std::move(text),
style::al_bottom);
} else {
p.drawText(
QRect(rect.x(), top, availableWidth, innerHeight),
ResolveQuickDialogLabel(label),
style::al_bottom);
}
}
} // namespace Dialogs

View file

@ -0,0 +1,57 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class History;
class PeerData;
namespace Dialogs::Ui {
enum class QuickDialogAction;
enum class QuickDialogActionLabel;
} // namespace Dialogs::Ui
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Window {
class SessionController;
} // namespace Window
namespace Dialogs {
void PerformQuickDialogAction(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
Ui::QuickDialogAction action,
FilterId filterId);
[[nodiscard]] QString ResolveQuickDialogLottieIconName(
Ui::QuickDialogActionLabel action);
[[nodiscard]] Ui::QuickDialogActionLabel ResolveQuickDialogLabel(
not_null<History*> history,
Ui::QuickDialogAction action,
FilterId filterId);
[[nodiscard]] QString ResolveQuickDialogLabel(Ui::QuickDialogActionLabel);
[[nodiscard]] const style::color &ResolveQuickActionBg(
Ui::QuickDialogActionLabel);
[[nodiscard]] const style::color &ResolveQuickActionBgActive(
Ui::QuickDialogActionLabel);
void DrawQuickAction(
QPainter &p,
const QRect &rect,
not_null<Lottie::Icon*> icon,
Ui::QuickDialogActionLabel label,
float64 iconRatio = 1.,
bool twoLines = false);
} // namespace Dialogs

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_widget.h"
#include "base/call_delayed.h"
#include "base/qt/qt_key_modifiers.h" #include "base/qt/qt_key_modifiers.h"
#include "base/options.h" #include "base/options.h"
#include "dialogs/ui/chat_search_in.h" #include "dialogs/ui/chat_search_in.h"
@ -15,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/ui/dialogs_suggestions.h" #include "dialogs/ui/dialogs_suggestions.h"
#include "dialogs/dialogs_inner_widget.h" #include "dialogs/dialogs_inner_widget.h"
#include "dialogs/dialogs_search_from_controllers.h" #include "dialogs/dialogs_search_from_controllers.h"
#include "dialogs/dialogs_quick_action.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -35,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/more_chats_bar.h" #include "ui/chat/more_chats_bar.h"
#include "ui/controls/download_bar.h" #include "ui/controls/download_bar.h"
#include "ui/controls/jump_down_button.h" #include "ui/controls/jump_down_button.h"
#include "ui/controls/swipe_handler.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
@ -46,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "api/api_chat_filters.h" #include "api/api_chat_filters.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "chat_helpers/message_field.h"
#include "core/application.h" #include "core/application.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "core/update_checker.h" #include "core/update_checker.h"
@ -59,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "storage/storage_domain.h" #include "storage/storage_domain.h"
#include "data/components/recent_peers.h" #include "data/components/recent_peers.h"
#include "data/components/sponsored_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
@ -366,6 +371,7 @@ Widget::Widget(
_storiesContents.events() | rpl::flatten_latest()) _storiesContents.events() | rpl::flatten_latest())
: nullptr) : nullptr)
, _searchTimer([=] { search(); }) , _searchTimer([=] { search(); })
, _peerSearch(&controller->session(), Api::PeerSearch::Type::WithSponsored)
, _singleMessageSearch(&controller->session()) { , _singleMessageSearch(&controller->session()) {
const auto makeChildListShown = [](PeerId peerId, float64 shown) { const auto makeChildListShown = [](PeerId peerId, float64 shown) {
return InnerWidget::ChildListShown{ peerId, shown }; return InnerWidget::ChildListShown{ peerId, shown };
@ -381,6 +387,10 @@ Widget::Widget(
_childListPeerId.value(), _childListPeerId.value(),
_childListShown.value(), _childListShown.value(),
makeChildListShown))); makeChildListShown)));
_scroll->heightValue() | rpl::start_with_next([=](int height) {
_inner->setMinimumHeight(height);
_inner->refresh();
}, _inner->lifetime());
_scrollToTop->raise(); _scrollToTop->raise();
_lockUnlock->toggle(false, anim::type::instant); _lockUnlock->toggle(false, anim::type::instant);
@ -671,18 +681,169 @@ Widget::Widget(
setupMoreChatsBar(); setupMoreChatsBar();
setupDownloadBar(); setupDownloadBar();
} }
setupSwipeBack();
if (session().settings().dialogsFiltersEnabled() if (session().settings().dialogsFiltersEnabled()
&& (Core::App().settings().chatFiltersHorizontal() && (Core::App().settings().chatFiltersHorizontal()
|| !controller->enoughSpaceForFilters())) { || !controller->enoughSpaceForFilters())) {
toggleFiltersMenu(true); toggleFiltersMenu(true);
} }
setupFrozenAccountBar();
}
void Widget::setupSwipeBack() {
const auto isMainList = [=] {
const auto current = controller()->activeChatsFilterCurrent();
const auto &chatsFilters = session().data().chatsFilters();
if (chatsFilters.has()) {
return chatsFilters.defaultId() == current;
}
return !current;
};
auto update = [=](Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (data.translation < 0
&& _inner
&& (Core::App().settings().quickDialogAction()
!= Ui::QuickDialogAction::Disabled)) {
_inner->setSwipeContextData(data.msgBareId, std::move(data));
} else {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[]() -> std::pair<QColor, QColor> {
return {
st::historyForwardChooseBg->c,
st::historyForwardChooseFg->c,
};
},
_swipeBackMirrored,
_swipeBackIconMirrored);
}
_swipeBackData.callback(data);
}
return;
} else {
if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
if (_inner) {
_inner->setSwipeContextData(data.msgBareId, std::nullopt);
_inner->update();
}
}
};
auto init = [=](int top, Qt::LayoutDirection direction) {
_swipeBackIconMirrored = false;
_swipeBackMirrored = false;
if (_childListShown.current()) {
return Ui::Controls::SwipeHandlerFinishData();
}
const auto isRightToLeft = direction == Qt::RightToLeft;
const auto action = Core::App().settings().quickDialogAction();
const auto isDisabled = action == Ui::QuickDialogAction::Disabled;
if (_inner) {
_inner->clearQuickActions();
if (!isRightToLeft) {
if (const auto key = _inner->calcSwipeKey(top);
key && !isDisabled) {
_inner->prepareQuickAction(key, action);
return Ui::Controls::SwipeHandlerFinishData{
.callback = [=, session = &session()] {
auto callback = [=, peerId = PeerId(key)] {
PerformQuickDialogAction(
controller(),
session->data().peer(peerId),
action,
_inner->filterId());
};
base::call_delayed(
st::slideWrapDuration,
session,
std::move(callback));
},
.msgBareId = key,
.speedRatio = 1.,
.reachRatioDuration = crl::time(
st::slideWrapDuration),
.provideReachOutRatio = true,
};
}
}
}
if (controller()->openedFolder().current()) {
if (!isRightToLeft) {
return Ui::Controls::SwipeHandlerFinishData();
}
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
_swipeBackData = {};
if (controller()->openedFolder().current()) {
if (!controller()->windowId().folder()) {
controller()->closeFolder();
}
}
});
}
if (controller()->shownForum().current()) {
if (!isRightToLeft) {
return Ui::Controls::SwipeHandlerFinishData();
}
const auto id = controller()->windowId();
const auto initial = id.forum();
if (initial) {
return Ui::Controls::SwipeHandlerFinishData();
}
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
_swipeBackData = {};
if (const auto forum = controller()->shownForum().current()) {
controller()->closeForum();
}
});
}
if (isRightToLeft && isMainList()) {
_swipeBackIconMirrored = true;
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
_swipeBackIconMirrored = false;
_swipeBackData = {};
if (isMainList()) {
showMainMenu();
}
});
}
if (session().data().chatsFilters().has() && isDisabled) {
_swipeBackMirrored = !isRightToLeft;
using namespace Window;
const auto next = !isRightToLeft;
if (CheckAndJumpToNearChatsFilter(controller(), next, false)) {
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
_swipeBackData = {};
CheckAndJumpToNearChatsFilter(controller(), next, true);
});
}
}
return Ui::Controls::SwipeHandlerFinishData();
};
Ui::Controls::SetupSwipeHandler({
.widget = _inner,
.scroll = _scroll.data(),
.update = std::move(update),
.init = std::move(init),
});
} }
void Widget::chosenRow(const ChosenRow &row) { void Widget::chosenRow(const ChosenRow &row) {
storiesToggleExplicitExpand(false); storiesToggleExplicitExpand(false);
if (!_searchState.query.isEmpty()) { if (!row.sponsoredRandomId.isEmpty()) {
auto &messages = session().sponsoredMessages();
messages.clicked(row.sponsoredRandomId, false, false);
} else if (!_searchState.query.isEmpty()) {
if (const auto history = row.key.history()) { if (const auto history = row.key.history()) {
session().recentPeers().bump(history->peer); session().recentPeers().bump(history->peer);
} }
@ -692,6 +853,7 @@ void Widget::chosenRow(const ChosenRow &row) {
const auto topicJump = history const auto topicJump = history
? history->peer->forumTopicFor(row.message.fullId.msg) ? history->peer->forumTopicFor(row.message.fullId.msg)
: nullptr; : nullptr;
if (topicJump) { if (topicJump) {
if (controller()->shownForum().current() == topicJump->forum()) { if (controller()->shownForum().current() == topicJump->forum()) {
controller()->closeForum(); controller()->closeForum();
@ -843,6 +1005,29 @@ void Widget::setupTouchChatPreview() {
}, _inner->lifetime()); }, _inner->lifetime());
} }
void Widget::setupFrozenAccountBar() {
session().frozenValue(
) | rpl::start_with_next([=] {
updateFrozenAccountBar();
updateControlsGeometry();
}, lifetime());
}
void Widget::updateFrozenAccountBar() {
if (_layout == Layout::Child
|| _openedForum
|| _openedFolder
|| !session().frozen()) {
_frozenAccountBar = nullptr;
} else if (!_frozenAccountBar) {
_frozenAccountBar = FrozenWriteRestriction(
this,
controller()->uiShow(),
FrozenWriteRestrictionType::DialogsList);
_frozenAccountBar->show();
}
}
void Widget::setupMoreChatsBar() { void Widget::setupMoreChatsBar() {
if (_layout == Layout::Child) { if (_layout == Layout::Child) {
return; return;
@ -1220,6 +1405,14 @@ void Widget::setupShortcuts() {
} }
return true; return true;
}); });
request->check(Command::ShowChatPreview, 1)
&& request->handle([=] {
if (_inner) {
Window::ActivateWindow(controller());
return _inner->showChatPreview();
}
return true;
});
} }
}, lifetime()); }, lifetime());
} }
@ -1258,6 +1451,9 @@ void Widget::updateControlsVisibility(bool fast) {
if (_moreChatsBar) { if (_moreChatsBar) {
_moreChatsBar->show(); _moreChatsBar->show();
} }
if (_frozenAccountBar) {
_frozenAccountBar->show();
}
if (_chatFilters) { if (_chatFilters) {
_chatFilters->show(); _chatFilters->show();
} }
@ -1541,7 +1737,7 @@ void Widget::changeOpenedSubsection(
change(); change();
refreshTopBars(); refreshTopBars();
updateControlsVisibility(true); updateControlsVisibility(true);
_peerSearchRequest = 0; _peerSearch.clear();
_api.request(base::take(_topicSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel();
if (animated == anim::type::normal) { if (animated == anim::type::normal) {
if (_connecting) { if (_connecting) {
@ -1576,6 +1772,7 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
if (_stories) { if (_stories) {
storiesExplicitCollapse(); storiesExplicitCollapse();
} }
updateFrozenAccountBar();
}, (folder != nullptr), animated); }, (folder != nullptr), animated);
} }
@ -1632,6 +1829,7 @@ void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
_api.request(base::take(_topicSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel();
_inner->changeOpenedForum(forum); _inner->changeOpenedForum(forum);
storiesToggleExplicitExpand(false); storiesToggleExplicitExpand(false);
updateFrozenAccountBar();
updateStoriesVisibility(); updateStoriesVisibility();
}, (forum != nullptr), animated); }, (forum != nullptr), animated);
} }
@ -1963,6 +2161,9 @@ void Widget::startWidthAnimation() {
} }
_widthAnimationCache = grabNonNarrowScrollFrame(); _widthAnimationCache = grabNonNarrowScrollFrame();
_scroll->hide(); _scroll->hide();
if (_frozenAccountBar) {
_frozenAccountBar->hide();
}
if (_chatFilters) { if (_chatFilters) {
_chatFilters->hide(); _chatFilters->hide();
} }
@ -1973,6 +2174,9 @@ void Widget::stopWidthAnimation() {
_widthAnimationCache = QPixmap(); _widthAnimationCache = QPixmap();
if (!_showAnimation) { if (!_showAnimation) {
_scroll->setVisible(!_suggestions); _scroll->setVisible(!_suggestions);
if (_frozenAccountBar) {
_frozenAccountBar->setVisible(!_suggestions);
}
if (_chatFilters) { if (_chatFilters) {
_chatFilters->setVisible(!_suggestions); _chatFilters->setVisible(!_suggestions);
} }
@ -2077,6 +2281,9 @@ void Widget::startSlideAnimation(
if (_moreChatsBar) { if (_moreChatsBar) {
_moreChatsBar->hide(); _moreChatsBar->hide();
} }
if (_frozenAccountBar) {
_frozenAccountBar->hide();
}
if (_chatFilters) { if (_chatFilters) {
_chatFilters->hide(); _chatFilters->hide();
} }
@ -2241,10 +2448,9 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
{ .posts = true, .start = true }, { .posts = true, .start = true },
&_postsProcess); &_postsProcess);
} }
_api.request(base::take(_peerSearchRequest)).cancel(); _peerSearch.clear();
_peerSearchQuery = QString();
peerSearchApplyEmpty(0);
_api.request(base::take(_topicSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel();
peerSearchReceived({});
return true; return true;
} else if (inCache) { } else if (inCache) {
const auto success = _singleMessageSearch.lookup(query, [=] { const auto success = _singleMessageSearch.lookup(query, [=] {
@ -2349,35 +2555,18 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
} else { } else {
_inner->searchRequested(false); _inner->searchRequested(false);
} }
const auto peerQuery = Api::ConvertPeerSearchQuery(query); if (peerSearchRequired()) {
if (searchForPeersRequired(peerQuery)) { const auto requestType = inCache
if (inCache) { ? Api::PeerSearch::RequestType::CacheOnly
auto i = _peerSearchCache.find(peerQuery); : Api::PeerSearch::RequestType::CacheOrRemote;
if (i != _peerSearchCache.end()) { _peerSearch.request(query, [=](Api::PeerSearchResult result) {
_peerSearchQuery = peerQuery; peerSearchReceived(result);
_peerSearchRequest = 0; }, requestType);
peerSearchReceived(i->second, 0);
}
} else if (_peerSearchQuery != peerQuery) {
_peerSearchQuery = peerQuery;
_peerSearchFull = false;
_peerSearchRequest = _api.request(MTPcontacts_Search(
MTP_string(_peerSearchQuery),
MTP_int(SearchPeopleLimit)
)).done([=](
const MTPcontacts_Found &result,
mtpRequestId requestId) {
peerSearchReceived(result, requestId);
}).fail([=](const MTP::Error &error, mtpRequestId requestId) {
peerSearchFailed(error, requestId);
}).send();
_peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery);
}
} else { } else {
_api.request(base::take(_peerSearchRequest)).cancel(); _peerSearch.clear();
_peerSearchQuery = peerQuery; peerSearchReceived({});
peerSearchApplyEmpty(0);
} }
const auto peerQuery = Api::ConvertPeerSearchQuery(query);
if (searchForTopicsRequired(peerQuery)) { if (searchForTopicsRequired(peerQuery)) {
if (inCache) { if (inCache) {
if (_topicSearchQuery != peerQuery) { if (_topicSearchQuery != peerQuery) {
@ -2396,11 +2585,8 @@ bool Widget::search(bool inCache, SearchRequestDelay delay) {
return result; return result;
} }
bool Widget::searchForPeersRequired(const QString &query) const { bool Widget::peerSearchRequired() const {
return _searchState.filterChatsList() return _searchState.filterChatsList() && !_openedForum;
&& !_openedForum
&& !query.isEmpty()
&& (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None);
} }
bool Widget::searchForTopicsRequired(const QString &query) const { bool Widget::searchForTopicsRequired(const QString &query) const {
@ -2800,31 +2986,10 @@ void Widget::searchReceived(
update(); update();
} }
void Widget::peerSearchReceived( void Widget::peerSearchReceived(Api::PeerSearchResult result) {
const MTPcontacts_Found &result, _inner->peerSearchReceived(std::move(result));
mtpRequestId requestId) { listScrollUpdated();
const auto state = _inner->state(); update();
auto q = _peerSearchQuery;
if (state == WidgetState::Filtered) {
auto i = _peerSearchQueries.find(requestId);
if (i != _peerSearchQueries.end()) {
_peerSearchCache[i->second] = result;
_peerSearchQueries.erase(i);
}
}
if (_peerSearchRequest == requestId) {
switch (result.type()) {
case mtpc_contacts_found: {
auto &d = result.c_contacts_found();
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
_inner->peerSearchReceived(q, d.vmy_results().v, d.vresults().v);
} break;
}
_peerSearchRequest = 0;
listScrollUpdated();
}
} }
void Widget::searchApplyEmpty( void Widget::searchApplyEmpty(
@ -2840,17 +3005,6 @@ void Widget::searchApplyEmpty(
process); process);
} }
void Widget::peerSearchApplyEmpty(mtpRequestId id) {
_peerSearchFull = true;
peerSearchReceived(
MTP_contacts_found(
MTP_vector<MTPPeer>(0),
MTP_vector<MTPPeer>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0)),
id);
}
void Widget::searchFailed( void Widget::searchFailed(
SearchRequestType type, SearchRequestType type,
const MTP::Error &error, const MTP::Error &error,
@ -2863,13 +3017,6 @@ void Widget::searchFailed(
} }
} }
void Widget::peerSearchFailed(const MTP::Error &error, mtpRequestId id) {
if (_peerSearchRequest == id) {
_peerSearchRequest = 0;
_peerSearchFull = true;
}
}
void Widget::dragEnterEvent(QDragEnterEvent *e) { void Widget::dragEnterEvent(QDragEnterEvent *e) {
using namespace Storage; using namespace Storage;
@ -3294,12 +3441,7 @@ bool Widget::applySearchState(SearchState state) {
clearSearchCache(searchCleared); clearSearchCache(searchCleared);
} }
if (state.query.isEmpty()) { if (state.query.isEmpty()) {
_peerSearchCache.clear(); _peerSearch.clear();
const auto queries = base::take(_peerSearchQueries);
for (const auto &[requestId, query] : queries) {
_api.request(requestId).cancel();
}
_peerSearchQuery = QString();
} }
if (_searchState.query != currentSearchQuery()) { if (_searchState.query != currentSearchQuery()) {
@ -3357,8 +3499,8 @@ void Widget::clearSearchCache(bool clearPosts) {
_topicSearchQuery = QString(); _topicSearchQuery = QString();
_topicSearchOffsetDate = 0; _topicSearchOffsetDate = 0;
_topicSearchOffsetId = _topicSearchOffsetTopicId = 0; _topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
_api.request(base::take(_peerSearchRequest)).cancel();
_api.request(base::take(_topicSearchRequest)).cancel(); _api.request(base::take(_topicSearchRequest)).cancel();
_peerSearch.clear();
cancelSearchRequest(); cancelSearchRequest();
} }
@ -3660,9 +3802,17 @@ void Widget::updateControlsGeometry() {
if (_chatFilters) { if (_chatFilters) {
_chatFilters->resizeToWidth(barw); _chatFilters->resizeToWidth(barw);
} }
if (_frozenAccountBar) {
_frozenAccountBar->resize(barw, _frozenAccountBar->height());
}
_updateScrollGeometryCached = [=] { _updateScrollGeometryCached = [=] {
const auto moreChatsBarTop = expandedStoriesTop const auto frozenBarTop = expandedStoriesTop
+ ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded); + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
if (_frozenAccountBar) {
_frozenAccountBar->move(0, frozenBarTop);
}
const auto moreChatsBarTop = frozenBarTop
+ (_frozenAccountBar ? _frozenAccountBar->height() : 0);
if (_moreChatsBar) { if (_moreChatsBar) {
_moreChatsBar->move(0, moreChatsBarTop); _moreChatsBar->move(0, moreChatsBarTop);
} }

View file

@ -7,9 +7,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "api/api_peer_search.h"
#include "base/timer.h" #include "base/timer.h"
#include "dialogs/dialogs_key.h" #include "dialogs/dialogs_key.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "ui/controls/swipe_handler_data.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/userpic_view.h" #include "ui/userpic_view.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
@ -187,9 +189,7 @@ private:
const MTPmessages_Messages &result, const MTPmessages_Messages &result,
not_null<SearchProcessState*> process, not_null<SearchProcessState*> process,
bool cacheResults = false); bool cacheResults = false);
void peerSearchReceived( void peerSearchReceived(Api::PeerSearchResult result);
const MTPcontacts_Found &result,
mtpRequestId requestId);
void escape(); void escape();
void submit(); void submit();
void cancelSearchRequest(); void cancelSearchRequest();
@ -200,17 +200,19 @@ private:
void setupSupportMode(); void setupSupportMode();
void setupTouchChatPreview(); void setupTouchChatPreview();
void setupFrozenAccountBar();
void setupConnectingWidget(); void setupConnectingWidget();
void setupMainMenuToggle(); void setupMainMenuToggle();
void setupMoreChatsBar(); void setupMoreChatsBar();
void setupDownloadBar(); void setupDownloadBar();
void setupShortcuts(); void setupShortcuts();
void setupStories(); void setupStories();
void setupSwipeBack();
void storiesExplicitCollapse(); void storiesExplicitCollapse();
void collectStoriesUserpicsViews(Data::StorySourcesList list); void collectStoriesUserpicsViews(Data::StorySourcesList list);
void storiesToggleExplicitExpand(bool expand); void storiesToggleExplicitExpand(bool expand);
void trackScroll(not_null<Ui::RpWidget*> widget); void trackScroll(not_null<Ui::RpWidget*> widget);
[[nodiscard]] bool searchForPeersRequired(const QString &query) const; [[nodiscard]] bool peerSearchRequired() const;
[[nodiscard]] bool searchForTopicsRequired(const QString &query) const; [[nodiscard]] bool searchForTopicsRequired(const QString &query) const;
// Child list may be unable to set specific search state. // Child list may be unable to set specific search state.
@ -221,6 +223,7 @@ private:
void showMainMenu(); void showMainMenu();
void clearSearchCache(bool clearPosts); void clearSearchCache(bool clearPosts);
void setSearchQuery(const QString &query, int cursorPosition = -1); void setSearchQuery(const QString &query, int cursorPosition = -1);
void updateFrozenAccountBar();
void updateControlsVisibility(bool fast = false); void updateControlsVisibility(bool fast = false);
void updateLockUnlockVisibility( void updateLockUnlockVisibility(
anim::type animated = anim::type::instant); anim::type animated = anim::type::instant);
@ -263,11 +266,9 @@ private:
SearchRequestType type, SearchRequestType type,
const MTP::Error &error, const MTP::Error &error,
not_null<SearchProcessState*> process); not_null<SearchProcessState*> process);
void peerSearchFailed(const MTP::Error &error, mtpRequestId requestId);
void searchApplyEmpty( void searchApplyEmpty(
SearchRequestType type, SearchRequestType type,
not_null<SearchProcessState*> process); not_null<SearchProcessState*> process);
void peerSearchApplyEmpty(mtpRequestId id);
void updateForceDisplayWide(); void updateForceDisplayWide();
void scrollToDefault(bool verytop = false); void scrollToDefault(bool verytop = false);
@ -298,6 +299,9 @@ private:
const Layout _layout = Layout::Main; const Layout _layout = Layout::Main;
int _narrowWidth = 0; int _narrowWidth = 0;
std::unique_ptr<Ui::AbstractButton> _frozenAccountBar;
object_ptr<Ui::RpWidget> _searchControls; object_ptr<Ui::RpWidget> _searchControls;
object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr }; object_ptr<HistoryView::TopBarWidget> _subsectionTopBar = { nullptr };
struct { struct {
@ -366,10 +370,6 @@ private:
base::Timer _searchTimer; base::Timer _searchTimer;
QString _peerSearchQuery;
bool _peerSearchFull = false;
mtpRequestId _peerSearchRequest = 0;
QString _topicSearchQuery; QString _topicSearchQuery;
TimeId _topicSearchOffsetDate = 0; TimeId _topicSearchOffsetDate = 0;
MsgId _topicSearchOffsetId = 0; MsgId _topicSearchOffsetId = 0;
@ -383,14 +383,17 @@ private:
ChatSearchTab _searchQueryTab = {}; ChatSearchTab _searchQueryTab = {};
ChatTypeFilter _searchQueryFilter = {}; ChatTypeFilter _searchQueryFilter = {};
Ui::Controls::SwipeBackResult _swipeBackData;
bool _swipeBackMirrored = false;
bool _swipeBackIconMirrored = false;
SearchProcessState _searchProcess; SearchProcessState _searchProcess;
SearchProcessState _migratedProcess; SearchProcessState _migratedProcess;
SearchProcessState _postsProcess; SearchProcessState _postsProcess;
int _historiesRequest = 0; // Not real mtpRequestId. int _historiesRequest = 0; // Not real mtpRequestId.
Api::PeerSearch _peerSearch;
Api::SingleMessageSearch _singleMessageSearch; Api::SingleMessageSearch _singleMessageSearch;
base::flat_map<QString, MTPcontacts_Found> _peerSearchCache;
base::flat_map<mtpRequestId, QString> _peerSearchQueries;
QPixmap _widthAnimationCache; QPixmap _widthAnimationCache;

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_list.h" #include "dialogs/dialogs_list.h"
#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/dialogs_quick_action.h"
#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_video_userpic.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -28,12 +29,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "history/view/history_view_send_action.h" #include "history/view/history_view_send_action.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "support/support_helper.h" #include "support/support_helper.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
@ -89,16 +93,18 @@ void PaintRowTopRight(
text); text);
} }
int PaintRightButton(QPainter &p, const PaintContext &context) { int PaintRightButtonImpl(QPainter &p, const PaintContext &context) {
if (context.width < st::columnMinimalWidthLeft) { if (context.width < st::columnMinimalWidthLeft) {
return 0; return 0;
} }
if (const auto rightButton = context.rightButton) { if (const auto rightButton = context.rightButton) {
Assert(rightButton->st != nullptr);
const auto size = rightButton->bg.size() / style::DevicePixelRatio(); const auto size = rightButton->bg.size() / style::DevicePixelRatio();
const auto left = context.width const auto left = context.width
- size.width() - size.width()
- st::dialogRowOpenBotRight; - rightButton->st->margin.right();
const auto top = st::dialogRowOpenBotTop; const auto top = rightButton->st->margin.top();
p.drawImage( p.drawImage(
left, left,
top, top,
@ -113,22 +119,22 @@ int PaintRightButton(QPainter &p, const PaintContext &context) {
left, left,
top, top,
size.width() - size.height() / 2, size.width() - size.height() / 2,
context.active (context.active
? &st::universalRippleAnimation.color->c ? &st::universalRippleAnimation.color->c
: &st::activeButtonBgRipple->c); : &rightButton->st->button.ripple.color->c));
if (rightButton->ripple->empty()) { if (rightButton->ripple->empty()) {
rightButton->ripple.reset(); rightButton->ripple.reset();
} }
} }
p.setPen(context.active p.setPen(context.active
? st::activeButtonBg ? rightButton->st->button.textBg
: context.selected : context.selected
? st::activeButtonFgOver ? rightButton->st->button.textFgOver
: st::activeButtonFg); : rightButton->st->button.textFg);
rightButton->text.draw(p, { rightButton->text.draw(p, {
.position = QPoint( .position = QPoint(
left + size.height() / 2, left + size.height() / 2,
top + (st::dialogRowOpenBotHeight - rightButton->text.minHeight()) / 2), top + rightButton->st->button.textTop),
.outerWidth = size.width() - size.height() / 2, .outerWidth = size.width() - size.height() / 2,
.availableWidth = size.width() - size.height() / 2, .availableWidth = size.width() - size.height() / 2,
.elisionLines = 1, .elisionLines = 1,
@ -348,11 +354,29 @@ void PaintRow(
draft = nullptr; draft = nullptr;
} }
const auto history = entry->asHistory();
const auto thread = entry->asThread();
const auto sublist = entry->asSublist();
auto bg = context.active auto bg = context.active
? st::dialogsBgActive ? st::dialogsBgActive
: context.selected : context.selected
? st::dialogsBgOver ? st::dialogsBgOver
: context.currentBg; : context.currentBg;
auto swipeTranslation = 0;
if (history
&& context.quickActionContext
&& !context.quickActionContext->ripple
&& (history->peer->id.value
== context.quickActionContext->data.msgBareId)) {
if (context.quickActionContext->data.translation != 0) {
swipeTranslation = context.quickActionContext->data.translation
* -2;
}
}
if (swipeTranslation) {
p.translate(-swipeTranslation, 0);
}
p.fillRect(geometry, bg); p.fillRect(geometry, bg);
if (!(flags & Flag::TopicJumpRipple)) { if (!(flags & Flag::TopicJumpRipple)) {
auto ripple = context.active auto ripple = context.active
@ -361,10 +385,6 @@ void PaintRow(
row->paintRipple(p, 0, 0, context.width, &ripple->c); row->paintRipple(p, 0, 0, context.width, &ripple->c);
} }
const auto history = entry->asHistory();
const auto thread = entry->asThread();
const auto sublist = entry->asSublist();
if (flags & Flag::SavedMessages) { if (flags & Flag::SavedMessages) {
EmptyUserpic::PaintSavedMessages( EmptyUserpic::PaintSavedMessages(
p, p,
@ -833,6 +853,60 @@ void PaintRow(
+ (tag->width() / style::DevicePixelRatio()); + (tag->width() / style::DevicePixelRatio());
} }
} }
if (swipeTranslation) {
p.translate(swipeTranslation, 0);
const auto swipeActionRect = QRect(
rect::right(geometry) - swipeTranslation,
geometry.y(),
swipeTranslation,
geometry.height());
p.setClipRegion(swipeActionRect);
const auto labelType = ResolveQuickDialogLabel(
history,
context.quickActionContext->action,
context.filter);
p.fillRect(swipeActionRect, ResolveQuickActionBg(labelType));
if (context.quickActionContext->data.reachRatio) {
p.setPen(Qt::NoPen);
p.setBrush(ResolveQuickActionBgActive(labelType));
const auto r = swipeTranslation
* context.quickActionContext->data.reachRatio;
const auto offset = st::dialogsQuickActionSize
+ st::dialogsQuickActionSize / 2.;
p.drawEllipse(QPointF(geometry.width() - offset, offset), r, r);
}
const auto quickWidth = st::dialogsQuickActionSize * 3;
if (context.quickActionContext->icon) {
DrawQuickAction(
p,
QRect(
rect::right(geometry) - quickWidth,
geometry.y(),
quickWidth,
geometry.height()),
context.quickActionContext->icon.get(),
labelType);
}
p.setClipping(false);
}
if (const auto quick = context.quickActionContext;
quick && quick->ripple && quick->rippleFg) {
const auto labelType = ResolveQuickDialogLabel(
history,
context.quickActionContext->action,
context.filter);
const auto ripple = ResolveQuickActionBg(labelType);
const auto size = st::dialogsQuickActionRippleSize;
const auto x = geometry.width() - size;
quick->ripple->paint(p, x, 0, size, &ripple->c);
quick->rippleFg->paint(p, x, 0, size, &st::premiumButtonFg->c);
if (quick->ripple->empty()) {
quick->ripple.reset();
}
if (quick->rippleFg->empty()) {
quick->rippleFg.reset();
}
}
} }
} // namespace } // namespace
@ -1189,4 +1263,8 @@ void PaintCollapsedRow(
} }
} }
int PaintRightButton(QPainter &p, const PaintContext &context) {
return PaintRightButtonImpl(p, context);
}
} // namespace Dialogs::Ui } // namespace Dialogs::Ui

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "dialogs/ui/dialogs_quick_action_context.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
namespace style { namespace style {
@ -18,9 +19,6 @@ namespace st {
extern const style::DialogRow &defaultDialogRow; extern const style::DialogRow &defaultDialogRow;
} // namespace st } // namespace st
namespace Ui {
} // namespace Ui
namespace Data { namespace Data {
class Forum; class Forum;
class Folder; class Folder;
@ -57,6 +55,7 @@ struct TopicJumpCache {
struct PaintContext { struct PaintContext {
RightButton *rightButton = nullptr; RightButton *rightButton = nullptr;
std::vector<QImage*> *chatsFilterTags = nullptr; std::vector<QImage*> *chatsFilterTags = nullptr;
QuickActionContext *quickActionContext = nullptr;
not_null<const style::DialogRow*> st; not_null<const style::DialogRow*> st;
TopicJumpCache *topicJumpCache = nullptr; TopicJumpCache *topicJumpCache = nullptr;
Data::Folder *folder = nullptr; Data::Folder *folder = nullptr;
@ -111,4 +110,6 @@ void PaintCollapsedRow(
int unread, int unread,
const PaintContext &context); const PaintContext &context);
int PaintRightButton(QPainter &p, const PaintContext &context);
} // namespace Dialogs::Ui } // namespace Dialogs::Ui

View file

@ -150,8 +150,7 @@ void MessageView::prepare(
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
Data::Forum *forum, Data::Forum *forum,
Fn<void()> customEmojiRepaint, Fn<void()> customEmojiRepaint,
ToPreviewOptions options, ToPreviewOptions options) {
Fn<void()> customLoadingFinishCallback) {
if (!forum) { if (!forum) {
_topics = nullptr; _topics = nullptr;
} else if (!_topics || _topics->forum() != forum) { } else if (!_topics || _topics->forum() != forum) {
@ -213,11 +212,9 @@ void MessageView::prepare(
if (!_loadingContext) { if (!_loadingContext) {
_loadingContext = std::make_unique<LoadingContext>(); _loadingContext = std::make_unique<LoadingContext>();
item->history()->session().downloaderTaskFinished( item->history()->session().downloaderTaskFinished(
) | rpl::start_with_next( ) | rpl::start_with_next([=] {
customLoadingFinishCallback _textCachedFor = nullptr;
? customLoadingFinishCallback }, _loadingContext->lifetime);
: Fn<void()>([=] { _textCachedFor = nullptr; }),
_loadingContext->lifetime);
} }
_loadingContext->context = std::move(preview.loadingContext); _loadingContext->context = std::move(preview.loadingContext);
} else { } else {
@ -370,7 +367,18 @@ void MessageView::paint(
if (image.hasSpoiler()) { if (image.hasSpoiler()) {
const auto frame = DefaultImageSpoiler().frame( const auto frame = DefaultImageSpoiler().frame(
_spoiler->index(context.now, pausedSpoiler)); _spoiler->index(context.now, pausedSpoiler));
FillSpoilerRect(p, mini, frame); if (image.isEllipse()) {
const auto radius = st::dialogsMiniPreview / 2;
static auto mask = Images::CornersMask(radius);
FillSpoilerRect(
p,
mini,
Images::CornersMaskRef(mask),
frame,
_cornersCache);
} else {
FillSpoilerRect(p, mini, frame);
}
} }
} }
rect.setLeft(rect.x() + w); rect.setLeft(rect.x() + w);

View file

@ -61,8 +61,7 @@ public:
not_null<const HistoryItem*> item, not_null<const HistoryItem*> item,
Data::Forum *forum, Data::Forum *forum,
Fn<void()> customEmojiRepaint, Fn<void()> customEmojiRepaint,
ToPreviewOptions options, ToPreviewOptions options);
Fn<void()> customLoadingFinishCallback = nullptr);
void paint( void paint(
Painter &p, Painter &p,
@ -95,6 +94,7 @@ private:
mutable std::unique_ptr<SpoilerAnimation> _spoiler; mutable std::unique_ptr<SpoilerAnimation> _spoiler;
mutable std::unique_ptr<LoadingContext> _loadingContext; mutable std::unique_ptr<LoadingContext> _loadingContext;
mutable const style::DialogsMiniIcon *_leftIcon = nullptr; mutable const style::DialogsMiniIcon *_leftIcon = nullptr;
mutable QImage _cornersCache;
mutable bool _hasPlainLinkAtBegin = false; mutable bool _hasPlainLinkAtBegin = false;
}; };

View file

@ -7,14 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
namespace HistoryView { namespace Dialogs::Ui {
struct ChatPaintGestureHorizontalData { using namespace ::Ui;
float64 ratio = 0.;
float64 reachRatio = 0.; enum class QuickDialogAction {
int64 msgBareId = 0; Mute,
int translation = 0; Pin,
int cursorTop = 0; Read,
Archive,
Delete,
Disabled,
}; };
} // namespace HistoryView } // namespace Dialogs::Ui

View file

@ -0,0 +1,47 @@
/*
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 "dialogs/ui/dialogs_quick_action.h"
#include "ui/controls/swipe_handler_data.h"
namespace Lottie {
class Icon;
} // namespace Lottie
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace Dialogs::Ui {
using namespace ::Ui;
enum class QuickDialogActionLabel {
Mute,
Unmute,
Pin,
Unpin,
Read,
Unread,
Archive,
Unarchive,
Delete,
Disabled,
};
struct QuickActionContext {
::Ui::Controls::SwipeContextData data;
std::unique_ptr<Lottie::Icon> icon;
std::unique_ptr<Ui::RippleAnimation> ripple;
std::unique_ptr<Ui::RippleAnimation> rippleFg;
QuickDialogAction action;
crl::time finishedAt = 0;
};
} // namespace Dialogs::Ui

View file

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/swipe_handler.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
@ -203,7 +204,7 @@ RecentRow::RecentRow(not_null<PeerData*> peer)
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->botInfo && user->botInfo->hasMainApp) { if (user->botInfo && user->botInfo->hasMainApp) {
return std::make_unique<Ui::Text::String>( return std::make_unique<Ui::Text::String>(
st::dialogRowOpenBotTextStyle, st::dialogRowOpenBotRecent.button.style,
tr::lng_profile_open_app_short(tr::now)); tr::lng_profile_open_app_short(tr::now));
} }
} }
@ -274,20 +275,15 @@ QSize RecentRow::rightActionSize() const {
if (_mainAppText && _badgeSize.isEmpty()) { if (_mainAppText && _badgeSize.isEmpty()) {
return QSize( return QSize(
_mainAppText->maxWidth() + _mainAppText->minHeight(), _mainAppText->maxWidth() + _mainAppText->minHeight(),
st::dialogRowOpenBotHeight); st::dialogRowOpenBotRecent.button.height);
} }
return _badgeSize; return _badgeSize;
} }
QMargins RecentRow::rightActionMargins() const { QMargins RecentRow::rightActionMargins() const {
if (_mainAppText && _badgeSize.isEmpty()) { if (_mainAppText && _badgeSize.isEmpty()) {
return QMargins( return st::dialogRowOpenBotRecent.margin;
0, } else if (_badgeSize.isEmpty()) {
st::dialogRowOpenBotRecentTop,
st::dialogRowOpenBotRight,
0);
}
if (_badgeSize.isEmpty()) {
return {}; return {};
} }
const auto x = st::recentPeersItem.photoPosition.x(); const auto x = st::recentPeersItem.photoPosition.x();
@ -320,8 +316,7 @@ void RecentRow::rightActionPaint(
p.setPen(actionSelected p.setPen(actionSelected
? st::activeButtonFgOver ? st::activeButtonFgOver
: st::activeButtonFg); : st::activeButtonFg);
const auto top = 0 const auto top = st::dialogRowOpenBotRecent.button.textTop;
+ (st::dialogRowOpenBotHeight - _mainAppText->minHeight()) / 2;
_mainAppText->draw(p, { _mainAppText->draw(p, {
.position = QPoint(x + size.height() / 2, y + top), .position = QPoint(x + size.height() / 2, y + top),
.outerWidth = outerWidth, .outerWidth = outerWidth,
@ -1552,6 +1547,61 @@ void Suggestions::setupApps() {
}); });
} }
Ui::Controls::SwipeHandlerArgs Suggestions::generateIncompleteSwipeArgs() {
_swipeLifetime.destroy();
auto update = [=](Ui::Controls::SwipeContextData data) {
if (data.translation != 0) {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[=]() -> std::pair<QColor, QColor> {
return {
st::historyForwardChooseBg->c,
st::historyForwardChooseFg->c,
};
},
data.translation < 0);
}
_swipeBackData.callback(data);
return;
} else if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
};
auto init = [=](int, Qt::LayoutDirection direction) {
if (!_tabs) {
return Ui::Controls::SwipeHandlerFinishData();
}
const auto activeSection = _tabs->activeSection();
const auto isToLeft = direction == Qt::RightToLeft;
if ((isToLeft && activeSection > 0)
|| (!isToLeft && activeSection < _tabKeys.size() - 1)) {
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
if (_tabs
&& _tabs->activeSection() == activeSection) {
_swipeBackData = {};
_tabs->setActiveSection(isToLeft
? activeSection - 1
: activeSection + 1);
}
});
}
return Ui::Controls::SwipeHandlerFinishData();
};
return { .widget = this, .update = update, .init = init };
}
void Suggestions::reinstallSwipe(not_null<Ui::ElasticScroll*> scroll) {
_swipeLifetime.destroy();
auto args = generateIncompleteSwipeArgs();
args.scroll = scroll;
args.onLifetime = &_swipeLifetime;
Ui::Controls::SetupSwipeHandler(std::move(args));
}
void Suggestions::selectJump(Qt::Key direction, int pageSize) { void Suggestions::selectJump(Qt::Key direction, int pageSize) {
switch (_key.current().tab) { switch (_key.current().tab) {
case Tab::Chats: selectJumpChats(direction, pageSize); return; case Tab::Chats: selectJumpChats(direction, pageSize); return;
@ -1977,6 +2027,18 @@ void Suggestions::finishShow() {
_appsScroll->setVisible(key == Key{ Tab::Apps }); _appsScroll->setVisible(key == Key{ Tab::Apps });
for (const auto &[mediaKey, list] : _mediaLists) { for (const auto &[mediaKey, list] : _mediaLists) {
list.wrap->setVisible(key == mediaKey); list.wrap->setVisible(key == mediaKey);
if (key == mediaKey) {
_swipeLifetime.destroy();
auto incomplete = generateIncompleteSwipeArgs();
list.wrap->replaceSwipeHandler(&incomplete);
}
}
if (key == Key{ Tab::Chats }) {
reinstallSwipe(_chatsScroll.get());
} else if (key == Key{ Tab::Channels }) {
reinstallSwipe(_channelsScroll.get());
} else if (key == Key{ Tab::Apps }) {
reinstallSwipe(_appsScroll.get());
} }
} }

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h" #include "base/object_ptr.h"
#include "base/timer.h" #include "base/timer.h"
#include "dialogs/ui/top_peers_strip.h" #include "dialogs/ui/top_peers_strip.h"
#include "ui/controls/swipe_handler_data.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
@ -32,6 +33,9 @@ enum class SharedMediaType : signed char;
} // namespace Storage } // namespace Storage
namespace Ui { namespace Ui {
namespace Controls {
struct SwipeHandlerArgs;
} // namespace Controls
class BoxContent; class BoxContent;
class ScrollArea; class ScrollArea;
class ElasticScroll; class ElasticScroll;
@ -157,6 +161,9 @@ private:
void setupChats(); void setupChats();
void setupChannels(); void setupChannels();
void setupApps(); void setupApps();
void reinstallSwipe(not_null<Ui::ElasticScroll*>);
[[nodiscard]] auto generateIncompleteSwipeArgs()
-> Ui::Controls::SwipeHandlerArgs;
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);
@ -253,6 +260,9 @@ private:
QPixmap _slideLeft; QPixmap _slideLeft;
QPixmap _slideRight; QPixmap _slideRight;
Ui::Controls::SwipeBackResult _swipeBackData;
rpl::lifetime _swipeLifetime;
int _slideLeftTop = 0; int _slideLeftTop = 0;
int _slideRightTop = 0; int _slideRightTop = 0;

View file

@ -1704,6 +1704,15 @@ ServiceAction ParseServiceAction(
.giftId = uint64(gift.vid().v), .giftId = uint64(gift.vid().v),
}; };
}); });
}, [&](const MTPDmessageActionPaidMessagesRefunded &data) {
result.content = ActionPaidMessagesRefunded{
.messages = data.vcount().v,
.stars = int64(data.vstars().v),
};
}, [&](const MTPDmessageActionPaidMessagesPrice &data) {
result.content = ActionPaidMessagesPrice{
.stars = int(data.vstars().v),
};
}, [](const MTPDmessageActionEmpty &data) {}); }, [](const MTPDmessageActionEmpty &data) {});
return result; return result;
} }

View file

@ -662,6 +662,15 @@ struct ActionStarGift {
bool limited = false; bool limited = false;
}; };
struct ActionPaidMessagesRefunded {
int messages = 0;
int64 stars = 0;
};
struct ActionPaidMessagesPrice {
int stars = 0;
};
struct ServiceAction { struct ServiceAction {
std::variant< std::variant<
v::null_t, v::null_t,
@ -707,7 +716,9 @@ struct ServiceAction {
ActionPaymentRefunded, ActionPaymentRefunded,
ActionGiftStars, ActionGiftStars,
ActionPrizeStars, ActionPrizeStars,
ActionStarGift> content; ActionStarGift,
ActionPaidMessagesRefunded,
ActionPaidMessagesPrice> content;
}; };
ServiceAction ParseServiceAction( ServiceAction ParseServiceAction(

View file

@ -1367,6 +1367,26 @@ auto HtmlWriter::Wrap::pushMessage(
+ " sent you a gift of " + " sent you a gift of "
+ QByteArray::number(data.stars) + QByteArray::number(data.stars)
+ " Telegram Stars."; + " Telegram Stars.";
}, [&](const ActionPaidMessagesRefunded &data) {
auto result = message.out
? ("You refunded "
+ QString::number(data.stars).toUtf8()
+ " Stars for "
+ QString::number(data.messages).toUtf8()
+ " messages to "
+ peers.wrapPeerName(dialog.peerId))
: (peers.wrapPeerName(dialog.peerId)
+ " refunded "
+ QString::number(data.stars).toUtf8()
+ " Stars for "
+ QString::number(data.messages).toUtf8()
+ " messages to you");
return result;
}, [&](const ActionPaidMessagesPrice &data) {
auto result = "Price per messages changed to "
+ QString::number(data.stars).toUtf8()
+ " Telegram Stars.";
return result;
}, [](v::null_t) { return QByteArray(); }); }, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) { if (!serviceText.isEmpty()) {
@ -1560,7 +1580,9 @@ auto HtmlWriter::Wrap::pushMessage(
block.append(popTag()); block.append(popTag());
} }
if (!message.reactions.empty()) { if (!message.reactions.empty()) {
block.append(pushDiv("reactions")); block.append(pushTag("span", {
{ "class", "reactions" },
}));
for (const auto &reaction : message.reactions) { for (const auto &reaction : message.reactions) {
auto reactionClass = QByteArray("reaction"); auto reactionClass = QByteArray("reaction");
for (const auto &recent : reaction.recent) { for (const auto &recent : reaction.recent) {
@ -1574,10 +1596,10 @@ auto HtmlWriter::Wrap::pushMessage(
reactionClass += " paid"; reactionClass += " paid";
} }
block.append(pushTag("div", { block.append(pushTag("span", {
{ "class", reactionClass }, { "class", reactionClass },
})); }));
block.append(pushTag("div", { block.append(pushTag("span", {
{ "class", "emoji" }, { "class", "emoji" },
})); }));
switch (reaction.type) { switch (reaction.type) {
@ -1596,7 +1618,7 @@ auto HtmlWriter::Wrap::pushMessage(
} }
block.append(popTag()); block.append(popTag());
if (!reaction.recent.empty()) { if (!reaction.recent.empty()) {
block.append(pushTag("div", { block.append(pushTag("span", {
{ "class", "userpics" }, { "class", "userpics" },
})); }));
for (const auto &recent : reaction.recent) { for (const auto &recent : reaction.recent) {
@ -1617,7 +1639,7 @@ auto HtmlWriter::Wrap::pushMessage(
} }
if (reaction.recent.empty() if (reaction.recent.empty()
|| (reaction.count > reaction.recent.size())) { || (reaction.count > reaction.recent.size())) {
block.append(pushTag("div", { block.append(pushTag("span", {
{ "class", "count" }, { "class", "count" },
})); }));
block.append(NumberToString(reaction.count)); block.append(NumberToString(reaction.count));
@ -2321,10 +2343,12 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
} else if (data.isVideoFile) { } else if (data.isVideoFile) {
// At least try to pushVideoFileMedia. // At least try to pushVideoFileMedia.
} else if (data.isAudioFile) { } else if (data.isAudioFile) {
result.title = (data.songPerformer.isEmpty() result.title = (!data.songPerformer.isEmpty()
|| data.songTitle.isEmpty()) && !data.songTitle.isEmpty())
? QByteArray("Audio file") ? (data.songPerformer + " \xe2\x80\x93 " + data.songTitle)
: data.songPerformer + " \xe2\x80\x93 " + data.songTitle; : !data.name.isEmpty()
? data.name
: QByteArray("Audio file");
result.status = FormatDuration(data.duration); result.status = FormatDuration(data.duration);
if (!hasFile) { if (!hasFile) {
result.status += ", " + FormatFileSize(data.file.size); result.status += ", " + FormatFileSize(data.file.size);

View file

@ -663,6 +663,15 @@ QByteArray SerializeMessage(
push("is_limited", data.limited); push("is_limited", data.limited);
push("is_anonymous", data.anonymous); push("is_anonymous", data.anonymous);
pushBare("gift_text", SerializeText(context, data.text)); pushBare("gift_text", SerializeText(context, data.text));
}, [&](const ActionPaidMessagesRefunded &data) {
pushActor();
pushAction("paid_messages_refund");
push("messages_count", data.messages);
push("stars_count", data.stars);
}, [&](const ActionPaidMessagesPrice &data) {
pushActor();
pushAction("paid_messages_price_change");
push("price_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

@ -1039,6 +1039,16 @@ void InnerWidget::restoreScrollPosition() {
_scrollToSignal.fire_copy(newVisibleTop); _scrollToSignal.fire_copy(newVisibleTop);
} }
Ui::ChatPaintContext InnerWidget::preparePaintContext(QRect clip) const {
return _controller->preparePaintContext({
.theme = _theme.get(),
.clip = clip,
.visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
.visibleAreaTop = _visibleTop,
.visibleAreaWidth = width(),
});
}
void InnerWidget::paintEvent(QPaintEvent *e) { void InnerWidget::paintEvent(QPaintEvent *e) {
if (_controller->contentOverlapped(this, e)) { if (_controller->contentOverlapped(this, e)) {
return; return;
@ -1051,13 +1061,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
auto clip = e->rect(); auto clip = e->rect();
auto context = _controller->preparePaintContext({ auto context = preparePaintContext(clip);
.theme = _theme.get(),
.clip = clip,
.visibleAreaPositionGlobal = mapToGlobal(QPoint(0, _visibleTop)),
.visibleAreaTop = _visibleTop,
.visibleAreaWidth = width(),
});
if (_items.empty() && _upLoaded && _downLoaded) { if (_items.empty() && _upLoaded && _downLoaded) {
paintEmpty(p, context.st); paintEmpty(p, context.st);
} else { } else {

View file

@ -36,6 +36,7 @@ class PopupMenu;
class ChatStyle; class ChatStyle;
class ChatTheme; class ChatTheme;
struct PeerUserpicView; struct PeerUserpicView;
struct ChatPaintContext;
} // namespace Ui } // namespace Ui
namespace Window { namespace Window {
@ -69,6 +70,8 @@ public:
return _channel; return _channel;
} }
Ui::ChatPaintContext preparePaintContext(QRect clip) const;
// Set the correct scroll position after being resized. // Set the correct scroll position after being resized.
void restoreScrollPosition(); void restoreScrollPosition();

View file

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_filter.h" #include "history/admin_log/history_admin_log_filter.h"
#include "profile/profile_back_button.h" #include "profile/profile_back_button.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "ui/chat/chat_style.h"
#include "ui/controls/swipe_handler.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
@ -341,6 +343,7 @@ Widget::Widget(
}); });
setupShortcuts(); setupShortcuts();
setupSwipeReply();
} }
void Widget::showFilter() { void Widget::showFilter() {
@ -416,6 +419,44 @@ void Widget::setupShortcuts() {
}, lifetime()); }, lifetime());
} }
void Widget::setupSwipeReply() {
auto update = [=](Ui::Controls::SwipeContextData data) {
if (data.translation > 0) {
if (!_swipeBackData.callback) {
_swipeBackData = Ui::Controls::SetupSwipeBack(
this,
[=]() -> std::pair<QColor, QColor> {
auto context = _inner->preparePaintContext({});
return {
context.st->msgServiceBg()->c,
context.st->msgServiceFg()->c,
};
});
}
_swipeBackData.callback(data);
return;
} else if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
};
auto init = [=](int, Qt::LayoutDirection direction) {
if (direction == Qt::RightToLeft) {
return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
controller()->showBackFromStack();
});
}
return Ui::Controls::SwipeHandlerFinishData();
};
Ui::Controls::SetupSwipeHandler({
.widget = _inner.data(),
.scroll = _scroll.data(),
.update = std::move(update),
.init = std::move(init),
});
}
std::shared_ptr<Window::SectionMemento> Widget::createMemento() { std::shared_ptr<Window::SectionMemento> Widget::createMemento() {
auto result = std::make_shared<SectionMemento>(channel()); auto result = std::make_shared<SectionMemento>(channel());
saveState(result.get()); saveState(result.get());

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