Merge 4.13.1

This commit is contained in:
ZavaruKitsu 2023-12-24 17:31:55 +03:00
commit 421aa31d59
177 changed files with 5409 additions and 1901 deletions

View file

@ -28,6 +28,7 @@ jobs:
run: | run: |
sudo apt update sudo apt update
curl -sSL https://install.python-poetry.org | python3 - curl -sSL https://install.python-poetry.org | python3 -
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
- name: Free up some disk space. - name: Free up some disk space.
uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8 uses: jlumbroso/free-disk-space@f68fdb76e2ea636224182cfb7377ff9a1708f9b8
@ -40,6 +41,4 @@ jobs:
- name: Push the Docker image. - name: Push the Docker image.
if: ${{ github.ref_name == github.event.repository.default_branch }} if: ${{ github.ref_name == github.event.repository.default_branch }}
run: | run: docker push $IMAGE_TAG
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker push $IMAGE_TAG

View file

@ -43,15 +43,6 @@ jobs:
linux: linux:
name: Rocky Linux 8 name: Rocky Linux 8
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: ghcr.io/${{ github.repository }}/centos_env
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: scl enable gcc-toolset-12 -- bash --noprofile --norc -eo pipefail {0}
strategy: strategy:
matrix: matrix:
@ -75,12 +66,13 @@ jobs:
- name: First set up. - name: First set up.
run: | run: |
gcc --version echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
ln -s /usr/src/Libraries docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
- name: Telegram Desktop build. - name: Telegram Desktop build.
run: | run: |
cd $REPO_NAME/Telegram cd $REPO_NAME
DEFINE="" DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then if [ -n "${{ matrix.defines }}" ]; then
@ -91,7 +83,11 @@ jobs:
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
fi fi
./configure.sh \ docker run --rm \
-v $PWD:/usr/src/tdesktop \
-e DEBUG=1 \
tdesktop:centos_env \
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
-D CMAKE_C_FLAGS_DEBUG="" \ -D CMAKE_C_FLAGS_DEBUG="" \
-D CMAKE_CXX_FLAGS_DEBUG="" \ -D CMAKE_CXX_FLAGS_DEBUG="" \
-D CMAKE_C_FLAGS="-Werror" \ -D CMAKE_C_FLAGS="-Werror" \
@ -101,8 +97,6 @@ jobs:
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \ -D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE $DEFINE
cmake --build ../out --config Debug --parallel
- name: Check. - name: Check.
run: | run: |
filePath="$REPO_NAME/out/Debug/Telegram" filePath="$REPO_NAME/out/Debug/Telegram"

Binary file not shown.

View file

@ -591,6 +591,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_background" = "Chat background"; "lng_settings_section_background" = "Chat background";
"lng_settings_bg_from_gallery" = "Choose from gallery"; "lng_settings_bg_from_gallery" = "Choose from gallery";
"lng_settings_bg_from_file" = "Choose from file"; "lng_settings_bg_from_file" = "Choose from file";
"lng_settings_bg_remove" = "Remove wallpaper";
"lng_settings_bg_theme_edit" = "Edit theme"; "lng_settings_bg_theme_edit" = "Edit theme";
"lng_settings_bg_theme_create" = "Create new theme"; "lng_settings_bg_theme_create" = "Create new theme";
"lng_settings_bg_cloud_themes" = "Custom themes"; "lng_settings_bg_cloud_themes" = "Custom themes";
@ -813,6 +814,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_manage_enabled_dictionary" = "Dictionary is enabled"; "lng_settings_manage_enabled_dictionary" = "Dictionary is enabled";
"lng_settings_manage_remove_dictionary" = "Remove Dictionary"; "lng_settings_manage_remove_dictionary" = "Remove Dictionary";
"lng_settings_gift_premium" = "Premium Gifting";
"lng_settings_gift_premium_users_confirm" = "Proceed";
"lng_settings_gift_premium_users_error#one" = "You can select maximum {count} user.";
"lng_settings_gift_premium_users_error#other" = "You can select maximum {count} users.";
"lng_backgrounds_header" = "Choose Wallpaper"; "lng_backgrounds_header" = "Choose Wallpaper";
"lng_theme_sure_keep" = "Keep this theme?"; "lng_theme_sure_keep" = "Keep this theme?";
"lng_theme_reverting#one" = "Reverting to the old theme in {count} second."; "lng_theme_reverting#one" = "Reverting to the old theme in {count} second.";
@ -833,6 +839,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_blur" = "Blurred"; "lng_background_blur" = "Blurred";
"lng_background_sure_delete" = "Are you sure you want to delete this background?"; "lng_background_sure_delete" = "Are you sure you want to delete this background?";
"lng_background_other_info" = "{user} will be able to apply this wallpaper"; "lng_background_other_info" = "{user} will be able to apply this wallpaper";
"lng_background_other_channel" = "All subscribers will see this wallpaper";
"lng_background_apply1" = "Apply the wallpaper in this chat."; "lng_background_apply1" = "Apply the wallpaper in this chat.";
"lng_background_apply2" = "Enjoy the view."; "lng_background_apply2" = "Enjoy the view.";
"lng_background_apply_button" = "Apply For This Chat"; "lng_background_apply_button" = "Apply For This Chat";
@ -841,6 +848,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_reset_default" = "Reset"; "lng_background_reset_default" = "Reset";
"lng_background_apply_me" = "Apply for me"; "lng_background_apply_me" = "Apply for me";
"lng_background_apply_both" = "Apply for me and {user}"; "lng_background_apply_both" = "Apply for me and {user}";
"lng_background_apply_channel" = "Apply For Channel";
"lng_download_path_ask" = "Ask download path for each file"; "lng_download_path_ask" = "Ask download path for each file";
"lng_download_path" = "Download path"; "lng_download_path" = "Download path";
@ -1682,7 +1690,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; "lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_some" = "Some winners of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_title" = "Similar channels";
@ -1836,6 +1844,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_message_title" = "Sponsored"; "lng_sponsored_message_title" = "Sponsored";
"lng_recommended_message_title" = "Recommended"; "lng_recommended_message_title" = "Recommended";
"lng_edited" = "edited"; "lng_edited" = "edited";
"lng_commented" = "commented";
"lng_edited_date" = "Edited: {date}"; "lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}"; "lng_sent_date" = "Sent: {date}";
"lng_views_tooltip#one" = "Views: {count}"; "lng_views_tooltip#one" = "Views: {count}";
@ -2055,6 +2064,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_per" = "{cost} / month"; "lng_premium_gift_per" = "{cost} / month";
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}."; "lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_premium_gift_terms_link" = "here"; "lng_premium_gift_terms_link" = "here";
"lng_premium_gifts_about_user1" = "Give **{user}** access to exclusive features.";
"lng_premium_gifts_about_user2" = "Give **{user}** and **{second_user}** access to exclusive features.";
"lng_premium_gifts_about_user3" = "Give **{user}**, **{second_user}** and **{name}** access to exclusive features.";
"lng_premium_gifts_about_user_more#one" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friend access to exclusive features.";
"lng_premium_gifts_about_user_more#other" = "Give **{user}**, **{second_user}**, **{name}** and **{count}** more friends access to exclusive features.";
"lng_premium_gifts_about_reward#one" = "You will receive {emoji}**{count}** boost.";
"lng_premium_gifts_about_reward#other" = "You will receive {emoji}**{count}** boosts.";
"lng_premium_gifts_about_paid_title" = "Gifts Sent!";
"lng_premium_gifts_about_paid1" = "**{user}** has been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid2" = "**{user}** and **{second_user}** have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid3" = "**{user}**, **{second_user}** and **{name}** have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_more#one" = "**{user}**, **{second_user}**, **{name}** and **{count}** other have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_more#other" = "**{user}**, **{second_user}**, **{name}** and **{count}** others have been notified about the gifts you purchased.";
"lng_premium_gifts_about_paid_below#one" = "They now have access to additional features.";
"lng_premium_gifts_about_paid_below#other" = "They now have access to additional features.";
"lng_premium_gifts_summary_subtitle" = "What's Included";
"lng_boost_channel_button" = "Boost Channel"; "lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again"; "lng_boost_again_button" = "Boost Again";
@ -2111,6 +2136,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; "lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color."; "lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
"lng_boost_channel_title_wallpaper" = "Enable wallpapers";
"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_channel_title_status" = "Enable emoji status";
"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_title_reactions" = "Custom reactions"; "lng_boost_channel_title_reactions" = "Custom reactions";
"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction."; "lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction.";
"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions."; "lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions.";
@ -2170,6 +2203,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users."; "lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users.";
"lng_giveaway_channels_confirm_title" = "Channel is Private"; "lng_giveaway_channels_confirm_title" = "Channel is Private";
"lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; "lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.";
"lng_giveaway_additional_prizes" = "Additional prizes";
"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions.";
"lng_giveaway_additional_prizes_ph" = "Enter your prize";
"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}.";
"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}.";
"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}.";
"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}.";
"lng_giveaway_show_winners" = "Show winners";
"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends.";
"lng_giveaway_created_title" = "Giveaway created"; "lng_giveaway_created_title" = "Giveaway created";
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; "lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
@ -2189,6 +2231,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#one" = "Giveaway Prize";
"lng_prizes_title#other" = "Giveaway Prizes"; "lng_prizes_title#other" = "Giveaway Prizes";
"lng_prizes_additional#one" = "**{count}** {prize}";
"lng_prizes_additional#other" = "**{count}** {prize}";
"lng_prizes_additional_with" = "with";
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}."; "lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}."; "lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
"lng_prizes_participants" = "Participants"; "lng_prizes_participants" = "Participants";
@ -2207,6 +2252,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; "lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; "lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers."; "lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize.";
"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes.";
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; "lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; "lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
@ -2232,6 +2279,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; "lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}"; "lng_prizes_badge" = "x{amount}";
"lng_prizes_results_title" = "Winners Selected!";
"lng_prizes_results_about#one" = "**{count}** winner of the {link} was randomly selected by Telegram.";
"lng_prizes_results_about#other" = "**{count}** winners of the {link} were randomly selected by Telegram.";
"lng_prizes_results_link" = "Giveaway";
"lng_prizes_results_winners" = "Winners";
"lng_prizes_results_more#one" = "and {count} more!";
"lng_prizes_results_more#other" = "and {count} more!";
"lng_prizes_results_all" = "All winners received gift links in private messages.";
"lng_prizes_results_some" = "Some winners couldn't be selected.";
"lng_gift_link_title" = "Gift Link"; "lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription."; "lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"lng_gift_link_label_from" = "From"; "lng_gift_link_label_from" = "From";
@ -2801,6 +2858,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_sign_messages" = "Sign messages"; "lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group"; "lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color"; "lng_edit_channel_color" = "Change name color";
"lng_edit_channel_level_min" = "Level 1+";
"lng_edit_channel_wallpaper" = "Channel wallpaper";
"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel.";
"lng_edit_channel_status" = "Channel emoji status";
"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name.";
"lng_edit_self_title" = "Edit your name"; "lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact"; "lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create"; "lng_add_contact" = "Create";
@ -3615,6 +3677,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}"; "lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}"; "lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}"; "lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
"lng_admin_log_change_profile_color" = "{from} changed channel profile color from {previous} to {color}";
"lng_admin_log_set_profile_background_emoji" = "{from} set channel profile background emoji to {emoji}";
"lng_admin_log_change_profile_background_emoji" = "{from} changed channel profile background emoji from {previous} to {emoji}";
"lng_admin_log_removed_profile_background_emoji" = "{from} removed channel profile background emoji {emoji}";
"lng_admin_log_change_wallpaper" = "{from} changed channel wallpaper";
"lng_admin_log_set_status" = "{from} set channel emoji status to {emoji}";
"lng_admin_log_change_status" = "{from} changed channel emoji status from {previous} to {emoji}";
"lng_admin_log_removed_status" = "{from} removed channel emoji status {emoji}";
"lng_admin_log_set_status_until" = "{from} set channel emoji status to {emoji} until {date}";
"lng_admin_log_change_status_until" = "{from} changed channel emoji status from {previous} to {emoji} until {date}";
"lng_admin_log_user_with_username" = "{name} ({mention})"; "lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}"; "lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}"; "lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
@ -4207,6 +4279,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}."; "lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}.";
"lng_request_peer_confirm_send" = "Send"; "lng_request_peer_confirm_send" = "Send";
"lng_request_user_title" = "Choose User"; "lng_request_user_title" = "Choose User";
"lng_request_users_title" = "Choose Users";
"lng_request_user_premium_yes" = "The user should have a Premium subscription."; "lng_request_user_premium_yes" = "The user should have a Premium subscription.";
"lng_request_user_premium_no" = "The user shouldn't have a Premium subscription."; "lng_request_user_premium_no" = "The user shouldn't have a Premium subscription.";
"lng_request_user_no" = "No such users"; "lng_request_user_no" = "No such users";

View file

@ -15,6 +15,7 @@
<file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file> <file alias="art/slot_2_idle.tgs">../../art/slot_2_idle.tgs</file>
<file alias="art/slot_back.tgs">../../art/slot_back.tgs</file> <file alias="art/slot_back.tgs">../../art/slot_back.tgs</file>
<file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file> <file alias="art/slot_pull.tgs">../../art/slot_pull.tgs</file>
<file alias="art/winners.tgs">../../art/winners.tgs</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file> <file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file> <file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file> <file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</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="4.12.2.0" /> Version="4.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 4,12,2,0 FILEVERSION 4,13,1,0
PRODUCTVERSION 4,12,2,0 PRODUCTVERSION 4,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", "4.12.2.0" VALUE "FileVersion", "4.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.12.2.0" VALUE "ProductVersion", "4.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 4,12,2,0 FILEVERSION 4,13,1,0
PRODUCTVERSION 4,12,2,0 PRODUCTVERSION 4,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", "4.12.2.0" VALUE "FileVersion", "4.13.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023" VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.12.2.0" VALUE "ProductVersion", "4.13.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -537,11 +537,12 @@ HANDLE _generateDumpFileAtPath(const WCHAR *path) {
GetLocalTime(&stLocalTime); GetLocalTime(&stLocalTime);
wsprintf(szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", wsprintf(
szPath, szExeName, updaterVersionStr, szFileName, L"%s%s-%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, szPath, szExeName, updaterVersionStr,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
GetCurrentProcessId(), GetCurrentThreadId()); stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); return CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
} }
@ -562,7 +563,7 @@ void _generateDump(EXCEPTION_POINTERS* pExceptionPointers) {
DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen); DWORD len = GetModuleFileName(GetModuleHandle(0), szPath, maxFileLen);
if (!len) return; if (!len) return;
WCHAR *pathEnd = szPath + len; WCHAR *pathEnd = szPath + len;
if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) { if (!_wcsicmp(pathEnd - wcslen(_exeName), _exeName)) {
wsprintf(pathEnd - wcslen(_exeName), L""); wsprintf(pathEnd - wcslen(_exeName), L"");

View file

@ -415,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
const auto peer = item->history()->peer; const auto peer = item->history()->peer;
const auto itemId = item->id; const auto itemId = item->id;
const auto id = int32(button->buttonId); const auto id = int32(button->buttonId);
const auto chosen = [=](not_null<PeerData*> result) { const auto chosen = [=](std::vector<not_null<PeerData*>> result) {
peer->session().api().request(MTPmessages_SendBotRequestedPeer( peer->session().api().request(MTPmessages_SendBotRequestedPeer(
peer->input, peer->input,
MTP_int(itemId), MTP_int(itemId),
MTP_int(id), MTP_int(id),
result->input MTP_vector_from_range(
result
| ranges::views::transform([](
not_null<PeerData*> peer) {
return MTPInputPeer(peer->input); }))
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result); peer->session().api().applyUpdates(result);
}).send(); }).send();

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_peer_colors.h" #include "api/api_peer_colors.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_peer.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
namespace Api { namespace Api {
@ -62,6 +63,16 @@ auto PeerColors::indicesValue() const
})); }));
} }
int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevels.find(index)
; i != end(_requiredLevels)) {
return i->second;
}
return 1;
}
void PeerColors::apply(const MTPDhelp_peerColors &data) { void PeerColors::apply(const MTPDhelp_peerColors &data) {
auto suggested = std::vector<uint8>(); auto suggested = std::vector<uint8>();
auto colors = std::make_shared< auto colors = std::make_shared<
@ -89,6 +100,7 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
}; };
const auto &list = data.vcolors().v; const auto &list = data.vcolors().v;
_requiredLevels.clear();
suggested.reserve(list.size()); suggested.reserve(list.size());
for (const auto &color : list) { for (const auto &color : list) {
const auto &data = color.data(); const auto &data = color.data();
@ -98,6 +110,9 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
continue; continue;
} }
const auto colorIndex = uint8(colorIndexBare); const auto colorIndex = uint8(colorIndexBare);
if (const auto min = data.vchannel_min_level()) {
_requiredLevels[colorIndex] = min->v;
}
if (!data.is_hidden()) { if (!data.is_hidden()) {
suggested.push_back(colorIndex); suggested.push_back(colorIndex);
} }

View file

@ -28,6 +28,10 @@ public:
[[nodiscard]] auto indicesValue() const [[nodiscard]] auto indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed>; -> rpl::producer<Ui::ColorIndicesCompressed>;
[[nodiscard]] int requiredLevelFor(
PeerId channel,
uint8 index) const;
private: private:
void request(); void request();
void apply(const MTPDhelp_peerColors &data); void apply(const MTPDhelp_peerColors &data);
@ -38,6 +42,7 @@ private:
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
base::Timer _timer; base::Timer _timer;
rpl::variable<std::vector<uint8>> _suggested; rpl::variable<std::vector<uint8>> _suggested;
base::flat_map<uint8, int> _requiredLevels;
rpl::event_stream<> _colorIndicesChanged; rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent; std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;

View file

@ -515,6 +515,7 @@ auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
case EmojiListType::Profile: return _profileEmojiList; case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList; case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList; case EmojiListType::Background: return _backgroundEmojiList;
case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
} }
Unexpected("Type in PeerPhoto::emojiList."); Unexpected("Type in PeerPhoto::emojiList.");
} }
@ -551,6 +552,8 @@ void PeerPhoto::requestEmojiList(EmojiListType type) {
? send(MTPaccount_GetDefaultProfilePhotoEmojis()) ? send(MTPaccount_GetDefaultProfilePhotoEmojis())
: (type == EmojiListType::Group) : (type == EmojiListType::Group)
? send(MTPaccount_GetDefaultGroupPhotoEmojis()) ? send(MTPaccount_GetDefaultGroupPhotoEmojis())
: (type == EmojiListType::NoChannelStatus)
? send(MTPaccount_GetChannelRestrictedStatusEmojis())
: send(MTPaccount_GetDefaultBackgroundEmojis()); : send(MTPaccount_GetDefaultBackgroundEmojis());
} }

View file

@ -32,6 +32,7 @@ public:
Profile, Profile,
Group, Group,
Background, Background,
NoChannelStatus,
}; };
struct UserPhoto { struct UserPhoto {
@ -112,6 +113,7 @@ private:
EmojiListData _profileEmojiList; EmojiListData _profileEmojiList;
EmojiListData _groupEmojiList; EmojiListData _groupEmojiList;
EmojiListData _backgroundEmojiList; EmojiListData _backgroundEmojiList;
EmojiListData _noChannelStatusEmojiList;
}; };

View file

@ -26,7 +26,7 @@ namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) { [[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return { return {
.from = peerFromMTP(data.vfrom_id()), .from = data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(), .to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.giveawayId = data.vgiveaway_msg_id().value_or_empty(), .giveawayId = data.vgiveaway_msg_id().value_or_empty(),
.date = data.vdate().v, .date = data.vdate().v,
@ -342,15 +342,12 @@ PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() { rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
const auto channel = _peer->asChannel();
if (!channel) {
return lifetime;
}
using TLOption = MTPPremiumGiftCodeOption; using TLOption = MTPPremiumGiftCodeOption;
_api.request(MTPpayments_GetPremiumGiftCodeOptions( _api.request(MTPpayments_GetPremiumGiftCodeOptions(
MTP_flags( MTP_flags(_peer->isChannel()
MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer), ? MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer
: MTPpayments_GetPremiumGiftCodeOptions::Flag(0)),
_peer->input _peer->input
)).done([=](const MTPVector<TLOption> &result) { )).done([=](const MTPVector<TLOption> &result) {
auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>(); auto tlMapOptions = base::flat_map<Amount, QVector<TLOption>>();
@ -420,6 +417,8 @@ const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
} }
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) { [[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size());
return _optionsForOnePerson.months[monthsIndex]; return _optionsForOnePerson.months[monthsIndex];
} }

View file

@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_story.h"
#include "history/history.h" #include "history/history.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "statistics/statistics_data_deserialize.h" #include "statistics/statistics_data_deserialize.h"
@ -359,161 +361,54 @@ PublicForwards::PublicForwards(
void PublicForwards::request( void PublicForwards::request(
const Data::PublicForwardsSlice::OffsetToken &token, const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) { Fn<void(Data::PublicForwardsSlice)> done) {
if (!_requestId) { if (_requestId) {
if (_fullId.messageId) { return;
requestMessage(token, std::move(done));
} else if (_fullId.storyId) {
requestStory(token, std::move(done));
}
} }
} const auto channel = StatisticsRequestSender::channel();
const auto processResult = [=](const MTPstats_PublicForwards &tl) {
void PublicForwards::requestMessage(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
Expects(_fullId.messageId);
const auto offsetPeer = channel()->owner().peer(token.fullId.peer);
const auto tlOffsetPeer = offsetPeer
? offsetPeer->input
: MTP_inputPeerEmpty();
constexpr auto kLimit = tl::make_int(100);
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
channel()->inputChannel,
MTP_int(_fullId.messageId.msg),
MTP_int(token.rate),
tlOffsetPeer,
MTP_int(token.fullId.msg),
kLimit
)).done([=, channel = channel()](const MTPmessages_Messages &result) {
using Messages = QVector<Data::RecentPostId>; using Messages = QVector<Data::RecentPostId>;
_requestId = 0; _requestId = 0;
auto nextToken = Data::PublicForwardsSlice::OffsetToken(); const auto &data = tl.data();
const auto process = [&](const MTPVector<MTPMessage> &messages) { auto &owner = channel->owner();
auto result = Messages();
for (const auto &message : messages.v) {
const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message);
if (const auto peer = channel->owner().peerLoaded(peerId)) {
if (lastDate) {
channel->owner().addNewMessage(
message,
MessageFlags(),
NewMessageType::Existing);
nextToken.fullId = { peerId, msgId };
result.push_back({ .messageId = nextToken.fullId });
}
}
}
return result;
};
auto allLoaded = false; owner.processUsers(data.vusers());
auto fullCount = 0; owner.processChats(data.vchats());
auto messages = result.match([&](const MTPDmessages_messages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = list.size();
return list;
}, [&](const MTPDmessages_messagesSlice &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
if (const auto nextRate = data.vnext_rate()) { const auto nextToken = data.vnext_offset()
const auto rateUpdated = (nextRate->v != token.rate); ? qs(*data.vnext_offset())
if (rateUpdated) { : Data::PublicForwardsSlice::OffsetToken();
nextToken.rate = nextRate->v;
} else {
allLoaded = true;
}
}
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_channelMessages &data) {
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
auto list = process(data.vmessages());
allLoaded = true;
fullCount = data.vcount().v;
return list;
}, [&](const MTPDmessages_messagesNotModified &) {
allLoaded = true;
return Messages();
});
_lastTotal = std::max(_lastTotal, fullCount);
done({
.list = std::move(messages),
.total = _lastTotal,
.allLoaded = allLoaded,
.token = nextToken,
});
}).fail([=] {
_requestId = 0;
}).send();
}
void PublicForwards::requestStory(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done) {
Expects(_fullId.storyId);
constexpr auto kLimit = tl::make_int(100);
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel()->input,
MTP_int(_fullId.storyId.story),
MTP_string(token.storyOffset),
kLimit
)).done([=, channel = channel()](
const MTPstats_PublicForwards &tlForwards) {
using Messages = QVector<Data::RecentPostId>;
_requestId = 0;
const auto &data = tlForwards.data();
channel->owner().processUsers(data.vusers());
channel->owner().processChats(data.vchats());
const auto nextToken = Data::PublicForwardsSlice::OffsetToken({
.storyOffset = data.vnext_offset().value_or_empty(),
});
const auto allLoaded = nextToken.storyOffset.isEmpty()
|| (nextToken.storyOffset == token.storyOffset);
const auto fullCount = data.vcount().v; const auto fullCount = data.vcount().v;
auto recentList = Messages(); auto recentList = Messages(data.vforwards().v.size());
for (const auto &tlForward : data.vforwards().v) { for (const auto &tlForward : data.vforwards().v) {
tlForward.match([&](const MTPDpublicForwardMessage &data) { tlForward.match([&](const MTPDpublicForwardMessage &data) {
const auto &message = data.vmessage(); const auto &message = data.vmessage();
const auto msgId = IdFromMessage(message); const auto msgId = IdFromMessage(message);
const auto peerId = PeerFromMessage(message); const auto peerId = PeerFromMessage(message);
const auto lastDate = DateFromMessage(message); const auto lastDate = DateFromMessage(message);
if (const auto peer = channel->owner().peerLoaded(peerId)) { if (const auto peer = owner.peerLoaded(peerId)) {
if (!lastDate) { if (!lastDate) {
return; return;
} }
channel->owner().addNewMessage( owner.addNewMessage(
message, message,
MessageFlags(), MessageFlags(),
NewMessageType::Existing); NewMessageType::Existing);
recentList.push_back({ .messageId = { peerId, msgId } }); recentList.push_back({ .messageId = { peerId, msgId } });
} }
}, [&](const MTPDpublicForwardStory &data) { }, [&](const MTPDpublicForwardStory &data) {
data.vstory().match([&](const MTPDstoryItem &d) { const auto story = owner.stories().applySingle(
recentList.push_back({ peerFromMTP(data.vpeer()),
.storyId = { peerFromMTP(data.vpeer()), d.vid().v } data.vstory());
}); if (story) {
}, [](const auto &) { recentList.push_back({ .storyId = story->fullId() });
}); }
}); });
} }
const auto allLoaded = nextToken.isEmpty() || (nextToken == token);
_lastTotal = std::max(_lastTotal, fullCount); _lastTotal = std::max(_lastTotal, fullCount);
done({ done({
.list = std::move(recentList), .list = std::move(recentList),
@ -521,9 +416,24 @@ void PublicForwards::requestStory(
.allLoaded = allLoaded, .allLoaded = allLoaded,
.token = nextToken, .token = nextToken,
}); });
}).fail([=] { };
_requestId = 0;
}).send(); constexpr auto kLimit = tl::make_int(100);
if (_fullId.messageId) {
_requestId = makeRequest(MTPstats_GetMessagePublicForwards(
channel->inputChannel,
MTP_int(_fullId.messageId.msg),
MTP_string(token),
kLimit
)).done(processResult).fail([=] { _requestId = 0; }).send();
} else if (_fullId.storyId) {
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel->input,
MTP_int(_fullId.storyId.story),
MTP_string(token),
kLimit
)).done(processResult).fail([=] { _requestId = 0; }).send();
}
} }
MessageStatistics::MessageStatistics( MessageStatistics::MessageStatistics(
@ -702,6 +612,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
_peer->input _peer->input
)).done([=](const MTPpremium_BoostsStatus &result) { )).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data(); const auto &data = result.data();
channel->updateLevelHint(data.vlevel().v);
const auto hasPremium = !!data.vpremium_audience(); const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium const auto premiumMemberCount = hasPremium
? std::max(0, int(data.vpremium_audience()->data().vpart().v)) ? std::max(0, int(data.vpremium_audience()->data().vpart().v))

View file

@ -77,13 +77,6 @@ public:
Fn<void(Data::PublicForwardsSlice)> done); Fn<void(Data::PublicForwardsSlice)> done);
private: private:
void requestMessage(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done);
void requestStory(
const Data::PublicForwardsSlice::OffsetToken &token,
Fn<void(Data::PublicForwardsSlice)> done);
const Data::RecentPostId _fullId; const Data::RecentPostId _fullId;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
int _lastTotal = 0; int _lastTotal = 0;

View file

@ -225,11 +225,11 @@ void ApiWrap::setupSupportMode() {
void ApiWrap::requestChangelog( void ApiWrap::requestChangelog(
const QString &sinceVersion, const QString &sinceVersion,
Fn<void(const MTPUpdates &result)> callback) { Fn<void(const MTPUpdates &result)> callback) {
request(MTPhelp_GetAppChangelog( //request(MTPhelp_GetAppChangelog(
MTP_string(sinceVersion) // MTP_string(sinceVersion)
)).done( //)).done(
callback // callback
).send(); //).send();
} }
void ApiWrap::refreshTopPromotion() { void ApiWrap::refreshTopPromotion() {

View file

@ -129,6 +129,8 @@ private:
int row) const; int row) const;
void validatePaperThumbnail(const Paper &paper) const; void validatePaperThumbnail(const Paper &paper) const;
[[nodiscard]] bool forChannel() const;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
PeerData * const _forPeer = nullptr; PeerData * const _forPeer = nullptr;
@ -176,6 +178,23 @@ void BackgroundBox::prepare() {
st::infoIconMediaPhoto, st::infoIconMediaPhoto,
st::infoSharedMediaButtonIconPosition); st::infoSharedMediaButtonIconPosition);
if (forChannel() && _forPeer->wallPaper()) {
const auto remove = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_settings_bg_remove(),
st::infoBlockButton));
object_ptr<Info::Profile::FloatingIcon>(
remove,
st::infoIconDeleteRed,
st::infoSharedMediaButtonIconPosition);
remove->setClickedCallback([=] {
if (const auto resolved = _inner->resolveResetCustomPaper()) {
chosen(*resolved);
}
});
}
button->setClickedCallback([=] { button->setClickedCallback([=] {
chooseFromFile(); chooseFromFile();
}); });
@ -290,6 +309,23 @@ void BackgroundBox::chosen(const Data::WallPaper &paper) {
closeBox(); closeBox();
} }
return; return;
} else if (forChannel()) {
if (_forPeer->wallPaper() && _forPeer->wallPaper()->equals(paper)) {
closeBox();
return;
}
const auto &themes = _forPeer->owner().cloudThemes();
for (const auto &theme : themes.chatThemes()) {
for (const auto &[type, themed] : theme.settings) {
if (themed.paper && themed.paper->equals(paper)) {
_controller->show(Box<BackgroundPreviewBox>(
_controller,
Data::WallPaper::FromEmojiId(theme.emoticon),
BackgroundPreviewArgs{ _forPeer }));
return;
}
}
}
} }
_controller->show(Box<BackgroundPreviewBox>( _controller->show(Box<BackgroundPreviewBox>(
_controller, _controller,
@ -316,6 +352,10 @@ void BackgroundBox::resetForPeer() {
} }
} }
bool BackgroundBox::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundBox::removePaper(const Data::WallPaper &paper) { void BackgroundBox::removePaper(const Data::WallPaper &paper) {
const auto session = &_controller->session(); const auto session = &_controller->session();
const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) { const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) {
@ -345,9 +385,16 @@ BackgroundBox::Inner::Inner(
, _session(session) , _session(session)
, _forPeer(forPeer) , _forPeer(forPeer)
, _api(&_session->mtp()) , _api(&_session->mtp())
, _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) { , _check(
std::make_unique<Ui::RoundCheckbox>(
st::overviewCheck,
[=] { update(); })) {
_check->setChecked(true, anim::type::instant); _check->setChecked(true, anim::type::instant);
resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); resize(
st::boxWideWidth,
(2 * (st::backgroundSize.height() + st::backgroundPadding)
+ st::backgroundPadding));
Window::Theme::IsNightModeValue( Window::Theme::IsNightModeValue(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updatePapers(); updatePapers();
@ -364,21 +411,31 @@ BackgroundBox::Inner::Inner(
_check->invalidateCache(); _check->invalidateCache();
}, lifetime()); }, lifetime());
using Update = Window::Theme::BackgroundUpdate; if (forChannel()) {
Window::Theme::Background()->updates( _session->data().cloudThemes().chatThemesUpdated(
) | rpl::start_with_next([=](const Update &update) { ) | rpl::start_with_next([=] {
if (update.type == Update::Type::New) { updatePapers();
sortPapers(); }, lifetime());
requestPapers(); } else {
this->update(); using Update = Window::Theme::BackgroundUpdate;
} Window::Theme::Background()->updates(
}, lifetime()); ) | rpl::start_with_next([=](const Update &update) {
if (update.type == Update::Type::New) {
sortPapers();
requestPapers();
this->update();
}
}, lifetime());
}
setMouseTracking(true); setMouseTracking(true);
} }
void BackgroundBox::Inner::requestPapers() { void BackgroundBox::Inner::requestPapers() {
if (forChannel()) {
_session->data().cloudThemes().refreshChatThemes();
return;
}
_api.request(MTPaccount_GetWallPapers( _api.request(MTPaccount_GetWallPapers(
MTP_long(_session->data().wallpapersHash()) MTP_long(_session->data().wallpapersHash())
)).done([=](const MTPaccount_WallPapers &result) { )).done([=](const MTPaccount_WallPapers &result) {
@ -395,7 +452,7 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const
} }
const auto nonCustom = Window::Theme::Background()->paper(); const auto nonCustom = Window::Theme::Background()->paper();
const auto themeEmoji = _forPeer->themeEmoji(); const auto themeEmoji = _forPeer->themeEmoji();
if (themeEmoji.isEmpty()) { if (forChannel() || themeEmoji.isEmpty()) {
return nonCustom; return nonCustom;
} }
const auto &themes = _forPeer->owner().cloudThemes(); const auto &themes = _forPeer->owner().cloudThemes();
@ -443,6 +500,8 @@ void BackgroundBox::Inner::pushCustomPapers() {
} }
void BackgroundBox::Inner::sortPapers() { void BackgroundBox::Inner::sortPapers() {
Expects(!forChannel());
const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr; const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
_currentId = currentCustom _currentId = currentCustom
? currentCustom->id() ? currentCustom->id()
@ -472,23 +531,60 @@ void BackgroundBox::Inner::sortPapers() {
} }
void BackgroundBox::Inner::updatePapers() { void BackgroundBox::Inner::updatePapers() {
if (_session->data().wallpapers().empty()) { if (forChannel()) {
return; if (_session->data().cloudThemes().chatThemes().empty()) {
return;
}
} else {
if (_session->data().wallpapers().empty()) {
return;
}
} }
_over = _overDown = Selection(); _over = _overDown = Selection();
_papers = _session->data().wallpapers( const auto was = base::take(_papers);
) | ranges::views::filter([&](const Data::WallPaper &paper) { if (forChannel()) {
return (!paper.isPattern() || !paper.backgroundColors().empty()) const auto now = _forPeer->wallPaper();
&& (!_forPeer const auto &list = _session->data().cloudThemes().chatThemes();
|| (!Data::IsDefaultWallPaper(paper) if (list.empty()) {
&& (Data::IsCloudWallPaper(paper) return;
|| Data::IsCustomWallPaper(paper)))); }
}) | ranges::views::transform([](const Data::WallPaper &paper) { using Type = Data::CloudThemeType;
return Paper{ paper }; const auto type = Window::Theme::IsNightMode()
}) | ranges::to_vector; ? Type::Dark
pushCustomPapers(); : Type::Light;
sortPapers(); _papers.reserve(list.size() + 1);
const auto nowEmojiId = now ? now->emojiId() : QString();
if (!now || !now->emojiId().isEmpty()) {
_papers.push_back({ Window::Theme::Background()->paper() });
_currentId = _papers.back().data.id();
} else {
_papers.push_back({ *now });
_currentId = now->id();
}
for (const auto &theme : list) {
const auto i = theme.settings.find(type);
if (i != end(theme.settings) && i->second.paper) {
_papers.push_back({ *i->second.paper });
if (nowEmojiId == theme.emoticon) {
_currentId = _papers.back().data.id();
}
}
}
} else {
_papers = _session->data().wallpapers(
) | ranges::views::filter([&](const Data::WallPaper &paper) {
return (!paper.isPattern() || !paper.backgroundColors().empty())
&& (!_forPeer
|| (!Data::IsDefaultWallPaper(paper)
&& (Data::IsCloudWallPaper(paper)
|| Data::IsCustomWallPaper(paper))));
}) | ranges::views::transform([](const Data::WallPaper &paper) {
return Paper{ paper };
}) | ranges::to_vector;
pushCustomPapers();
sortPapers();
}
resizeToContentAndPreload(); resizeToContentAndPreload();
} }
@ -587,6 +683,10 @@ void BackgroundBox::Inner::validatePaperThumbnail(
paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
} }
bool BackgroundBox::Inner::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundBox::Inner::paintPaper( void BackgroundBox::Inner::paintPaper(
QPainter &p, QPainter &p,
const Paper &paper, const Paper &paper,
@ -604,7 +704,8 @@ void BackgroundBox::Inner::paintPaper(
const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
_check->paint(p, checkLeft, checkTop, width()); _check->paint(p, checkLeft, checkTop, width());
} else if (Data::IsCloudWallPaper(paper.data) } else if (!forChannel()
&& Data::IsCloudWallPaper(paper.data)
&& !Data::IsDefaultWallPaper(paper.data) && !Data::IsDefaultWallPaper(paper.data)
&& !Data::IsLegacy2DefaultWallPaper(paper.data) && !Data::IsLegacy2DefaultWallPaper(paper.data)
&& !Data::IsLegacy3DefaultWallPaper(paper.data) && !Data::IsLegacy3DefaultWallPaper(paper.data)
@ -642,7 +743,8 @@ void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
- st::stickerPanDeleteIconBg.width(); - st::stickerPanDeleteIconBg.width();
const auto deleteBottom = row * (height + skip) + skip const auto deleteBottom = row * (height + skip) + skip
+ st::stickerPanDeleteIconBg.height(); + st::stickerPanDeleteIconBg.height();
const auto inDelete = (x >= deleteLeft) const auto inDelete = !forChannel()
&& (x >= deleteLeft)
&& (y < deleteBottom) && (y < deleteBottom)
&& Data::IsCloudWallPaper(data) && Data::IsCloudWallPaper(data)
&& !Data::IsDefaultWallPaper(data) && !Data::IsDefaultWallPaper(data)

View file

@ -38,6 +38,7 @@ private:
const Data::WallPaper &paper) const; const Data::WallPaper &paper) const;
void removePaper(const Data::WallPaper &paper); void removePaper(const Data::WallPaper &paper);
void resetForPeer(); void resetForPeer();
[[nodiscard]] bool forChannel() const;
void chooseFromFile(); void chooseFromFile();

View file

@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/background_preview_box.h" #include "boxes/background_preview_box.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/boxes/boost_box.h"
#include "ui/controls/chat_service_checkbox.h" #include "ui/controls/chat_service_checkbox.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
@ -29,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/history_view_message.h" #include "history/view/history_view_message.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -159,6 +163,25 @@ constexpr auto kDefaultDimming = 50;
return result; return result;
} }
[[nodiscard]] Data::WallPaper Resolve(
not_null<Main::Session*> session,
const Data::WallPaper &paper,
bool dark) {
if (paper.emojiId().isEmpty()) {
return paper;
}
const auto &themes = session->data().cloudThemes();
if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
using Type = Data::CloudThemeType;
const auto type = dark ? Type::Dark : Type::Light;
const auto i = theme->settings.find(type);
if (i != end(theme->settings) && i->second.paper) {
return *i->second.paper;
}
}
return paper;
}
} // namespace } // namespace
struct BackgroundPreviewBox::OverridenStyle { struct BackgroundPreviewBox::OverridenStyle {
@ -196,15 +219,17 @@ BackgroundPreviewBox::BackgroundPreviewBox(
? tr::lng_background_apply2(tr::now) ? tr::lng_background_apply2(tr::now)
: tr::lng_background_text2(tr::now)), : tr::lng_background_text2(tr::now)),
true)) true))
, _paper(paper) , _paperEmojiId(paper.emojiId())
, _paper(
Resolve(&controller->session(), paper, Window::Theme::IsNightMode()))
, _media(_paper.document() ? _paper.document()->createMediaView() : nullptr) , _media(_paper.document() ? _paper.document()->createMediaView() : nullptr)
, _radial([=](crl::time now) { radialAnimationCallback(now); }) , _radial([=](crl::time now) { radialAnimationCallback(now); })
, _appNightMode(Window::Theme::IsNightModeValue()) , _appNightMode(Window::Theme::IsNightModeValue())
, _boxDarkMode(_appNightMode.current()) , _boxDarkMode(_appNightMode.current())
, _dimmingIntensity(std::clamp(paper.patternIntensity(), 0, 100)) , _dimmingIntensity(std::clamp(_paper.patternIntensity(), 0, 100))
, _dimmed(_forPeer , _dimmed(_forPeer
&& (paper.document() || paper.localThumbnail()) && (_paper.document() || _paper.localThumbnail())
&& !paper.isPattern()) { && !_paper.isPattern()) {
if (_media) { if (_media) {
_media->thumbnailWanted(_paper.fileOrigin()); _media->thumbnailWanted(_paper.fileOrigin());
} }
@ -244,7 +269,36 @@ BackgroundPreviewBox::BackgroundPreviewBox(
BackgroundPreviewBox::~BackgroundPreviewBox() = default; BackgroundPreviewBox::~BackgroundPreviewBox() = default;
void BackgroundPreviewBox::recreate(bool dark) {
_paper = Resolve(
&_controller->session(),
Data::WallPaper::FromEmojiId(_paperEmojiId),
dark);
_media = _paper.document()
? _paper.document()->createMediaView()
: nullptr;
if (_media) {
_media->thumbnailWanted(_paper.fileOrigin());
}
_full = QImage();
_generated = _scaled = _blurred = _fadeOutThumbnail = QPixmap();
_generating = {};
generateBackground();
_paper.loadDocument();
if (const auto document = _paper.document()) {
if (document->loading()) {
_radial.start(_media->progress());
}
}
checkLoadedDocument();
updateServiceBg(_paper.backgroundColors());
update();
}
void BackgroundPreviewBox::applyDarkMode(bool dark) { void BackgroundPreviewBox::applyDarkMode(bool dark) {
if (!_paperEmojiId.isEmpty()) {
recreate(dark);
}
const auto equals = (dark == Window::Theme::IsNightMode()); const auto equals = (dark == Window::Theme::IsNightMode());
const auto &palette = (dark ? _darkPalette : _lightPalette); const auto &palette = (dark ? _darkPalette : _lightPalette);
if (!equals && !palette) { if (!equals && !palette) {
@ -410,6 +464,10 @@ auto BackgroundPreviewBox::prepareOverridenStyle(bool dark)
return result; return result;
} }
bool BackgroundPreviewBox::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
void BackgroundPreviewBox::generateBackground() { void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) { if (_paper.backgroundColors().empty()) {
return; return;
@ -435,7 +493,9 @@ void BackgroundPreviewBox::resetTitle() {
void BackgroundPreviewBox::rebuildButtons(bool dark) { void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons(); clearButtons();
addButton(_forPeer addButton(forChannel()
? tr::lng_background_apply_channel()
: _forPeer
? tr::lng_background_apply_button() ? tr::lng_background_apply_button()
: tr::lng_settings_apply(), [=] { apply(); }); : tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); });
@ -624,6 +684,36 @@ void BackgroundPreviewBox::setExistingForPeer(
_controller->finishChatThemeEdit(_forPeer); _controller->finishChatThemeEdit(_forPeer);
} }
void BackgroundPreviewBox::checkLevelForChannel() {
Expects(forChannel());
const auto show = _controller->uiShow();
_forPeerLevelCheck = true;
const auto weak = Ui::MakeWeak(this);
CheckBoostLevel(show, _forPeer, [=](int level) {
if (!weak) {
return std::optional<Ui::AskBoostReason>();
}
const auto appConfig = &_forPeer->session().account().appConfig();
const auto defaultRequired = appConfig->get<int>(
"channel_wallpaper_level_min",
9);
const auto customRequired = appConfig->get<int>(
"channel_custom_wallpaper_level_min",
10);
const auto required = _paperEmojiId.isEmpty()
? customRequired
: defaultRequired;
if (level >= required) {
applyForPeer(false);
return std::optional<Ui::AskBoostReason>();
}
return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostWallpaper{ required }
});
}, [=] { _forPeerLevelCheck = false; });
}
void BackgroundPreviewBox::applyForPeer() { void BackgroundPreviewBox::applyForPeer() {
Expects(_forPeer != nullptr); Expects(_forPeer != nullptr);
@ -636,105 +726,110 @@ void BackgroundPreviewBox::applyForPeer() {
} }
} }
if (!_fromMessageId && _forPeer->session().premiumPossible()) { if (forChannel()) {
if (_forBothOverlay) { checkLevelForChannel();
return; return;
} } else if (_fromMessageId || !_forPeer->session().premiumPossible()) {
const auto size = this->size() * style::DevicePixelRatio();
const auto bg = Images::DitherImage(
Images::BlurLargeImage(
Ui::GrabWidgetToImage(this).scaled(
size / style::ConvertScale(4),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
24).scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
this,
object_ptr<Ui::RpWidget>(this));
const auto overlay = _forBothOverlay->entity();
sizeValue() | rpl::start_with_next([=](QSize size) {
_forBothOverlay->setGeometry({ QPoint(), size });
overlay->setGeometry({ QPoint(), size });
}, _forBothOverlay->lifetime());
overlay->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(overlay);
p.drawImage(0, 0, bg);
p.fillRect(clip, QColor(0, 0, 0, 64));
}, overlay->lifetime());
using namespace Ui;
const auto forMe = CreateChild<RoundButton>(
overlay,
tr::lng_background_apply_me(),
st::backgroundConfirm);
forMe->setClickedCallback([=] {
applyForPeer(false);
});
using namespace rpl::mappers;
const auto forBoth = ::Settings::CreateLockedButton(
overlay,
tr::lng_background_apply_both(
lt_user,
rpl::single(_forPeer->shortName())),
st::backgroundConfirm,
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
forBoth->setClickedCallback([=] {
if (_forPeer->session().premium()) {
applyForPeer(true);
} else {
ShowPremiumPreviewBox(
_controller->uiShow(),
PremiumPreview::Wallpapers);
}
});
const auto cancel = CreateChild<RoundButton>(
overlay,
tr::lng_cancel(),
st::backgroundConfirmCancel);
cancel->setClickedCallback([=] {
const auto raw = _forBothOverlay.release();
raw->shownValue() | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
delete raw;
}), raw->lifetime());
raw->toggle(false, anim::type::normal);
});
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
overlay->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto padding = st::backgroundConfirmPadding;
const auto width = size.width()
- padding.left()
- padding.right();
const auto height = cancel->height();
auto top = size.height() - padding.bottom() - height;
cancel->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forBoth->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forMe->setGeometry(padding.left(), top, width, height);
}, _forBothOverlay->lifetime());
_forBothOverlay->hide(anim::type::instant);
_forBothOverlay->show(anim::type::normal);
} else {
applyForPeer(false); applyForPeer(false);
return;
} else if (_forBothOverlay) {
return;
} }
const auto size = this->size() * style::DevicePixelRatio();
const auto bg = Images::DitherImage(
Images::BlurLargeImage(
Ui::GrabWidgetToImage(this).scaled(
size / style::ConvertScale(4),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation),
24).scaled(
size,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
_forBothOverlay = std::make_unique<Ui::FadeWrap<>>(
this,
object_ptr<Ui::RpWidget>(this));
const auto overlay = _forBothOverlay->entity();
sizeValue() | rpl::start_with_next([=](QSize size) {
_forBothOverlay->setGeometry({ QPoint(), size });
overlay->setGeometry({ QPoint(), size });
}, _forBothOverlay->lifetime());
overlay->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(overlay);
p.drawImage(0, 0, bg);
p.fillRect(clip, QColor(0, 0, 0, 64));
}, overlay->lifetime());
using namespace Ui;
const auto forMe = CreateChild<RoundButton>(
overlay,
tr::lng_background_apply_me(),
st::backgroundConfirm);
forMe->setClickedCallback([=] {
applyForPeer(false);
});
using namespace rpl::mappers;
const auto forBoth = ::Settings::CreateLockedButton(
overlay,
tr::lng_background_apply_both(
lt_user,
rpl::single(_forPeer->shortName())),
st::backgroundConfirm,
Data::AmPremiumValue(&_forPeer->session()) | rpl::map(!_1));
forBoth->setClickedCallback([=] {
if (_forPeer->session().premium()) {
applyForPeer(true);
} else {
ShowPremiumPreviewBox(
_controller->uiShow(),
PremiumPreview::Wallpapers);
}
});
const auto cancel = CreateChild<RoundButton>(
overlay,
tr::lng_cancel(),
st::backgroundConfirmCancel);
cancel->setClickedCallback([=] {
const auto raw = _forBothOverlay.release();
raw->shownValue() | rpl::filter(
!rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next(crl::guard(raw, [=] {
delete raw;
}), raw->lifetime());
raw->toggle(false, anim::type::normal);
});
forMe->setTextTransform(RoundButton::TextTransform::NoTransform);
forBoth->setTextTransform(RoundButton::TextTransform::NoTransform);
cancel->setTextTransform(RoundButton::TextTransform::NoTransform);
overlay->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto padding = st::backgroundConfirmPadding;
const auto width = size.width()
- padding.left()
- padding.right();
const auto height = cancel->height();
auto top = size.height() - padding.bottom() - height;
cancel->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forBoth->setGeometry(padding.left(), top, width, height);
top -= height + padding.top();
forMe->setGeometry(padding.left(), top, width, height);
}, _forBothOverlay->lifetime());
_forBothOverlay->hide(anim::type::instant);
_forBothOverlay->show(anim::type::normal);
} }
void BackgroundPreviewBox::applyForPeer(bool both) { void BackgroundPreviewBox::applyForPeer(bool both) {
if (Data::IsCustomWallPaper(_paper)) { using namespace Data;
if (forChannel() && !_paperEmojiId.isEmpty()) {
setExistingForPeer(WallPaper::FromEmojiId(_paperEmojiId), both);
} else if (IsCustomWallPaper(_paper)) {
uploadForPeer(both); uploadForPeer(both);
} else { } else {
setExistingForPeer(_paper, both); setExistingForPeer(_paper, both);
@ -855,7 +950,7 @@ int BackgroundPreviewBox::textsTop() const {
- st::historyPaddingBottom - st::historyPaddingBottom
- (_service ? _service->height() : 0) - (_service ? _service->height() : 0)
- _text1->height() - _text1->height()
- _text2->height(); - (forChannel() ? _text2->height() : 0);
} }
QRect BackgroundPreviewBox::radialRect() const { QRect BackgroundPreviewBox::radialRect() const {
@ -885,10 +980,11 @@ void BackgroundPreviewBox::paintTexts(Painter &p, crl::time ms) {
context.outbg = _text1->hasOutLayout(); context.outbg = _text1->hasOutLayout();
_text1->draw(p, context); _text1->draw(p, context);
p.translate(0, height1); p.translate(0, height1);
if (!forChannel()) {
context.outbg = _text2->hasOutLayout(); context.outbg = _text2->hasOutLayout();
_text2->draw(p, context); _text2->draw(p, context);
p.translate(0, height2); p.translate(0, height2);
}
} }
void BackgroundPreviewBox::radialAnimationCallback(crl::time now) { void BackgroundPreviewBox::radialAnimationCallback(crl::time now) {
@ -988,7 +1084,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
_service = GenerateServiceItem( _service = GenerateServiceItem(
delegate(), delegate(),
_serviceHistory, _serviceHistory,
((_forPeer && !_fromMessageId) (forChannel()
? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId)
? tr::lng_background_other_info( ? tr::lng_background_other_info(
tr::now, tr::now,
lt_user, lt_user,

View file

@ -94,18 +94,24 @@ private:
void applyDarkMode(bool dark); void applyDarkMode(bool dark);
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark); [[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
[[nodiscard]] bool forChannel() const;
void checkLevelForChannel();
void recreate(bool dark);
void resetTitle(); void resetTitle();
void rebuildButtons(bool dark); void rebuildButtons(bool dark);
void createDimmingSlider(bool dark); void createDimmingSlider(bool dark);
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
PeerData * const _forPeer = nullptr; PeerData * const _forPeer = nullptr;
bool _forPeerLevelCheck = false;
FullMsgId _fromMessageId; FullMsgId _fromMessageId;
std::unique_ptr<Ui::ChatStyle> _chatStyle; std::unique_ptr<Ui::ChatStyle> _chatStyle;
const not_null<History*> _serviceHistory; const not_null<History*> _serviceHistory;
AdminLog::OwnedItem _service; AdminLog::OwnedItem _service;
AdminLog::OwnedItem _text1; AdminLog::OwnedItem _text1;
AdminLog::OwnedItem _text2; AdminLog::OwnedItem _text2;
QString _paperEmojiId;
Data::WallPaper _paper; Data::WallPaper _paper;
std::shared_ptr<Data::DocumentMedia> _media; std::shared_ptr<Data::DocumentMedia> _media;
QImage _full; QImage _full;

View file

@ -12,18 +12,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "boxes/peer_list_controllers.h" // ContactsBoxController.
#include "boxes/peers/prepare_short_info_box.h" #include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/data_boosts.h" #include "data/data_boosts.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_media_types.h" // Data::Giveaway #include "data/data_media_types.h" // Data::GiveawayStart.
#include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_subscription_option.h" #include "data/data_subscription_option.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "payments/payments_checkout_process.h"
#include "payments/payments_form.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks. #include "ui/boxes/boost_box.h" // StartFireworks.
@ -33,11 +41,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_top_bar.h" #include "ui/effects/premium_top_bar.h"
#include "ui/effects/spoiler_mess.h" #include "ui/effects/spoiler_mess.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h" #include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h" #include "ui/wrap/padding_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/table_layout.h" #include "ui/wrap/table_layout.h"
#include "window/window_peer_menu.h" // ShowChooseRecipientBox. #include "window/window_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
@ -52,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kDiscountDivider = 5.; constexpr auto kDiscountDivider = 5.;
constexpr auto kUserpicsMax = size_t(3);
using GiftOption = Data::SubscriptionOption; using GiftOption = Data::SubscriptionOption;
using GiftOptions = Data::SubscriptionOptions; using GiftOptions = Data::SubscriptionOptions;
@ -72,6 +84,137 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
return result; return result;
} }
using TagUser1 = lngtag_user;
using TagUser2 = lngtag_second_user;
using TagUser3 = lngtag_name;
[[nodiscard]] rpl::producer<TextWithEntities> ComplexAboutLabel(
const std::vector<not_null<UserData*>> &users,
tr::phrase<TagUser1> phrase1,
tr::phrase<TagUser1, TagUser2> phrase2,
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
Expects(!users.empty());
const auto count = users.size();
const auto nameValue = [&](not_null<UserData*> user) {
return user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
};
if (count == 1) {
return phrase1(
lt_user,
nameValue(users.front()),
Ui::Text::RichLangValue);
} else if (count == 2) {
return phrase2(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
Ui::Text::RichLangValue);
} else if (count == 3) {
return phrase3(
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
} else {
return phraseMore(
lt_count,
rpl::single(count - kUserpicsMax) | tr::to_count(),
lt_user,
nameValue(users.front()),
lt_second_user,
nameValue(users[1]),
lt_name,
nameValue(users[2]),
Ui::Text::RichLangValue);
}
}
[[nodiscard]] not_null<Ui::RpWidget*> CircleBadge(
not_null<Ui::RpWidget*> parent,
const QString &text) {
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
auto gradient = QLinearGradient(
QPointF(0, full.height()),
QPointF(full.width(), 0));
gradient.setStops(Ui::Premium::GiftGradientStops());
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(full);
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawEllipse(inner);
p.setFont(st::premiumGiftsUserpicBadgeFont);
p.setPen(st::premiumButtonFg);
p.drawText(full, text, style::al_center);
}, widget->lifetime());
widget->resize(full.size());
return widget;
}
[[nodiscard]] not_null<Ui::RpWidget*> UserpicsContainer(
not_null<Ui::RpWidget*> parent,
std::vector<not_null<UserData*>> users) {
Expects(!users.empty());
if (users.size() == 1) {
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
parent.get(),
users.front(),
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
return userpic;
}
const auto &singleSize = st::defaultUserpicButton.size;
const auto container = Ui::CreateChild<Ui::RpWidget>(parent.get());
const auto single = singleSize.width();
const auto shift = single - st::boostReplaceUserpicsShift;
const auto maxWidth = users.size() * (single - shift) + shift;
container->resize(maxWidth, singleSize.height());
container->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
/ 2;
for (auto i = 0; i < users.size(); i++) {
const auto bg = Ui::CreateChild<Ui::RpWidget>(container);
bg->resize(singleSize);
bg->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(bg);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawEllipse(bg->rect());
}, bg->lifetime());
bg->moveToLeft(std::max(0, i * (single - shift)), 0);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
bg,
users[i],
st::premiumGiftsUserpicButton);
userpic->moveToLeft(diff, diff);
}
return container;
}
void GiftBox( void GiftBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
@ -95,12 +238,11 @@ void GiftBox(
+ st::defaultUserpicButton.size.height())); + st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars; using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true); const auto stars = box->lifetime().make_state<ColoredMiniStars>(
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
top, top,
user, true);
st::defaultUserpicButton);
const auto userpic = UserpicsContainer(top, { user });
userpic->setAttribute(Qt::WA_TransparentForMouseEvents); userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
top->widthValue( top->widthValue(
) | rpl::start_with_next([=](int width) { ) | rpl::start_with_next([=](int width) {
@ -211,7 +353,7 @@ void GiftBox(
auto raw = Settings::CreateSubscribeButton({ auto raw = Settings::CreateSubscribeButton({
controller, controller,
box, box,
[] { return QString("gift"); }, [] { return u"gift"_q; },
state->buttonText.events(), state->buttonText.events(),
Ui::Premium::GiftGradientStops(), Ui::Premium::GiftGradientStops(),
[=] { [=] {
@ -222,10 +364,8 @@ void GiftBox(
}, },
}); });
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw); auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
- stButton.buttonPadding.left() box->setShowFinishedCallback([raw = button.data()] {
- stButton.buttonPadding.right());
box->setShowFinishedCallback([raw = button.data()]{
raw->startGlareAnimation(); raw->startGlareAnimation();
}); });
box->addButton(std::move(button)); box->addButton(std::move(button));
@ -239,6 +379,302 @@ void GiftBox(
}, box->lifetime()); }, box->lifetime());
} }
void GiftsBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
std::vector<not_null<UserData*>> users,
not_null<Api::PremiumGiftCodeOptions*> api) {
Expects(!users.empty());
const auto boxWidth = st::boxWideWidth;
box->setWidth(boxWidth);
box->setNoContentMargin(true);
const auto buttonsParent = box->verticalLayout().get();
const auto session = &users.front()->session();
struct State {
rpl::event_stream<QString> buttonText;
rpl::variable<bool> confirmButtonBusy = false;
rpl::variable<bool> isPaymentComplete = false;
};
const auto state = box->lifetime().make_state<State>();
const auto userpicPadding = st::premiumGiftUserpicPadding;
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
buttonsParent,
userpicPadding.top()
+ userpicPadding.bottom()
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
top,
true);
const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
const auto userpics = UserpicsContainer(
top,
{ users.begin(), users.begin() + maxWithUserpic });
top->widthValue(
) | rpl::start_with_next([=](int width) {
userpics->moveToLeft(
(width - userpics->width()) / 2,
userpicPadding.top());
const auto center = top->rect().center();
const auto size = QSize(
userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
userpics->height());
const auto ministarsRect = QRect(
QPoint(center.x() - size.width(), center.y() - size.height()),
QPoint(center.x() + size.width(), center.y() + size.height()));
stars->setPosition(ministarsRect.topLeft());
stars->setSize(ministarsRect.size());
}, userpics->lifetime());
if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
const auto badge = CircleBadge(
userpics,
QChar('+') + QString::number(rest));
badge->moveToRight(0, userpics->height() - badge->height());
}
top->paintRequest(
) | rpl::start_with_next([=](const QRect &r) {
auto p = QPainter(top);
p.fillRect(r, Qt::transparent);
stars->paint(p);
}, top->lifetime());
const auto close = Ui::CreateChild<Ui::IconButton>(
buttonsParent,
st::infoTopBarClose);
close->setClickedCallback([=] { box->closeBox(); });
buttonsParent->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0, width);
}, close->lifetime());
// Header.
const auto &padding = st::premiumGiftAboutPadding;
const auto available = boxWidth - padding.left() - padding.right();
const auto &stTitle = st::premiumPreviewAboutTitle;
auto titleLabel = object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->isPaymentComplete.value(),
tr::lng_premium_gifts_about_paid_title(),
tr::lng_premium_gift_title()),
stTitle);
titleLabel->resizeToWidth(available);
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
std::move(titleLabel)),
st::premiumGiftTitlePadding);
// About.
{
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
auto text = rpl::conditional(
state->isPaymentComplete.value(),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_paid1,
tr::lng_premium_gifts_about_paid2,
tr::lng_premium_gifts_about_paid3,
tr::lng_premium_gifts_about_paid_more
) | rpl::map([count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_paid_below(
tr::now,
lt_count,
float64(count),
Ui::Text::RichLangValue));
return text;
}),
ComplexAboutLabel(
users,
tr::lng_premium_gifts_about_user1,
tr::lng_premium_gifts_about_user2,
tr::lng_premium_gifts_about_user3,
tr::lng_premium_gifts_about_user_more
) | rpl::map([=, count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_reward(
tr::now,
lt_count,
count * BoostsForGift(session),
lt_emoji,
emoji,
Ui::Text::RichLangValue));
return text;
})
);
const auto label = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(box, st::premiumPreviewAbout)),
padding)->entity();
std::move(
text
) | rpl::start_with_next([=](const TextWithEntities &t) {
using namespace Core;
label->setMarkedText(t, MarkedTextContext{ .session = session });
}, label->lifetime());
label->setTextColorOverride(stTitle.textFg->c);
label->resizeToWidth(available);
}
// List.
const auto optionsContainer = buttonsParent->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
buttonsParent,
object_ptr<Ui::VerticalLayout>(buttonsParent)));
const auto options = api->options(users.size());
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
const auto groupValueChangedCallback = [=](int value) {
Expects(value < options.size() && value >= 0);
auto text = tr::lng_premium_gift_button(
tr::now,
lt_cost,
options[value].costTotal);
state->buttonText.fire(std::move(text));
};
group->setChangedCallback(groupValueChangedCallback);
Ui::Premium::AddGiftOptions(
optionsContainer->entity(),
group,
options,
st::premiumGiftOption);
optionsContainer->toggleOn(
state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
anim::type::instant);
// Summary.
{
{
// Will be hidden after payment.
const auto content = optionsContainer->entity();
Ui::AddSkip(content);
Ui::AddDivider(content);
Ui::AddSkip(content);
Ui::AddSubsectionTitle(
content,
tr::lng_premium_gifts_summary_subtitle());
}
const auto content = box->addRow(
object_ptr<Ui::VerticalLayout>(box),
{});
auto buttonCallback = [=](PremiumPreview section) {
stars->setPaused(true);
const auto previewBoxShown = [=](
not_null<Ui::BoxContent*> previewBox) {
previewBox->boxClosing(
) | rpl::start_with_next(crl::guard(box, [=] {
stars->setPaused(false);
}), previewBox->lifetime());
};
ShowPremiumPreviewBox(
controller->uiShow(),
section,
previewBoxShown,
true);
};
Settings::AddSummaryPremium(
content,
controller,
u"gift"_q,
std::move(buttonCallback));
}
// Footer.
{
box->addRow(
object_ptr<Ui::DividerLabel>(
box,
object_ptr<Ui::FlatLabel>(
box,
session->api().premium().statusTextValue(), // TODO.
st::premiumGiftTerms),
st::defaultBoxDividerLabelPadding),
{});
}
// Button.
const auto &stButton = st::premiumGiftBox;
box->setStyle(stButton);
auto raw = Settings::CreateSubscribeButton({
controller,
box,
[] { return u"gift"_q; },
rpl::combine(
state->buttonText.events(),
state->confirmButtonBusy.value(),
state->isPaymentComplete.value()
) | rpl::map([](const QString &text, bool busy, bool paid) {
return busy
? QString()
: paid
? tr::lng_close(tr::now)
: text;
}),
Ui::Premium::GiftGradientStops(),
});
raw->setClickedCallback([=] {
if (state->confirmButtonBusy.current()) {
return;
}
if (state->isPaymentComplete.current()) {
return box->closeBox();
}
auto invoice = api->invoice(
users.size(),
api->monthsFromPreset(group->value()));
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
state->confirmButtonBusy = true;
const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) {
if (const auto strong = weak.data()) {
strong->window()->setFocus();
state->confirmButtonBusy = false;
if (result == Payments::CheckoutResult::Paid) {
state->isPaymentComplete = true;
Ui::StartFireworks(box->parentWidget());
}
}
};
Payments::CheckoutProcess::Start(std::move(invoice), done);
});
{
using namespace Info::Statistics;
const auto loadingAnimation = InfiniteRadialAnimationWidget(
raw,
raw->height() / 2);
AddChildToWidgetCenter(raw, loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
box->setShowFinishedCallback([raw = button.data()] {
raw->startGlareAnimation();
});
box->addButton(std::move(button));
groupValueChangedCallback(0);
}
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink( [[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const QString &slug) { const QString &slug) {
@ -370,18 +806,20 @@ void AddTable(
container, container,
st::giveawayGiftCodeTable), st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin); st::giveawayGiftCodeTableMargin);
AddTableRow( if (current.from) {
table, AddTableRow(
tr::lng_gift_link_label_from(), table,
controller, tr::lng_gift_link_label_from(),
current.from); controller,
if (current.to) { current.from);
}
if (current.from && current.to) {
AddTableRow( AddTableRow(
table, table,
tr::lng_gift_link_label_to(), tr::lng_gift_link_label_to(),
controller, controller,
current.to); current.to);
} else { } else if (current.from) {
AddTableRow( AddTableRow(
table, table,
tr::lng_gift_link_label_to(), tr::lng_gift_link_label_to(),
@ -394,7 +832,7 @@ void AddTable(
lt_duration, lt_duration,
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(), GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities)); Ui::Text::WithEntities));
if (!skipReason) { if (!skipReason && current.from) {
const auto reason = AddTableRow( const auto reason = AddTableRow(
table, table,
tr::lng_gift_link_label_reason(), tr::lng_gift_link_label_reason(),
@ -439,6 +877,112 @@ void GiftPremiumValidator::cancel() {
_requestId = 0; _requestId = 0;
} }
void GiftPremiumValidator::showChoosePeerBox() {
if (_manyGiftsLifetime) {
return;
}
using namespace Api;
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
_controller->session().user());
const auto show = _controller->uiShow();
api->request(
) | rpl::start_with_error_done([=](const QString &error) {
show->showToast(error);
}, [=] {
const auto maxAmount = *ranges::max_element(api->availablePresets());
class Controller final : public ContactsBoxController {
public:
Controller(
not_null<Main::Session*> session,
Fn<bool(int)> checkErrorCallback)
: ContactsBoxController(session)
, _checkErrorCallback(std::move(checkErrorCallback)) {
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
return !user->isSelf()
? ContactsBoxController::createRow(user)
: nullptr;
}
void rowClicked(not_null<PeerListRow*> row) override {
const auto checked = !row->checked();
if (checked
&& _checkErrorCallback
&& _checkErrorCallback(
delegate()->peerListSelectedRowsCount())) {
return;
}
delegate()->peerListSetRowChecked(row, checked);
}
private:
const Fn<bool(int)> _checkErrorCallback;
};
auto initBox = [=](not_null<PeerListBox*> peersBox) {
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
auto process = [=] {
const auto selected = peersBox->collectSelectedRows();
const auto users = ranges::views::all(
selected
) | ranges::views::transform([](not_null<PeerData*> p) {
return p->asUser();
}) | ranges::views::filter([](UserData *u) -> bool {
return u;
}) | ranges::to<std::vector<not_null<UserData*>>>();
if (!users.empty()) {
const auto giftBox = show->show(
Box(GiftsBox, _controller, users, api));
giftBox->boxClosing(
) | rpl::start_with_next([=] {
_manyGiftsLifetime.destroy();
}, giftBox->lifetime());
}
(*ignoreClose) = true;
peersBox->closeBox();
};
peersBox->setTitle(tr::lng_premium_gift_title());
peersBox->addButton(
tr::lng_settings_gift_premium_users_confirm(),
std::move(process));
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
peersBox->boxClosing(
) | rpl::start_with_next([=] {
if (!(*ignoreClose)) {
_manyGiftsLifetime.destroy();
}
}, peersBox->lifetime());
};
auto listController = std::make_unique<Controller>(
&_controller->session(),
[=](int count) {
if (count <= maxAmount) {
return false;
}
show->showToast(tr::lng_settings_gift_premium_users_error(
tr::now,
lt_count,
maxAmount));
return true;
});
show->showBox(
Box<PeerListBox>(
std::move(listController),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}, _manyGiftsLifetime);
}
void GiftPremiumValidator::showBox(not_null<UserData*> user) { void GiftPremiumValidator::showBox(not_null<UserData*> user) {
if (_requestId) { if (_requestId) {
return; return;
@ -723,10 +1267,22 @@ void GiftCodePendingBox(
void ResolveGiftCode( void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
const QString &slug) { const QString &slug,
PeerId fromId,
PeerId toId) {
const auto done = [=](Api::GiftCode code) { const auto done = [=](Api::GiftCode code) {
const auto session = &controller->session();
const auto selfId = session->userPeerId();
if (!code) { if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now)); controller->showToast(tr::lng_gift_link_expired(tr::now));
} else if (!code.from && fromId == selfId) {
code.from = fromId;
code.to = toId;
const auto self = (fromId == selfId);
const auto peer = session->data().peer(self ? toId : fromId);
const auto months = code.months;
const auto parent = controller->parentController();
Settings::ShowGiftPremium(parent, peer, months, self);
} else { } else {
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug)); controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
} }
@ -739,8 +1295,11 @@ void ResolveGiftCode(
void GiveawayInfoBox( void GiveawayInfoBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
Data::Giveaway giveaway, std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results,
Api::GiveawayInfo info) { Api::GiveawayInfo info) {
Expects(start || results);
using State = Api::GiveawayState; using State = Api::GiveawayState;
const auto finished = (info.state == State::Finished) const auto finished = (info.state == State::Finished)
|| (info.state == State::Refunded); || (info.state == State::Refunded);
@ -749,10 +1308,31 @@ void GiveawayInfoBox(
? tr::lng_prizes_end_title ? tr::lng_prizes_end_title
: tr::lng_prizes_how_title)()); : tr::lng_prizes_how_title)());
const auto first = !giveaway.channels.empty() const auto first = results
? giveaway.channels.front()->name() ? results->channel->name()
: !start->channels.empty()
? start->channels.front()->name()
: u"channel"_q; : u"channel"_q;
auto text = (finished auto text = TextWithEntities();
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_won(
tr::now,
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86"))));
text.append("\n\n");
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(Ui::Text::Bold(tr::lng_prizes_you_didnt(tr::now)));
text.append("\n\n");
}
const auto quantity = start
? start->quantity
: (results->winnersCount + results->unclaimedCount);
const auto months = start ? start->months : results->months;
text.append((finished
? tr::lng_prizes_end_text ? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)( : tr::lng_prizes_how_text)(
tr::now, tr::now,
@ -760,18 +1340,21 @@ void GiveawayInfoBox(
tr::lng_prizes_admins( tr::lng_prizes_admins(
tr::now, tr::now,
lt_count, lt_count,
giveaway.quantity, quantity,
lt_channel, lt_channel,
Ui::Text::Bold(first), Ui::Text::Bold(first),
lt_duration, lt_duration,
TextWithEntities{ GiftDuration(giveaway.months) }, TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue), Ui::Text::RichLangValue),
Ui::Text::RichLangValue); Ui::Text::RichLangValue));
const auto many = (giveaway.channels.size() > 1); const auto many = start
? (start->channels.size() > 1)
: (results->additionalPeersCount > 0);
const auto count = info.winnersCount const auto count = info.winnersCount
? info.winnersCount ? info.winnersCount
: giveaway.quantity; : quantity;
auto winners = giveaway.all const auto all = start ? start->all : results->all;
auto winners = all
? (many ? (many
? tr::lng_prizes_winners_all_of_many ? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)( : tr::lng_prizes_winners_all_of_one)(
@ -793,13 +1376,30 @@ void GiveawayInfoBox(
Ui::Text::Bold( Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.startDate))), langDateTime(base::unixtime::parse(info.startDate))),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
const auto additionalPrize = results
? results->additionalPrize
: start->additionalPrize;
if (!additionalPrize.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_additional_added(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_prize,
TextWithEntities{ additionalPrize },
Ui::Text::RichLangValue));
}
const auto untilDate = start
? start->untilDate
: results->untilDate;
text.append("\n\n").append((finished text.append("\n\n").append((finished
? tr::lng_prizes_end_when_finish ? tr::lng_prizes_end_when_finish
: tr::lng_prizes_how_when_finish)( : tr::lng_prizes_how_when_finish)(
tr::now, tr::now,
lt_date, lt_date,
Ui::Text::Bold(langDayOfMonthFull( Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())), base::unixtime::parse(untilDate).date())),
lt_winners, lt_winners,
winners, winners,
Ui::Text::RichLangValue)); Ui::Text::RichLangValue));
@ -810,17 +1410,9 @@ void GiveawayInfoBox(
info.activatedCount, info.activatedCount,
Ui::Text::RichLangValue)); Ui::Text::RichLangValue));
} }
if (!info.giftCode.isEmpty()) { if (!info.giftCode.isEmpty()
text.append("\n\n"); || info.state == State::Finished
text.append(tr::lng_prizes_you_won( || info.state == State::Preparing) {
tr::now,
lt_cup,
QString::fromUtf8("\xf0\x9f\x8f\x86")));
} else if (info.state == State::Finished) {
text.append("\n\n");
text.append(tr::lng_prizes_you_didnt(tr::now));
} else if (info.state == State::Preparing) {
} else if (info.state != State::Refunded) { } else if (info.state != State::Refunded) {
if (info.adminChannelId) { if (info.adminChannelId) {
const auto channel = controller->session().data().channel( const auto channel = controller->session().data().channel(
@ -858,7 +1450,7 @@ void GiveawayInfoBox(
Ui::Text::Bold(first), Ui::Text::Bold(first),
lt_date, lt_date,
Ui::Text::Bold(langDayOfMonthFull( Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())), base::unixtime::parse(untilDate).date())),
Ui::Text::RichLangValue)); Ui::Text::RichLangValue));
} }
} }
@ -902,14 +1494,15 @@ void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId messageId, MsgId messageId,
Data::Giveaway giveaway) { std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results) {
const auto show = [=](Api::GiveawayInfo info) { const auto show = [=](Api::GiveawayInfo info) {
if (!info) { if (!info) {
controller->showToast( controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now)); tr::lng_confirm_phone_link_invalid(tr::now));
} else { } else {
controller->uiShow()->showBox( controller->uiShow()->showBox(
Box(GiveawayInfoBox, controller, giveaway, info)); Box(GiveawayInfoBox, controller, start, results, info));
} }
}; };
controller->session().api().premium().resolveGiveawayInfo( controller->session().api().premium().resolveGiveawayInfo(

View file

@ -16,7 +16,8 @@ struct GiftCode;
} // namespace Api } // namespace Api
namespace Data { namespace Data {
struct Giveaway; struct GiveawayStart;
struct GiveawayResults;
} // namespace Data } // namespace Data
namespace Ui { namespace Ui {
@ -33,6 +34,7 @@ public:
GiftPremiumValidator(not_null<Window::SessionController*> controller); GiftPremiumValidator(not_null<Window::SessionController*> controller);
void showBox(not_null<UserData*> user); void showBox(not_null<UserData*> user);
void showChoosePeerBox();
void cancel(); void cancel();
private: private:
@ -41,6 +43,8 @@ private:
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
rpl::lifetime _manyGiftsLifetime;
}; };
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months); [[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
@ -56,10 +60,13 @@ void GiftCodePendingBox(
const Api::GiftCode &data); const Api::GiftCode &data);
void ResolveGiftCode( void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
const QString &slug); const QString &slug,
PeerId fromId = 0,
PeerId toId = 0);
void ResolveGiveawayInfo( void ResolveGiveawayInfo(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,
MsgId messageId, MsgId messageId,
Data::Giveaway giveaway); std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results);

View file

@ -40,11 +40,14 @@ public:
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot, not_null<UserData*> bot,
RequestPeerQuery query, RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> callback); Fn<void(std::vector<not_null<PeerData*>>)> callback);
Main::Session &session() const override; Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
[[nodiscard]] rpl::producer<int> selectedCountValue() const;
void submit();
QString savedMessagesChatStatus() const override { QString savedMessagesChatStatus() const override {
return tr::lng_saved_forward_here(tr::now); return tr::lng_saved_forward_here(tr::now);
} }
@ -60,7 +63,9 @@ private:
not_null<UserData*> _bot; not_null<UserData*> _bot;
RequestPeerQuery _query; RequestPeerQuery _query;
base::flat_set<not_null<PeerData*>> _commonGroups; base::flat_set<not_null<PeerData*>> _commonGroups;
Fn<void(not_null<PeerData*>)> _callback; base::flat_set<not_null<PeerData*>> _selected;
rpl::variable<int> _selectedCount;
Fn<void(std::vector<not_null<PeerData*>>)> _callback;
}; };
@ -253,10 +258,10 @@ object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot, not_null<UserData*> bot,
RequestPeerQuery query, RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> done) { Fn<void(std::vector<not_null<PeerData*>>)> done) {
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>(); const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto callback = [=](not_null<PeerData*> peer) { auto callback = [=](not_null<PeerData*> peer) {
done(peer); done({ peer });
if (const auto strong = weak->data()) { if (const auto strong = weak->data()) {
strong->closeBox(); strong->closeBox();
} }
@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot, not_null<UserData*> bot,
RequestPeerQuery query, RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> callback) Fn<void(std::vector<not_null<PeerData*>>)> callback)
: ChatsListBoxController(&navigation->session()) : ChatsListBoxController(&navigation->session())
, _navigation(navigation) , _navigation(navigation)
, _bot(bot) , _bot(bot)
@ -415,6 +420,8 @@ void ChoosePeerBoxController::prepareViewHook() {
switch (_query.type) { switch (_query.type) {
case Type::User: return (_query.userIsBot == Restriction::Yes) case Type::User: return (_query.userIsBot == Restriction::Yes)
? tr::lng_request_bot_title() ? tr::lng_request_bot_title()
: (_query.maxQuantity > 1)
? tr::lng_request_users_title()
: tr::lng_request_user_title(); : tr::lng_request_user_title();
case Type::Group: return tr::lng_request_group_title(); case Type::Group: return tr::lng_request_group_title();
case Type::Broadcast: return tr::lng_request_channel_title(); case Type::Broadcast: return tr::lng_request_channel_title();
@ -425,10 +432,24 @@ void ChoosePeerBoxController::prepareViewHook() {
} }
void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) { void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto limit = _query.maxQuantity;
const auto multiselect = (limit > 1);
const auto peer = row->peer(); const auto peer = row->peer();
if (multiselect) {
if (_selected.contains(peer) || _selected.size() < limit) {
delegate()->peerListSetRowChecked(row, !row->checked());
if (row->checked()) {
_selected.emplace(peer);
} else {
_selected.remove(peer);
}
_selectedCount = int(_selected.size());
}
return;
}
const auto done = [callback = _callback, peer] { const auto done = [callback = _callback, peer] {
const auto onstack = callback; const auto onstack = callback;
onstack(peer); onstack({ peer });
}; };
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
done(); done();
@ -438,6 +459,15 @@ void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
return _selectedCount.value();
}
void ChoosePeerBoxController::submit() {
const auto onstack = _callback;
onstack(ranges::to_vector(_selected));
}
auto ChoosePeerBoxController::createRow(not_null<History*> history) auto ChoosePeerBoxController::createRow(not_null<History*> history)
-> std::unique_ptr<Row> { -> std::unique_ptr<Row> {
return FilterPeerByQuery(history->peer, _query, _commonGroups) return FilterPeerByQuery(history->peer, _query, _commonGroups)
@ -474,7 +504,7 @@ void ShowChoosePeerBox(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot, not_null<UserData*> bot,
RequestPeerQuery query, RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> chosen) { Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
const auto needCommonGroups = query.isBotParticipant const auto needCommonGroups = query.isBotParticipant
&& (query.type == RequestPeerQuery::Type::Group) && (query.type == RequestPeerQuery::Type::Group)
&& !query.myRights; && !query.myRights;
@ -488,22 +518,39 @@ void ShowChoosePeerBox(
return; return;
} }
const auto weak = std::make_shared<QPointer<Ui::BoxContent>>(); const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
auto initBox = [=](not_null<PeerListBox*> box) { auto callback = [=, done = std::move(chosen)](
box->addButton(tr::lng_cancel(), [box] { std::vector<not_null<PeerData*>> peers) {
box->closeBox(); done(std::move(peers));
});
};
auto callback = [=, done = std::move(chosen)](not_null<PeerData*> peer) {
done(peer);
if (const auto strong = weak->data()) { if (const auto strong = weak->data()) {
strong->closeBox(); strong->closeBox();
} }
}; };
const auto limit = query.maxQuantity;
auto controller = std::make_unique<ChoosePeerBoxController>(
navigation,
bot,
query,
std::move(callback));
auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> box) {
ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
box->clearButtons();
if (limit > 1) {
box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
}
if (count > 0) {
box->addButton(tr::lng_intro_submit(), [=] {
ptr->submit();
if (*weak) {
(*weak)->closeBox();
}
});
}
box->addButton(tr::lng_cancel(), [box] {
box->closeBox();
});
}, box->lifetime());
};
*weak = navigation->parentController()->show(Box<PeerListBox>( *weak = navigation->parentController()->show(Box<PeerListBox>(
std::make_unique<ChoosePeerBoxController>( std::move(controller),
navigation,
bot,
query,
std::move(callback)),
std::move(initBox))); std::move(initBox)));
} }

View file

@ -21,4 +21,4 @@ void ShowChoosePeerBox(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot, not_null<UserData*> bot,
RequestPeerQuery query, RequestPeerQuery query,
Fn<void(not_null<PeerData*>)> chosen); Fn<void(std::vector<not_null<PeerData*>>)> chosen);

View file

@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_peer_colors.h" #include "api/api_peer_colors.h"
#include "api/api_peer_photo.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h" #include "boxes/peers/replace_boost_box.h"
#include "boxes/background_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_emoji_statuses.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
@ -428,11 +431,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog; return HistoryView::Context::AdminLog;
} }
struct SetValues {
uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0;
TimeId statusUntil = 0;
bool statusChanged = false;
};
void Set( void Set(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
uint8 colorIndex, SetValues values) {
DocumentId backgroundEmojiId) {
const auto wasIndex = peer->colorIndex(); const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId(); const auto wasEmojiId = peer->backgroundEmojiId();
@ -444,7 +453,7 @@ void Set(
peer, peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji); UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
}; };
setLocal(colorIndex, backgroundEmojiId); setLocal(values.colorIndex, values.backgroundEmojiId);
const auto done = [=] { const auto done = [=] {
show->showToast(peer->isSelf() show->showToast(peer->isSelf()
@ -452,8 +461,11 @@ void Set(
: tr::lng_settings_color_changed_channel(tr::now)); : tr::lng_settings_color_changed_channel(tr::now));
}; };
const auto fail = [=](const MTP::Error &error) { const auto fail = [=](const MTP::Error &error) {
setLocal(wasIndex, wasEmojiId); const auto type = error.type();
show->showToast(error.type()); if (type != u"CHAT_NOT_MODIFIED"_q) {
setLocal(wasIndex, wasEmojiId);
show->showToast(type);
}
}; };
const auto send = [&](auto &&request) { const auto send = [&](auto &&request) {
peer->session().api().request( peer->session().api().request(
@ -464,15 +476,23 @@ void Set(
using Flag = MTPaccount_UpdateColor::Flag; using Flag = MTPaccount_UpdateColor::Flag;
send(MTPaccount_UpdateColor( send(MTPaccount_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id), MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(colorIndex), MTP_int(values.colorIndex),
MTP_long(backgroundEmojiId))); MTP_long(values.backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag; using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor( send(MTPchannels_UpdateColor(
MTP_flags(Flag::f_background_emoji_id), MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
channel->inputChannel, channel->inputChannel,
MTP_int(colorIndex), MTP_int(values.colorIndex),
MTP_long(backgroundEmojiId))); MTP_long(values.backgroundEmojiId)));
if (values.statusChanged
&& (values.statusId || peer->emojiStatusId())) {
peer->owner().emojiStatuses().set(
channel,
values.statusId,
values.statusUntil);
}
} else { } else {
Unexpected("Invalid peer type in Set(colorIndex)."); Unexpected("Invalid peer type in Set(colorIndex).");
} }
@ -481,13 +501,13 @@ void Set(
void Apply( void Apply(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
uint8 colorIndex, SetValues values,
DocumentId backgroundEmojiId,
Fn<void()> close, Fn<void()> close,
Fn<void()> cancel) { Fn<void()> cancel) {
const auto session = &peer->session(); const auto session = &peer->session();
if (peer->colorIndex() == colorIndex if (peer->colorIndex() == values.colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) { && peer->backgroundEmojiId() == values.backgroundEmojiId
&& !values.statusChanged) {
close(); close();
} else if (peer->isSelf() && !session->premium()) { } else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast( Settings::ShowPremiumPromoToast(
@ -502,39 +522,45 @@ void Apply(
u"name_color"_q); u"name_color"_q);
cancel(); cancel();
} else if (peer->isSelf()) { } else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId); Set(show, peer, values);
close(); close();
} else { } else {
session->api().request(MTPpremium_GetBoostsStatus( CheckBoostLevel(show, peer, [=](int level) {
peer->input const auto peerColors = &peer->session().api().peerColors();
)).done([=](const MTPpremium_BoostsStatus &result) { const auto colorRequired = peerColors->requiredLevelFor(
const auto &data = result.data(); peer->id,
const auto required = session->account().appConfig().get<int>( values.colorIndex);
"channel_color_level_min", const auto iconRequired = values.backgroundEmojiId
5); ? session->account().appConfig().get<int>(
if (data.vlevel().v >= required) { "channel_bg_icon_level_min",
Set(show, peer, colorIndex, backgroundEmojiId); 5)
: 0;
const auto statusRequired = (values.statusChanged
&& values.statusId)
? session->account().appConfig().get<int>(
"channel_emoji_status_level_min",
8)
: 0;
const auto required = std::max({
colorRequired,
iconRequired,
statusRequired,
});
if (level >= required) {
Set(show, peer, values);
close(); close();
return; return std::optional<Ui::AskBoostReason>();
} }
const auto openStatistics = [=] { const auto reason = [&]() -> Ui::AskBoostReason {
if (const auto controller = show->resolveWindow( if (level < statusRequired) {
ChatHelpers::WindowUsage::PremiumPromo)) { return { Ui::AskBoostEmojiStatus{ statusRequired } };
controller->showSection(Info::Boosts::Make(peer)); } else if (level < iconRequired) {
return { Ui::AskBoostChannelColor{ iconRequired } };
} }
}; return { Ui::AskBoostChannelColor{ colorRequired } };
auto counters = ParseBoostCounters(result); }();
counters.mine = 0; // Don't show current level as just-reached. return std::make_optional(reason);
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ }, cancel);
.link = qs(data.vboost_url()),
.boost = counters,
.reason = { Ui::AskBoostChannelColor{ required } },
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
} }
} }
@ -672,15 +698,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto right = Ui::CreateChild<Ui::RpWidget>(raw); const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show(); right->show();
using namespace Info::Profile;
struct State { struct State {
Info::Profile::EmojiStatusPanel panel; EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji; std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0; DocumentId emojiId = 0;
uint8 index = 0; uint8 index = 0;
}; };
const auto state = right->lifetime().make_state<State>(); const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen( state->panel.someCustomChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime()); ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id);
}, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index; state->index = index;
@ -748,7 +777,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
state->panel.show({ state->panel.show({
.controller = controller, .controller = controller,
.button = right, .button = right,
.currentBackgroundEmojiId = state->emojiId, .ensureAddedEmojiId = state->emojiId,
.customTextColor = customTextColor, .customTextColor = customTextColor,
.backgroundEmojiMode = true, .backgroundEmojiMode = true,
}); });
@ -758,6 +787,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result; return result;
} }
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiStatusButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
parent,
tr::lng_edit_channel_status(),
*st);
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
using namespace Info::Profile;
struct State {
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId statusId = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
statusIdChosen(chosen.id, chosen.until);
}, raw->lifetime());
const auto session = &show->session();
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
state->statusId = id;
state->emoji = id
? session->data().customEmojiManager().create(
id,
[=] { right->update(); })
: nullptr;
right->resize(
(id ? emojiWidth : noneWidth) + added,
right->height());
right->update();
}, right->lifetime());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::start_with_next([=] {
if (state->panel.paintBadgeFrame(right)) {
return;
}
auto p = QPainter(right);
const auto height = right->height();
if (state->emoji) {
state->emoji->paint(p, {
.textColor = anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
0.5),
.position = QPoint(added, (height - emojiSize) / 2),
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(st::windowActiveTextFg);
p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now));
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedEmojiId = state->statusId,
.channelStatusMode = true,
});
}
});
return result;
}
} // namespace } // namespace
void EditPeerColorBox( void EditPeerColorBox(
@ -772,12 +903,16 @@ void EditPeerColorBox(
struct State { struct State {
rpl::variable<uint8> index; rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId; rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
bool changing = false; bool changing = false;
bool applying = false; bool applying = false;
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex(); state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId(); state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>( box->addRow(object_ptr<PreviewWrap>(
box, box,
@ -820,14 +955,61 @@ void EditPeerColorBox(
? tr::lng_settings_color_emoji_about() ? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel()); : tr::lng_settings_color_emoji_about_channel());
if (const auto channel = peer->asChannel()) {
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_edit_channel_wallpaper(),
st::settingsButtonNoIcon)
)->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto strong = show->resolveWindow(usage)) {
show->show(Box<BackgroundBox>(strong, channel));
}
});
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
tr::lng_edit_channel_wallpaper_about());
// Preload exceptions list.
const auto peerPhoto = &channel->session().api().peerPhoto();
[[maybe_unused]] auto list = peerPhoto->emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
);
const auto statuses = &channel->owner().emojiStatuses();
statuses->refreshChannelDefault();
statuses->refreshChannelColored();
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiStatusButton(
container,
show,
state->statusId.value(),
[=](DocumentId id, TimeId until) {
state->statusId = id;
state->statusUntil = until;
state->statusChanged = true;
}));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
}
box->addButton(tr::lng_settings_apply(), [=] { box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) { if (state->applying) {
return; return;
} }
state->applying = true; state->applying = true;
const auto index = state->index.current(); Apply(show, peer, {
const auto emojiId = state->emojiId.current(); state->index.current(),
Apply(show, peer, index, emojiId, crl::guard(box, [=] { state->emojiId.current(),
state->statusId.current(),
state->statusUntil,
state->statusChanged,
}, crl::guard(box, [=] {
box->closeBox(); box->closeBox();
}), crl::guard(box, [=] { }), crl::guard(box, [=] {
state->applying = false; state->applying = false;
@ -842,11 +1024,12 @@ void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
auto label = peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon( const auto button = AddButtonWithIcon(
container, container,
(peer->isSelf() rpl::duplicate(label),
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color()),
st::settingsColorButton, st::settingsColorButton,
{ &st::menuIconChangeColors }); { &st::menuIconChangeColors });
@ -873,7 +1056,7 @@ void AddPeerColorButton(
rpl::combine( rpl::combine(
button->widthValue(), button->widthValue(),
tr::lng_settings_theme_name_color(), rpl::duplicate(label),
rpl::duplicate(colorIndexValue) rpl::duplicate(colorIndexValue)
) | rpl::start_with_next([=]( ) | rpl::start_with_next([=](
int width, int width,
@ -920,3 +1103,39 @@ void AddPeerColorButton(
show->show(Box(EditPeerColorBox, show, peer, style, theme)); show->show(Box(EditPeerColorBox, show, peer, style, theme));
}); });
} }
void CheckBoostLevel(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
Fn<void()> cancel) {
peer->session().api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
if (const auto channel = peer->asChannel()) {
channel->updateLevelHint(data.vlevel().v);
}
const auto reason = askMore(data.vlevel().v);
if (!reason) {
return;
}
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
auto counters = ParseBoostCounters(result);
counters.mine = 0; // Don't show current level as just-reached.
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = counters,
.reason = *reason,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
}

View file

@ -16,6 +16,7 @@ class GenericBox;
class ChatStyle; class ChatStyle;
class ChatTheme; class ChatTheme;
class VerticalLayout; class VerticalLayout;
struct AskBoostReason;
} // namespace Ui } // namespace Ui
void EditPeerColorBox( void EditPeerColorBox(
@ -29,3 +30,9 @@ void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer); not_null<PeerData*> peer);
void CheckBoostLevel(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
Fn<std::optional<Ui::AskBoostReason>(int level)> askMore,
Fn<void()> cancel);

View file

@ -1294,6 +1294,9 @@ void Controller::editReactions() {
_peer->input _peer->input
)).done([=](const MTPpremium_BoostsStatus &result) { )).done([=](const MTPpremium_BoostsStatus &result) {
_controls.levelRequested = false; _controls.levelRequested = false;
if (const auto channel = _peer->asChannel()) {
channel->updateLevelHint(result.data().vlevel().v);
}
const auto link = qs(result.data().vboost_url()); const auto link = qs(result.data().vboost_url());
const auto weak = base::make_weak(_navigation->parentController()); const auto weak = base::make_weak(_navigation->parentController());
auto counters = ParseBoostCounters(result); auto counters = ParseBoostCounters(result);

View file

@ -66,6 +66,7 @@ struct Descriptor {
bool fromSettings = false; bool fromSettings = false;
Fn<void()> hiddenCallback; Fn<void()> hiddenCallback;
Fn<void(not_null<Ui::BoxContent*>)> shownCallback; Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
bool hideSubscriptionButton = false;
}; };
bool operator==(const Descriptor &a, const Descriptor &b) { bool operator==(const Descriptor &a, const Descriptor &b) {
@ -1025,7 +1026,8 @@ void PreviewBox(
state->preload(); state->preload();
} }
}; };
if (descriptor.fromSettings && show->session().premium()) { if ((descriptor.fromSettings && show->session().premium())
|| descriptor.hideSubscriptionButton) {
box->setShowFinishedCallback(showFinished); box->setShowFinishedCallback(showFinished);
box->addButton(tr::lng_close(), [=] { box->closeBox(); }); box->addButton(tr::lng_close(), [=] { box->closeBox(); });
} else { } else {
@ -1151,7 +1153,7 @@ void DecorateListPromoBox(
box->boxClosing() | rpl::start_with_next(hidden, box->lifetime()); box->boxClosing() | rpl::start_with_next(hidden, box->lifetime());
} }
if (session->premium()) { if (session->premium() || descriptor.hideSubscriptionButton) {
box->addButton(tr::lng_close(), [=] { box->addButton(tr::lng_close(), [=] {
box->closeBox(); box->closeBox();
}); });
@ -1286,10 +1288,12 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox( void ShowPremiumPreviewBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
PremiumPreview section, PremiumPreview section,
Fn<void(not_null<Ui::BoxContent*>)> shown) { Fn<void(not_null<Ui::BoxContent*>)> shown,
bool hideSubscriptionButton) {
Show(std::move(show), Descriptor{ Show(std::move(show), Descriptor{
.section = section, .section = section,
.shownCallback = std::move(shown), .shownCallback = std::move(shown),
.hideSubscriptionButton = hideSubscriptionButton,
}); });
} }

View file

@ -73,7 +73,8 @@ void ShowPremiumPreviewBox(
void ShowPremiumPreviewBox( void ShowPremiumPreviewBox(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
PremiumPreview section, PremiumPreview section,
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr); Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr,
bool hideSubscriptionButton = false);
void ShowPremiumPreviewToBuy( void ShowPremiumPreviewToBuy(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,

View file

@ -211,7 +211,7 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
return Type::Other; return Type::Other;
} else if (const auto browser = detectBrowser()) { } else if (const auto browser = detectBrowser()) {
return *browser; return *browser;
} else if (device.contains("iphone")) { } else if (device.contains("iphone")) {
return Type::iPhone; return Type::iPhone;
} else if (device.contains("ipad")) { } else if (device.contains("ipad")) {
return Type::iPad; return Type::iPad;
@ -221,9 +221,9 @@ void RenameBox(not_null<Ui::GenericBox*> box) {
return *desktop; return *desktop;
} else if (platform.contains("android") || system.contains("android")) { } else if (platform.contains("android") || system.contains("android")) {
return Type::Android; return Type::Android;
} else if (platform.contains("ios") || system.contains("ios")) { } else if (platform.contains("ios") || system.contains("ios")) {
return Type::iPhone; return Type::iPhone;
} }
return Type::Other; return Type::Other;
} }

View file

@ -305,7 +305,7 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
const auto skip = Core::App().settings().skipTranslationLanguages(); const auto skip = Core::App().settings().skipTranslationLanguages();
return result.known() && ranges::contains(skip, result); return result.known() && ranges::contains(skip, result);
#else #else
return false; return false;
#endif #endif
} }

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/emoji_list_widget.h"
#include "api/api_peer_photo.h"
#include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h" #include "ui/controls/tabbed_search.h"
@ -477,10 +479,23 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
} }
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) { if (_mode != Mode::RecentReactions
&& _mode != Mode::BackgroundEmoji
&& _mode != Mode::ChannelStatus) {
setupSearch(); setupSearch();
} }
if (_mode == Mode::ChannelStatus) {
session().api().peerPhoto().emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
) | rpl::start_with_next([=](const std::vector<DocumentId> &list) {
_restrictedCustomList = { begin(list), end(list) };
if (!_custom.empty()) {
refreshCustom();
}
}, lifetime());
}
_customSingleSize = Data::FrameSizeFromTag( _customSingleSize = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Large Data::CustomEmojiManager::SizeTag::Large
) / style::DevicePixelRatio(); ) / style::DevicePixelRatio();
@ -1034,7 +1049,9 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
if (!id && _mode == Mode::EmojiStatus) { if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f"); const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } }); _recent.push_back({ .id = { Ui::Emoji::Find(star) } });
} else if (!id && _mode == Mode::BackgroundEmoji) { } else if (!id
&& (_mode == Mode::BackgroundEmoji
|| _mode == Mode::ChannelStatus)) {
const auto fakeId = DocumentId(5246772116543512028ULL); const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f"); const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({ _recent.push_back({
@ -1070,7 +1087,7 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
: st::defaultPopupMenu)); : st::defaultPopupMenu));
if (_mode == Mode::Full) { if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index); fillRecentMenu(menu, section, index);
} else if (_mode == Mode::EmojiStatus) { } else if (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus) {
fillEmojiStatusMenu(menu, section, index); fillEmojiStatusMenu(menu, section, index);
} }
if (menu->empty()) { if (menu->empty()) {
@ -1205,7 +1222,7 @@ void EmojiListWidget::validateEmojiPaintContext(
auto value = Ui::Text::CustomEmojiPaintContext{ auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_customTextColor .textColor = (_customTextColor
? _customTextColor() ? _customTextColor()
: (_mode == Mode::EmojiStatus) : (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus)
? anim::color( ? anim::color(
st::stickerPanPremium1, st::stickerPanPremium1,
st::stickerPanPremium2, st::stickerPanPremium2,
@ -1402,6 +1419,10 @@ void EmojiListWidget::drawRecent(
_emojiPaintContext->position = position _emojiPaintContext->position = position
+ _innerPosition + _innerPosition
+ _customPosition; + _customPosition;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
custom->paint(p, *_emojiPaintContext); custom->paint(p, *_emojiPaintContext);
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) { } else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) { if (_mode == Mode::EmojiStatus) {
@ -1642,6 +1663,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
Settings::ShowPremium(resolved, u"infinite_reactions"_q); Settings::ShowPremium(resolved, u"infinite_reactions"_q);
break; break;
case Mode::EmojiStatus: case Mode::EmojiStatus:
case Mode::ChannelStatus:
Settings::ShowPremium(resolved, u"emoji_status"_q); Settings::ShowPremium(resolved, u"emoji_status"_q);
break; break;
case Mode::TopicIcon: case Mode::TopicIcon:
@ -2018,8 +2040,9 @@ void EmojiListWidget::refreshCustom() {
auto it = sets.find(setId); auto it = sets.find(setId);
if (it == sets.cend() if (it == sets.cend()
|| it->second->stickers.isEmpty() || it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji || (_mode == Mode::BackgroundEmoji && !it->second->textColor())
&& !it->second->textColor())) { || (_mode == Mode::ChannelStatus
&& !it->second->channelStatus())) {
return; return;
} }
const auto canRemove = !!(it->second->flags const auto canRemove = !!(it->second->flags
@ -2070,7 +2093,9 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>(); auto set = std::vector<CustomOne>();
set.reserve(list.size()); set.reserve(list.size());
for (const auto document : list) { for (const auto document : list) {
if (const auto sticker = document->sticker()) { if (_restrictedCustomList.contains(document->id)) {
continue;
} else if (const auto sticker = document->sticker()) {
set.push_back({ set.push_back({
.custom = resolveCustomEmoji(document, setId), .custom = resolveCustomEmoji(document, setId),
.document = document, .document = document,

View file

@ -71,6 +71,7 @@ enum class EmojiListMode {
Full, Full,
TopicIcon, TopicIcon,
EmojiStatus, EmojiStatus,
ChannelStatus,
FullReactions, FullReactions,
RecentReactions, RecentReactions,
UserpicBuilder, UserpicBuilder,
@ -384,6 +385,7 @@ private:
bool _grabbingChosen = false; bool _grabbingChosen = false;
QVector<EmojiPtr> _emoji[kEmojiSectionCount]; QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom; std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList;
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji; base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
base::flat_map< base::flat_map<
DocumentId, DocumentId,

View file

@ -26,6 +26,7 @@ const QString DicePacks::kDartString = QString::fromUtf8("\xF0\x9F\x8E\xAF");
const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0"); const QString DicePacks::kSlotString = QString::fromUtf8("\xF0\x9F\x8E\xB0");
const QString DicePacks::kFballString = QString::fromUtf8("\xE2\x9A\xBD"); const QString DicePacks::kFballString = QString::fromUtf8("\xE2\x9A\xBD");
const QString DicePacks::kBballString = QString::fromUtf8("\xF0\x9F\x8F\x80"); const QString DicePacks::kBballString = QString::fromUtf8("\xF0\x9F\x8F\x80");
const QString DicePacks::kPartyPopper = QString::fromUtf8("\xf0\x9f\x8e\x89");
DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji) DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
: _session(session) : _session(session)
@ -35,7 +36,7 @@ DicePack::DicePack(not_null<Main::Session*> session, const QString &emoji)
DicePack::~DicePack() = default; DicePack::~DicePack() = default;
DocumentData *DicePack::lookup(int value) { DocumentData *DicePack::lookup(int value) {
if (!_requestId) { if (!_requestId && _emoji != DicePacks::kPartyPopper) {
load(); load();
} }
tryGenerateLocalZero(); tryGenerateLocalZero();
@ -117,6 +118,8 @@ void DicePack::tryGenerateLocalZero() {
generateLocal(8, u"slot_0_idle"_q); generateLocal(8, u"slot_0_idle"_q);
generateLocal(14, u"slot_1_idle"_q); generateLocal(14, u"slot_1_idle"_q);
generateLocal(20, u"slot_2_idle"_q); generateLocal(20, u"slot_2_idle"_q);
} else if (_emoji == DicePacks::kPartyPopper) {
generateLocal(0, u"winners"_q);
} }
} }

View file

@ -44,6 +44,7 @@ public:
static const QString kSlotString; static const QString kSlotString;
static const QString kFballString; static const QString kFballString;
static const QString kBballString; static const QString kBballString;
static const QString kPartyPopper;
[[nodiscard]] static bool IsSlot(const QString &emoji) { [[nodiscard]] static bool IsSlot(const QString &emoji) {
return (emoji == kSlotString); return (emoji == kSlotString);

View file

@ -332,6 +332,7 @@ TabbedSelector::TabbedSelector(
: TabbedSelector(parent, { : TabbedSelector(parent, {
.show = std::move(show), .show = std::move(show),
.st = ((mode == Mode::EmojiStatus .st = ((mode == Mode::EmojiStatus
|| mode == Mode::ChannelStatus
|| mode == Mode::BackgroundEmoji || mode == Mode::BackgroundEmoji
|| mode == Mode::FullReactions) || mode == Mode::FullReactions)
? st::statusEmojiPan ? st::statusEmojiPan
@ -521,6 +522,8 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.show = _show, .show = _show,
.mode = (_mode == Mode::EmojiStatus .mode = (_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus ? EmojiMode::EmojiStatus
: _mode == Mode::ChannelStatus
? EmojiMode::ChannelStatus
: _mode == Mode::BackgroundEmoji : _mode == Mode::BackgroundEmoji
? EmojiMode::BackgroundEmoji ? EmojiMode::BackgroundEmoji
: _mode == Mode::FullReactions : _mode == Mode::FullReactions

View file

@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly, EmojiOnly,
MediaEditor, MediaEditor,
EmojiStatus, EmojiStatus,
ChannelStatus,
BackgroundEmoji, BackgroundEmoji,
FullReactions, FullReactions,
RecentReactions, RecentReactions,

View file

@ -428,11 +428,12 @@ void Application::showOpenGLCrashNotification() {
Local::writeSettings(); Local::writeSettings();
Restart(); Restart();
}; };
const auto keepDisabled = [=] { const auto keepDisabled = [=](Fn<void()> close) {
Ui::GL::ForceDisable(true); Ui::GL::ForceDisable(true);
Ui::GL::CrashCheckFinish(); Ui::GL::CrashCheckFinish();
settings().setDisableOpenGL(true); settings().setDisableOpenGL(true);
Local::writeSettings(); Local::writeSettings();
close();
}; };
_lastActivePrimaryWindow->show(Ui::MakeConfirmBox({ _lastActivePrimaryWindow->show(Ui::MakeConfirmBox({
.text = "" .text = ""

View file

@ -344,9 +344,19 @@ bool ResolveUsernameOrPhone(
qthelp::UrlParamNameTransform::ToLower); qthelp::UrlParamNameTransform::ToLower);
const auto domainParam = params.value(u"domain"_q); const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q); const auto appnameParam = params.value(u"appname"_q);
const auto myContext = context.value<ClickHandlerContext>();
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) { if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
ResolveGiftCode(controller, appnameParam); const auto itemId = myContext.itemId;
const auto item = controller->session().data().message(itemId);
const auto fromId = item ? item->from()->id : PeerId();
const auto selfId = controller->session().userPeerId();
const auto toId = !item
? PeerId()
: (fromId == selfId)
? item->history()->peer->id
: selfId;
ResolveGiftCode(controller, appnameParam, fromId, toId);
return true; return true;
} }
@ -413,7 +423,6 @@ bool ResolveUsernameOrPhone(
startToken = params.value(u"startapp"_q); startToken = params.value(u"startapp"_q);
} }
} }
const auto myContext = context.value<ClickHandlerContext>();
controller->window().activate(); controller->window().activate();
controller->showPeerByLink(Window::PeerByLinkInfo{ controller->showPeerByLink(Window::PeerByLinkInfo{
.usernameOrId = domain, .usernameOrId = domain,
@ -732,7 +741,8 @@ void ExportTestChatTheme(
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)), MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)), MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
MTP_int(fields.paper->patternIntensity()), MTP_int(fields.paper->patternIntensity()),
MTP_int(0))); MTP_int(0), // rotation
MTPstring())); // emoticon
}; };
const auto light = inputSettings(Data::CloudThemeType::Light); const auto light = inputSettings(Data::CloudThemeType::Light);
if (!light) { if (!light) {

View file

@ -40,10 +40,12 @@ namespace {
base::options::toggle OptionForceWaylandFractionalScaling({ base::options::toggle OptionForceWaylandFractionalScaling({
.id = kOptionForceWaylandFractionalScaling, .id = kOptionForceWaylandFractionalScaling,
.name = "Force enable fractional-scale-v1", .name = "Enable xdg-output fractional scaling",
.description = "Enable fractional-scale-v1 on Wayland without " .description = "Enable xdg-output based fractional scaling on Wayland. "
"This works without fractional-scale-v1 and without "
"precise High DPI scaling. " "precise High DPI scaling. "
"Requires Qt with Desktop App Toolkit patches.", "Requires Qt with Desktop App Toolkit patches.",
.defaultValue = true,
.scope = [] { .scope = [] {
#ifdef DESKTOP_APP_QT_PATCHED #ifdef DESKTOP_APP_QT_PATCHED
return Platform::IsWayland(); return Platform::IsWayland();
@ -251,12 +253,7 @@ void Sandbox::setupScreenScale() {
logEnv("QT_USE_PHYSICAL_DPI"); logEnv("QT_USE_PHYSICAL_DPI");
logEnv("QT_FONT_DPI"); logEnv("QT_FONT_DPI");
// Like Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor. const auto useRatio = std::clamp(qCeil(ratio), 1, 3);
// Round up for .75 and higher. This favors "small UI" over "large UI".
const auto roundedRatio = ((ratio - qFloor(ratio)) < 0.75)
? qFloor(ratio)
: qCeil(ratio);
const auto useRatio = std::clamp(roundedRatio, 1, 3);
style::SetDevicePixelRatio(useRatio); style::SetDevicePixelRatio(useRatio);
const auto screen = Sandbox::primaryScreen(); const auto screen = Sandbox::primaryScreen();

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 = 4012002; constexpr auto AppVersion = 4013001;
constexpr auto AppVersionStr = "4.12.2"; constexpr auto AppVersionStr = "4.13.1";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/session/send_as_peers.h" #include "main/session/send_as_peers.h"
@ -948,6 +949,14 @@ void ChannelData::processTopics(const MTPVector<MTPForumTopic> &topics) {
} }
} }
int ChannelData::levelHint() const {
return _levelHint;
}
void ChannelData::updateLevelHint(int levelHint) {
_levelHint = levelHint;
}
namespace Data { namespace Data {
void ApplyMigration( void ApplyMigration(
@ -1142,6 +1151,13 @@ void ApplyChannelUpdate(
session->sendAsPeers().setChosen(channel, PeerId()); session->sendAsPeers().setChosen(channel, PeerId());
} }
if (const auto paper = update.vwallpaper()) {
channel->setWallPaper(
Data::WallPaper::Create(&channel->session(), *paper));
} else {
channel->setWallPaper({});
}
// For clearUpTill() call. // For clearUpTill() call.
channel->owner().sendHistoryChangeNotifications(); channel->owner().sendHistoryChangeNotifications();
} }

View file

@ -463,6 +463,9 @@ public:
void processTopics(const MTPVector<MTPForumTopic> &topics); void processTopics(const MTPVector<MTPForumTopic> &topics);
[[nodiscard]] int levelHint() const;
void updateLevelHint(int levelHint);
// Still public data members. // Still public data members.
uint64 access = 0; uint64 access = 0;
@ -497,6 +500,7 @@ private:
int _restrictedCount = 0; int _restrictedCount = 0;
int _kickedCount = 0; int _kickedCount = 0;
int _pendingRequestsCount = 0; int _pendingRequestsCount = 0;
int _levelHint = 0;
std::vector<UserId> _recentRequesters; std::vector<UserId> _recentRequesters;
MsgId _availableMinId = 0; MsgId _availableMinId = 0;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h" #include "data/data_emoji_statuses.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_channel.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_document.h" #include "data/data_document.h"
@ -53,6 +54,7 @@ EmojiStatuses::EmojiStatuses(not_null<Session*> owner)
kRefreshDefaultListEach kRefreshDefaultListEach
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
refreshDefault(); refreshDefault();
refreshChannelDefault();
}, _lifetime); }, _lifetime);
} }
@ -74,6 +76,14 @@ void EmojiStatuses::refreshColored() {
requestColored(); requestColored();
} }
void EmojiStatuses::refreshChannelDefault() {
requestChannelDefault();
}
void EmojiStatuses::refreshChannelColored() {
requestChannelColored();
}
void EmojiStatuses::refreshRecentDelayed() { void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) { if (_recentRequestId || _recentRequestScheduled) {
return; return;
@ -91,6 +101,8 @@ const std::vector<DocumentId> &EmojiStatuses::list(Type type) const {
case Type::Recent: return _recent; case Type::Recent: return _recent;
case Type::Default: return _default; case Type::Default: return _default;
case Type::Colored: return _colored; case Type::Colored: return _colored;
case Type::ChannelDefault: return _channelDefault;
case Type::ChannelColored: return _channelColored;
} }
Unexpected("Type in EmojiStatuses::list."); Unexpected("Type in EmojiStatuses::list.");
} }
@ -103,20 +115,24 @@ rpl::producer<> EmojiStatuses::defaultUpdates() const {
return _defaultUpdated.events(); return _defaultUpdated.events();
} }
rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
return _channelDefaultUpdated.events();
}
void EmojiStatuses::registerAutomaticClear( void EmojiStatuses::registerAutomaticClear(
not_null<UserData*> user, not_null<PeerData*> peer,
TimeId until) { TimeId until) {
if (!until) { if (!until) {
_clearing.remove(user); _clearing.remove(peer);
if (_clearing.empty()) { if (_clearing.empty()) {
_clearingTimer.cancel(); _clearingTimer.cancel();
} }
} else if (auto &already = _clearing[user]; already != until) { } else if (auto &already = _clearing[peer]; already != until) {
already = until; already = until;
const auto i = ranges::min_element(_clearing, {}, [](auto &&pair) { const auto i = ranges::min_element(_clearing, {}, [](auto &&pair) {
return pair.second; return pair.second;
}); });
if (i->first == user) { if (i->first == peer) {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
if (now < until) { if (now < until) {
processClearingIn(until - now); processClearingIn(until - now);
@ -266,7 +282,7 @@ void EmojiStatuses::requestDefault() {
} }
auto &api = _owner->session().api(); auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses( _defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_recentHash) MTP_long(_defaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) { )).done([=](const MTPaccount_EmojiStatuses &result) {
_defaultRequestId = 0; _defaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) { result.match([&](const MTPDaccount_emojiStatuses &data) {
@ -299,6 +315,45 @@ void EmojiStatuses::requestColored() {
}).send(); }).send();
} }
void EmojiStatuses::requestChannelDefault() {
if (_channelDefaultRequestId) {
return;
}
auto &api = _owner->session().api();
_channelDefaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_channelDefaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_channelDefaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
updateChannelDefault(data);
}, [&](const MTPDaccount_emojiStatusesNotModified &) {
});
}).fail([=] {
_channelDefaultRequestId = 0;
_channelDefaultHash = 0;
}).send();
}
void EmojiStatuses::requestChannelColored() {
if (_channelColoredRequestId) {
return;
}
auto &api = _owner->session().api();
_channelColoredRequestId = api.request(MTPmessages_GetStickerSet(
MTP_inputStickerSetEmojiChannelDefaultStatuses(),
MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) {
_channelColoredRequestId = 0;
result.match([&](const MTPDmessages_stickerSet &data) {
updateChannelColored(data);
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).fail([=] {
_channelColoredRequestId = 0;
}).send();
}
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v; _recentHash = data.vhash().v;
_recent = ListFromMTP(data); _recent = ListFromMTP(data);
@ -321,27 +376,57 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
_coloredUpdated.fire({}); _coloredUpdated.fire({});
} }
void EmojiStatuses::set(DocumentId id, TimeId until) { void EmojiStatuses::updateChannelDefault(
auto &api = _owner->session().api(); const MTPDaccount_emojiStatuses &data) {
if (_sentRequestId) { _channelDefaultHash = data.vhash().v;
api.request(base::take(_sentRequestId)).cancel(); _channelDefault = ListFromMTP(data);
_channelDefaultUpdated.fire({});
}
void EmojiStatuses::updateChannelColored(
const MTPDmessages_stickerSet &data) {
const auto &list = data.vdocuments().v;
_channelColored.clear();
_channelColored.reserve(list.size());
for (const auto &sticker : data.vdocuments().v) {
_channelColored.push_back(_owner->processDocument(sticker)->id);
} }
_owner->session().user()->setEmojiStatus(id, until); _channelColoredUpdated.fire({});
_sentRequestId = api.request(MTPaccount_UpdateEmojiStatus( }
!id
void EmojiStatuses::set(DocumentId id, TimeId until) {
set(_owner->session().user(), id, until);
}
void EmojiStatuses::set(
not_null<PeerData*> peer,
DocumentId id,
TimeId until) {
auto &api = _owner->session().api();
auto &requestId = _sentRequests[peer];
if (requestId) {
api.request(base::take(requestId)).cancel();
}
peer->setEmojiStatus(id, until);
const auto send = [&](auto &&request) {
requestId = api.request(
std::move(request)
).done([=] {
_sentRequests.remove(peer);
}).fail([=] {
_sentRequests.remove(peer);
}).send();
};
const auto status = !id
? MTP_emojiStatusEmpty() ? MTP_emojiStatusEmpty()
: !until : !until
? MTP_emojiStatus(MTP_long(id)) ? MTP_emojiStatus(MTP_long(id))
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)) : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until));
)).done([=] { if (peer->isSelf()) {
_sentRequestId = 0; send(MTPaccount_UpdateEmojiStatus(status));
}).fail([=] { } else if (const auto channel = peer->asChannel()) {
_sentRequestId = 0; send(MTPchannels_UpdateEmojiStatus(channel->inputChannel, status));
}).send(); }
}
bool EmojiStatuses::setting() const {
return _sentRequestId != 0;;
} }
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) { EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) {

View file

@ -36,22 +36,26 @@ public:
void refreshRecentDelayed(); void refreshRecentDelayed();
void refreshDefault(); void refreshDefault();
void refreshColored(); void refreshColored();
void refreshChannelDefault();
void refreshChannelColored();
enum class Type { enum class Type {
Recent, Recent,
Default, Default,
Colored, Colored,
ChannelDefault,
ChannelColored,
}; };
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const; [[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
[[nodiscard]] rpl::producer<> recentUpdates() const; [[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const; [[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> coloredUpdates() const; [[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
void set(DocumentId id, TimeId until = 0); void set(DocumentId id, TimeId until = 0);
[[nodiscard]] bool setting() const; void set(not_null<PeerData*> peer, DocumentId id, TimeId until = 0);
void registerAutomaticClear(not_null<UserData*> user, TimeId until); void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
using Groups = std::vector<Ui::EmojiGroup>; using Groups = std::vector<Ui::EmojiGroup>;
[[nodiscard]] rpl::producer<Groups> emojiGroupsValue() const; [[nodiscard]] rpl::producer<Groups> emojiGroupsValue() const;
@ -71,10 +75,14 @@ private:
void requestRecent(); void requestRecent();
void requestDefault(); void requestDefault();
void requestColored(); void requestColored();
void requestChannelDefault();
void requestChannelColored();
void updateRecent(const MTPDaccount_emojiStatuses &data); void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data);
void updateColored(const MTPDmessages_stickerSet &data); void updateColored(const MTPDmessages_stickerSet &data);
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
void updateChannelColored(const MTPDmessages_stickerSet &data);
void processClearingIn(TimeId wait); void processClearingIn(TimeId wait);
void processClearing(); void processClearing();
@ -87,9 +95,13 @@ private:
std::vector<DocumentId> _recent; std::vector<DocumentId> _recent;
std::vector<DocumentId> _default; std::vector<DocumentId> _default;
std::vector<DocumentId> _colored; std::vector<DocumentId> _colored;
std::vector<DocumentId> _channelDefault;
std::vector<DocumentId> _channelColored;
rpl::event_stream<> _recentUpdated; rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated; rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _coloredUpdated; rpl::event_stream<> _coloredUpdated;
rpl::event_stream<> _channelDefaultUpdated;
rpl::event_stream<> _channelColoredUpdated;
mtpRequestId _recentRequestId = 0; mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false; bool _recentRequestScheduled = false;
@ -100,9 +112,14 @@ private:
mtpRequestId _coloredRequestId = 0; mtpRequestId _coloredRequestId = 0;
mtpRequestId _sentRequestId = 0; mtpRequestId _channelDefaultRequestId = 0;
uint64 _channelDefaultHash = 0;
base::flat_map<not_null<UserData*>, TimeId> _clearing; mtpRequestId _channelColoredRequestId = 0;
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
base::Timer _clearingTimer; base::Timer _clearingTimer;
GroupsType _emojiGroups; GroupsType _emojiGroups;

View file

@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_story.h" #include "data/data_story.h"
#include "data/data_user.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "core/application.h" #include "core/application.h"
@ -363,10 +364,10 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
return result; return result;
} }
Giveaway ComputeGiveawayData( GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data) { const MTPDmessageMediaGiveaway &data) {
auto result = Giveaway{ auto result = GiveawayStart{
.untilDate = data.vuntil_date().v, .untilDate = data.vuntil_date().v,
.quantity = data.vquantity().v, .quantity = data.vquantity().v,
.months = data.vmonths().v, .months = data.vmonths().v,
@ -383,6 +384,35 @@ Giveaway ComputeGiveawayData(
result.countries.push_back(qs(country)); result.countries.push_back(qs(country));
} }
} }
if (const auto additional = data.vprize_description()) {
result.additionalPrize = qs(*additional);
}
return result;
}
GiveawayResults ComputeGiveawayResultsData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveawayResults &data) {
const auto additional = data.vadditional_peers_count();
auto result = GiveawayResults{
.channel = item->history()->owner().channel(data.vchannel_id()),
.untilDate = data.vuntil_date().v,
.launchId = data.vlaunch_msg_id().v,
.additionalPeersCount = additional.value_or_empty(),
.winnersCount = data.vwinners_count().v,
.unclaimedCount = data.vunclaimed_count().v,
.months = data.vmonths().v,
.refunded = data.is_refunded(),
.all = !data.is_only_new_subscribers(),
};
result.winners.reserve(data.vwinners().v.size());
const auto owner = &item->history()->owner();
for (const auto &id : data.vwinners().v) {
result.winners.push_back(owner->user(UserId(id)));
}
if (const auto additional = data.vprize_description()) {
result.additionalPrize = qs(*additional);
}
return result; return result;
} }
@ -453,7 +483,11 @@ bool Media::storyMention() const {
return false; return false;
} }
const Giveaway *Media::giveaway() const { const GiveawayStart *Media::giveawayStart() const {
return nullptr;
}
const GiveawayResults *Media::giveawayResults() const {
return nullptr; return nullptr;
} }
@ -1506,15 +1540,57 @@ bool MediaWebPage::replyPreviewLoaded() const {
} }
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
auto text = options.ignoreMessageText const auto caption = [&] {
? TextWithEntities() const auto text = options.ignoreMessageText
: options.translated ? TextWithEntities()
? parent()->translatedText() : options.translated
: parent()->originalText(); ? parent()->translatedText()
if (text.empty()) { : parent()->originalText();
text = Ui::Text::Colorized(_page->url); return text.empty() ? Ui::Text::Colorized(_page->url) : text;
}();
const auto pageTypeWithPreview = _page->type == WebPageType::Photo
|| _page->type == WebPageType::Video
|| _page->type == WebPageType::Document;
if (pageTypeWithPreview || !_page->collage.items.empty()) {
if (auto found = FindCachedPreview(options.existing, _page, false)) {
return { .text = caption, .images = { std::move(found) } };
}
auto context = std::any();
auto images = std::vector<ItemPreviewImage>();
auto prepared = ItemPreviewImage();
const auto radius = ImageRoundRadius::Small;
if (const auto photo = MediaWebPage::photo()) {
const auto media = photo->createMediaView();
prepared = PreparePhotoPreview(parent(), media, radius, false);
if (prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
} else {
const auto document = MediaWebPage::document();
if (document
&& document->hasThumbnail()
&& (document->isGifv() || document->isVideoFile())) {
const auto media = document->createMediaView();
prepared = PrepareFilePreview(parent(), media, radius, false);
if (prepared || !prepared.cacheKey) {
images.push_back(std::move(prepared));
if (!prepared.cacheKey) {
context = media;
}
}
}
}
return {
.text = caption,
.images = std::move(images),
.loadingContext = std::move(context),
};
} else {
return { .text = caption };
} }
return { .text = text };
} }
TextWithEntities MediaWebPage::notificationText() const { TextWithEntities MediaWebPage::notificationText() const {
@ -2196,51 +2272,103 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
} }
} }
MediaGiveaway::MediaGiveaway( MediaGiveawayStart::MediaGiveawayStart(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
const Giveaway &data) const GiveawayStart &data)
: Media(parent) : Media(parent)
, _giveaway(data) { , _data(data) {
parent->history()->session().giftBoxStickersPacks().load(); parent->history()->session().giftBoxStickersPacks().load();
} }
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) { std::unique_ptr<Media> MediaGiveawayStart::clone(
return std::make_unique<MediaGiveaway>(parent, _giveaway); not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveawayStart>(parent, _data);
} }
const Giveaway *MediaGiveaway::giveaway() const { const GiveawayStart *MediaGiveawayStart::giveawayStart() const {
return &_giveaway; return &_data;
} }
TextWithEntities MediaGiveaway::notificationText() const { TextWithEntities MediaGiveawayStart::notificationText() const {
return { return {
.text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity), .text = tr::lng_prizes_title(tr::now, lt_count, _data.quantity),
}; };
} }
QString MediaGiveaway::pinnedTextSubstring() const { QString MediaGiveawayStart::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB") return QString::fromUtf8("\xC2\xAB")
+ notificationText().text + notificationText().text
+ QString::fromUtf8("\xC2\xBB"); + QString::fromUtf8("\xC2\xBB");
} }
TextForMimeData MediaGiveaway::clipboardText() const { TextForMimeData MediaGiveawayStart::clipboardText() const {
return TextForMimeData(); return TextForMimeData();
} }
bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaGiveawayStart::updateInlineResultMedia(const MTPMessageMedia &media) {
return true; return true;
} }
bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) { bool MediaGiveawayStart::updateSentMedia(const MTPMessageMedia &media) {
return true; return true;
} }
std::unique_ptr<HistoryView::Media> MediaGiveaway::createView( std::unique_ptr<HistoryView::Media> MediaGiveawayStart::createView(
not_null<HistoryView::Element*> message, not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent, not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) { HistoryView::Element *replacing) {
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway); return std::make_unique<HistoryView::MediaInBubble>(
message,
HistoryView::GenerateGiveawayStart(message, &_data));
}
MediaGiveawayResults::MediaGiveawayResults(
not_null<HistoryItem*> parent,
const GiveawayResults &data)
: Media(parent)
, _data(data) {
}
std::unique_ptr<Media> MediaGiveawayResults::clone(
not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveawayResults>(parent, _data);
}
const GiveawayResults *MediaGiveawayResults::giveawayResults() const {
return &_data;
}
TextWithEntities MediaGiveawayResults::notificationText() const {
return {
.text = tr::lng_prizes_results_title(tr::now),
};
}
QString MediaGiveawayResults::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB")
+ notificationText().text
+ QString::fromUtf8("\xC2\xBB");
}
TextForMimeData MediaGiveawayResults::clipboardText() const {
return TextForMimeData();
}
bool MediaGiveawayResults::updateInlineResultMedia(const MTPMessageMedia &media) {
return true;
}
bool MediaGiveawayResults::updateSentMedia(const MTPMessageMedia &media) {
return true;
}
std::unique_ptr<HistoryView::Media> MediaGiveawayResults::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::MediaInBubble>(
message,
HistoryView::GenerateGiveawayResults(message, &_data));
} }
} // namespace Data } // namespace Data

View file

@ -90,15 +90,30 @@ struct Invoice {
bool isTest = false; bool isTest = false;
}; };
struct Giveaway { struct GiveawayStart {
std::vector<not_null<ChannelData*>> channels; std::vector<not_null<ChannelData*>> channels;
std::vector<QString> countries; std::vector<QString> countries;
QString additionalPrize;
TimeId untilDate = 0; TimeId untilDate = 0;
int quantity = 0; int quantity = 0;
int months = 0; int months = 0;
bool all = false; bool all = false;
}; };
struct GiveawayResults {
not_null<ChannelData*> channel;
std::vector<not_null<PeerData*>> winners;
QString additionalPrize;
TimeId untilDate = 0;
MsgId launchId = 0;
int additionalPeersCount = 0;
int winnersCount = 0;
int unclaimedCount = 0;
int months = 0;
bool refunded = false;
bool all = false;
};
struct GiftCode { struct GiftCode {
QString slug; QString slug;
ChannelData *channel = nullptr; ChannelData *channel = nullptr;
@ -135,7 +150,8 @@ public:
virtual FullStoryId storyId() const; virtual FullStoryId storyId() const;
virtual bool storyExpired(bool revalidate = false); virtual bool storyExpired(bool revalidate = false);
virtual bool storyMention() const; virtual bool storyMention() const;
virtual const Giveaway *giveaway() const; virtual const GiveawayStart *giveawayStart() const;
virtual const GiveawayResults *giveawayResults() const;
virtual bool uploading() const; virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
@ -634,15 +650,15 @@ private:
}; };
class MediaGiveaway final : public Media { class MediaGiveawayStart final : public Media {
public: public:
MediaGiveaway( MediaGiveawayStart(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
const Giveaway &data); const GiveawayStart &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
const Giveaway *giveaway() const override; const GiveawayStart *giveawayStart() const override;
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
@ -656,7 +672,33 @@ public:
HistoryView::Element *replacing = nullptr) override; HistoryView::Element *replacing = nullptr) override;
private: private:
Giveaway _giveaway; GiveawayStart _data;
};
class MediaGiveawayResults final : public Media {
public:
MediaGiveawayResults(
not_null<HistoryItem*> parent,
const GiveawayResults &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
const GiveawayResults *giveawayResults() const override;
TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override;
TextForMimeData clipboardText() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override;
std::unique_ptr<HistoryView::Media> createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing = nullptr) override;
private:
GiveawayResults _data;
}; };
@ -670,8 +712,12 @@ private:
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Giveaway ComputeGiveawayData( [[nodiscard]] GiveawayStart ComputeGiveawayStartData(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data); const MTPDmessageMediaGiveaway &data);
[[nodiscard]] GiveawayResults ComputeGiveawayResultsData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveawayResults &data);
} // namespace Data } // namespace Data

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_participant_status.h" #include "data/data_chat_participant_status.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_emoji_statuses.h"
#include "data/data_message_reaction_id.h" #include "data/data_message_reaction_id.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_folder.h" #include "data/data_folder.h"
@ -926,6 +927,24 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
_backgroundEmojiId = id; _backgroundEmojiId = id;
return true; return true;
} }
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = Data::ParseEmojiStatus(status);
setEmojiStatus(parsed.id, parsed.until);
}
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
if (_emojiStatusId != emojiStatusId) {
_emojiStatusId = emojiStatusId;
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
}
owner().emojiStatuses().registerAutomaticClear(this, until);
}
DocumentId PeerData::emojiStatusId() const {
return _emojiStatusId;
}
bool PeerData::isSelf() const { bool PeerData::isSelf() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return (user->flags() & UserDataFlag::Self); return (user->flags() & UserDataFlag::Self);

View file

@ -178,6 +178,10 @@ public:
[[nodiscard]] DocumentId backgroundEmojiId() const; [[nodiscard]] DocumentId backgroundEmojiId() const;
bool changeBackgroundEmojiId(DocumentId id); bool changeBackgroundEmojiId(DocumentId id);
void setEmojiStatus(const MTPEmojiStatus &status);
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
[[nodiscard]] DocumentId emojiStatusId() const;
[[nodiscard]] bool isUser() const { [[nodiscard]] bool isUser() const {
return peerIsUser(id); return peerIsUser(id);
} }
@ -466,6 +470,7 @@ private:
base::flat_set<QString> _nameWords; // for filtering base::flat_set<QString> _nameWords; // for filtering
base::flat_set<QChar> _nameFirstLetters; base::flat_set<QChar> _nameFirstLetters;
DocumentId _emojiStatusId = 0;
uint64 _backgroundEmojiId = 0; uint64 _backgroundEmojiId = 0;
crl::time _lastFullUpdate = 0; crl::time _lastFullUpdate = 0;

View file

@ -64,7 +64,7 @@ private:
// In case this is a problem the ~Gif code should be rewritten. // In case this is a problem the ~Gif code should be rewritten.
const not_null<PhotoData*> _owner; const not_null<PhotoData*> _owner;
mutable std::unique_ptr<Image> _inlineThumbnail; mutable std::unique_ptr<Image> _inlineThumbnail;
std::array<PhotoImage, kPhotoSizeCount> _images; std::array<PhotoImage, kPhotoSizeCount> _images;
QByteArray _videoBytesSmall; QByteArray _videoBytesSmall;
QByteArray _videoBytesLarge; QByteArray _videoBytesLarge;

View file

@ -856,6 +856,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel); const auto wasCallNotEmpty = Data::ChannelHasActiveCall(channel);
channel->updateLevelHint(data.vlevel().value_or_empty());
if (const auto count = data.vparticipants_count()) { if (const auto count = data.vparticipants_count()) {
channel->setMembersCount(count->v); channel->setMembersCount(count->v);
} }
@ -865,6 +866,11 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
channel->setDefaultRestrictions(ChatRestrictions()); channel->setDefaultRestrictions(ChatRestrictions());
} }
if (const auto &status = data.vemoji_status()) {
channel->setEmojiStatus(*status);
} else {
channel->setEmojiStatus(0);
}
if (minimal) { if (minimal) {
if (channel->input.type() == mtpc_inputPeerEmpty if (channel->input.type() == mtpc_inputPeerEmpty
|| channel->inputChannel.type() == mtpc_inputChannelEmpty) { || channel->inputChannel.type() == mtpc_inputChannelEmpty) {
@ -3451,7 +3457,7 @@ void Session::webpageApplyFields(
data.vid().v, data.vid().v,
}; };
if (const auto embed = data.vstory()) { if (const auto embed = data.vstory()) {
story = stories().applyFromWebpage( story = stories().applySingle(
peerFromMTP(data.vpeer()), peerFromMTP(data.vpeer()),
*embed); *embed);
} else if (const auto maybe = stories().lookup(storyId)) { } else if (const auto maybe = stories().lookup(storyId)) {

View file

@ -148,11 +148,7 @@ struct RecentPostId final {
}; };
struct PublicForwardsSlice final { struct PublicForwardsSlice final {
struct OffsetToken final { using OffsetToken = QString;
int rate = 0;
FullMsgId fullId;
QString storyOffset;
};
QVector<RecentPostId> list; QVector<RecentPostId> list;
int total = 0; int total = 0;
bool allLoaded = false; bool allLoaded = false;

View file

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_statistics_chart.h" #include "data/data_statistics_chart.h"
#include "statistics/statistics_format_values.h" #include "statistics/statistics_format_values.h"
#include "styles/style_basic.h"
#include "styles/style_statistics.h"
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
#include <QtCore/QLocale> #include <QtCore/QLocale>
@ -46,6 +48,11 @@ void StatisticalChart::measure() {
const auto dateCount = int((end - start) / timeStep) + 10; const auto dateCount = int((end - start) / timeStep) + 10;
daysLookup.reserve(dateCount); daysLookup.reserve(dateCount);
constexpr auto kOneDay = 3600 * 24 * 1000; constexpr auto kOneDay = 3600 * 24 * 1000;
// View data.
auto maxWidth = 0;
const auto &defaultFont = st::statisticsDetailsBottomCaptionStyle.font;
for (auto i = 0; i < dateCount; i++) { for (auto i = 0; i < dateCount; i++) {
const auto r = (start + (i * timeStep)) / 1000; const auto r = (start + (i * timeStep)) / 1000;
if (timeStep == 1) { if (timeStep == 1) {
@ -59,7 +66,9 @@ void StatisticalChart::measure() {
} else { } else {
daysLookup.push_back(Statistic::LangDayMonth(r)); daysLookup.push_back(Statistic::LangDayMonth(r));
} }
maxWidth = std::max(maxWidth, defaultFont->width(daysLookup.back()));
} }
dayStringMaxWidth = maxWidth;
oneDayPercentage = timeStep / float64(end - start); oneDayPercentage = timeStep / float64(end - start);
} }

View file

@ -66,6 +66,9 @@ struct StatisticalChart {
bool hasPercentages = false; bool hasPercentages = false;
bool weekFormat = false; bool weekFormat = false;
// View data.
int dayStringMaxWidth = 0;
}; };
struct StatisticalGraph final { struct StatisticalGraph final {

View file

@ -245,14 +245,13 @@ void Stories::apply(not_null<PeerData*> peer, const MTPPeerStories *data) {
} }
} }
Story *Stories::applyFromWebpage(PeerId peerId, const MTPstoryItem &story) { Story *Stories::applySingle(PeerId peerId, const MTPstoryItem &story) {
// AyuGram disableStories // AyuGram disableStories
const auto settings = &AyuSettings::getInstance(); const auto settings = &AyuSettings::getInstance();
if (settings->disableStories) if (settings->disableStories)
{ {
return nullptr; return nullptr;
} }
const auto idDates = parseAndApply( const auto idDates = parseAndApply(
_owner->peer(peerId), _owner->peer(peerId),
story, story,
@ -1424,6 +1423,96 @@ void Stories::loadViewsSlice(
} }
} }
void Stories::loadReactionsSlice(
not_null<PeerData*> peer,
StoryId id,
QString offset,
Fn<void(StoryViews)> done) {
Expects(peer->isChannel());
if (_reactionsStoryPeer == peer
&& _reactionsStoryId == id
&& _reactionsOffset == offset) {
if (_reactionsRequestId) {
_reactionsDone = std::move(done);
}
return;
}
_reactionsStoryPeer = peer;
_reactionsStoryId = id;
_reactionsOffset = offset;
_reactionsDone = std::move(done);
using Flag = MTPstories_GetStoryReactionsList::Flag;
const auto api = &_owner->session().api();
_owner->session().api().request(_reactionsRequestId).cancel();
_reactionsRequestId = api->request(MTPstories_GetStoryReactionsList(
MTP_flags(offset.isEmpty() ? Flag() : Flag::f_offset),
_reactionsStoryPeer->input,
MTP_int(_reactionsStoryId),
MTPReaction(),
MTP_string(_reactionsOffset),
MTP_int(kViewsPerPage)
)).done([=](const MTPstories_StoryReactionsList &result) {
_reactionsRequestId = 0;
const auto &data = result.data();
auto slice = StoryViews{
.nextOffset = data.vnext_offset().value_or_empty(),
.reactions = data.vcount().v,
.total = data.vcount().v,
};
_owner->processUsers(data.vusers());
_owner->processChats(data.vchats());
slice.list.reserve(data.vreactions().v.size());
for (const auto &reaction : data.vreactions().v) {
reaction.match([&](const MTPDstoryReaction &data) {
slice.list.push_back({
.peer = _owner->peer(peerFromMTP(data.vpeer_id())),
.reaction = ReactionFromMTP(data.vreaction()),
.date = data.vdate().v,
});
}, [&](const MTPDstoryReactionPublicRepost &data) {
const auto story = applySingle(
peerFromMTP(data.vpeer_id()),
data.vstory());
if (story) {
slice.list.push_back({
.peer = story->peer(),
.repostId = story->id(),
});
}
}, [&](const MTPDstoryReactionPublicForward &data) {
const auto item = _owner->addNewMessage(
data.vmessage(),
{},
NewMessageType::Existing);
if (item) {
slice.list.push_back({
.peer = item->history()->peer,
.forwardId = item->id,
});
}
});
}
const auto fullId = FullStoryId{
.peer = _reactionsStoryPeer->id,
.story = _reactionsStoryId,
};
if (const auto story = lookup(fullId)) {
(*story)->applyChannelReactionsSlice(_reactionsOffset, slice);
}
if (const auto done = base::take(_reactionsDone)) {
done(std::move(slice));
}
}).fail([=] {
_reactionsRequestId = 0;
if (const auto done = base::take(_reactionsDone)) {
done({});
}
}).send();
}
void Stories::sendViewsSliceRequest() { void Stories::sendViewsSliceRequest() {
Expects(_viewsStoryPeer != nullptr); Expects(_viewsStoryPeer != nullptr);
Expects(_viewsStoryPeer->isSelf()); Expects(_viewsStoryPeer->isSelf());
@ -1445,17 +1534,43 @@ void Stories::sendViewsSliceRequest() {
auto slice = StoryViews{ auto slice = StoryViews{
.nextOffset = data.vnext_offset().value_or_empty(), .nextOffset = data.vnext_offset().value_or_empty(),
.reactions = data.vreactions_count().v, .reactions = data.vreactions_count().v,
.forwards = data.vforwards_count().v,
.views = data.vviews_count().v,
.total = data.vcount().v, .total = data.vcount().v,
}; };
_owner->processUsers(data.vusers()); _owner->processUsers(data.vusers());
_owner->processChats(data.vchats());
slice.list.reserve(data.vviews().v.size()); slice.list.reserve(data.vviews().v.size());
for (const auto &view : data.vviews().v) { for (const auto &view : data.vviews().v) {
slice.list.push_back({ view.match([&](const MTPDstoryView &data) {
.peer = _owner->peer(peerFromUser(view.data().vuser_id())), slice.list.push_back({
.reaction = (view.data().vreaction() .peer = _owner->peer(peerFromUser(data.vuser_id())),
? ReactionFromMTP(*view.data().vreaction()) .reaction = (data.vreaction()
: Data::ReactionId()), ? ReactionFromMTP(*data.vreaction())
.date = view.data().vdate().v, : Data::ReactionId()),
.date = data.vdate().v,
});
}, [&](const MTPDstoryViewPublicRepost &data) {
const auto story = applySingle(
peerFromMTP(data.vpeer_id()),
data.vstory());
if (story) {
slice.list.push_back({
.peer = story->peer(),
.repostId = story->id(),
});
}
}, [&](const MTPDstoryViewPublicForward &data) {
const auto item = _owner->addNewMessage(
data.vmessage(),
{},
NewMessageType::Existing);
if (item) {
slice.list.push_back({
.peer = item->history()->peer,
.forwardId = item->id,
});
}
}); });
} }
const auto fullId = FullStoryId{ const auto fullId = FullStoryId{

View file

@ -149,7 +149,7 @@ public:
void apply(const MTPDupdateReadStories &data); void apply(const MTPDupdateReadStories &data);
void apply(const MTPStoriesStealthMode &stealthMode); void apply(const MTPStoriesStealthMode &stealthMode);
void apply(not_null<PeerData*> peer, const MTPPeerStories *data); void apply(not_null<PeerData*> peer, const MTPPeerStories *data);
Story *applyFromWebpage(PeerId peerId, const MTPstoryItem &story); Story *applySingle(PeerId peerId, const MTPstoryItem &story);
void loadAround(FullStoryId id, StoriesContext context); void loadAround(FullStoryId id, StoriesContext context);
const StoriesSource *source(PeerId id) const; const StoriesSource *source(PeerId id) const;
@ -182,6 +182,11 @@ public:
StoryId id, StoryId id,
QString offset, QString offset,
Fn<void(StoryViews)> done); Fn<void(StoryViews)> done);
void loadReactionsSlice(
not_null<PeerData*> peer,
StoryId id,
QString offset,
Fn<void(StoryViews)> done);
[[nodiscard]] bool hasArchive(not_null<PeerData*> peer) const; [[nodiscard]] bool hasArchive(not_null<PeerData*> peer) const;
@ -379,6 +384,12 @@ private:
Fn<void(StoryViews)> _viewsDone; Fn<void(StoryViews)> _viewsDone;
mtpRequestId _viewsRequestId = 0; mtpRequestId _viewsRequestId = 0;
PeerData *_reactionsStoryPeer = nullptr;
StoryId _reactionsStoryId = 0;
QString _reactionsOffset;
Fn<void(StoryViews)> _reactionsDone;
mtpRequestId _reactionsRequestId = 0;
base::flat_set<FullStoryId> _preloaded; base::flat_set<FullStoryId> _preloaded;
std::vector<FullStoryId> _toPreloadSources[kStorySourcesListCount]; std::vector<FullStoryId> _toPreloadSources[kStorySourcesListCount];
std::vector<FullStoryId> _toPreloadViewer; std::vector<FullStoryId> _toPreloadViewer;

View file

@ -81,8 +81,11 @@ using UpdateFlag = StoryUpdate::Flag;
}, [](const MTPDgeoPointEmpty &) { }, [](const MTPDgeoPointEmpty &) {
}); });
}, [&](const MTPDmediaAreaSuggestedReaction &data) { }, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
LOG(("API Error: Unexpected inputMediaAreaVenue in API data.")); LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
}); });
return result; return result;
} }
@ -99,8 +102,32 @@ using UpdateFlag = StoryUpdate::Flag;
.flipped = data.is_flipped(), .flipped = data.is_flipped(),
.dark = data.is_dark(), .dark = data.is_dark(),
}); });
}, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
LOG(("API Error: Unexpected inputMediaAreaVenue in API data.")); LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
});
return result;
}
[[nodiscard]] auto ParseChannelPost(const MTPMediaArea &area)
-> std::optional<ChannelPost> {
auto result = std::optional<ChannelPost>();
area.match([&](const MTPDmediaAreaVenue &data) {
}, [&](const MTPDmediaAreaGeoPoint &data) {
}, [&](const MTPDmediaAreaSuggestedReaction &data) {
}, [&](const MTPDmediaAreaChannelPost &data) {
result.emplace(ChannelPost{
.area = ParseArea(data.vcoordinates()),
.itemId = FullMsgId(
peerFromChannel(data.vchannel_id()),
data.vmsg_id().v),
});
}, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) {
LOG(("API Error: Unexpected inputMediaAreaVenue from API."));
}); });
return result; return result;
} }
@ -130,6 +157,13 @@ using UpdateFlag = StoryUpdate::Flag;
return {}; return {};
} }
[[nodiscard]] bool RepostModified(const MTPDstoryItem &data) {
if (const auto forwarded = data.vfwd_from()) {
return forwarded->data().is_modified();
}
return false;
}
} // namespace } // namespace
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
@ -151,7 +185,7 @@ private:
Fn<void(QByteArray)> _done; Fn<void(QByteArray)> _done;
base::flat_set<int> _requestedOffsets; base::flat_set<int> _requestedOffsets;
int64 _full = 0; int64 _full = 0;
int _nextRequestOffset = 0; int _nextRequestOffset = 0;
bool _finished = false; bool _finished = false;
bool _failed = false; bool _failed = false;
@ -245,7 +279,8 @@ Story::Story(
, _repostSourceName(RepostSourceName(data)) , _repostSourceName(RepostSourceName(data))
, _repostSourceId(RepostSourceId(data)) , _repostSourceId(RepostSourceId(data))
, _date(data.vdate().v) , _date(data.vdate().v)
, _expires(data.vexpire_date().v) { , _expires(data.vexpire_date().v)
, _repostModified(RepostModified(data)) {
applyFields(std::move(media), data, now, true); applyFields(std::move(media), data, now, true);
} }
@ -478,10 +513,22 @@ const StoryViews &Story::viewsList() const {
return _views; return _views;
} }
int Story::views() const { const StoryViews &Story::channelReactionsList() const {
return _channelReactions;
}
int Story::interactions() const {
return _views.total; return _views.total;
} }
int Story::views() const {
return _views.views;
}
int Story::forwards() const {
return _views.forwards;
}
int Story::reactions() const { int Story::reactions() const {
return _views.reactions; return _views.reactions;
} }
@ -490,12 +537,19 @@ void Story::applyViewsSlice(
const QString &offset, const QString &offset,
const StoryViews &slice) { const StoryViews &slice) {
const auto changed = (_views.reactions != slice.reactions) const auto changed = (_views.reactions != slice.reactions)
|| (_views.views != slice.views)
|| (_views.forwards != slice.forwards)
|| (_views.total != slice.total); || (_views.total != slice.total);
_views.reactions = slice.reactions; _views.reactions = slice.reactions;
_views.forwards = slice.forwards;
_views.views = slice.views;
_views.total = slice.total; _views.total = slice.total;
_views.known = true; _views.known = true;
if (offset.isEmpty()) { if (offset.isEmpty()) {
_views = slice; _views = slice;
if (!_channelReactions.total) {
_channelReactions.total = _views.reactions + _views.forwards;
}
} else if (_views.nextOffset == offset) { } else if (_views.nextOffset == offset) {
_views.list.insert( _views.list.insert(
end(_views.list), end(_views.list),
@ -509,6 +563,15 @@ void Story::applyViewsSlice(
_views.list, _views.list,
Data::ReactionId(), Data::ReactionId(),
&StoryView::reaction); &StoryView::reaction);
_views.forwards = _views.total
- ranges::count(
_views.list,
0,
[](const StoryView &view) {
return view.repostId
? view.repostId
: view.forwardId.bare;
});
} }
} }
const auto known = int(_views.list.size()); const auto known = int(_views.list.size());
@ -535,6 +598,33 @@ void Story::applyViewsSlice(
} }
} }
void Story::applyChannelReactionsSlice(
const QString &offset,
const StoryViews &slice) {
const auto changed = (_channelReactions.reactions != slice.reactions)
|| (_channelReactions.total != slice.total);
_channelReactions.reactions = slice.reactions;
_channelReactions.total = slice.total;
_channelReactions.known = true;
if (offset.isEmpty()) {
_channelReactions = slice;
} else if (_channelReactions.nextOffset == offset) {
_channelReactions.list.insert(
end(_channelReactions.list),
begin(slice.list),
end(slice.list));
_channelReactions.nextOffset = slice.nextOffset;
if (_channelReactions.nextOffset.isEmpty()) {
_channelReactions.total = int(_channelReactions.list.size());
}
}
if (changed) {
_peer->session().changes().storyUpdated(
this,
UpdateFlag::ViewsChanged);
}
}
const std::vector<StoryLocation> &Story::locations() const { const std::vector<StoryLocation> &Story::locations() const {
return _locations; return _locations;
} }
@ -543,6 +633,10 @@ const std::vector<SuggestedReaction> &Story::suggestedReactions() const {
return _suggestedReactions; return _suggestedReactions;
} }
const std::vector<ChannelPost> &Story::channelPosts() const {
return _channelPosts;
}
void Story::applyChanges( void Story::applyChanges(
StoryMedia media, StoryMedia media,
const MTPDstoryItem &data, const MTPDstoryItem &data,
@ -555,6 +649,7 @@ Story::ViewsCounts Story::parseViewsCounts(
const Data::ReactionId &mine) { const Data::ReactionId &mine) {
auto result = ViewsCounts{ auto result = ViewsCounts{
.views = data.vviews_count().v, .views = data.vviews_count().v,
.forwards = data.vforwards_count().value_or_empty(),
.reactions = data.vreactions_count().value_or_empty(), .reactions = data.vreactions_count().value_or_empty(),
}; };
if (const auto list = data.vrecent_viewers()) { if (const auto list = data.vrecent_viewers()) {
@ -633,6 +728,7 @@ void Story::applyFields(
viewsKnown = true; viewsKnown = true;
} else { } else {
counts.views = _views.total; counts.views = _views.total;
counts.forwards = _views.forwards;
counts.reactions = _views.reactions; counts.reactions = _views.reactions;
counts.viewers = _recentViewers; counts.viewers = _recentViewers;
for (const auto &suggested : _suggestedReactions) { for (const auto &suggested : _suggestedReactions) {
@ -643,9 +739,8 @@ void Story::applyFields(
} }
auto locations = std::vector<StoryLocation>(); auto locations = std::vector<StoryLocation>();
auto suggestedReactions = std::vector<SuggestedReaction>(); auto suggestedReactions = std::vector<SuggestedReaction>();
auto channelPosts = std::vector<ChannelPost>();
if (const auto areas = data.vmedia_areas()) { if (const auto areas = data.vmedia_areas()) {
locations.reserve(areas->v.size());
suggestedReactions.reserve(areas->v.size());
for (const auto &area : areas->v) { for (const auto &area : areas->v) {
if (const auto location = ParseLocation(area)) { if (const auto location = ParseLocation(area)) {
locations.push_back(*location); locations.push_back(*location);
@ -656,6 +751,8 @@ void Story::applyFields(
reaction->count = i->second; reaction->count = i->second;
} }
suggestedReactions.push_back(*reaction); suggestedReactions.push_back(*reaction);
} else if (auto post = ParseChannelPost(area)) {
channelPosts.push_back(*post);
} }
} }
} }
@ -667,6 +764,7 @@ void Story::applyFields(
const auto locationsChanged = (_locations != locations); const auto locationsChanged = (_locations != locations);
const auto suggestedReactionsChanged const auto suggestedReactionsChanged
= (_suggestedReactions != suggestedReactions); = (_suggestedReactions != suggestedReactions);
const auto channelPostsChanged = (_channelPosts != channelPosts);
const auto reactionChanged = (_sentReactionId != reaction); const auto reactionChanged = (_sentReactionId != reaction);
_out = out; _out = out;
@ -689,6 +787,9 @@ void Story::applyFields(
if (suggestedReactionsChanged) { if (suggestedReactionsChanged) {
_suggestedReactions = std::move(suggestedReactions); _suggestedReactions = std::move(suggestedReactions);
} }
if (channelPostsChanged) {
_channelPosts = std::move(channelPosts);
}
if (reactionChanged) { if (reactionChanged) {
_sentReactionId = reaction; _sentReactionId = reaction;
} }
@ -697,7 +798,8 @@ void Story::applyFields(
const auto changed = editedChanged const auto changed = editedChanged
|| captionChanged || captionChanged
|| mediaChanged || mediaChanged
|| locationsChanged; || locationsChanged
|| channelPostsChanged;
const auto reactionsChanged = reactionChanged const auto reactionsChanged = reactionChanged
|| suggestedReactionsChanged; || suggestedReactionsChanged;
if (!initial && (changed || reactionsChanged)) { if (!initial && (changed || reactionsChanged)) {
@ -717,17 +819,27 @@ void Story::applyFields(
} }
void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) { void Story::updateViewsCounts(ViewsCounts &&counts, bool known, bool initial) {
const auto viewsChanged = (_views.total != counts.views) const auto total = _views.total
? _views.total
: (counts.views + counts.forwards);
const auto viewsChanged = (_views.total != total)
|| (_views.forwards != counts.forwards)
|| (_views.reactions != counts.reactions) || (_views.reactions != counts.reactions)
|| (_recentViewers != counts.viewers); || (_recentViewers != counts.viewers);
if (_views.reactions != counts.reactions if (_views.reactions != counts.reactions
|| _views.total != counts.views || _views.forwards != counts.forwards
|| _views.total != total
|| _views.known != known) { || _views.known != known) {
_views = StoryViews{ _views = StoryViews{
.reactions = counts.reactions, .reactions = counts.reactions,
.total = counts.views, .forwards = counts.forwards,
.views = counts.views,
.total = total,
.known = known, .known = known,
}; };
if (!_channelReactions.total) {
_channelReactions.total = _views.reactions + _views.forwards;
}
} }
if (viewsChanged) { if (viewsChanged) {
_recentViewers = std::move(counts.viewers); _recentViewers = std::move(counts.viewers);
@ -762,6 +874,10 @@ bool Story::repost() const {
return _repostSourcePeer || !_repostSourceName.isEmpty(); return _repostSourcePeer || !_repostSourceName.isEmpty();
} }
bool Story::repostModified() const {
return _repostModified;
}
PeerData *Story::repostSourcePeer() const { PeerData *Story::repostSourcePeer() const {
return _repostSourcePeer; return _repostSourcePeer;
} }

View file

@ -61,6 +61,8 @@ struct StoryMedia {
struct StoryView { struct StoryView {
not_null<PeerData*> peer; not_null<PeerData*> peer;
Data::ReactionId reaction; Data::ReactionId reaction;
StoryId repostId = 0;
MsgId forwardId = 0;
TimeId date = 0; TimeId date = 0;
friend inline bool operator==(StoryView, StoryView) = default; friend inline bool operator==(StoryView, StoryView) = default;
@ -70,6 +72,8 @@ struct StoryViews {
std::vector<StoryView> list; std::vector<StoryView> list;
QString nextOffset; QString nextOffset;
int reactions = 0; int reactions = 0;
int forwards = 0;
int views = 0;
int total = 0; int total = 0;
bool known = false; bool known = false;
}; };
@ -109,6 +113,15 @@ struct SuggestedReaction {
const SuggestedReaction &) = default; const SuggestedReaction &) = default;
}; };
struct ChannelPost {
StoryArea area;
FullMsgId itemId;
friend inline bool operator==(
const ChannelPost &,
const ChannelPost &) = default;
};
class Story final { class Story final {
public: public:
Story( Story(
@ -166,13 +179,21 @@ public:
[[nodiscard]] auto recentViewers() const [[nodiscard]] auto recentViewers() const
-> const std::vector<not_null<PeerData*>> &; -> const std::vector<not_null<PeerData*>> &;
[[nodiscard]] const StoryViews &viewsList() const; [[nodiscard]] const StoryViews &viewsList() const;
[[nodiscard]] const StoryViews &channelReactionsList() const;
[[nodiscard]] int interactions() const;
[[nodiscard]] int views() const; [[nodiscard]] int views() const;
[[nodiscard]] int forwards() const;
[[nodiscard]] int reactions() const; [[nodiscard]] int reactions() const;
void applyViewsSlice(const QString &offset, const StoryViews &slice); void applyViewsSlice(const QString &offset, const StoryViews &slice);
void applyChannelReactionsSlice(
const QString &offset,
const StoryViews &slice);
[[nodiscard]] const std::vector<StoryLocation> &locations() const; [[nodiscard]] const std::vector<StoryLocation> &locations() const;
[[nodiscard]] auto suggestedReactions() const [[nodiscard]] auto suggestedReactions() const
-> const std::vector<SuggestedReaction> &; -> const std::vector<SuggestedReaction> &;
[[nodiscard]] auto channelPosts() const
-> const std::vector<ChannelPost> &;
void applyChanges( void applyChanges(
StoryMedia media, StoryMedia media,
@ -182,6 +203,7 @@ public:
[[nodiscard]] TimeId lastUpdateTime() const; [[nodiscard]] TimeId lastUpdateTime() const;
[[nodiscard]] bool repost() const; [[nodiscard]] bool repost() const;
[[nodiscard]] bool repostModified() const;
[[nodiscard]] PeerData *repostSourcePeer() const; [[nodiscard]] PeerData *repostSourcePeer() const;
[[nodiscard]] QString repostSourceName() const; [[nodiscard]] QString repostSourceName() const;
[[nodiscard]] StoryId repostSourceId() const; [[nodiscard]] StoryId repostSourceId() const;
@ -189,6 +211,7 @@ public:
private: private:
struct ViewsCounts { struct ViewsCounts {
int views = 0; int views = 0;
int forwards = 0;
int reactions = 0; int reactions = 0;
base::flat_map<Data::ReactionId, int> reactionsCounts; base::flat_map<Data::ReactionId, int> reactionsCounts;
std::vector<not_null<PeerData*>> viewers; std::vector<not_null<PeerData*>> viewers;
@ -217,7 +240,9 @@ private:
std::vector<not_null<PeerData*>> _recentViewers; std::vector<not_null<PeerData*>> _recentViewers;
std::vector<StoryLocation> _locations; std::vector<StoryLocation> _locations;
std::vector<SuggestedReaction> _suggestedReactions; std::vector<SuggestedReaction> _suggestedReactions;
std::vector<ChannelPost> _channelPosts;
StoryViews _views; StoryViews _views;
StoryViews _channelReactions;
const TimeId _date = 0; const TimeId _date = 0;
const TimeId _expires = 0; const TimeId _expires = 0;
TimeId _lastUpdateTime = 0; TimeId _lastUpdateTime = 0;
@ -227,6 +252,7 @@ private:
bool _privacyCloseFriends : 1 = false; bool _privacyCloseFriends : 1 = false;
bool _privacyContacts : 1 = false; bool _privacyContacts : 1 = false;
bool _privacySelectedContacts : 1 = false; bool _privacySelectedContacts : 1 = false;
const bool _repostModified : 1 = false;
bool _noForwards : 1 = false; bool _noForwards : 1 = false;
bool _edited : 1 = false; bool _edited : 1 = false;

View file

@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_bot_command.h" #include "data/data_peer_bot_command.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_emoji_statuses.h"
#include "data/data_wall_paper.h" #include "data/data_wall_paper.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
#include "history/history.h" #include "history/history.h"
@ -78,23 +77,6 @@ void UserData::setPhoto(const MTPUserProfilePhoto &photo) {
}); });
} }
void UserData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = Data::ParseEmojiStatus(status);
setEmojiStatus(parsed.id, parsed.until);
}
void UserData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
if (_emojiStatusId != emojiStatusId) {
_emojiStatusId = emojiStatusId;
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
}
owner().emojiStatuses().registerAutomaticClear(this, until);
}
DocumentId UserData::emojiStatusId() const {
return _emojiStatusId;
}
auto UserData::unavailableReasons() const auto UserData::unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & { -> const std::vector<Data::UnavailableReason> & {
return _unavailableReasons; return _unavailableReasons;

View file

@ -76,7 +76,6 @@ public:
UserData(not_null<Data::Session*> owner, PeerId id); UserData(not_null<Data::Session*> owner, PeerId id);
void setPhoto(const MTPUserProfilePhoto &photo); void setPhoto(const MTPUserProfilePhoto &photo);
void setEmojiStatus(const MTPEmojiStatus &status);
void setName( void setName(
const QString &newFirstName, const QString &newFirstName,
@ -85,9 +84,6 @@ public:
const QString &newUsername); const QString &newUsername);
void setUsernames(const Data::Usernames &newUsernames); void setUsernames(const Data::Usernames &newUsernames);
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
[[nodiscard]] DocumentId emojiStatusId() const;
void setUsername(const QString &username); void setUsername(const QString &username);
void setPhone(const QString &newPhone); void setPhone(const QString &newPhone);
void setBotInfoVersion(int version); void setBotInfoVersion(int version);
@ -199,8 +195,6 @@ private:
static constexpr auto kInaccessibleAccessHashOld static constexpr auto kInaccessibleAccessHashOld
= 0xFFFFFFFFFFFFFFFFULL; = 0xFFFFFFFFFFFFFFFFULL;
DocumentId _emojiStatusId = 0;
}; };
namespace Data { namespace Data {

View file

@ -198,9 +198,14 @@ WallPaperId WallPaper::id() const {
return _id; return _id;
} }
QString WallPaper::emojiId() const {
return _emojiId;
}
bool WallPaper::equals(const WallPaper &paper) const { bool WallPaper::equals(const WallPaper &paper) const {
return (_flags == paper._flags) return (_flags == paper._flags)
&& (_slug == paper._slug) && (_slug == paper._slug)
&& (_emojiId == paper._emojiId)
&& (_backgroundColors == paper._backgroundColors) && (_backgroundColors == paper._backgroundColors)
&& (_rotation == paper._rotation) && (_rotation == paper._rotation)
&& (_intensity == paper._intensity) && (_intensity == paper._intensity)
@ -362,6 +367,7 @@ MTPWallPaperSettings WallPaper::mtpSettings() const {
MTP_flags((_blurred ? Flag::f_blur : Flag(0)) MTP_flags((_blurred ? Flag::f_blur : Flag(0))
| Flag::f_intensity | Flag::f_intensity
| Flag::f_rotation | Flag::f_rotation
| (_emojiId.isEmpty() ? Flag() : Flag::f_emoticon)
| flagForIndex(0) | flagForIndex(0)
| flagForIndex(1) | flagForIndex(1)
| flagForIndex(2) | flagForIndex(2)
@ -371,7 +377,8 @@ MTPWallPaperSettings WallPaper::mtpSettings() const {
serializeForIndex(2), serializeForIndex(2),
serializeForIndex(3), serializeForIndex(3),
MTP_int(_intensity), MTP_int(_intensity),
MTP_int(_rotation)); MTP_int(_rotation),
MTP_string(_emojiId));
} }
WallPaper WallPaper::withUrlParams( WallPaper WallPaper::withUrlParams(
@ -519,6 +526,7 @@ std::optional<WallPaper> WallPaper::Create(const MTPDwallPaperNoFile &data) {
if (const auto rotation = data.vrotation()) { if (const auto rotation = data.vrotation()) {
result._rotation = rotation->v; result._rotation = rotation->v;
} }
result._emojiId = qs(data.vemoticon().value_or_empty());
}); });
} }
return result; return result;
@ -690,6 +698,12 @@ std::optional<WallPaper> WallPaper::FromColorsSlug(const QString &slug) {
return result; return result;
} }
WallPaper WallPaper::FromEmojiId(const QString &emojiId) {
auto result = WallPaper(0);
result._emojiId = emojiId;
return result;
}
WallPaper WallPaper::ConstructDefault() { WallPaper WallPaper::ConstructDefault() {
auto result = WallPaper( auto result = WallPaper(
kDefaultBackground kDefaultBackground

View file

@ -45,6 +45,7 @@ public:
[[nodiscard]] bool equals(const WallPaper &paper) const; [[nodiscard]] bool equals(const WallPaper &paper) const;
[[nodiscard]] WallPaperId id() const; [[nodiscard]] WallPaperId id() const;
[[nodiscard]] QString emojiId() const;
[[nodiscard]] bool isNull() const; [[nodiscard]] bool isNull() const;
[[nodiscard]] QString key() const; [[nodiscard]] QString key() const;
[[nodiscard]] const std::vector<QColor> backgroundColors() const; [[nodiscard]] const std::vector<QColor> backgroundColors() const;
@ -102,6 +103,7 @@ public:
qint32 legacyId); qint32 legacyId);
[[nodiscard]] static std::optional<WallPaper> FromColorsSlug( [[nodiscard]] static std::optional<WallPaper> FromColorsSlug(
const QString &slug); const QString &slug);
[[nodiscard]] static WallPaper FromEmojiId(const QString &emojiId);
[[nodiscard]] static WallPaper ConstructDefault(); [[nodiscard]] static WallPaper ConstructDefault();
private: private:
@ -114,6 +116,7 @@ private:
UserId _ownerId = 0; UserId _ownerId = 0;
WallPaperFlags _flags; WallPaperFlags _flags;
QString _slug; QString _slug;
QString _emojiId;
std::vector<QColor> _backgroundColors; std::vector<QColor> _backgroundColors;
int _rotation = 0; int _rotation = 0;

View file

@ -141,7 +141,7 @@ WebPageType ParseWebPageType(
const QString &type, const QString &type,
const QString &embedUrl, const QString &embedUrl,
bool hasIV) { bool hasIV) {
if (type == u"video"_q || !embedUrl.isEmpty()) { if (type == u"video"_q || type == u"gif"_q || !embedUrl.isEmpty()) {
return WebPageType::Video; return WebPageType::Video;
} else if (type == u"photo"_q) { } else if (type == u"photo"_q) {
return WebPageType::Photo; return WebPageType::Photo;

View file

@ -54,7 +54,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
| (data.is_emojis() ? Flag::Emoji : Flag()) | (data.is_emojis() ? Flag::Emoji : Flag())
| (data.vinstalled_date() ? Flag::Installed : Flag()) | (data.vinstalled_date() ? Flag::Installed : Flag())
| (data.is_videos() ? Flag::Webm : Flag()) | (data.is_videos() ? Flag::Webm : Flag())
| (data.is_text_color() ? Flag::TextColor : Flag()); | (data.is_text_color() ? Flag::TextColor : Flag())
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag());
} }
StickersSet::StickersSet( StickersSet::StickersSet(
@ -113,6 +114,10 @@ bool StickersSet::textColor() const {
return flags & StickersSetFlag::TextColor; return flags & StickersSetFlag::TextColor;
} }
bool StickersSet::channelStatus() const {
return flags & StickersSetFlag::ChannelStatus;
}
void StickersSet::setThumbnail(const ImageWithLocation &data) { void StickersSet::setThumbnail(const ImageWithLocation &data) {
Data::UpdateCloudFile( Data::UpdateCloudFile(
_thumbnail, _thumbnail,

View file

@ -58,6 +58,7 @@ enum class StickersSetFlag {
Webm = (1 << 8), Webm = (1 << 8),
Emoji = (1 << 9), Emoji = (1 << 9),
TextColor = (1 << 10), TextColor = (1 << 10),
ChannelStatus = (1 << 11),
}; };
inline constexpr bool is_flag_type(StickersSetFlag) { return true; }; inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
using StickersSetFlags = base::flags<StickersSetFlag>; using StickersSetFlags = base::flags<StickersSetFlag>;
@ -86,6 +87,7 @@ public:
[[nodiscard]] StickerSetIdentifier identifier() const; [[nodiscard]] StickerSetIdentifier identifier() const;
[[nodiscard]] StickersType type() const; [[nodiscard]] StickersType type() const;
[[nodiscard]] bool textColor() const; [[nodiscard]] bool textColor() const;
[[nodiscard]] bool channelStatus() const;
void setThumbnail(const ImageWithLocation &data); void setThumbnail(const ImageWithLocation &data);

View file

@ -396,7 +396,7 @@ dialogsPremiumIcon: ThreeStateIcon {
historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }}; historySendingIcon: icon {{ "dialogs/dialogs_sending", historySendingOutIconFg, point(5px, 5px) }};
historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }}; historySendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(5px, 5px) }};
historyViewsSendingIcon: icon {{ "dialogs/dialogs_sending", historySendingInIconFg, point(3px, 0px) }}; historyViewsSendingIcon: icon {{ "dialogs/dialogs_sending", historySendingInIconFg, point(3px, 0px) }};
historyViewsSendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }}; historyViewsSendingInvertedIcon: icon {{ "dialogs/dialogs_sending", historySendingInvertedIconFg, point(3px, 0px) }};
dialogsUpdateButton: FlatButton { dialogsUpdateButton: FlatButton {
color: activeButtonFg; color: activeButtonFg;

View file

@ -2979,7 +2979,7 @@ RowDescriptor Widget::resolveChatNext(RowDescriptor from) const {
} }
RowDescriptor Widget::resolveChatPrevious(RowDescriptor from) const { RowDescriptor Widget::resolveChatPrevious(RowDescriptor from) const {
return _inner->resolveChatPrevious(from); return _inner->resolveChatPrevious(from);
} }
void Widget::keyPressEvent(QKeyEvent *e) { void Widget::keyPressEvent(QKeyEvent *e) {

View file

@ -52,6 +52,112 @@ QString PrepareStoryFileName(
+ extension; + extension;
} }
std::vector<std::vector<HistoryMessageMarkupButton>> ButtonRowsFromTL(
const MTPDreplyInlineMarkup &data) {
const auto &list = data.vrows().v;
if (list.isEmpty()) {
return {};
}
using Type = HistoryMessageMarkupButton::Type;
auto rows = std::vector<std::vector<HistoryMessageMarkupButton>>();
rows.reserve(list.size());
for (const auto &tlRow : list) {
auto row = std::vector<HistoryMessageMarkupButton>();
row.reserve(tlRow.data().vbuttons().v.size());
for (const auto &button : tlRow.data().vbuttons().v) {
button.match([&](const MTPDkeyboardButton &data) {
row.push_back({ Type::Default, qs(data.vtext()) });
}, [&](const MTPDkeyboardButtonCallback &data) {
row.push_back({
(data.is_requires_password()
? Type::CallbackWithPassword
: Type::Callback),
qs(data.vtext()),
qba(data.vdata())
});
}, [&](const MTPDkeyboardButtonRequestGeoLocation &data) {
row.push_back({ Type::RequestLocation, qs(data.vtext()) });
}, [&](const MTPDkeyboardButtonRequestPhone &data) {
row.push_back({ Type::RequestPhone, qs(data.vtext()) });
}, [&](const MTPDkeyboardButtonRequestPeer &data) {
row.push_back({
Type::RequestPeer,
qs(data.vtext()),
QByteArray("unsupported"),
QString(),
int64(data.vbutton_id().v),
});
}, [&](const MTPDkeyboardButtonUrl &data) {
row.push_back({
Type::Url,
qs(data.vtext()),
qba(data.vurl())
});
}, [&](const MTPDkeyboardButtonSwitchInline &data) {
const auto type = data.is_same_peer()
? Type::SwitchInlineSame
: Type::SwitchInline;
row.push_back({ type, qs(data.vtext()), qba(data.vquery()) });
}, [&](const MTPDkeyboardButtonGame &data) {
row.push_back({ Type::Game, qs(data.vtext()) });
}, [&](const MTPDkeyboardButtonBuy &data) {
row.push_back({ Type::Buy, qs(data.vtext()) });
}, [&](const MTPDkeyboardButtonUrlAuth &data) {
row.push_back({
Type::Auth,
qs(data.vtext()),
qba(data.vurl()),
qs(data.vfwd_text().value_or_empty()),
data.vbutton_id().v,
});
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
const auto quiz = [&] {
if (!data.vquiz()) {
return QByteArray();
}
return data.vquiz()->match([&](const MTPDboolTrue&) {
return QByteArray(1, 1);
}, [&](const MTPDboolFalse&) {
return QByteArray(1, 0);
});
}();
row.push_back({
Type::RequestPoll,
qs(data.vtext()),
quiz
});
}, [&](const MTPDkeyboardButtonUserProfile &data) {
row.push_back({
Type::UserProfile,
qs(data.vtext()),
QByteArray::number(data.vuser_id().v)
});
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
}, [&](const MTPDinputKeyboardButtonUserProfile &data) {
}, [&](const MTPDkeyboardButtonWebView &data) {
row.push_back({
Type::WebView,
qs(data.vtext()),
data.vurl().v
});
}, [&](const MTPDkeyboardButtonSimpleWebView &data) {
row.push_back({
Type::SimpleWebView,
qs(data.vtext()),
data.vurl().v
});
});
}
if (!row.empty()) {
rows.push_back(std::move(row));
}
}
return rows;
}
} // namespace } // namespace
uint8 PeerColorIndex(BareId bareId) { uint8 PeerColorIndex(BareId bareId) {
@ -575,8 +681,8 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
return result; return result;
} }
Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) { GiveawayStart ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
auto result = Giveaway{ auto result = GiveawayStart{
.untilDate = data.vuntil_date().v, .untilDate = data.vuntil_date().v,
.quantity = data.vquantity().v, .quantity = data.vquantity().v,
.months = data.vmonths().v, .months = data.vmonths().v,
@ -1090,6 +1196,8 @@ Media ParseMedia(
// #TODO export stories // #TODO export stories
}, [&](const MTPDmessageMediaGiveaway &data) { }, [&](const MTPDmessageMediaGiveaway &data) {
result.content = ParseGiveaway(data); result.content = ParseGiveaway(data);
}, [&](const MTPDmessageMediaGiveawayResults &data) {
// #TODO export giveaway
}, [](const MTPDmessageMediaEmpty &data) {}); }, [](const MTPDmessageMediaEmpty &data) {});
return result; return result;
} }
@ -1327,7 +1435,9 @@ ServiceAction ParseServiceAction(
result.content = content; result.content = content;
}, [&](const MTPDmessageActionRequestedPeer &data) { }, [&](const MTPDmessageActionRequestedPeer &data) {
auto content = ActionRequestedPeer(); auto content = ActionRequestedPeer();
content.peerId = ParsePeerId(data.vpeer()); for (const auto &peer : data.vpeers().v) {
content.peers.push_back(ParsePeerId(peer));
}
content.buttonId = data.vbutton_id().v; content.buttonId = data.vbutton_id().v;
result.content = content; result.content = content;
}, [&](const MTPDmessageActionGiftCode &data) { }, [&](const MTPDmessageActionGiftCode &data) {
@ -1496,6 +1606,14 @@ Message ParseMessage(
} }
context.botId = 0; context.botId = 0;
} }
if (const auto replyMarkup = data.vreply_markup()) {
replyMarkup->match([](const MTPDreplyKeyboardMarkup &) {
}, [&](const MTPDreplyInlineMarkup &data) {
result.inlineButtonRows = ButtonRowsFromTL(data);
}, [](const MTPDreplyKeyboardHide &) {
}, [](const MTPDreplyKeyboardForceReply &) {
});
}
result.text = ParseText( result.text = ParseText(
data.vmessage(), data.vmessage(),
data.ventities().value_or_empty()); data.ventities().value_or_empty());

View file

@ -197,7 +197,7 @@ struct Poll {
bool closed = false; bool closed = false;
}; };
struct Giveaway { struct GiveawayStart {
std::vector<ChannelId> channels; std::vector<ChannelId> channels;
TimeId untilDate = 0; TimeId untilDate = 0;
int quantity = 0; int quantity = 0;
@ -336,7 +336,7 @@ struct Media {
Game, Game,
Invoice, Invoice,
Poll, Poll,
Giveaway, GiveawayStart,
UnsupportedMedia> content; UnsupportedMedia> content;
TimeId ttl = 0; TimeId ttl = 0;
@ -547,7 +547,7 @@ struct ActionGiftCode {
}; };
struct ActionRequestedPeer { struct ActionRequestedPeer {
PeerId peerId = 0; std::vector<PeerId> peers;
int buttonId = 0; int buttonId = 0;
}; };
@ -665,6 +665,33 @@ inline bool operator>=(MessageId a, MessageId b) {
return !(a < b); return !(a < b);
} }
struct HistoryMessageMarkupButton {
enum class Type {
Default,
Url,
Callback,
CallbackWithPassword,
RequestPhone,
RequestLocation,
RequestPoll,
RequestPeer,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
Auth,
UserProfile,
WebView,
SimpleWebView,
};
Type type;
QString text;
QByteArray data;
QString forwardText;
int64 buttonId = 0;
};
struct Message { struct Message {
int32 id = 0; int32 id = 0;
TimeId date = 0; TimeId date = 0;
@ -686,6 +713,7 @@ struct Message {
Media media; Media media;
ServiceAction action; ServiceAction action;
bool out = false; bool out = false;
std::vector<std::vector<HistoryMessageMarkupButton>> inlineButtonRows;
File &file(); File &file();
const File &file() const; const File &file() const;

View file

@ -613,7 +613,7 @@ private:
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data); [[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
[[nodiscard]] QByteArray pushGiveaway( [[nodiscard]] QByteArray pushGiveaway(
const PeersMap &peers, const PeersMap &peers,
const Data::Giveaway &data); const Data::GiveawayStart &data);
File _file; File _file;
QByteArray _composedStart; QByteArray _composedStart;
@ -1112,7 +1112,7 @@ auto HtmlWriter::Wrap::pushMessage(
if (data.recurringUsed) { if (data.recurringUsed) {
return "You were charged " + amount + " via recurring payment"; return "You were charged " + amount + " via recurring payment";
} }
auto result = "You have successfully transferred " auto result = "You have successfully transferred "
+ amount + amount
+ " for " + " for "
+ wrapReplyToLink("this invoice"); + wrapReplyToLink("this invoice");
@ -1501,7 +1501,7 @@ QByteArray HtmlWriter::Wrap::pushMedia(
return pushPhotoMedia(*photo, basePath); return pushPhotoMedia(*photo, basePath);
} else if (const auto poll = std::get_if<Poll>(&content)) { } else if (const auto poll = std::get_if<Poll>(&content)) {
return pushPoll(*poll); return pushPoll(*poll);
} else if (const auto giveaway = std::get_if<Giveaway>(&content)) { } else if (const auto giveaway = std::get_if<GiveawayStart>(&content)) {
return pushGiveaway(peers, *giveaway); return pushGiveaway(peers, *giveaway);
} }
Assert(v::is_null(content)); Assert(v::is_null(content));
@ -1826,7 +1826,7 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) {
QByteArray HtmlWriter::Wrap::pushGiveaway( QByteArray HtmlWriter::Wrap::pushGiveaway(
const PeersMap &peers, const PeersMap &peers,
const Data::Giveaway &data) { const Data::GiveawayStart &data) {
auto result = pushDiv("media_wrap clearfix"); auto result = pushDiv("media_wrap clearfix");
result.append(pushDiv("media_giveaway")); result.append(pushDiv("media_giveaway"));
@ -2028,7 +2028,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
result.description = data.description; result.description = data.description;
result.status = Data::FormatMoneyAmount(data.amount, data.currency); result.status = Data::FormatMoneyAmount(data.amount, data.currency);
}, [](const Poll &data) { }, [](const Poll &data) {
}, [](const Giveaway &data) { }, [](const GiveawayStart &data) {
}, [](const UnsupportedMedia &data) { }, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message."); Unexpected("Unsupported message.");
}, [](v::null_t) {}); }, [](v::null_t) {});

View file

@ -592,7 +592,11 @@ QByteArray SerializeMessage(
pushActor(); pushActor();
pushAction("requested_peer"); pushAction("requested_peer");
push("button_id", data.buttonId); push("button_id", data.buttonId);
push("peer_id", data.peerId.value); auto values = std::vector<QByteArray>();
for (const auto &one : data.peers) {
values.push_back(Data::NumberToString(one.value));
}
push("peers", SerializeArray(context, values));
}, [&](const ActionGiftCode &data) { }, [&](const ActionGiftCode &data) {
pushAction("gift_code_prize"); pushAction("gift_code_prize");
push("gift_code", data.code); push("gift_code", data.code);
@ -753,7 +757,7 @@ QByteArray SerializeMessage(
{ "total_voters", NumberToString(data.totalVotes) }, { "total_voters", NumberToString(data.totalVotes) },
{ "answers", serialized } { "answers", serialized }
})); }));
}, [&](const Giveaway &data) { }, [&](const GiveawayStart &data) {
context.nesting.push_back(Context::kObject); context.nesting.push_back(Context::kObject);
const auto channels = ranges::views::all( const auto channels = ranges::views::all(
data.channels data.channels
@ -776,6 +780,76 @@ QByteArray SerializeMessage(
pushBare("text", SerializeText(context, message.text)); pushBare("text", SerializeText(context, message.text));
pushBare("text_entities", SerializeText(context, message.text, true)); pushBare("text_entities", SerializeText(context, message.text, true));
if (!message.inlineButtonRows.empty()) {
const auto typeString = [](
const HistoryMessageMarkupButton &entry) -> QByteArray {
using Type = HistoryMessageMarkupButton::Type;
switch (entry.type) {
case Type::Default: return "default";
case Type::Url: return "url";
case Type::Callback: return "callback";
case Type::CallbackWithPassword: return "callback_with_password";
case Type::RequestPhone: return "request_phone";
case Type::RequestLocation: return "request_location";
case Type::RequestPoll: return "request_poll";
case Type::RequestPeer: return "request_peer";
case Type::SwitchInline: return "switch_inline";
case Type::SwitchInlineSame: return "switch_inline_same";
case Type::Game: return "game";
case Type::Buy: return "buy";
case Type::Auth: return "auth";
case Type::UserProfile: return "user_profile";
case Type::WebView: return "web_view";
case Type::SimpleWebView: return "simple_web_view";
}
Unexpected("Type in HistoryMessageMarkupButton::Type.");
};
const auto serializeRow = [&](
const std::vector<HistoryMessageMarkupButton> &row) {
context.nesting.push_back(Context::kArray);
const auto buttons = ranges::views::all(
row
) | ranges::views::transform([&](
const HistoryMessageMarkupButton &entry) {
auto pairs = std::vector<std::pair<QByteArray, QByteArray>>();
pairs.push_back({
"type",
SerializeString(typeString(entry)),
});
if (!entry.text.isEmpty()) {
pairs.push_back({
"text",
SerializeString(entry.text.toUtf8()),
});
}
if (!entry.data.isEmpty()) {
pairs.push_back({ "data", SerializeString(entry.data) });
}
if (!entry.forwardText.isEmpty()) {
pairs.push_back({
"forward_text",
SerializeString(entry.forwardText.toUtf8()),
});
}
if (entry.buttonId) {
pairs.push_back({
"button_id",
NumberToString(entry.buttonId),
});
}
return SerializeObject(context, pairs);
}) | ranges::to_vector;
context.nesting.pop_back();
return SerializeArray(context, buttons);
};
context.nesting.push_back(Context::kArray);
const auto rows = ranges::views::all(
message.inlineButtonRows
) | ranges::views::transform(serializeRow) | ranges::to_vector;
context.nesting.pop_back();
pushBare("inline_bot_buttons", SerializeArray(context, rows));
}
return serialized(); return serialized();
} }
@ -1078,7 +1152,7 @@ Result JsonWriter::writeFrequentContacts(const Data::ContactsList &data) {
{ "id", Data::NumberToString(Data::PeerToBareId(top.peer.id())) }, { "id", Data::NumberToString(Data::PeerToBareId(top.peer.id())) },
{ "category", SerializeString(category) }, { "category", SerializeString(category) },
{ "type", SerializeString(type) }, { "type", SerializeString(type) },
{ "name", StringAllowNull(top.peer.name()) }, { "name", StringAllowNull(top.peer.name()) },
{ "rating", Data::NumberToString(top.rating) }, { "rating", Data::NumberToString(top.rating) },
})); }));
} }

View file

@ -1243,7 +1243,7 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
showContextInFolder(lnkDocument); showContextInFolder(lnkDocument);
}, &st::menuIconShowInFolder); }, &st::menuIconShowInFolder);
} }
_menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] {
saveDocumentToFile(lnkDocument); saveDocumentToFile(lnkDocument);
}), &st::menuIconDownload); }), &st::menuIconDownload);
if (lnkDocument->hasAttachedStickers()) { if (lnkDocument->hasAttachedStickers()) {

View file

@ -767,8 +767,10 @@ void GenerateItems(
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic; using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic; using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam; using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor; using LogChangePeerColor = MTPDchannelAdminLogEventActionChangePeerColor;
using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji; using LogChangeProfilePeerColor = MTPDchannelAdminLogEventActionChangeProfilePeerColor;
using LogChangeWallpaper = MTPDchannelAdminLogEventActionChangeWallpaper;
using LogChangeEmojiStatus = MTPDchannelAdminLogEventActionChangeEmojiStatus;
const auto session = &history->session(); const auto session = &history->session();
const auto id = event.vid().v; const auto id = event.vid().v;
@ -1817,51 +1819,170 @@ void GenerateItems(
addSimpleServiceMessage(text); addSimpleServiceMessage(text);
}; };
const auto createChangeColor = [&](const LogChangeColor &data) { const auto createColorChange = [&](
const auto text = tr::lng_admin_log_change_color( const MTPPeerColor &was,
tr::now, const MTPPeerColor &now,
lt_from, const auto &colorPhrase,
fromLinkText, const auto &setEmoji,
lt_previous, const auto &removeEmoji,
{ '#' + QString::number(data.vprev_value().v + 1) }, const auto &changeEmoji) {
lt_color, const auto prevColor = was.data().vcolor();
{ '#' + QString::number(data.vnew_value().v + 1) }, const auto nextColor = now.data().vcolor();
Ui::Text::WithEntities); if (prevColor != nextColor) {
addSimpleServiceMessage(text); const auto wrap = [&](tl::conditional<MTPint> value) {
}; return value
? value->v
const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) { : Data::DecideColorIndex(history->peer->id);
const auto was = data.vprev_value().v; };
const auto now = data.vnew_value().v; const auto text = colorPhrase(
const auto text = !was
? tr::lng_admin_log_set_background_emoji(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(now)),
Ui::Text::WithEntities)
: !now
? tr::lng_admin_log_removed_background_emoji(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(was)),
Ui::Text::WithEntities)
: tr::lng_admin_log_change_background_emoji(
tr::now, tr::now,
lt_from, lt_from,
fromLinkText, fromLinkText,
lt_previous, lt_previous,
Ui::Text::SingleCustomEmoji( { '#' + QString::number(wrap(prevColor) + 1) },
Data::SerializeCustomEmojiId(was)), lt_color,
{ '#' + QString::number(wrap(nextColor) + 1) },
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
}
const auto prevEmoji = was.data().vbackground_emoji_id().value_or_empty();
const auto nextEmoji = now.data().vbackground_emoji_id().value_or_empty();
if (prevEmoji != nextEmoji) {
const auto text = !prevEmoji
? setEmoji(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
Ui::Text::WithEntities)
: !nextEmoji
? removeEmoji(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(prevEmoji)),
Ui::Text::WithEntities)
: changeEmoji(
tr::now,
lt_from,
fromLinkText,
lt_previous,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(prevEmoji)),
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
}
};
const auto createChangePeerColor = [&](const LogChangePeerColor &data) {
createColorChange(
data.vprev_value(),
data.vnew_value(),
tr::lng_admin_log_change_color,
tr::lng_admin_log_set_background_emoji,
tr::lng_admin_log_removed_background_emoji,
tr::lng_admin_log_change_background_emoji);
};
const auto createChangeProfilePeerColor = [&](const LogChangeProfilePeerColor &data) {
createColorChange(
data.vprev_value(),
data.vnew_value(),
tr::lng_admin_log_change_profile_color,
tr::lng_admin_log_set_profile_background_emoji,
tr::lng_admin_log_removed_profile_background_emoji,
tr::lng_admin_log_change_profile_background_emoji);
};
const auto createChangeWallpaper = [&](const LogChangeWallpaper &data) {
addSimpleServiceMessage(tr::lng_admin_log_change_wallpaper(
tr::now,
lt_from,
fromLinkText,
Ui::Text::WithEntities));
};
const auto createChangeEmojiStatus = [&](const LogChangeEmojiStatus &data) {
const auto parse = [](const MTPEmojiStatus &status) {
return status.match([](
const MTPDemojiStatus &data) {
return data.vdocument_id().v;
}, [](const MTPDemojiStatusEmpty &) {
return DocumentId();
}, [](const MTPDemojiStatusUntil &data) {
return data.vdocument_id().v;
});
};
const auto prevEmoji = parse(data.vprev_value());
const auto nextEmoji = parse(data.vnew_value());
const auto nextUntil = data.vnew_value().match([](
const MTPDemojiStatusUntil &data) {
return data.vuntil().v;
}, [](const auto &) { return TimeId(); });
const auto text = !prevEmoji
? (nextUntil
? tr::lng_admin_log_set_status_until(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
lt_date,
TextWithEntities{
langDateTime(base::unixtime::parse(nextUntil)) },
Ui::Text::WithEntities)
: tr::lng_admin_log_set_status(
tr::now,
lt_from,
fromLinkText,
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
Ui::Text::WithEntities))
: !nextEmoji
? tr::lng_admin_log_removed_status(
tr::now,
lt_from,
fromLinkText,
lt_emoji, lt_emoji,
Ui::Text::SingleCustomEmoji( Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(now)), Data::SerializeCustomEmojiId(prevEmoji)),
Ui::Text::WithEntities); Ui::Text::WithEntities)
: (nextUntil
? tr::lng_admin_log_change_status_until(
tr::now,
lt_from,
fromLinkText,
lt_previous,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(prevEmoji)),
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
lt_date,
TextWithEntities{
langDateTime(base::unixtime::parse(nextUntil)) },
Ui::Text::WithEntities)
: tr::lng_admin_log_change_status(
tr::now,
lt_from,
fromLinkText,
lt_previous,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(prevEmoji)),
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(nextEmoji)),
Ui::Text::WithEntities));
addSimpleServiceMessage(text); addSimpleServiceMessage(text);
}; };
@ -1909,8 +2030,10 @@ void GenerateItems(
createDeleteTopic, createDeleteTopic,
createPinTopic, createPinTopic,
createToggleAntiSpam, createToggleAntiSpam,
createChangeColor, createChangePeerColor,
createChangeBackgroundEmoji); createChangeProfilePeerColor,
createChangeWallpaper,
createChangeEmojiStatus);
} }
} // namespace AdminLog } // namespace AdminLog

View file

@ -442,43 +442,6 @@ HistoryInner::HistoryInner(
_migrated->translateTo(_history->translatedTo()); _migrated->translateTo(_history->translatedTo());
} }
#if 0
if (const auto channel = _history->peer->asBroadcast()) {
if (channel->amCreator()) {
const auto weak = base::make_weak(_controller);
channel->session().api().request(MTPpayments_GetPremiumGiftCodeOptions(
MTP_flags(MTPpayments_GetPremiumGiftCodeOptions::Flag::f_boost_peer),
channel->input
)).done(crl::guard(weak, [=](const MTPVector<MTPPremiumGiftCodeOption> &result) {
if (result.v.isEmpty()) {
return;
}
const auto &data = result.v.front().data();
const auto randomId = base::RandomValue<uint64>();
Payments::CheckoutProcess::Start(
Payments::InvoicePremiumGiftCode{
.purpose = Payments::InvoicePremiumGiftCodeGiveaway{
.boostPeer = channel,
//.additionalChannels = ,
.untilDate = (base::unixtime::now() + 300),
.onlyNewSubscribers = true,
},
.randomId = randomId,
.currency = qs(data.vcurrency()),
.amount = data.vamount().v,
.storeProduct = qs(data.vstore_product().value_or_empty()),
.storeQuantity = data.vstore_quantity().value_or_empty(),
.users = data.vusers().v,
.months = data.vmonths().v,
},
crl::guard(weak, [=](auto) { weak->window().activate(); }));
})).fail(crl::guard(weak, [=](const MTP::Error &error) {
weak.get()->showToast(error.type());
})).send();
}
}
#endif
Window::ChatThemeValueFromPeer( Window::ChatThemeValueFromPeer(
controller, controller,
_peer _peer
@ -2573,6 +2536,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
? link->copyToClipboardContextItemText() ? link->copyToClipboardContextItemText()
: QString(); : QString();
if (item && item->isSponsored()) {
FillSponsoredMessagesMenu(controller, item->fullId(), _menu);
}
if (isUponSelected > 0) { if (isUponSelected > 0) {
addReplyAction(item); addReplyAction(item);
const auto selectedText = getSelectedText(); const auto selectedText = getSelectedText();
@ -2635,10 +2601,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}, &st::menuIconCopy); }, &st::menuIconCopy);
} }
} }
if (item->isSponsored()) {
const auto itemId = item->fullId();
FillSponsoredMessagesMenu(controller, itemId, _menu);
}
if (!item->isService() && view && actionText.isEmpty()) { if (!item->isService() && view && actionText.isEmpty()) {
if (!hasCopyRestriction(item) if (!hasCopyRestriction(item)
&& (view->hasVisibleText() || mediaHasTextForCopy)) { && (view->hasVisibleText() || mediaHasTextForCopy)) {

View file

@ -316,9 +316,13 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
media.vid().v, media.vid().v,
}, media.is_via_mention()); }, media.is_via_mention());
}, [&](const MTPDmessageMediaGiveaway &media) -> Result { }, [&](const MTPDmessageMediaGiveaway &media) -> Result {
return std::make_unique<Data::MediaGiveaway>( return std::make_unique<Data::MediaGiveawayStart>(
item, item,
Data::ComputeGiveawayData(item, media)); Data::ComputeGiveawayStartData(item, media));
}, [&](const MTPDmessageMediaGiveawayResults &media) -> Result {
return std::make_unique<Data::MediaGiveawayResults>(
item,
Data::ComputeGiveawayResultsData(item, media));
}, [](const MTPDmessageMediaEmpty &) -> Result { }, [](const MTPDmessageMediaEmpty &) -> Result {
return nullptr; return nullptr;
}, [](const MTPDmessageMediaUnsupported &) -> Result { }, [](const MTPDmessageMediaUnsupported &) -> Result {
@ -809,6 +813,8 @@ HistoryServiceDependentData *HistoryItem::GetServiceDependentData() {
return info; return info;
} else if (const auto same = Get<HistoryServiceSameBackground>()) { } else if (const auto same = Get<HistoryServiceSameBackground>()) {
return same; return same;
} else if (const auto results = Get<HistoryServiceGiveawayResults>()) {
return results;
} }
return nullptr; return nullptr;
} }
@ -3757,6 +3763,8 @@ void HistoryItem::createServiceFromMtp(const MTPDmessageService &message) {
} else { } else {
RemoveComponents(HistoryServiceSameBackground::Bit()); RemoveComponents(HistoryServiceSameBackground::Bit());
} }
} else if (type == mtpc_messageActionGiveawayResults) {
UpdateComponents(HistoryServiceGiveawayResults::Bit());
} }
if (const auto replyTo = message.vreply_to()) { if (const auto replyTo = message.vreply_to()) {
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
@ -3971,7 +3979,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
lt_from, lt_from,
fromLinkText(), // Link 1. fromLinkText(), // Link 1.
lt_user, lt_user,
Ui::Text::Link(user->name(), 2), // Link 2. Ui::Text::Link(user->name(), 2), // Link 2.
Ui::Text::WithEntities); Ui::Text::WithEntities);
} }
return result; return result;
@ -4520,18 +4528,45 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
auto prepareRequestedPeer = [&]( auto prepareRequestedPeer = [&](
const MTPDmessageActionRequestedPeer &action) { const MTPDmessageActionRequestedPeer &action) {
const auto peerId = peerFromMTP(action.vpeer());
const auto peer = history()->owner().peer(peerId);
auto result = PreparedServiceText{}; auto result = PreparedServiceText{};
result.links.push_back(fromLink());
const auto &list = action.vpeers().v;
for (auto i = 0, count = int(list.size()); i != count; ++i) {
const auto id = peerFromMTP(list[i]);
auto user = _history->owner().peer(id);
result.links.push_back(user->createOpenLink());
auto linkText = Ui::Text::Link(user->name(), 2 + i);
if (i == 0) {
result.text = linkText;
} else if (i + 1 == count) {
result.text = tr::lng_action_add_users_and_last(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
} else {
result.text = tr::lng_action_add_users_and_one(
tr::now,
lt_accumulated,
result.text,
lt_user,
linkText,
Ui::Text::WithEntities);
}
}
result.text = tr::lng_action_shared_chat_with_bot( result.text = tr::lng_action_shared_chat_with_bot(
tr::now, tr::now,
lt_chat, lt_chat,
Ui::Text::Link(peer->name(), 1), result.text,
lt_bot, lt_bot,
Ui::Text::Link(history()->peer->name(), 2), Ui::Text::Link(history()->peer->name(), 2),
Ui::Text::WithEntities); Ui::Text::WithEntities);
result.links.push_back(peer->createOpenLink());
result.links.push_back(history()->peer->createOpenLink());
return result; return result;
}; };
@ -4574,19 +4609,35 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) { auto prepareGiftCode = [&](const MTPDmessageActionGiftCode &action) {
auto result = PreparedServiceText(); auto result = PreparedServiceText();
_history->session().giftBoxStickersPacks().load(); _history->session().giftBoxStickersPacks().load();
result.text = { if (const auto boosted = action.vboost_peer()) {
(action.is_unclaimed() result.text = {
? tr::lng_prize_unclaimed_about (action.is_unclaimed()
: action.is_via_giveaway() ? tr::lng_prize_unclaimed_about
? tr::lng_prize_about : action.is_via_giveaway()
: tr::lng_prize_gift_about)( ? tr::lng_prize_about
: tr::lng_prize_gift_about)(
tr::now,
lt_channel,
_from->owner().peer(
peerFromMTP(*action.vboost_peer()))->name()),
};
} else {
const auto isSelf = (_from->id == _from->session().userPeerId());
const auto peer = isSelf ? _history->peer : _from;
result.links.push_back(peer->createOpenLink());
result.text = (isSelf
? tr::lng_action_gift_received_me
: tr::lng_action_gift_received)(
tr::now, tr::now,
lt_channel, lt_user,
(action.vboost_peer() Ui::Text::Link(peer->name(), 1), // Link 1.
? _from->owner().peer( lt_cost,
peerFromMTP(*action.vboost_peer()))->name() { Ui::FillAmountAndCurrency(
: "a channel")), action.vamount().value_or_empty(),
}; qs(action.vcurrency().value_or_empty())) },
Ui::Text::WithEntities);
}
return result; return result;
}; };
@ -4741,7 +4792,7 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
_from, _from,
Data::GiftCode{ Data::GiftCode{
.slug = qs(data.vslug()), .slug = qs(data.vslug()),
.channel = (peerIsChannel(boostedId) .channel = (boostedId
? history()->owner().channel(boostedId).get() ? history()->owner().channel(boostedId).get()
: nullptr), : nullptr),
.months = data.vmonths().v, .months = data.vmonths().v,

View file

@ -216,7 +216,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
lt_channel, lt_channel,
Ui::Text::Link(phrase.text, 1), // Link 1. Ui::Text::Link(phrase.text, 1), // Link 1.
lt_inline_bot, lt_inline_bot,
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2. Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
Ui::Text::WithEntities); Ui::Text::WithEntities);
} else { } else {
phrase = tr::lng_forwarded_via( phrase = tr::lng_forwarded_via(
@ -224,7 +224,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
lt_user, lt_user,
Ui::Text::Link(phrase.text, 1), // Link 1. Ui::Text::Link(phrase.text, 1), // Link 1.
lt_inline_bot, lt_inline_bot,
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2. Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
Ui::Text::WithEntities); Ui::Text::WithEntities);
} }
} else { } else {

View file

@ -599,6 +599,11 @@ struct HistoryServiceSameBackground
, public HistoryServiceDependentData { , public HistoryServiceDependentData {
}; };
struct HistoryServiceGiveawayResults
: public RuntimeComponent<HistoryServiceGiveawayResults, HistoryItem>
, public HistoryServiceDependentData {
};
enum class HistorySelfDestructType { enum class HistorySelfDestructType {
Photo, Photo,
Video, Video,

View file

@ -499,6 +499,8 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
: Result::Good; : Result::Good;
}, [](const MTPDmessageMediaGiveaway &) { }, [](const MTPDmessageMediaGiveaway &) {
return Result::Good; return Result::Good;
}, [](const MTPDmessageMediaGiveawayResults &) {
return Result::Good;
}, [](const MTPDmessageMediaUnsupported &) { }, [](const MTPDmessageMediaUnsupported &) {
return Result::Unsupported; return Result::Unsupported;
}); });

View file

@ -37,10 +37,11 @@ namespace {
} }
[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( [[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL(
const MTPRequestPeerType &query) { const MTPDkeyboardButtonRequestPeer &query) {
using Type = RequestPeerQuery::Type; using Type = RequestPeerQuery::Type;
using Restriction = RequestPeerQuery::Restriction; using Restriction = RequestPeerQuery::Restriction;
auto result = RequestPeerQuery(); auto result = RequestPeerQuery();
result.maxQuantity = query.vmax_quantity().v;
const auto restriction = [](const MTPBool *value) { const auto restriction = [](const MTPBool *value) {
return !value return !value
? Restriction::Any ? Restriction::Any
@ -51,7 +52,7 @@ namespace {
const auto rights = [](const MTPChatAdminRights *value) { const auto rights = [](const MTPChatAdminRights *value) {
return value ? ChatAdminRightsInfo(*value).flags : ChatAdminRights(); return value ? ChatAdminRightsInfo(*value).flags : ChatAdminRights();
}; };
query.match([&](const MTPDrequestPeerTypeUser &data) { query.vpeer_type().match([&](const MTPDrequestPeerTypeUser &data) {
result.type = Type::User; result.type = Type::User;
result.userIsBot = restriction(data.vbot()); result.userIsBot = restriction(data.vbot());
result.userIsPremium = restriction(data.vpremium()); result.userIsPremium = restriction(data.vpremium());
@ -134,7 +135,7 @@ void HistoryMessageMarkupData::fillRows(
}, [&](const MTPDkeyboardButtonRequestPhone &data) { }, [&](const MTPDkeyboardButtonRequestPhone &data) {
row.emplace_back(Type::RequestPhone, qs(data.vtext())); row.emplace_back(Type::RequestPhone, qs(data.vtext()));
}, [&](const MTPDkeyboardButtonRequestPeer &data) { }, [&](const MTPDkeyboardButtonRequestPeer &data) {
const auto query = RequestPeerQueryFromTL(data.vpeer_type()); const auto query = RequestPeerQueryFromTL(data);
row.emplace_back( row.emplace_back(
Type::RequestPeer, Type::RequestPeer,
qs(data.vtext()), qs(data.vtext()),

View file

@ -45,6 +45,8 @@ struct RequestPeerQuery {
Yes, Yes,
No, No,
}; };
int maxQuantity = 0;
Type type = Type::User; Type type = Type::User;
Restriction userIsBot = Restriction::Any; Restriction userIsBot = Restriction::Any;
Restriction userIsPremium = Restriction::Any; Restriction userIsPremium = Restriction::Any;

View file

@ -4590,7 +4590,7 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
if (k->key() == Qt::Key_Up) { if (k->key() == Qt::Key_Up) {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
// Cmd + Up is used instead of Home. // Cmd + Up is used instead of Home.
if (!_field->textCursor().atStart()) { if (HasSendText(_field)) {
return false; return false;
} }
#endif #endif
@ -4598,7 +4598,7 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
} else if (k->key() == Qt::Key_Down) { } else if (k->key() == Qt::Key_Down) {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
// Cmd + Down is used instead of End. // Cmd + Down is used instead of End.
if (!_field->textCursor().atEnd()) { if (HasSendText(_field)) {
return false; return false;
} }
#endif #endif

View file

@ -98,16 +98,15 @@ namespace {
[[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus( [[nodiscard]] rpl::producer<TextWithEntities> PeerCustomStatus(
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
const auto user = peer->asUser(); if (peer->isChat()) {
if (!user) {
return rpl::single(TextWithEntities()); return rpl::single(TextWithEntities());
} }
const auto owner = &user->owner(); const auto owner = &peer->owner();
return user->session().changes().peerFlagsValue( return peer->session().changes().peerFlagsValue(
user, peer,
Data::PeerUpdate::Flag::EmojiStatus Data::PeerUpdate::Flag::EmojiStatus
) | rpl::map([=] { ) | rpl::map([=] {
const auto id = user->emojiStatusId(); const auto id = peer->emojiStatusId();
return id return id
? ResolveIsCustom(owner, id) ? ResolveIsCustom(owner, id)
: rpl::single(TextWithEntities()); : rpl::single(TextWithEntities());
@ -715,8 +714,9 @@ void ContactStatus::setupShareHandler(not_null<UserData*> user) {
void ContactStatus::setupUnarchiveHandler(not_null<PeerData*> peer) { void ContactStatus::setupUnarchiveHandler(not_null<PeerData*> peer) {
_inner->unarchiveClicks( _inner->unarchiveClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=, show = _controller->uiShow()] {
Window::ToggleHistoryArchived(peer->owner().history(peer), false); using namespace Window;
ToggleHistoryArchived(show, peer->owner().history(peer), false);
peer->owner().notifySettings().resetToDefault(peer); peer->owner().notifySettings().resetToDefault(peer);
if (const auto settings = peer->settings()) { if (const auto settings = peer->settings()) {
const auto flags = PeerSetting::AutoArchived const auto flags = PeerSetting::AutoArchived

View file

@ -809,7 +809,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
return {}; return {};
} }
const auto from = item->from(); const auto from = item->from();
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
.arg(peerToChannel(peerId).bare) .arg(peerToChannel(peerId).bare)
.arg(topicRootId.bare); .arg(topicRootId.bare);
const auto fromLink = [&](int index) { const auto fromLink = [&](int index) {
@ -1392,6 +1392,14 @@ bool Element::allowTextSelectionByHandler(
return false; return false;
} }
bool Element::usesBubblePattern(const PaintContext &context) const {
return (context.selection != FullSelection)
&& hasOutLayout()
&& context.bubblesPattern
&& !context.viewport.isEmpty()
&& !context.bubblesPattern->pixmap.size().isEmpty();
}
bool Element::hasVisibleText() const { bool Element::hasVisibleText() const {
return false; return false;
} }

View file

@ -465,6 +465,8 @@ public:
[[nodiscard]] virtual bool allowTextSelectionByHandler( [[nodiscard]] virtual bool allowTextSelectionByHandler(
const ClickHandlerPtr &handler) const; const ClickHandlerPtr &handler) const;
[[nodiscard]] bool usesBubblePattern(const PaintContext &context) const;
struct VerticalRepaintRange { struct VerticalRepaintRange {
int top = 0; int top = 0;
int height = 0; int height = 0;

View file

@ -182,7 +182,7 @@ void KeyboardStyle::paintButtonBg(
const auto &small = sti->msgServiceBgCornersSmall; const auto &small = sti->msgServiceBgCornersSmall;
const auto &large = sti->msgServiceBgCornersLarge; const auto &large = sti->msgServiceBgCornersLarge;
auto corners = Ui::CornersPixmaps(); auto corners = Ui::CornersPixmaps();
int radiuses[4]; int radiuses[4];
for (auto i = 0; i != 4; ++i) { for (auto i = 0; i != 4; ++i) {
const auto isLarge = (rounding[i] == Corner::Large); const auto isLarge = (rounding[i] == Corner::Large);
corners.p[i] = (isLarge ? large : small).p[i]; corners.p[i] = (isLarge ? large : small).p[i];
@ -403,6 +403,11 @@ Message::Message(
, _bottomInfo( , _bottomInfo(
&data->history()->owner().reactions(), &data->history()->owner().reactions(),
BottomInfoDataFromMessage(this)) { BottomInfoDataFromMessage(this)) {
if (const auto media = data->media()) {
if (media->giveawayResults()) {
_hideReply = 1;
}
}
initLogEntryOriginal(); initLogEntryOriginal();
initPsa(); initPsa();
refreshReactions(); refreshReactions();
@ -1405,13 +1410,12 @@ void Message::paintFromName(
const auto y = trect.top(); const auto y = trect.top();
auto color = nameFg; auto color = nameFg;
color.setAlpha(115); color.setAlpha(115);
const auto user = from->asUser(); const auto id = from ? from->emojiStatusId() : 0;
const auto id = user ? user->emojiStatusId() : 0;
if (_fromNameStatus->id != id) { if (_fromNameStatus->id != id) {
const auto that = const_cast<Message*>(this); const auto that = const_cast<Message*>(this);
_fromNameStatus->custom = id _fromNameStatus->custom = id
? std::make_unique<Ui::Text::LimitedLoopsEmoji>( ? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
user->owner().customEmojiManager().create( history()->owner().customEmojiManager().create(
id, id,
[=] { that->customEmojiRepaint(); }), [=] { that->customEmojiRepaint(); }),
kPlayStatusLimit) kPlayStatusLimit)
@ -3012,7 +3016,8 @@ void Message::validateFromNameText(PeerData *from) const {
from->name(), from->name(),
Ui::NameTextOptions()); Ui::NameTextOptions());
} }
if (from->isPremium()) { if (from->isPremium()
|| (from->isChannel() && from != history()->peer)) {
if (!_fromNameStatus) { if (!_fromNameStatus) {
_fromNameStatus = std::make_unique<FromNameStatus>(); _fromNameStatus = std::make_unique<FromNameStatus>();
const auto size = st::emojiSize; const auto size = st::emojiSize;

View file

@ -673,6 +673,8 @@ TextState Service::textState(QPoint point, StateRequest request) const {
} }
} else if (const auto same = item->Get<HistoryServiceSameBackground>()) { } else if (const auto same = item->Get<HistoryServiceSameBackground>()) {
result.link = same->lnk; result.link = same->lnk;
} else if (const auto results = item->Get<HistoryServiceGiveawayResults>()) {
result.link = results->lnk;
} else if (media && data()->showSimilarChannels()) { } else if (media && data()->showSimilarChannels()) {
result = media->textState(mediaPoint, request); result = media->textState(mediaPoint, request);
} }

View file

@ -23,14 +23,21 @@ namespace {
[[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler(
not_null<Data::Media*> media) { not_null<Data::Media*> media) {
const auto giveaway = media->giveaway(); const auto start = media->giveawayStart();
Assert(giveaway != nullptr); const auto results = media->giveawayResults();
Assert(start || results);
const auto peer = media->parent()->history()->peer; const auto peer = media->parent()->history()->peer;
const auto messageId = media->parent()->id; const auto messageId = media->parent()->id;
if (media->parent()->isSending() || media->parent()->hasFailed()) { if (media->parent()->isSending() || media->parent()->hasFailed()) {
return nullptr; return nullptr;
} }
const auto info = *giveaway; const auto maybeStart = start
? *start
: std::optional<Data::GiveawayStart>();
const auto maybeResults = results
? *results
: std::optional<Data::GiveawayResults>();
return std::make_shared<LambdaClickHandler>([=]( return std::make_shared<LambdaClickHandler>([=](
ClickContext context) { ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
@ -38,13 +45,18 @@ namespace {
if (!controller) { if (!controller) {
return; return;
} }
ResolveGiveawayInfo(controller, peer, messageId, info); ResolveGiveawayInfo(
controller,
peer,
messageId,
maybeStart,
maybeResults);
}); });
} }
[[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) { [[nodiscard]] QString MakeMediaButtonText(not_null<Data::Media*> media) {
const auto giveaway = media->giveaway(); Expects(media->giveawayStart() || media->giveawayResults());
Assert(giveaway != nullptr);
return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now)); return Ui::Text::Upper(tr::lng_prizes_how_works(tr::now));
} }
@ -72,7 +84,7 @@ struct ViewButton::Inner {
}; };
bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) { bool ViewButton::MediaHasViewButton(not_null<Data::Media*> media) {
return (media->giveaway() != nullptr); return media->giveawayStart() || media->giveawayResults();
} }
ViewButton::Inner::Inner( ViewButton::Inner::Inner(

View file

@ -942,13 +942,13 @@ void Gif::drawCornerStatus(
const auto statusX = position.x() + st::msgDateImgDelta + padding.x(); const auto statusX = position.x() + st::msgDateImgDelta + padding.x();
const auto statusY = position.y() + st::msgDateImgDelta + padding.y(); const auto statusY = position.y() + st::msgDateImgDelta + padding.y();
const auto around = style::rtlrect(statusX - padding.x(), statusY - padding.y(), statusW, statusH, width()); const auto around = style::rtlrect(statusX - padding.x(), statusY - padding.y(), statusW, statusH, width());
const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3) - padding.y()) : 0); const auto statusTextTop = statusY + (cornerDownload ? (((statusH - 2 * st::normalFont->height) / 3) - padding.y()) : 0);
Ui::FillRoundRect(p, around, sti->msgDateImgBg, sti->msgDateImgBgCorners); Ui::FillRoundRect(p, around, sti->msgDateImgBg, sti->msgDateImgBgCorners);
p.setFont(st::normalFont); p.setFont(st::normalFont);
p.setPen(st->msgDateImgFg()); p.setPen(st->msgDateImgFg());
p.drawTextLeft(statusX + addLeft, statusTextTop, width(), text, statusW - 2 * padding.x()); p.drawTextLeft(statusX + addLeft, statusTextTop, width(), text, statusW - 2 * padding.x());
if (cornerDownload) { if (cornerDownload) {
const auto downloadTextTop = statusY + st::normalFont->height + (2 * (statusH - 2 * st::normalFont->height) / 3) - padding.y(); const auto downloadTextTop = statusY + st::normalFont->height + (2 * (statusH - 2 * st::normalFont->height) / 3) - padding.y();
p.drawTextLeft(statusX + addLeft, downloadTextTop, width(), _downloadSize, statusW - 2 * padding.x()); p.drawTextLeft(statusX + addLeft, downloadTextTop, width(), _downloadSize, statusW - 2 * padding.x());
const auto inner = QRect(statusX + padding.y() - padding.x(), statusY, st::historyVideoDownloadSize, st::historyVideoDownloadSize); const auto inner = QRect(statusX + padding.y() - padding.x(), statusY, st::historyVideoDownloadSize, st::historyVideoDownloadSize);
const auto &icon = _data->loading() const auto &icon = _data->loading()

View file

@ -11,7 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_sticker.h"
namespace Data { namespace Data {
struct Giveaway; struct GiveawayStart;
struct GiveawayResults;
} // namespace Data } // namespace Data
namespace Dialogs::Stories { namespace Dialogs::Stories {
@ -24,12 +25,31 @@ class RippleAnimation;
namespace HistoryView { namespace HistoryView {
class Giveaway final : public Media { class MediaInBubble final : public Media {
public: public:
Giveaway( class Part : public Object {
public:
virtual ~Part() = default;
virtual void draw(
Painter &p,
const PaintContext &context,
int outerWidth) const = 0;
[[nodiscard]] virtual TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const;
virtual void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed);
[[nodiscard]] virtual bool hasHeavyPart();
virtual void unloadHeavyPart();
};
MediaInBubble(
not_null<Element*> parent, not_null<Element*> parent,
not_null<Data::Giveaway*> giveaway); Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate);
~Giveaway(); ~MediaInBubble();
void draw(Painter &p, const PaintContext &context) const override; void draw(Painter &p, const PaintContext &context) const override;
TextState textState(QPoint point, StateRequest request) const override; TextState textState(QPoint point, StateRequest request) const override;
@ -49,7 +69,7 @@ public:
} }
bool toggleSelectionByHandlerClick( bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override { const ClickHandlerPtr &p) const override {
return true; return true;
} }
bool dragItemByHandler(const ClickHandlerPtr &p) const override { bool dragItemByHandler(const ClickHandlerPtr &p) const override {
@ -62,8 +82,134 @@ public:
bool hasHeavyPart() const override; bool hasHeavyPart() const override;
private: private:
struct Entry {
std::unique_ptr<Part> object;
};
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
[[nodiscard]] QMargins inBubblePadding() const;
std::vector<Entry> _entries;
};
class TextMediaInBubblePart final : public MediaInBubble::Part {
public:
TextMediaInBubblePart(
TextWithEntities text,
QMargins margins,
const base::flat_map<uint16, ClickHandlerPtr> &links = {});
void draw(
Painter &p,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class TextDelimeterPart final : public MediaInBubble::Part {
public:
TextDelimeterPart(const QString &text, QMargins margins);
void draw(
Painter &p,
const PaintContext &context,
int outerWidth) const override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
Ui::Text::String _text;
QMargins _margins;
};
class StickerWithBadgePart final : public MediaInBubble::Part {
public:
struct Data {
DocumentData *sticker = nullptr;
int skipTop = 0;
bool isGiftBoxSticker = false;
explicit operator bool() const {
return sticker != nullptr;
}
};
StickerWithBadgePart(
not_null<Element*> parent,
Fn<Data()> lookup,
QString badge);
void draw(
Painter &p,
const PaintContext &context,
int outerWidth) const override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
void ensureCreated() const;
void validateBadge(const PaintContext &context) const;
void paintBadge(Painter &p, const PaintContext &context) const;
const not_null<Element*> _parent;
Fn<Data()> _lookup;
QString _badgeText;
mutable int _skipTop = 0;
mutable std::optional<Sticker> _sticker;
mutable QColor _badgeFg;
mutable QColor _badgeBorder;
mutable QImage _badge;
mutable QImage _badgeCache;
};
class PeerBubbleListPart final : public MediaInBubble::Part {
public:
PeerBubbleListPart(
not_null<Element*> parent,
const std::vector<not_null<PeerData*>> &list);
~PeerBubbleListPart();
void draw(
Painter &p,
const PaintContext &context,
int outerWidth) const override;
TextState textState(
QPoint point,
StateRequest request,
int outerWidth) const override;
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
private:
int layout(int x, int y, int available);
using Thumbnail = Dialogs::Stories::Thumbnail; using Thumbnail = Dialogs::Stories::Thumbnail;
struct Channel { struct Peer {
Ui::Text::String name; Ui::Text::String name;
std::shared_ptr<Thumbnail> thumbnail; std::shared_ptr<Thumbnail> thumbnail;
QRect geometry; QRect geometry;
@ -74,50 +220,21 @@ private:
uint8 colorIndex = 0; uint8 colorIndex = 0;
}; };
void paintBadge(Painter &p, const PaintContext &context) const; const not_null<Element*> _parent;
void paintChannels(Painter &p, const PaintContext &context) const; std::vector<Peer> _peers;
int layoutChannels(int x, int y, int available);
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
void fillFromData(not_null<Data::Giveaway*> giveaway);
void ensureStickerCreated() const;
void validateBadge(const PaintContext &context) const;
[[nodiscard]] QMargins inBubblePadding() const;
mutable std::optional<Sticker> _sticker;
Ui::Text::String _prizesTitle;
Ui::Text::String _prizes;
Ui::Text::String _participantsTitle;
Ui::Text::String _participants;
std::vector<Channel> _channels;
Ui::Text::String _countries;
Ui::Text::String _winnersTitle;
Ui::Text::String _winners;
mutable QColor _badgeFg;
mutable QColor _badgeBorder;
mutable QImage _badge;
mutable QImage _badgeCache;
mutable QPoint _lastPoint; mutable QPoint _lastPoint;
int _months = 0; mutable bool _subscribed = false;
int _quantity = 0;
int _stickerTop = 0;
int _prizesTitleTop = 0;
int _prizesTop = 0;
int _prizesWidth = 0;
int _participantsTitleTop = 0;
int _participantsTop = 0;
int _participantsWidth = 0;
int _countriesTop = 0;
int _countriesWidth = 0;
int _winnersTitleTop = 0;
int _winnersTop = 0;
mutable uint8 _subscribedToThumbnails : 1 = 0;
}; };
[[nodiscard]] auto GenerateGiveawayStart(
not_null<Element*> parent,
not_null<Data::GiveawayStart*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
[[nodiscard]] auto GenerateGiveawayResults(
not_null<Element*> parent,
not_null<Data::GiveawayResults*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaInBubble::Part>)>)>;
} // namespace HistoryView } // namespace HistoryView

View file

@ -341,11 +341,7 @@ auto Media::getBubbleSelectionIntervals(
} }
bool Media::usesBubblePattern(const PaintContext &context) const { bool Media::usesBubblePattern(const PaintContext &context) const {
return (context.selection != FullSelection) return _parent->usesBubblePattern(context);
&& _parent->hasOutLayout()
&& context.bubblesPattern
&& !context.viewport.isEmpty()
&& !context.bubblesPattern->pixmap.size().isEmpty();
} }
PointState Media::pointState(QPoint point) const { PointState Media::pointState(QPoint point) const {

View file

@ -459,7 +459,7 @@ PointState GroupedMedia::pointState(QPoint point) const {
return PointState::Outside; return PointState::Outside;
} }
const auto groupPadding = groupedPadding(); const auto groupPadding = groupedPadding();
point -= QPoint(0, groupPadding.top()); point -= QPoint(0, groupPadding.top());
for (const auto &part : _parts) { for (const auto &part : _parts) {
if (part.geometry.contains(point)) { if (part.geometry.contains(point)) {
return PointState::GroupPart; return PointState::GroupPart;

View file

@ -662,10 +662,10 @@ QRect Photo::enlargeRect() const {
const auto enlargeInner = st::historyPageEnlargeSize; const auto enlargeInner = st::historyPageEnlargeSize;
const auto enlargeOuter = 2 * skip + enlargeInner; const auto enlargeOuter = 2 * skip + enlargeInner;
return { return {
width() - enlargeOuter + skip, width() - enlargeOuter + skip,
skip, skip,
enlargeInner, enlargeInner,
enlargeInner, enlargeInner,
}; };
} }

View file

@ -43,7 +43,7 @@ QSize PremiumGift::size() {
} }
QString PremiumGift::title() { QString PremiumGift::title() {
return _data.slug.isEmpty() return gift()
? tr::lng_premium_summary_title(tr::now) ? tr::lng_premium_summary_title(tr::now)
: _data.unclaimed : _data.unclaimed
? tr::lng_prize_unclaimed_title(tr::now) ? tr::lng_prize_unclaimed_title(tr::now)
@ -51,7 +51,7 @@ QString PremiumGift::title() {
} }
TextWithEntities PremiumGift::subtitle() { TextWithEntities PremiumGift::subtitle() {
if (_data.slug.isEmpty()) { if (gift()) {
return { GiftDuration(_data.months) }; return { GiftDuration(_data.months) };
} }
const auto name = _data.channel ? _data.channel->name() : "channel"; const auto name = _data.channel ? _data.channel->name() : "channel";
@ -78,7 +78,7 @@ TextWithEntities PremiumGift::subtitle() {
} }
rpl::producer<QString> PremiumGift::button() { rpl::producer<QString> PremiumGift::button() {
return _data.slug.isEmpty() return (gift() && (outgoingGift() || !_data.unclaimed))
? tr::lng_sticker_premium_view() ? tr::lng_sticker_premium_view()
: tr::lng_prize_open(); : tr::lng_prize_open();
} }
@ -90,14 +90,16 @@ ClickHandlerPtr PremiumGift::createViewLink() {
return std::make_shared<LambdaClickHandler>([=](ClickContext context) { return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
const auto selfId = controller->session().userPeerId();
const auto self = (from->id == selfId);
if (data.slug.isEmpty()) { if (data.slug.isEmpty()) {
const auto selfId = controller->session().userPeerId();
const auto self = (from->id == selfId);
const auto peer = self ? to : from; const auto peer = self ? to : from;
const auto months = data.months; const auto months = data.months;
Settings::ShowGiftPremium(controller, peer, months, self); Settings::ShowGiftPremium(controller, peer, months, self);
} else { } else {
ResolveGiftCode(controller, data.slug); const auto fromId = from->id;
const auto toId = self ? to->id : selfId;
ResolveGiftCode(controller, data.slug, fromId, toId);
} }
} }
}); });
@ -119,7 +121,7 @@ void PremiumGift::draw(
} }
bool PremiumGift::hideServiceText() { bool PremiumGift::hideServiceText() {
return !_data.slug.isEmpty(); return !gift();
} }
void PremiumGift::stickerClearLoopPlayed() { void PremiumGift::stickerClearLoopPlayed() {
@ -146,6 +148,18 @@ void PremiumGift::unloadHeavyPart() {
} }
} }
bool PremiumGift::incomingGift() const {
return gift() && !_parent->data()->out();
}
bool PremiumGift::outgoingGift() const {
return gift() && _parent->data()->out();
}
bool PremiumGift::gift() const {
return _data.slug.isEmpty() || !_data.channel;
}
void PremiumGift::ensureStickerCreated() const { void PremiumGift::ensureStickerCreated() const {
if (_sticker) { if (_sticker) {
return; return;

View file

@ -46,6 +46,9 @@ public:
void unloadHeavyPart() override; void unloadHeavyPart() override;
private: private:
[[nodiscard]] bool incomingGift() const;
[[nodiscard]] bool outgoingGift() const;
[[nodiscard]] bool gift() const;
void ensureStickerCreated() const; void ensureStickerCreated() const;
const not_null<Element*> _parent; const not_null<Element*> _parent;

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -471,13 +472,28 @@ ThemeDocumentBox::ThemeDocumentBox(
not_null<Element*> parent, not_null<Element*> parent,
const Data::WallPaper &paper) const Data::WallPaper &paper)
: _parent(parent) : _parent(parent)
, _preview( , _emojiId(paper.emojiId()) {
parent, Window::WallPaperResolved(
&_parent->history()->owner(),
&paper
) | rpl::start_with_next([=](const Data::WallPaper *paper) {
_parent->repaint();
if (!paper) {
_preview.reset();
} else {
createPreview(*paper);
}
}, _lifetime);
}
void ThemeDocumentBox::createPreview(const Data::WallPaper &paper) {
_preview.emplace(
_parent,
paper.document(), paper.document(),
paper, paper,
st::msgServicePhotoWidth) { st::msgServicePhotoWidth);
_preview.initDimensions(); _preview->initDimensions();
_preview.resizeGetHeight(_preview.maxWidth()); _preview->resizeGetHeight(_preview->maxWidth());
} }
ThemeDocumentBox::~ThemeDocumentBox() = default; ThemeDocumentBox::~ThemeDocumentBox() = default;
@ -487,7 +503,9 @@ int ThemeDocumentBox::top() {
} }
QSize ThemeDocumentBox::size() { QSize ThemeDocumentBox::size() {
return { _preview.maxWidth(), _preview.minHeight() }; return _preview
? QSize(_preview->maxWidth(), _preview->minHeight())
: QSize(st::msgServicePhotoWidth, st::msgServicePhotoWidth);
} }
QString ThemeDocumentBox::title() { QString ThemeDocumentBox::title() {
@ -509,8 +527,11 @@ rpl::producer<QString> ThemeDocumentBox::button() {
} }
ClickHandlerPtr ThemeDocumentBox::createViewLink() { ClickHandlerPtr ThemeDocumentBox::createViewLink() {
const auto out = _parent->data()->out();
const auto to = _parent->history()->peer; const auto to = _parent->history()->peer;
if (to->isChannel()) {
return nullptr;
}
const auto out = _parent->data()->out();
const auto media = _parent->data()->media(); const auto media = _parent->data()->media();
const auto weak = base::make_weak(_parent); const auto weak = base::make_weak(_parent);
const auto paper = media ? media->paper() : nullptr; const auto paper = media ? media->paper() : nullptr;
@ -557,9 +578,11 @@ void ThemeDocumentBox::draw(
Painter &p, Painter &p,
const PaintContext &context, const PaintContext &context,
const QRect &geometry) { const QRect &geometry) {
p.translate(geometry.topLeft()); if (_preview) {
_preview.draw(p, context); p.translate(geometry.topLeft());
p.translate(-geometry.topLeft()); _preview->draw(p, context);
p.translate(-geometry.topLeft());
}
} }
void ThemeDocumentBox::stickerClearLoopPlayed() { void ThemeDocumentBox::stickerClearLoopPlayed() {
@ -572,11 +595,13 @@ std::unique_ptr<StickerPlayer> ThemeDocumentBox::stickerTakePlayer(
} }
bool ThemeDocumentBox::hasHeavyPart() { bool ThemeDocumentBox::hasHeavyPart() {
return _preview.hasHeavyPart(); return _preview && _preview->hasHeavyPart();
} }
void ThemeDocumentBox::unloadHeavyPart() { void ThemeDocumentBox::unloadHeavyPart() {
_preview.unloadHeavyPart(); if (_preview) {
_preview->unloadHeavyPart();
}
} }
} // namespace HistoryView } // namespace HistoryView

View file

@ -119,8 +119,12 @@ public:
void unloadHeavyPart() override; void unloadHeavyPart() override;
private: private:
void createPreview(const Data::WallPaper &paper);
const not_null<Element*> _parent; const not_null<Element*> _parent;
ThemeDocument _preview; QString _emojiId;
std::optional<ThemeDocument> _preview;
rpl::lifetime _lifetime;
}; };

View file

@ -9,34 +9,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "lang/lang_keys.h" #include "data/data_file_click_handler.h"
#include "history/history_item_components.h" #include "data/data_photo_media.h"
#include "history/history_item.h" #include "data/data_session.h"
#include "data/data_sponsored_messages.h"
#include "data/data_web_page.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_reply.h"
#include "history/view/history_view_sponsored_click_handler.h" #include "history/view/history_view_sponsored_click_handler.h"
#include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_theme_document.h" #include "lang/lang_keys.h"
#include "ui/image/image.h" #include "main/main_session.h"
#include "ui/chat/chat_style.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/power_saving.h"
#include "ui/text/format_values.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/format_values.h"
#include "ui/chat/chat_style.h"
#include "ui/cached_round_corners.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_wall_paper.h"
#include "data/data_media_types.h"
#include "data/data_web_page.h"
#include "data/data_photo.h"
#include "data/data_photo_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_sponsored_messages.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView { namespace HistoryView {
@ -44,27 +36,29 @@ namespace {
constexpr auto kMaxOriginalEntryLines = 8192; constexpr auto kMaxOriginalEntryLines = 8192;
int articleThumbWidth(not_null<PhotoData*> thumb, int height) { [[nodiscard]] int ArticleThumbWidth(not_null<PhotoData*> thumb, int height) {
const auto size = thumb->location(Data::PhotoSize::Thumbnail); const auto size = thumb->location(Data::PhotoSize::Thumbnail);
return size.height() return size.height()
? qMax(qMin(height * size.width() / size.height(), height), 1) ? std::max(std::min(height * size.width() / size.height(), height), 1)
: 1; : 1;
} }
int articleThumbHeight(not_null<Data::PhotoMedia*> thumb, int width) { [[nodiscard]] int ArticleThumbHeight(
not_null<Data::PhotoMedia*> thumb,
int width) {
const auto size = thumb->size(Data::PhotoSize::Thumbnail); const auto size = thumb->size(Data::PhotoSize::Thumbnail);
return size.width() return size.width()
? std::max(size.height() * width / size.width(), 1) ? std::max(size.height() * width / size.width(), 1)
: 1; : 1;
} }
std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia( [[nodiscard]] std::vector<std::unique_ptr<Data::Media>> PrepareCollageMedia(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
const WebPageCollage &data) { const WebPageCollage &data) {
auto result = std::vector<std::unique_ptr<Data::Media>>(); auto result = std::vector<std::unique_ptr<Data::Media>>();
result.reserve(data.items.size()); result.reserve(data.items.size());
const auto spoiler = false;
for (const auto &item : data.items) { for (const auto &item : data.items) {
const auto spoiler = false;
if (const auto document = std::get_if<DocumentData*>(&item)) { if (const auto document = std::get_if<DocumentData*>(&item)) {
const auto skipPremiumEffect = false; const auto skipPremiumEffect = false;
result.push_back(std::make_unique<Data::MediaFile>( result.push_back(std::make_unique<Data::MediaFile>(
@ -205,14 +199,12 @@ QSize WebPage::countOptimalSize() {
_openl = nullptr; _openl = nullptr;
_attach = nullptr; _attach = nullptr;
_collage = PrepareCollageMedia(_parent->data(), _data->collage); _collage = PrepareCollageMedia(_parent->data(), _data->collage);
const auto min = st::msgMinWidth const auto min = st::msgMinWidth - rect::m::sum::h(_st.padding);
- _st.padding.left()
- _st.padding.right();
_siteName = Ui::Text::String(min); _siteName = Ui::Text::String(min);
_title = Ui::Text::String(min); _title = Ui::Text::String(min);
_description = Ui::Text::String(min); _description = Ui::Text::String(min);
} }
auto lineHeight = UnitedLineHeight(); const auto lineHeight = UnitedLineHeight();
if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) { if (!_openl && (!_data->url.isEmpty() || _sponsoredData)) {
const auto previewOfHiddenUrl = [&] { const auto previewOfHiddenUrl = [&] {
@ -272,7 +264,7 @@ QSize WebPage::countOptimalSize() {
} }
// init layout // init layout
auto title = TextUtilities::SingleLine(_data->title.isEmpty() const auto title = TextUtilities::SingleLine(_data->title.isEmpty()
? _data->author ? _data->author
: _data->title); : _data->title);
using Flag = MediaWebPageFlag; using Flag = MediaWebPageFlag;
@ -296,13 +288,13 @@ QSize WebPage::countOptimalSize() {
// init strings // init strings
if (_description.isEmpty() && !_data->description.text.isEmpty()) { if (_description.isEmpty() && !_data->description.text.isEmpty()) {
auto text = _data->description; const auto &text = _data->description;
if (isLogEntryOriginal()) { if (isLogEntryOriginal()) {
// Fix layout for small bubbles (narrow media caption edit log entries). // Fix layout for small bubbles
// (narrow media caption edit log entries).
_description = Ui::Text::String(st::minPhotoSize _description = Ui::Text::String(st::minPhotoSize
- padding.left() - rect::m::sum::h(padding));
- padding.right());
} }
using MarkedTextContext = Core::MarkedTextContext; using MarkedTextContext = Core::MarkedTextContext;
auto context = MarkedTextContext{ auto context = MarkedTextContext{
@ -329,11 +321,10 @@ QSize WebPage::countOptimalSize() {
Ui::WebpageTextTitleOptions()); Ui::WebpageTextTitleOptions());
} }
if (_title.isEmpty() && !title.isEmpty()) { if (_title.isEmpty() && !title.isEmpty()) {
auto titleWithEntities = Ui::Text::Link(title, _data->url);
if (!_siteNameLines && !_data->url.isEmpty()) { if (!_siteNameLines && !_data->url.isEmpty()) {
_title.setMarkedText( _title.setMarkedText(
st::webPageTitleStyle, st::webPageTitleStyle,
std::move(titleWithEntities), Ui::Text::Link(title, _data->url),
Ui::WebpageTextTitleOptions()); Ui::WebpageTextTitleOptions());
} else { } else {
@ -345,19 +336,27 @@ QSize WebPage::countOptimalSize() {
} }
// init dimensions // init dimensions
auto skipBlockWidth = _parent->skipBlockWidth(); const auto skipBlockWidth = _parent->skipBlockWidth();
auto maxWidth = skipBlockWidth; auto maxWidth = skipBlockWidth;
auto minHeight = 0; auto minHeight = 0;
auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight; const auto siteNameHeight = _siteName.isEmpty() ? 0 : lineHeight;
auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight; const auto titleMinHeight = _title.isEmpty() ? 0 : lineHeight;
auto descMaxLines = isLogEntryOriginal() ? kMaxOriginalEntryLines : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1)); const auto descMaxLines = isLogEntryOriginal()
auto descriptionMinHeight = _description.isEmpty() ? 0 : qMin(_description.minHeight(), descMaxLines * lineHeight); ? kMaxOriginalEntryLines
auto articleMinHeight = siteNameHeight + titleMinHeight + descriptionMinHeight; : (3 + (siteNameHeight ? 0 : 1) + (titleMinHeight ? 0 : 1));
auto articlePhotoMaxWidth = 0; const auto descriptionMinHeight = _description.isEmpty()
if (_asArticle) { ? 0
articlePhotoMaxWidth = st::webPagePhotoDelta + qMax(articleThumbWidth(_data->photo, articleMinHeight), lineHeight); : std::min(_description.minHeight(), descMaxLines * lineHeight);
} const auto articleMinHeight = siteNameHeight
+ titleMinHeight
+ descriptionMinHeight;
const auto articlePhotoMaxWidth = _asArticle
? st::webPagePhotoDelta
+ std::max(
ArticleThumbWidth(_data->photo, articleMinHeight),
lineHeight)
: 0;
if (!_siteName.isEmpty()) { if (!_siteName.isEmpty()) {
accumulate_max(maxWidth, _siteName.maxWidth() + articlePhotoMaxWidth); accumulate_max(maxWidth, _siteName.maxWidth() + articlePhotoMaxWidth);
@ -368,32 +367,38 @@ QSize WebPage::countOptimalSize() {
minHeight += titleMinHeight; minHeight += titleMinHeight;
} }
if (!_description.isEmpty()) { if (!_description.isEmpty()) {
accumulate_max(maxWidth, _description.maxWidth() + articlePhotoMaxWidth); accumulate_max(
maxWidth,
_description.maxWidth() + articlePhotoMaxWidth);
minHeight += descriptionMinHeight; minHeight += descriptionMinHeight;
} }
if (_attach) { if (_attach) {
auto attachAtTop = _siteName.isEmpty() && _title.isEmpty() && _description.isEmpty(); const auto attachAtTop = _siteName.isEmpty()
if (!attachAtTop) minHeight += st::mediaInBubbleSkip; && _title.isEmpty()
&& _description.isEmpty();
if (!attachAtTop) {
minHeight += st::mediaInBubbleSkip;
}
_attach->initDimensions(); _attach->initDimensions();
auto bubble = _attach->bubbleMargins(); const auto bubble = _attach->bubbleMargins();
auto maxMediaWidth = _attach->maxWidth() - bubble.left() - bubble.right(); auto maxMediaWidth = _attach->maxWidth() - rect::m::sum::h(bubble);
if (isBubbleBottom() && _attach->customInfoLayout()) { if (isBubbleBottom() && _attach->customInfoLayout()) {
maxMediaWidth += skipBlockWidth; maxMediaWidth += skipBlockWidth;
} }
accumulate_max(maxWidth, maxMediaWidth); accumulate_max(maxWidth, maxMediaWidth);
minHeight += _attach->minHeight() - bubble.top() - bubble.bottom(); minHeight += _attach->minHeight() - rect::m::sum::v(bubble);
} }
if (_data->type == WebPageType::Video && _data->duration) { if (_data->type == WebPageType::Video && _data->duration) {
_duration = Ui::FormatDurationText(_data->duration); _duration = Ui::FormatDurationText(_data->duration);
_durationWidth = st::msgDateFont->width(_duration); _durationWidth = st::msgDateFont->width(_duration);
} }
if (_openButtonWidth) { if (_openButtonWidth) {
const auto &margins = st::historyPageButtonPadding; maxWidth += rect::m::sum::h(st::historyPageButtonPadding)
maxWidth += margins.left() + _openButtonWidth + margins.right(); + _openButtonWidth;
} }
maxWidth += padding.left() + padding.right(); maxWidth += rect::m::sum::h(padding);
minHeight += padding.top() + padding.bottom(); minHeight += rect::m::sum::v(padding);
if (_asArticle) { if (_asArticle) {
minHeight = resizeGetHeight(maxWidth); minHeight = resizeGetHeight(maxWidth);
@ -406,45 +411,51 @@ QSize WebPage::countCurrentSize(int newWidth) {
return { newWidth, minHeight() }; return { newWidth, minHeight() };
} }
auto padding = inBubblePadding() + innerMargin(); const auto padding = inBubblePadding() + innerMargin();
auto innerWidth = newWidth - padding.left() - padding.right(); const auto innerWidth = newWidth - rect::m::sum::h(padding);
auto newHeight = 0; auto newHeight = 0;
auto lineHeight = UnitedLineHeight(); const auto lineHeight = UnitedLineHeight();
auto linesMax = (_sponsoredData || isLogEntryOriginal()) const auto linesMax = (_sponsoredData || isLogEntryOriginal())
? kMaxOriginalEntryLines ? kMaxOriginalEntryLines
: 5; : 5;
auto siteNameHeight = _siteNameLines ? lineHeight : 0; const auto siteNameHeight = _siteNameLines ? lineHeight : 0;
const auto twoTitleLines = 2 * st::webPageTitleFont->height;
const auto descriptionLineHeight = st::webPageDescriptionFont->height;
const auto asSponsored = (!!_sponsoredData); const auto asSponsored = (!!_sponsoredData);
if (asArticle() || asSponsored) { if (asArticle() || asSponsored) {
const auto sponsoredUserpic = (asSponsored && _sponsoredData->peer);
constexpr auto kSponsoredUserpicLines = 2; constexpr auto kSponsoredUserpicLines = 2;
_pixh = (asSponsored ? kSponsoredUserpicLines : linesMax) * lineHeight; _pixh = lineHeight
* (asSponsored ? kSponsoredUserpicLines : linesMax);
do { do {
_pixw = asSponsored ? _pixh : articleThumbWidth(_data->photo, _pixh); _pixw = asSponsored
auto wleft = asSponsored ? _pixh
? innerWidth - st::webPagePhotoDelta - qMax(_pixw, lineHeight) : ArticleThumbWidth(_data->photo, _pixh);
: innerWidth; const auto wleft = innerWidth
- st::webPagePhotoDelta
- std::max(_pixw, lineHeight);
newHeight = siteNameHeight; newHeight = siteNameHeight;
if (_title.isEmpty()) { if (_title.isEmpty()) {
_titleLines = 0; _titleLines = 0;
} else { } else {
if (_title.countHeight(wleft) < 2 * st::webPageTitleFont->height) { _titleLines = (_title.countHeight(wleft) < twoTitleLines)
_titleLines = 1; ? 1
} else { : 2;
_titleLines = 2;
}
newHeight += _titleLines * lineHeight; newHeight += _titleLines * lineHeight;
} }
auto descriptionHeight = _description.countHeight(wleft); const auto descriptionHeight = _description.countHeight(
if (descriptionHeight < (linesMax - _siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { sponsoredUserpic ? innerWidth : wleft);
const auto restLines = (linesMax - _siteNameLines - _titleLines);
if (descriptionHeight < restLines * descriptionLineHeight) {
// We have height for all the lines. // We have height for all the lines.
_descriptionLines = -1; _descriptionLines = -1;
newHeight += descriptionHeight; newHeight += descriptionHeight;
} else { } else {
_descriptionLines = (linesMax - _siteNameLines - _titleLines); _descriptionLines = restLines;
newHeight += _descriptionLines * lineHeight; newHeight += _descriptionLines * lineHeight;
} }
@ -460,55 +471,55 @@ QSize WebPage::countCurrentSize(int newWidth) {
if (_title.isEmpty()) { if (_title.isEmpty()) {
_titleLines = 0; _titleLines = 0;
} else { } else {
if (_title.countHeight(innerWidth) < 2 * st::webPageTitleFont->height) { _titleLines = (_title.countHeight(innerWidth) < twoTitleLines)
_titleLines = 1; ? 1
} else { : 2;
_titleLines = 2;
}
newHeight += _titleLines * lineHeight; newHeight += _titleLines * lineHeight;
} }
if (_description.isEmpty()) { if (_description.isEmpty()) {
_descriptionLines = 0; _descriptionLines = 0;
} else { } else {
auto descriptionHeight = _description.countHeight(innerWidth); const auto restLines = (linesMax - _siteNameLines - _titleLines);
if (descriptionHeight < (linesMax - _siteNameLines - _titleLines) * st::webPageDescriptionFont->height) { const auto descriptionHeight = _description.countHeight(
innerWidth);
if (descriptionHeight < restLines * descriptionLineHeight) {
// We have height for all the lines. // We have height for all the lines.
_descriptionLines = -1; _descriptionLines = -1;
newHeight += descriptionHeight; newHeight += descriptionHeight;
} else { } else {
_descriptionLines = (linesMax - _siteNameLines - _titleLines); _descriptionLines = restLines;
newHeight += _descriptionLines * lineHeight; newHeight += _descriptionLines * lineHeight;
} }
} }
if (_attach) { if (_attach) {
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines; const auto attachAtTop = !_siteNameLines
if (!attachAtTop) newHeight += st::mediaInBubbleSkip; && !_titleLines
&& !_descriptionLines;
if (!attachAtTop) {
newHeight += st::mediaInBubbleSkip;
}
auto bubble = _attach->bubbleMargins(); const auto bubble = _attach->bubbleMargins();
_attach->resizeGetHeight(innerWidth + rect::m::sum::h(bubble));
_attach->resizeGetHeight(innerWidth + bubble.left() + bubble.right()); newHeight += _attach->height() - rect::m::sum::v(bubble);
newHeight += _attach->height() - bubble.top() - bubble.bottom();
} }
} }
newHeight += padding.top() + padding.bottom(); newHeight += rect::m::sum::v(padding);
return { newWidth, newHeight }; return { newWidth, newHeight };
} }
TextSelection WebPage::toTitleSelection( TextSelection WebPage::toTitleSelection(TextSelection selection) const {
TextSelection selection) const {
return UnshiftItemSelection(selection, _siteName); return UnshiftItemSelection(selection, _siteName);
} }
TextSelection WebPage::fromTitleSelection( TextSelection WebPage::fromTitleSelection(TextSelection selection) const {
TextSelection selection) const {
return ShiftItemSelection(selection, _siteName); return ShiftItemSelection(selection, _siteName);
} }
TextSelection WebPage::toDescriptionSelection( TextSelection WebPage::toDescriptionSelection(TextSelection selection) const {
TextSelection selection) const {
return UnshiftItemSelection(toTitleSelection(selection), _title); return UnshiftItemSelection(toTitleSelection(selection), _title);
} }
@ -553,7 +564,7 @@ void WebPage::unloadHeavyPart() {
} }
void WebPage::draw(Painter &p, const PaintContext &context) const { void WebPage::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { if (width() < rect::m::sum::h(st::msgPadding) + 1) {
return; return;
} }
const auto st = context.st; const auto st = context.st;
@ -561,20 +572,48 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
const auto stm = context.messageStyle(); const auto stm = context.messageStyle();
const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); const auto bubble = _attach ? _attach->bubbleMargins() : QMargins();
const auto full = QRect(0, 0, width(), height()); const auto full = Rect(currentSize());
auto outer = full.marginsRemoved(inBubblePadding()); const auto outer = full - inBubblePadding();
auto inner = outer.marginsRemoved(innerMargin()); const auto inner = outer - innerMargin();
const auto attachAdditionalInfoText = _attach
? _attach->additionalInfoString()
: QString();
auto tshift = inner.top(); auto tshift = inner.top();
auto paintw = inner.width(); auto paintw = inner.width();
auto attachAdditionalInfoText = _attach ? _attach->additionalInfoString() : QString();
const auto selected = context.selected(); const auto selected = context.selected();
const auto colorIndex = parent()->colorIndex(); const auto view = parent();
const auto colorIndex = view->colorIndex();
const auto cache = context.outbg const auto cache = context.outbg
? stm->replyCache[st->colorPatternIndex(colorIndex)].get() ? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
: st->coloredReplyCache(selected, colorIndex).get(); : st->coloredReplyCache(selected, colorIndex).get();
const auto from = view->data()->displayFrom();
const auto backgroundEmojiId = from
? from->backgroundEmojiId()
: DocumentId();
const auto backgroundEmoji = backgroundEmojiId
? st->backgroundEmojiData(backgroundEmojiId).get()
: nullptr;
const auto backgroundEmojiCache = backgroundEmoji
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
selected,
context.outbg,
true,
colorIndex + 1)]
: nullptr;
Ui::Text::ValidateQuotePaintCache(*cache, _st); Ui::Text::ValidateQuotePaintCache(*cache, _st);
Ui::Text::FillQuotePaint(p, outer, *cache, _st); Ui::Text::FillQuotePaint(p, outer, *cache, _st);
if (backgroundEmoji) {
ValidateBackgroundEmoji(
backgroundEmojiId,
backgroundEmoji,
backgroundEmojiCache,
cache,
view);
if (!backgroundEmojiCache->frames[0].isNull()) {
FillBackgroundEmoji(p, outer, false, *backgroundEmojiCache);
}
}
if (_ripple) { if (_ripple) {
_ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg2); _ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg2);
@ -583,32 +622,36 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
} }
} }
const auto asSponsored = (!!_sponsoredData);
auto lineHeight = UnitedLineHeight(); auto lineHeight = UnitedLineHeight();
if (asArticle()) { if (asArticle()) {
ensurePhotoMediaCreated(); ensurePhotoMediaCreated();
QPixmap pix; auto pix = QPixmap();
auto pw = qMax(_pixw, lineHeight); const auto pw = qMax(_pixw, lineHeight);
auto ph = _pixh; const auto ph = _pixh;
auto pixw = _pixw, pixh = articleThumbHeight(_photoMedia.get(), _pixw); auto pixw = _pixw;
auto pixh = ArticleThumbHeight(_photoMedia.get(), _pixw);
const auto maxsize = _photoMedia->size(Data::PhotoSize::Thumbnail); const auto maxsize = _photoMedia->size(Data::PhotoSize::Thumbnail);
const auto maxw = style::ConvertScale(maxsize.width()); const auto maxw = style::ConvertScale(maxsize.width());
const auto maxh = style::ConvertScale(maxsize.height()); const auto maxh = style::ConvertScale(maxsize.height());
if (pixw * ph != pixh * pw) { if (pixw * ph != pixh * pw) {
float64 coef = (pixw * ph > pixh * pw) ? qMin(ph / float64(pixh), maxh / float64(pixh)) : qMin(pw / float64(pixw), maxw / float64(pixw)); const auto coef = (pixw * ph > pixh * pw)
pixh = qRound(pixh * coef); ? std::min(ph / float64(pixh), maxh / float64(pixh))
pixw = qRound(pixw * coef); : std::min(pw / float64(pixw), maxw / float64(pixw));
pixh = std::round(pixh * coef);
pixw = std::round(pixw * coef);
} }
const auto size = QSize(pixw, pixh); const auto size = QSize(pixw, pixh);
const auto args = Images::PrepareArgs{ const auto args = Images::PrepareArgs{
.options = Images::Option::RoundSmall, .options = Images::Option::RoundSmall,
.outer = { pw, ph }, .outer = { pw, ph },
}; };
if (const auto thumbnail = _photoMedia->image( using namespace Data;
Data::PhotoSize::Thumbnail)) { if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
pix = thumbnail->pixSingle(size, args); pix = thumbnail->pixSingle(size, args);
} else if (const auto small = _photoMedia->image( } else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
Data::PhotoSize::Small)) {
pix = small->pixSingle(size, args.blurred()); pix = small->pixSingle(size, args.blurred());
} else if (const auto blurred = _photoMedia->thumbnailInline()) { } else if (const auto blurred = _photoMedia->thumbnailInline()) {
pix = blurred->pixSingle(size, args.blurred()); pix = blurred->pixSingle(size, args.blurred());
@ -618,12 +661,21 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
const auto st = context.st; const auto st = context.st;
Ui::FillRoundRect( Ui::FillRoundRect(
p, p,
style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()), style::rtlrect(
inner.left() + paintw - pw,
tshift,
pw,
_pixh,
width()),
st->msgSelectOverlay(), st->msgSelectOverlay(),
st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small)); st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small));
} }
paintw -= pw + st::webPagePhotoDelta; if (!asSponsored) {
} else if (_sponsoredData && _sponsoredData->peer) { // Ignore photo width in sponsored messages,
// as its width only affects the title.
paintw -= pw + st::webPagePhotoDelta;
}
} else if (asSponsored && _sponsoredData->peer) {
const auto size = _pixh; const auto size = _pixh;
const auto sizeHq = size * style::DevicePixelRatio(); const auto sizeHq = size * style::DevicePixelRatio();
const auto userpicPos = QPoint(inner.left() + paintw - size, tshift); const auto userpicPos = QPoint(inner.left() + paintw - size, tshift);
@ -645,29 +697,53 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
? stm->semiboldPalette ? stm->semiboldPalette
: st->coloredTextPalette(selected, colorIndex)); : st->coloredTextPalette(selected, colorIndex));
auto endskip = 0; const auto endskip = _siteName.hasSkipBlock()
if (_siteName.hasSkipBlock()) { ? _parent->skipBlockWidth()
endskip = _parent->skipBlockWidth(); : 0;
} _siteName.drawLeftElided(
_siteName.drawLeftElided(p, inner.left(), tshift, paintw, width(), _siteNameLines, style::al_left, 0, -1, endskip, false, context.selection); p,
inner.left(),
tshift,
paintw,
width(),
_siteNameLines,
style::al_left,
0,
-1,
endskip,
false,
context.selection);
tshift += lineHeight; tshift += lineHeight;
p.setTextPalette(stm->textPalette); p.setTextPalette(stm->textPalette);
} }
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
if (_titleLines) { if (_titleLines) {
auto endskip = 0; const auto endskip = _title.hasSkipBlock()
if (_title.hasSkipBlock()) { ? _parent->skipBlockWidth()
endskip = _parent->skipBlockWidth(); : 0;
} const auto titleWidth = asSponsored
_title.drawLeftElided(p, inner.left(), tshift, paintw, width(), _titleLines, style::al_left, 0, -1, endskip, false, toTitleSelection(context.selection)); ? (paintw - _pixh - st::webPagePhotoDelta)
: paintw;
_title.drawLeftElided(
p,
inner.left(),
tshift,
titleWidth,
width(),
_titleLines,
style::al_left,
0,
-1,
endskip,
false,
toTitleSelection(context.selection));
tshift += _titleLines * lineHeight; tshift += _titleLines * lineHeight;
} }
if (_descriptionLines) { if (_descriptionLines) {
auto endskip = 0; const auto endskip = _description.hasSkipBlock()
if (_description.hasSkipBlock()) { ? _parent->skipBlockWidth()
endskip = _parent->skipBlockWidth(); : 0;
}
_parent->prepareCustomEmojiPaint(p, context, _description); _parent->prepareCustomEmojiPaint(p, context, _description);
_description.draw(p, { _description.draw(p, {
.position = { inner.left(), tshift }, .position = { inner.left(), tshift },
@ -688,12 +764,17 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
: _description.countHeight(paintw); : _description.countHeight(paintw);
} }
if (_attach) { if (_attach) {
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines; const auto attachAtTop = !_siteNameLines
if (!attachAtTop) tshift += st::mediaInBubbleSkip; && !_titleLines
&& !_descriptionLines;
if (!attachAtTop) {
tshift += st::mediaInBubbleSkip;
}
auto attachLeft = inner.left() - bubble.left(); const auto attachLeft = rtl()
auto attachTop = tshift - bubble.top(); ? (width() - (inner.left() - bubble.left()) - _attach->width())
if (rtl()) attachLeft = width() - attachLeft - _attach->width(); : (inner.left() - bubble.left());
const auto attachTop = tshift - bubble.top();
p.translate(attachLeft, attachTop); p.translate(attachLeft, attachTop);
@ -703,8 +784,8 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
).withSelection(context.selected() ).withSelection(context.selected()
? FullSelection ? FullSelection
: TextSelection())); : TextSelection()));
auto pixwidth = _attach->width(); const auto pixwidth = _attach->width();
auto pixheight = _attach->height(); const auto pixheight = _attach->height();
if (_data->type == WebPageType::Video if (_data->type == WebPageType::Video
&& _collage.empty() && _collage.empty()
@ -712,22 +793,47 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
&& !_data->document) { && !_data->document) {
if (_attach->isReadyForOpen()) { if (_attach->isReadyForOpen()) {
if (_data->siteName == u"YouTube"_q) { if (_data->siteName == u"YouTube"_q) {
st->youtubeIcon().paint(p, (pixwidth - st::youtubeIcon.width()) / 2, (pixheight - st::youtubeIcon.height()) / 2, width()); st->youtubeIcon().paint(
p,
(pixwidth - st::youtubeIcon.width()) / 2,
(pixheight - st::youtubeIcon.height()) / 2,
width());
} else { } else {
st->videoIcon().paint(p, (pixwidth - st::videoIcon.width()) / 2, (pixheight - st::videoIcon.height()) / 2, width()); st->videoIcon().paint(
p,
(pixwidth - st::videoIcon.width()) / 2,
(pixheight - st::videoIcon.height()) / 2,
width());
} }
} }
if (_durationWidth) { if (_durationWidth) {
auto dateX = pixwidth - _durationWidth - st::msgDateImgDelta - 2 * st::msgDateImgPadding.x(); const auto dateX = pixwidth
auto dateY = pixheight - st::msgDateFont->height - 2 * st::msgDateImgPadding.y() - st::msgDateImgDelta; - _durationWidth
auto dateW = pixwidth - dateX - st::msgDateImgDelta; - st::msgDateImgDelta
auto dateH = pixheight - dateY - st::msgDateImgDelta; - 2 * st::msgDateImgPadding.x();
const auto dateY = pixheight
- st::msgDateFont->height
- 2 * st::msgDateImgPadding.y()
- st::msgDateImgDelta;
const auto dateW = pixwidth - dateX - st::msgDateImgDelta;
const auto dateH = pixheight - dateY - st::msgDateImgDelta;
Ui::FillRoundRect(p, dateX, dateY, dateW, dateH, sti->msgDateImgBg, sti->msgDateImgBgCorners); Ui::FillRoundRect(
p,
dateX,
dateY,
dateW,
dateH,
sti->msgDateImgBg,
sti->msgDateImgBgCorners);
p.setFont(st::msgDateFont); p.setFont(st::msgDateFont);
p.setPen(st->msgDateImgFg()); p.setPen(st->msgDateImgFg());
p.drawTextLeft(dateX + st::msgDateImgPadding.x(), dateY + st::msgDateImgPadding.y(), pixwidth, _duration); p.drawTextLeft(
dateX + st::msgDateImgPadding.x(),
dateY + st::msgDateImgPadding.y(),
pixwidth,
_duration);
} }
} }
@ -736,7 +842,11 @@ void WebPage::draw(Painter &p, const PaintContext &context) const {
if (!attachAdditionalInfoText.isEmpty()) { if (!attachAdditionalInfoText.isEmpty()) {
p.setFont(st::msgDateFont); p.setFont(st::msgDateFont);
p.setPen(stm->msgDateFg); p.setPen(stm->msgDateFg);
p.drawTextLeft(st::msgPadding.left(), outer.y() + outer.height() + st::mediaInBubbleSkip, width(), attachAdditionalInfoText); p.drawTextLeft(
st::msgPadding.left(),
outer.y() + outer.height() + st::mediaInBubbleSkip,
width(),
attachAdditionalInfoText);
} }
} }
@ -763,74 +873,91 @@ bool WebPage::asArticle() const {
TextState WebPage::textState(QPoint point, StateRequest request) const { TextState WebPage::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent); auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { if (width() < rect::m::sum::h(st::msgPadding) + 1) {
return result; return result;
} }
const auto bubble = _attach ? _attach->bubbleMargins() : QMargins(); const auto bubble = _attach ? _attach->bubbleMargins() : QMargins();
const auto full = QRect(0, 0, width(), height()); const auto full = Rect(currentSize());
auto outer = full.marginsRemoved(inBubblePadding()); auto outer = full - inBubblePadding();
if (_sponsoredData) { if (_sponsoredData) {
outer.translate(0, st::msgDateFont->height); outer.translate(0, st::msgDateFont->height);
} }
auto inner = outer.marginsRemoved(innerMargin()); const auto inner = outer - innerMargin();
auto tshift = inner.top(); auto tshift = inner.top();
auto paintw = inner.width(); auto paintw = inner.width();
auto lineHeight = UnitedLineHeight(); const auto lineHeight = UnitedLineHeight();
auto inThumb = false; auto inThumb = false;
if (asArticle()) { if (asArticle()) {
auto pw = qMax(_pixw, lineHeight); const auto pw = std::max(_pixw, lineHeight);
if (style::rtlrect(inner.left() + paintw - pw, tshift, pw, _pixh, width()).contains(point)) { inThumb = style::rtlrect(
inThumb = true; inner.left() + paintw - pw,
} tshift,
pw,
_pixh,
width()).contains(point);
paintw -= pw + st::webPagePhotoDelta; paintw -= pw + st::webPagePhotoDelta;
} }
int symbolAdd = 0; auto symbolAdd = int(0);
if (_siteNameLines) { if (_siteNameLines) {
if (point.y() >= tshift && point.y() < tshift + lineHeight) { if (point.y() >= tshift && point.y() < tshift + lineHeight) {
Ui::Text::StateRequestElided siteNameRequest = request.forText(); auto siteNameRequest = Ui::Text::StateRequestElided(
request.forText());
siteNameRequest.lines = _siteNameLines; siteNameRequest.lines = _siteNameLines;
result = TextState(_parent, _siteName.getStateElidedLeft( result = TextState(
point - QPoint(inner.left(), tshift), _parent,
paintw, _siteName.getStateElidedLeft(
width(), point - QPoint(inner.left(), tshift),
siteNameRequest)); paintw,
width(),
siteNameRequest));
} else if (point.y() >= tshift + lineHeight) { } else if (point.y() >= tshift + lineHeight) {
symbolAdd += _siteName.length(); symbolAdd += _siteName.length();
} }
tshift += lineHeight; tshift += lineHeight;
} }
if (_titleLines) { if (_titleLines) {
if (point.y() >= tshift && point.y() < tshift + _titleLines * lineHeight) { if (point.y() >= tshift
Ui::Text::StateRequestElided titleRequest = request.forText(); && point.y() < tshift + _titleLines * lineHeight) {
auto titleRequest = Ui::Text::StateRequestElided(
request.forText());
titleRequest.lines = _titleLines; titleRequest.lines = _titleLines;
result = TextState(_parent, _title.getStateElidedLeft( result = TextState(
point - QPoint(inner.left(), tshift), _parent,
paintw, _title.getStateElidedLeft(
width(), point - QPoint(inner.left(), tshift),
titleRequest)); paintw,
width(),
titleRequest));
} else if (point.y() >= tshift + _titleLines * lineHeight) { } else if (point.y() >= tshift + _titleLines * lineHeight) {
symbolAdd += _title.length(); symbolAdd += _title.length();
} }
tshift += _titleLines * lineHeight; tshift += _titleLines * lineHeight;
} }
if (_descriptionLines) { if (_descriptionLines) {
auto descriptionHeight = (_descriptionLines > 0) ? _descriptionLines * lineHeight : _description.countHeight(paintw); const auto descriptionHeight = (_descriptionLines > 0)
? _descriptionLines * lineHeight
: _description.countHeight(paintw);
if (point.y() >= tshift && point.y() < tshift + descriptionHeight) { if (point.y() >= tshift && point.y() < tshift + descriptionHeight) {
if (_descriptionLines > 0) { if (_descriptionLines > 0) {
Ui::Text::StateRequestElided descriptionRequest = request.forText(); auto descriptionRequest = Ui::Text::StateRequestElided(
request.forText());
descriptionRequest.lines = _descriptionLines; descriptionRequest.lines = _descriptionLines;
result = TextState(_parent, _description.getStateElidedLeft( result = TextState(
point - QPoint(inner.left(), tshift), _parent,
paintw, _description.getStateElidedLeft(
width(), point - QPoint(inner.left(), tshift),
descriptionRequest)); paintw,
width(),
descriptionRequest));
} else { } else {
result = TextState(_parent, _description.getStateLeft( result = TextState(
point - QPoint(inner.left(), tshift), _parent,
paintw, _description.getStateLeft(
width(), point - QPoint(inner.left(), tshift),
request.forText())); paintw,
width(),
request.forText()));
} }
} else if (point.y() >= tshift + descriptionHeight) { } else if (point.y() >= tshift + descriptionHeight) {
symbolAdd += _description.length(); symbolAdd += _description.length();
@ -840,14 +967,26 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
if (inThumb) { if (inThumb) {
result.link = _openl; result.link = _openl;
} else if (_attach) { } else if (_attach) {
auto attachAtTop = !_siteNameLines && !_titleLines && !_descriptionLines; const auto attachAtTop = !_siteNameLines
if (!attachAtTop) tshift += st::mediaInBubbleSkip; && !_titleLines
&& !_descriptionLines;
if (!attachAtTop) {
tshift += st::mediaInBubbleSkip;
}
if (QRect(inner.left(), tshift, paintw, inner.top() + inner.height() - tshift).contains(point)) { const auto rect = QRect(
auto attachLeft = inner.left() - bubble.left(); inner.left(),
auto attachTop = tshift - bubble.top(); tshift,
if (rtl()) attachLeft = width() - attachLeft - _attach->width(); paintw,
result = _attach->textState(point - QPoint(attachLeft, attachTop), request); inner.top() + inner.height() - tshift);
if (rect.contains(point)) {
const auto attachLeft = rtl()
? width() - (inner.left() - bubble.left()) - _attach->width()
: (inner.left() - bubble.left());
const auto attachTop = tshift - bubble.top();
result = _attach->textState(
point - QPoint(attachLeft, attachTop),
request);
if (result.cursor == CursorState::Enlarge) { if (result.cursor == CursorState::Enlarge) {
result.cursor = CursorState::None; result.cursor = CursorState::None;
} else { } else {
@ -879,27 +1018,37 @@ ClickHandlerPtr WebPage::replaceAttachLink(
return _openl; return _openl;
} }
TextSelection WebPage::adjustSelection(TextSelection selection, TextSelectType type) const { TextSelection WebPage::adjustSelection(
if ((!_titleLines && !_descriptionLines) || selection.to <= _siteName.length()) { TextSelection selection,
TextSelectType type) const {
if ((!_titleLines && !_descriptionLines)
|| selection.to <= _siteName.length()) {
return _siteName.adjustSelection(selection, type); return _siteName.adjustSelection(selection, type);
} }
auto titlesLength = _siteName.length() + _title.length(); const auto titlesLength = _siteName.length() + _title.length();
auto titleSelection = _title.adjustSelection(toTitleSelection(selection), type); const auto titleSelection = _title.adjustSelection(
if ((!_siteNameLines && !_descriptionLines) || (selection.from >= _siteName.length() && selection.to <= titlesLength)) { toTitleSelection(selection),
type);
if ((!_siteNameLines && !_descriptionLines)
|| (selection.from >= _siteName.length()
&& selection.to <= titlesLength)) {
return fromTitleSelection(titleSelection); return fromTitleSelection(titleSelection);
} }
auto descriptionSelection = _description.adjustSelection(toDescriptionSelection(selection), type); const auto descriptionSelection = _description.adjustSelection(
toDescriptionSelection(selection),
type);
if ((!_siteNameLines && !_titleLines) || selection.from >= titlesLength) { if ((!_siteNameLines && !_titleLines) || selection.from >= titlesLength) {
return fromDescriptionSelection(descriptionSelection); return fromDescriptionSelection(descriptionSelection);
} }
auto siteNameSelection = _siteName.adjustSelection(selection, type); return {
if (!_descriptionLines || selection.to <= titlesLength) { _siteName.adjustSelection(selection, type).from,
return { siteNameSelection.from, fromTitleSelection(titleSelection).to }; (!_descriptionLines || selection.to <= titlesLength)
} ? fromTitleSelection(titleSelection).to
return { siteNameSelection.from, fromDescriptionSelection(descriptionSelection).to }; : fromDescriptionSelection(descriptionSelection).to,
};
} }
uint16 WebPage::fullSelectionLength() const { uint16 WebPage::fullSelectionLength() const {
@ -920,8 +1069,8 @@ void WebPage::clickHandlerPressedChanged(
if (p == _openl) { if (p == _openl) {
if (pressed) { if (pressed) {
if (!_ripple) { if (!_ripple) {
const auto full = QRect(0, 0, width(), height()); const auto full = Rect(currentSize());
const auto outer = full.marginsRemoved(inBubblePadding()); const auto outer = full - inBubblePadding();
const auto owner = &parent()->history()->owner(); const auto owner = &parent()->history()->owner();
_ripple = std::make_unique<Ui::RippleAnimation>( _ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation, st::defaultRippleAnimation,
@ -957,23 +1106,20 @@ void WebPage::playAnimation(bool autoplay) {
} }
bool WebPage::isDisplayed() const { bool WebPage::isDisplayed() const {
const auto item = _parent->data();
return !_data->pendingTill return !_data->pendingTill
&& !_data->failed && !_data->failed
&& !item->Has<HistoryMessageLogEntryOriginal>(); && !_parent->data()->Has<HistoryMessageLogEntryOriginal>();
} }
QString WebPage::additionalInfoString() const { QString WebPage::additionalInfoString() const {
return _attach ? _attach->additionalInfoString() : QString(); return _attach ? _attach->additionalInfoString() : QString();
} }
bool WebPage::toggleSelectionByHandlerClick( bool WebPage::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
const ClickHandlerPtr &p) const {
return _attach && _attach->toggleSelectionByHandlerClick(p); return _attach && _attach->toggleSelectionByHandlerClick(p);
} }
bool WebPage::allowTextSelectionByHandler( bool WebPage::allowTextSelectionByHandler(const ClickHandlerPtr &p) const {
const ClickHandlerPtr &p) const {
return (p == _openl); return (p == _openl);
} }
@ -983,8 +1129,7 @@ bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const {
TextForMimeData WebPage::selectedText(TextSelection selection) const { TextForMimeData WebPage::selectedText(TextSelection selection) const {
auto siteNameResult = _siteName.toTextForMimeData(selection); auto siteNameResult = _siteName.toTextForMimeData(selection);
auto titleResult = _title.toTextForMimeData( auto titleResult = _title.toTextForMimeData(toTitleSelection(selection));
toTitleSelection(selection));
auto descriptionResult = _description.toTextForMimeData( auto descriptionResult = _description.toTextForMimeData(
toDescriptionSelection(selection)); toDescriptionSelection(selection));
if (titleResult.empty() && descriptionResult.empty()) { if (titleResult.empty() && descriptionResult.empty()) {
@ -996,12 +1141,18 @@ TextForMimeData WebPage::selectedText(TextSelection selection) const {
} else if (siteNameResult.empty()) { } else if (siteNameResult.empty()) {
return titleResult.append('\n').append(std::move(descriptionResult)); return titleResult.append('\n').append(std::move(descriptionResult));
} else if (titleResult.empty()) { } else if (titleResult.empty()) {
return siteNameResult.append('\n').append(std::move(descriptionResult)); return siteNameResult
.append('\n')
.append(std::move(descriptionResult));
} else if (descriptionResult.empty()) { } else if (descriptionResult.empty()) {
return siteNameResult.append('\n').append(std::move(titleResult)); return siteNameResult.append('\n').append(std::move(titleResult));
} }
return siteNameResult.append('\n').append(std::move(titleResult)).append('\n').append(std::move(descriptionResult)); return siteNameResult
.append('\n')
.append(std::move(titleResult))
.append('\n')
.append(std::move(descriptionResult));
} }
QMargins WebPage::inBubblePadding() const { QMargins WebPage::inBubblePadding() const {

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