Merge tag 'v4.11.1' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/history/history_inner_widget.cpp
#	Telegram/SourceFiles/platform/win/tray_win.cpp
#	Telegram/lib_ui
This commit is contained in:
ZavaruKitsu 2023-10-29 14:35:43 +03:00
commit 53e4b685b4
251 changed files with 10939 additions and 3220 deletions

View file

@ -265,6 +265,8 @@ PRIVATE
boxes/peers/edit_participant_box.h boxes/peers/edit_participant_box.h
boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.cpp
boxes/peers/edit_participants_box.h boxes/peers/edit_participants_box.h
boxes/peers/edit_peer_color_box.cpp
boxes/peers/edit_peer_color_box.h
boxes/peers/edit_peer_common.h boxes/peers/edit_peer_common.h
boxes/peers/edit_peer_info_box.cpp boxes/peers/edit_peer_info_box.cpp
boxes/peers/edit_peer_info_box.h boxes/peers/edit_peer_info_box.h
@ -718,6 +720,8 @@ PRIVATE
history/view/controls/history_view_compose_controls.h history/view/controls/history_view_compose_controls.h
history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.cpp
history/view/controls/history_view_compose_search.h history/view/controls/history_view_compose_search.h
history/view/controls/history_view_draft_options.cpp
history/view/controls/history_view_draft_options.h
history/view/controls/history_view_forward_panel.cpp history/view/controls/history_view_forward_panel.cpp
history/view/controls/history_view_forward_panel.h history/view/controls/history_view_forward_panel.h
history/view/controls/history_view_ttl_button.cpp history/view/controls/history_view_ttl_button.cpp
@ -726,6 +730,8 @@ PRIVATE
history/view/controls/history_view_voice_record_bar.h history/view/controls/history_view_voice_record_bar.h
history/view/controls/history_view_voice_record_button.cpp history/view/controls/history_view_voice_record_button.cpp
history/view/controls/history_view_voice_record_button.h history/view/controls/history_view_voice_record_button.h
history/view/controls/history_view_webpage_processor.cpp
history/view/controls/history_view_webpage_processor.h
history/view/media/history_view_call.cpp history/view/media/history_view_call.cpp
history/view/media/history_view_call.h history/view/media/history_view_call.h
history/view/media/history_view_contact.cpp history/view/media/history_view_contact.cpp
@ -744,6 +750,8 @@ PRIVATE
history/view/media/history_view_game.h history/view/media/history_view_game.h
history/view/media/history_view_gif.cpp history/view/media/history_view_gif.cpp
history/view/media/history_view_gif.h history/view/media/history_view_gif.h
history/view/media/history_view_giveaway.cpp
history/view/media/history_view_giveaway.h
history/view/media/history_view_invoice.cpp history/view/media/history_view_invoice.cpp
history/view/media/history_view_invoice.h history/view/media/history_view_invoice.h
history/view/media/history_view_large_emoji.cpp history/view/media/history_view_large_emoji.cpp
@ -883,20 +891,6 @@ PRIVATE
history/history_view_highlight_manager.h history/history_view_highlight_manager.h
history/history_widget.cpp history/history_widget.cpp
history/history_widget.h history/history_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
info/boosts/info_boosts_inner_widget.cpp info/boosts/info_boosts_inner_widget.cpp
info/boosts/info_boosts_inner_widget.h info/boosts/info_boosts_inner_widget.h
info/boosts/info_boosts_widget.cpp info/boosts/info_boosts_widget.cpp
@ -983,6 +977,20 @@ PRIVATE
info/userpic/info_userpic_emoji_builder_preview.h info/userpic/info_userpic_emoji_builder_preview.h
info/userpic/info_userpic_emoji_builder_widget.cpp info/userpic/info_userpic_emoji_builder_widget.cpp
info/userpic/info_userpic_emoji_builder_widget.h info/userpic/info_userpic_emoji_builder_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h inline_bots/bot_attach_web_view.h
inline_bots/inline_bot_layout_internal.cpp inline_bots/inline_bot_layout_internal.cpp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reconnecting#other" = "Reconnect in {count} s..."; "lng_reconnecting#other" = "Reconnect in {count} s...";
"lng_reconnecting_try_now" = "Try now"; "lng_reconnecting_try_now" = "Try now";
"lng_code_block_header_copy" = "copy";
"lng_status_service_notifications" = "service notifications"; "lng_status_service_notifications" = "service notifications";
"lng_status_support" = "support"; "lng_status_support" = "support";
"lng_status_bot" = "bot"; "lng_status_bot" = "bot";
@ -402,6 +404,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_search_from" = "From: {user}"; "lng_dlg_search_from" = "From: {user}";
"lng_settings_save" = "Save"; "lng_settings_save" = "Save";
"lng_settings_apply" = "Apply";
"lng_username_title" = "Username"; "lng_username_title" = "Username";
"lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number."; "lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.";
@ -770,11 +773,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_clear_payment_info_clear" = "Clear"; "lng_clear_payment_info_clear" = "Clear";
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data."; "lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
"lng_settings_theme_settings" = "Theme settings";
"lng_settings_theme_name_color" = "Your name color";
"lng_settings_auto_night_mode" = "Auto-Night mode"; "lng_settings_auto_night_mode" = "Auto-Night mode";
"lng_settings_auto_night_enabled" = "Match the system settings"; "lng_settings_auto_night_mode_off" = "Off";
"lng_settings_auto_night_mode_on" = "System";
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first."; "lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
"lng_settings_auto_night_disable" = "Disable"; "lng_settings_auto_night_disable" = "Disable";
"lng_settings_color_title" = "Color preview";
"lng_settings_color_reply" = "Reply to your message";
"lng_settings_color_reply_channel" = "Reply to your channel message";
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color.";
"lng_settings_color_link_name" = "Telegram";
"lng_settings_color_link_title" = "Link Preview";
"lng_settings_color_link_description" = "Your selected color will also tint the link preview.";
"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages.";
"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages.";
"lng_settings_color_emoji" = "Add icons to replies";
"lng_settings_color_emoji_remove" = "Remove icon";
"lng_settings_color_emoji_off" = "Off";
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
"lng_settings_color_changed" = "Your name color has been updated!";
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
"lng_suggest_hide_new_title" = "Hide new chats?"; "lng_suggest_hide_new_title" = "Hide new chats?";
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?"; "lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
"lng_suggest_hide_new_to_settings" = "Go to Settings"; "lng_suggest_hide_new_to_settings" = "Go to Settings";
@ -802,7 +827,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!"; "lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
"lng_background_text2" = "I can't even take you seriously right now."; "lng_background_text2" = "I can't even take you seriously right now.";
"lng_background_bad_link" = "This background link appears to be invalid."; "lng_background_bad_link" = "This background link appears to be invalid.";
"lng_background_apply" = "Apply";
"lng_background_share" = "Share"; "lng_background_share" = "Share";
"lng_background_link_copied" = "Link copied to clipboard"; "lng_background_link_copied" = "Link copied to clipboard";
"lng_background_blur" = "Blurred"; "lng_background_blur" = "Blurred";
@ -1142,6 +1166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mute_box_title" = "Mute notifications for..."; "lng_mute_box_title" = "Mute notifications for...";
"lng_preview_loading" = "Getting Link Info..."; "lng_preview_loading" = "Getting Link Info...";
"lng_preview_cant" = "Could not generate preview for this link.";
"lng_profile_settings_section" = "Settings"; "lng_profile_settings_section" = "Settings";
"lng_profile_bot_settings" = "Bot Settings"; "lng_profile_bot_settings" = "Bot Settings";
@ -1640,6 +1665,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_story_mention_button" = "View Story"; "lng_action_story_mention_button" = "View Story";
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available."; "lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available."; "lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months"; "lng_premium_gift_duration_months#other" = "for {count} months";
@ -2034,6 +2060,129 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; "lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace"; "lng_boost_now_replace" = "Replace";
"lng_boost_channel_title_color" = "Enable colors";
"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_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
"lng_boost_channel_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or";
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
"lng_boost_channel_gifting_link" = "Get boosts >";
"lng_giveaway_new_title" = "Boosts via Gifts";
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
"lng_giveaway_create_option" = "Create Giveaway";
"lng_giveaway_create_subtitle" = "winners are chosen randomly";
"lng_giveaway_award_option" = "Award Specific Users";
"lng_giveaway_award_subtitle" = "Select recipients >";
"lng_giveaway_award_chosen#one" = "{count} recipient >";
"lng_giveaway_award_chosen#other" = "{count} recipients >";
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
"lng_giveaway_channels_title" = "Channels included in the giveaway";
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts";
"lng_giveaway_channels_add" = "Add Channel";
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
"lng_giveaway_users_title" = "Users eligible for the giveaway";
"lng_giveaway_users_all" = "All subscribers";
"lng_giveaway_users_new" = "Only new subscribers";
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
"lng_giveaway_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium";
"lng_giveaway_date_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time";
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
"lng_giveaway_duration_price" = "{price} x {amount}";
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_giveaway_duration_about_link" = "here";
"lng_giveaway_date_select" = "Select Date and Time";
"lng_giveaway_date_confirm" = "Confirm";
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
"lng_giveaway_recipients_save" = "Save Recipients";
"lng_giveaway_recipients_deselect" = "Deselect All";
"lng_prize_title" = "Congratulations!";
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}.";
"lng_prize_gift_about" = "You've received a gift from {channel}.";
"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}.";
"lng_prize_open" = "Open Gift Link";
"lng_prize_unclaimed_title" = "Unclaimed Prize";
"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}.";
"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}.";
"lng_prizes_title#one" = "Giveaway Prize";
"lng_prizes_title#other" = "Giveaway Prizes";
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
"lng_prizes_participants" = "Participants";
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
"lng_prizes_countries" = "from {countries}";
"lng_prizes_countries_and_one" = "{countries}, {country}";
"lng_prizes_countries_and_last" = "{countries} and {country}";
"lng_prizes_date" = "Winners Selection Date";
"lng_prizes_how_works" = "Learn more";
"lng_prizes_how_title" = "About this giveaway";
"lng_prizes_end_title" = "Giveaway ended";
"lng_prizes_how_text" = "This giveaway is 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#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
"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_activated#one" = "**{count}** of the winners already used their gift link.";
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}.";
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}.";
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels.";
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels.";
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}";
"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}";
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}";
"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}.";
"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}.";
"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel}).";
"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started.";
"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway.";
"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}.";
"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels).";
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
"lng_prizes_view_prize" = "View my prize";
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway.";
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}";
"lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"lng_gift_link_label_from" = "From";
"lng_gift_link_label_to" = "To";
"lng_gift_link_label_to_unclaimed" = "No recipient";
"lng_gift_link_label_gift" = "Gift";
"lng_gift_link_gift_premium" = "Telegram Premium {duration}";
"lng_gift_link_label_reason" = "Reason";
"lng_gift_link_reason_giveaway" = "Giveaway";
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
"lng_gift_link_reason_chosen" = "You were selected by the channel";
"lng_gift_link_label_date" = "Date";
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
"lng_gift_link_also_send_link" = "send this link";
"lng_gift_link_use" = "Use Link";
"lng_gift_link_used_title" = "Used Gift Link";
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
"lng_gift_link_used_footer" = "This link was used on {date}.";
"lng_gift_link_expired" = "Gift code link expired";
"lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts."; "lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
@ -2213,6 +2362,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_saved_messages" = "Saved Messages"; "lng_saved_messages" = "Saved Messages";
"lng_saved_short" = "Save"; "lng_saved_short" = "Save";
"lng_saved_forward_here" = "Forward messages here for quick access"; "lng_saved_forward_here" = "Forward messages here for quick access";
"lng_saved_quote_here" = "Quote here to save";
"lng_scheduled_messages" = "Scheduled Messages"; "lng_scheduled_messages" = "Scheduled Messages";
"lng_scheduled_messages_empty" = "No scheduled messages here yet..."; "lng_scheduled_messages_empty" = "No scheduled messages here yet...";
@ -2420,6 +2570,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_attached_stickers" = "Attached Stickers"; "lng_context_attached_stickers" = "Attached Stickers";
"lng_context_to_msg" = "Go To Message"; "lng_context_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply"; "lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit"; "lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message"; "lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now"; "lng_context_send_now_msg" = "Send now";
@ -2524,6 +2675,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_inline_switch_choose" = "Choose conversation..."; "lng_inline_switch_choose" = "Choose conversation...";
"lng_inline_switch_cant" = "Sorry, no way to write here :("; "lng_inline_switch_cant" = "Sorry, no way to write here :(";
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
"lng_reply_show_in_chat" = "Show in Chat";
"lng_reply_remove" = "Do Not Reply";
"lng_reply_about_quote" = "You can select specific part to quote.";
"lng_reply_options_header" = "Reply to Message";
"lng_reply_options_quote" = "Update Quote";
"lng_reply_header_short" = "Reply";
"lng_reply_quote_selected" = "Quote Selected";
"lng_link_options_header" = "Link Preview Settings";
"lng_link_header_short" = "Link";
"lng_link_move_up" = "Move Up";
"lng_link_move_down" = "Move Down";
"lng_link_shrink_photo" = "Shrink Photo";
"lng_link_enlarge_photo" = "Enlarge Photo";
"lng_link_remove" = "Do Not Preview";
"lng_link_about_choose" = "Click on a link to generate its preview.";
"lng_share_cant" = "Sorry, no way to share here :("; "lng_share_cant" = "Sorry, no way to share here :(";
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?"; "lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
@ -2546,6 +2715,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_bot_title" = "Edit bot"; "lng_edit_bot_title" = "Edit bot";
"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_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";
@ -2674,6 +2844,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_formatting_italic" = "Italic"; "lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_underline" = "Underline"; "lng_menu_formatting_underline" = "Underline";
"lng_menu_formatting_strike_out" = "Strike-through"; "lng_menu_formatting_strike_out" = "Strike-through";
"lng_menu_formatting_blockquote" = "Quote";
"lng_menu_formatting_monospace" = "Monospace"; "lng_menu_formatting_monospace" = "Monospace";
"lng_menu_formatting_spoiler" = "Spoiler"; "lng_menu_formatting_spoiler" = "Spoiler";
"lng_menu_formatting_link_create" = "Create link"; "lng_menu_formatting_link_create" = "Create link";
@ -2686,7 +2857,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_formatting_link_create" = "Create"; "lng_formatting_link_create" = "Create";
"lng_text_copied" = "Text copied to clipboard."; "lng_text_copied" = "Text copied to clipboard.";
"lng_code_copied" = "Code copied to clipboard."; "lng_code_copied" = "Block copied to clipboard.";
"lng_spellchecker_submenu" = "Spelling"; "lng_spellchecker_submenu" = "Spelling";
"lng_spellchecker_add" = "Add to Dictionary"; "lng_spellchecker_add" = "Add to Dictionary";
@ -3352,6 +3523,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}"; "lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam"; "lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam"; "lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}";
"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_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
"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}";

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.10.4.0" /> Version="4.11.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,10,4,0 FILEVERSION 4,11,1,0
PRODUCTVERSION 4,10,4,0 PRODUCTVERSION 4,11,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.10.4.0" VALUE "FileVersion", "4.11.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.10.4.0" VALUE "ProductVersion", "4.11.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,10,4,0 FILEVERSION 4,11,1,0
PRODUCTVERSION 4,10,4,0 PRODUCTVERSION 4,11,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.10.4.0" VALUE "FileVersion", "4.11.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.10.4.0" VALUE "ProductVersion", "4.11.1.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -169,9 +169,7 @@ void SendBotCallbackData(
void HideSingleUseKeyboard( void HideSingleUseKeyboard(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) { not_null<HistoryItem*> item) {
controller->content()->hideSingleUseKeyboard( controller->content()->hideSingleUseKeyboard(item->fullId());
item->history()->peer,
item->id);
} }
} // namespace } // namespace
@ -312,12 +310,14 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::Default: { case ButtonType::Default: {
// Copy string before passing it to the sending method // Copy string before passing it to the sending method
// because the original button can be destroyed inside. // because the original button can be destroyed inside.
const auto replyTo = item->isRegular() ? item->id : 0; const auto replyTo = item->isRegular()
? item->fullId()
: FullMsgId();
controller->content()->sendBotCommand({ controller->content()->sendBotCommand({
.peer = item->history()->peer, .peer = item->history()->peer,
.command = QString(button->text), .command = QString(button->text),
.context = item->fullId(), .context = item->fullId(),
.replyTo = replyTo, .replyTo = { replyTo },
}); });
} break; } break;
@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
case ButtonType::RequestPhone: { case ButtonType::RequestPhone: {
HideSingleUseKeyboard(controller, item); HideSingleUseKeyboard(controller, item);
const auto itemId = item->id; const auto itemId = item->fullId();
const auto topicRootId = item->topicRootId(); const auto topicRootId = item->topicRootId();
const auto history = item->history(); const auto history = item->history();
controller->show(Ui::MakeConfirmBox({ controller->show(Ui::MakeConfirmBox({
@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
auto action = Api::SendAction(history); auto action = Api::SendAction(history);
action.clearDraft = false; action.clearDraft = false;
action.replyTo = { action.replyTo = {
.msgId = itemId, .messageId = itemId,
.topicRootId = topicRootId, .topicRootId = topicRootId,
}; };
history->session().api().shareContact( history->session().api().shareContact(
@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
chosen |= PollData::Flag::Quiz; chosen |= PollData::Flag::Quiz;
} }
} }
const auto replyToId = MsgId(0); const auto replyTo = FullReplyTo();
const auto topicRootId = MsgId(0);
Window::PeerMenuCreatePoll( Window::PeerMenuCreatePoll(
controller, controller,
item->history()->peer, item->history()->peer,
replyToId, replyTo,
topicRootId,
chosen, chosen,
disabled); disabled);
} break; } break;

View file

@ -19,8 +19,8 @@ SendAction::SendAction(
SendOptions options) SendOptions options)
: history(thread->owningHistory()) : history(thread->owningHistory())
, options(options) , options(options)
, replyTo({ .msgId = thread->topicRootId() }) { , replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) {
replyTo.topicRootId = replyTo.msgId; replyTo.topicRootId = replyTo.messageId.msg;
} }
SendOptions DefaultSendWhenOnlineOptions() { SendOptions DefaultSendWhenOnlineOptions() {
@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() {
} }
MTPInputReplyTo SendAction::mtpReplyTo() const { MTPInputReplyTo SendAction::mtpReplyTo() const {
return Data::ReplyToForMTP(&history->owner(), replyTo); return Data::ReplyToForMTP(history, replyTo);
} }
} // namespace Api } // namespace Api

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/data_drafts.h"
class History; class History;
namespace Data { namespace Data {
@ -22,7 +24,6 @@ struct SendOptions {
TimeId scheduled = 0; TimeId scheduled = 0;
bool silent = false; bool silent = false;
bool handleSupportSwitch = false; bool handleSupportSwitch = false;
bool removeWebPageId = false;
bool hideViaBot = false; bool hideViaBot = false;
}; };
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions(); [[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
@ -54,7 +55,7 @@ struct MessageToSend {
SendAction action; SendAction action;
TextWithTags textWithTags; TextWithTags textWithTags;
WebPageId webPageId = 0; Data::WebPageDraft webPage;
}; };
struct RemoteFileInfo { struct RemoteFileInfo {

View file

@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_media.h" #include "api/api_media.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "data/data_histories.h"
#include "data/data_scheduled_messages.h" #include "data/data_scheduled_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -45,6 +47,7 @@ template <typename DoneCallback, typename FailCallback>
mtpRequestId EditMessage( mtpRequestId EditMessage(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const TextWithEntities &textWithEntities, const TextWithEntities &textWithEntities,
Data::WebPageDraft webpage,
SendOptions options, SendOptions options,
DoneCallback &&done, DoneCallback &&done,
FailCallback &&fail, FailCallback &&fail,
@ -65,15 +68,21 @@ mtpRequestId EditMessage(
const auto emptyFlag = MTPmessages_EditMessage::Flag(0); const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
const auto flags = emptyFlag const auto flags = emptyFlag
| (!text.isEmpty() || media | ((!text.isEmpty() || media)
? MTPmessages_EditMessage::Flag::f_message ? MTPmessages_EditMessage::Flag::f_message
: emptyFlag) : emptyFlag)
| ((media && inputMedia.has_value()) | ((media && inputMedia.has_value())
? MTPmessages_EditMessage::Flag::f_media ? MTPmessages_EditMessage::Flag::f_media
: emptyFlag) : emptyFlag)
| (options.removeWebPageId | (webpage.removed
? MTPmessages_EditMessage::Flag::f_no_webpage ? MTPmessages_EditMessage::Flag::f_no_webpage
: emptyFlag) : emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty() | (!sentEntities.v.isEmpty()
? MTPmessages_EditMessage::Flag::f_entities ? MTPmessages_EditMessage::Flag::f_entities
: emptyFlag) : emptyFlag)
@ -89,7 +98,7 @@ mtpRequestId EditMessage(
item->history()->peer->input, item->history()->peer->input,
MTP_int(id), MTP_int(id),
MTP_string(text), MTP_string(text),
inputMedia.value_or(MTPInputMedia()), inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
MTPReplyMarkup(), MTPReplyMarkup(),
sentEntities, sentEntities,
MTP_int(options.scheduled) MTP_int(options.scheduled)
@ -133,9 +142,15 @@ mtpRequestId EditMessage(
FailCallback &&fail, FailCallback &&fail,
std::optional<MTPInputMedia> inputMedia = std::nullopt) { std::optional<MTPInputMedia> inputMedia = std::nullopt) {
const auto &text = item->originalText(); const auto &text = item->originalText();
const auto webpage = (!item->media() || !item->media()->webpage())
? Data::WebPageDraft{ .removed = true }
: Data::WebPageDraft{
.id = item->media()->webpage()->id,
};
return EditMessage( return EditMessage(
item, item,
text, text,
webpage,
options, options,
std::forward<DoneCallback>(done), std::forward<DoneCallback>(done),
std::forward<FailCallback>(fail), std::forward<FailCallback>(fail),
@ -216,12 +231,19 @@ mtpRequestId EditCaption(
SendOptions options, SendOptions options,
Fn<void()> done, Fn<void()> done,
Fn<void(const QString &)> fail) { Fn<void(const QString &)> fail) {
return EditMessage(item, caption, options, done, fail); return EditMessage(
item,
caption,
Data::WebPageDraft(),
options,
done,
fail);
} }
mtpRequestId EditTextMessage( mtpRequestId EditTextMessage(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const TextWithEntities &caption, const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options, SendOptions options,
Fn<void(mtpRequestId requestId)> done, Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &, mtpRequestId requestId)> fail) { Fn<void(const QString &, mtpRequestId requestId)> fail) {
@ -229,7 +251,7 @@ mtpRequestId EditTextMessage(
applyUpdates(); applyUpdates();
done(id); done(id);
}; };
return EditMessage(item, caption, options, callback, fail); return EditMessage(item, caption, webpage, options, callback, fail);
} }
} // namespace Api } // namespace Api

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class HistoryItem; class HistoryItem;
namespace Data {
struct WebPageDraft;
} // namespace Data
namespace MTP { namespace MTP {
class Error; class Error;
} // namespace MTP } // namespace MTP
@ -48,6 +52,7 @@ mtpRequestId EditCaption(
mtpRequestId EditTextMessage( mtpRequestId EditTextMessage(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const TextWithEntities &caption, const TextWithEntities &caption,
Data::WebPageDraft webpage,
SendOptions options, SendOptions options,
Fn<void(mtpRequestId requestId)> done, Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail); Fn<void(const QString &error, mtpRequestId requestId)> fail);

View file

@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos(
_userPhotosRequests.emplace(user, requestId); _userPhotosRequests.emplace(user, requestId);
} }
auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
switch (type) {
case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList;
}
Unexpected("Type in PeerPhoto::emojiList.");
}
auto PeerPhoto::emojiList(EmojiListType type) const
-> const EmojiListData & {
return const_cast<PeerPhoto*>(this)->emojiList(type);
}
void PeerPhoto::requestEmojiList(EmojiListType type) { void PeerPhoto::requestEmojiList(EmojiListType type) {
if (_requestIdEmojiList) { auto &list = emojiList(type);
if (list.requestId) {
return; return;
} }
const auto isGroup = (type == EmojiListType::Group); const auto send = [&](auto &&request) {
const auto d = [=](const MTPEmojiList &result) { return _api.request(
_requestIdEmojiList = 0; std::move(request)
result.match([](const MTPDemojiListNotModified &data) { ).done([=](const MTPEmojiList &result) {
}, [&](const MTPDemojiList &data) { auto &list = emojiList(type);
auto &list = isGroup ? _profileEmojiList : _groupEmojiList; list.requestId = 0;
list = ranges::views::all( result.match([](const MTPDemojiListNotModified &data) {
data.vdocument_id().v }, [&](const MTPDemojiList &data) {
) | ranges::views::transform(&MTPlong::v) | ranges::to_vector; list.list = ranges::views::all(
}); data.vdocument_id().v
) | ranges::views::transform(
&MTPlong::v
) | ranges::to_vector;
});
}).fail([=] {
emojiList(type).requestId = 0;
}).send();
}; };
const auto f = [=] { _requestIdEmojiList = 0; }; list.requestId = (type == EmojiListType::Profile)
_requestIdEmojiList = isGroup ? send(MTPaccount_GetDefaultProfilePhotoEmojis())
? _api.request( : (type == EmojiListType::Group)
MTPaccount_GetDefaultGroupPhotoEmojis() ? send(MTPaccount_GetDefaultGroupPhotoEmojis())
).done(d).fail(f).send() : send(MTPaccount_GetDefaultBackgroundEmojis());
: _api.request(
MTPaccount_GetDefaultProfilePhotoEmojis()
).done(d).fail(f).send();
} }
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue( rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
EmojiListType type) { EmojiListType type) {
auto &list = (type == EmojiListType::Group) auto &list = emojiList(type);
? _profileEmojiList if (list.list.current().empty() && !list.requestId) {
: _groupEmojiList;
if (list.current().empty() && !_requestIdEmojiList) {
requestEmojiList(type); requestEmojiList(type);
} }
return list.value(); return list.list.value();
} }
// Non-personal photo in case a personal photo is set. // Non-personal photo in case a personal photo is set.

View file

@ -31,6 +31,7 @@ public:
enum class EmojiListType { enum class EmojiListType {
Profile, Profile,
Group, Group,
Background,
}; };
struct UserPhoto { struct UserPhoto {
@ -73,6 +74,10 @@ private:
Suggestion, Suggestion,
Fallback, Fallback,
}; };
struct EmojiListData {
rpl::variable<EmojiList> list;
mtpRequestId requestId = 0;
};
void ready( void ready(
const FullMsgId &msgId, const FullMsgId &msgId,
@ -84,6 +89,9 @@ private:
UploadType type, UploadType type,
Fn<void()> done); Fn<void()> done);
[[nodiscard]] EmojiListData &emojiList(EmojiListType type);
[[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -101,9 +109,9 @@ private:
not_null<UserData*>, not_null<UserData*>,
not_null<PhotoData*>> _nonPersonalPhotos; not_null<PhotoData*>> _nonPersonalPhotos;
mtpRequestId _requestIdEmojiList = 0; EmojiListData _profileEmojiList;
rpl::variable<EmojiList> _profileEmojiList; EmojiListData _groupEmojiList;
rpl::variable<EmojiList> _groupEmojiList; EmojiListData _backgroundEmojiList;
}; };

View file

@ -47,7 +47,7 @@ void Polls::create(
const auto history = action.history; const auto history = action.history;
const auto peer = history->peer; const auto peer = history->peer;
const auto topicRootId = action.replyTo.msgId const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId ? action.replyTo.topicRootId
: 0; : 0;
auto sendFlags = MTPmessages_SendMedia::Flags(0); auto sendFlags = MTPmessages_SendMedia::Flags(0);

View file

@ -17,6 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
namespace Api { namespace Api {
namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return {
.from = peerFromMTP(data.vfrom_id()),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
.date = data.vdate().v,
.used = data.vused_date().value_or_empty(),
.months = data.vmonths().v,
.giveaway = data.is_via_giveaway(),
};
}
} // namespace
Premium::Premium(not_null<ApiWrap*> api) Premium::Premium(not_null<ApiWrap*> api)
: _session(&api->session()) : _session(&api->session())
@ -183,6 +198,115 @@ void Premium::reloadCloudSet() {
}).send(); }).send();
} }
void Premium::checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done) {
if (_giftCodeRequestId) {
if (_giftCodeSlug == slug) {
return;
}
_api.request(_giftCodeRequestId).cancel();
}
_giftCodeSlug = slug;
_giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode(
MTP_string(slug)
)).done([=](const MTPpayments_CheckedGiftCode &result) {
_giftCodeRequestId = 0;
const auto &data = result.data();
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
done(updateGiftCode(slug, Parse(data)));
}).fail([=](const MTP::Error &error) {
_giftCodeRequestId = 0;
done(updateGiftCode(slug, {}));
}).send();
}
GiftCode Premium::updateGiftCode(
const QString &slug,
const GiftCode &code) {
auto &now = _giftCodes[slug];
if (now != code) {
now = code;
_giftCodeUpdated.fire_copy(slug);
}
return code;
}
rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
return _giftCodeUpdated.events_starting_with_copy(
slug
) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] {
const auto i = _giftCodes.find(slug);
return (i != end(_giftCodes)) ? i->second : GiftCode();
});
}
void Premium::applyGiftCode(const QString &slug, Fn<void(QString)> done) {
_api.request(MTPpayments_ApplyGiftCode(
MTP_string(slug)
)).done([=](const MTPUpdates &result) {
_session->api().applyUpdates(result);
done({});
}).fail([=](const MTP::Error &error) {
done(error.type());
}).send();
}
void Premium::resolveGiveawayInfo(
not_null<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> done) {
Expects(done != nullptr);
_giveawayInfoDone = std::move(done);
if (_giveawayInfoRequestId) {
if (_giveawayInfoPeer == peer
&& _giveawayInfoMessageId == messageId) {
return;
}
_api.request(_giveawayInfoRequestId).cancel();
}
_giveawayInfoPeer = peer;
_giveawayInfoMessageId = messageId;
_giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo(
_giveawayInfoPeer->input,
MTP_int(_giveawayInfoMessageId.bare)
)).done([=](const MTPpayments_GiveawayInfo &result) {
_giveawayInfoRequestId = 0;
auto info = GiveawayInfo();
result.match([&](const MTPDpayments_giveawayInfo &data) {
info.participating = data.is_participating();
info.state = data.is_preparing_results()
? GiveawayState::Preparing
: GiveawayState::Running;
info.adminChannelId = data.vadmin_disallowed_chat_id()
? ChannelId(*data.vadmin_disallowed_chat_id())
: ChannelId();
info.disallowedCountry = qs(
data.vdisallowed_country().value_or_empty());
info.tooEarlyDate
= data.vjoined_too_early_date().value_or_empty();
info.startDate = data.vstart_date().v;
}, [&](const MTPDpayments_giveawayInfoResults &data) {
info.state = data.is_refunded()
? GiveawayState::Refunded
: GiveawayState::Finished;
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
info.activatedCount = data.vactivated_count().v;
info.finishDate = data.vfinish_date().v;
info.startDate = data.vstart_date().v;
});
_giveawayInfoDone(std::move(info));
}).fail([=] {
_giveawayInfoRequestId = 0;
_giveawayInfoDone({});
}).send();
}
const Data::SubscriptionOptions &Premium::subscriptionOptions() const { const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions; return _subscriptionOptions;
} }

View file

@ -18,6 +18,49 @@ class Session;
namespace Api { namespace Api {
struct GiftCode {
PeerId from = 0;
PeerId to = 0;
MsgId giveawayId = 0;
TimeId date = 0;
TimeId used = 0; // 0 if not used.
int months = 0;
bool giveaway = false;
explicit operator bool() const {
return months != 0;
}
friend inline bool operator==(
const GiftCode&,
const GiftCode&) = default;
};
enum class GiveawayState {
Invalid,
Running,
Preparing,
Finished,
Refunded,
};
struct GiveawayInfo {
QString giftCode;
QString disallowedCountry;
ChannelId adminChannelId = 0;
GiveawayState state = GiveawayState::Invalid;
TimeId tooEarlyDate = 0;
TimeId finishDate = 0;
TimeId startDate = 0;
int winnersCount = 0;
int activatedCount = 0;
bool participating = false;
explicit operator bool() const {
return state != GiveawayState::Invalid;
}
};
class Premium final { class Premium final {
public: public:
explicit Premium(not_null<ApiWrap*> api); explicit Premium(not_null<ApiWrap*> api);
@ -40,6 +83,19 @@ public:
[[nodiscard]] int64 monthlyAmount() const; [[nodiscard]] int64 monthlyAmount() const;
[[nodiscard]] QString monthlyCurrency() const; [[nodiscard]] QString monthlyCurrency() const;
void checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done);
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
const QString &slug) const;
void applyGiftCode(const QString &slug, Fn<void(QString)> done);
void resolveGiveawayInfo(
not_null<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> done);
[[nodiscard]] auto subscriptionOptions() const [[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &; -> const Data::SubscriptionOptions &;
@ -71,6 +127,16 @@ private:
int64 _monthlyAmount = 0; int64 _monthlyAmount = 0;
QString _monthlyCurrency; QString _monthlyCurrency;
mtpRequestId _giftCodeRequestId = 0;
QString _giftCodeSlug;
base::flat_map<QString, GiftCode> _giftCodes;
rpl::event_stream<QString> _giftCodeUpdated;
mtpRequestId _giveawayInfoRequestId = 0;
PeerData *_giveawayInfoPeer = nullptr;
MsgId _giveawayInfoMessageId = 0;
Fn<void(GiveawayInfo)> _giveawayInfoDone;
Data::SubscriptionOptions _subscriptionOptions; Data::SubscriptionOptions _subscriptionOptions;
}; };

View file

@ -368,9 +368,9 @@ void SendConfirmedFile(
if (!isEditing) { if (!isEditing) {
const auto histories = &session->data().histories(); const auto histories = &session->data().histories();
file->to.replyTo.msgId = histories->convertTopicReplyToId( file->to.replyTo.messageId = histories->convertTopicReplyToId(
history, history,
file->to.replyTo.msgId); file->to.replyTo.messageId);
file->to.replyTo.topicRootId = histories->convertTopicReplyToId( file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
history, history,
file->to.replyTo.topicRootId); file->to.replyTo.topicRootId);

View file

@ -434,19 +434,42 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
const auto requestPrivateForwards = [=]( const auto requestPrivateForwards = [=](
const Data::StatisticalGraph &messageGraph) { const Data::StatisticalGraph &messageGraph) {
_api.request(MTPstats_GetBroadcastStats( _api.request(MTPchannels_GetMessages(
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)), _channel->inputChannel,
_channel->inputChannel MTP_vector<MTPInputMessage>(
)).done([=](const MTPstats_BroadcastStats &result) { 1,
const auto channelStats = ChannelStatisticsFromTL(result.data()); MTP_inputMessageID(MTP_int(_fullId.msg))))
auto info = Data::StatisticsMessageInteractionInfo(); ).done([=](const MTPmessages_Messages &result) {
for (const auto &r : channelStats.recentMessageInteractions) { const auto process = [&](const MTPVector<MTPMessage> &messages) {
if (r.messageId == _fullId.msg) { const auto &message = messages.v.front();
info = r; return message.match([&](const MTPDmessage &data) {
break; return Data::StatisticsMessageInteractionInfo{
} .messageId = IdFromMessage(message),
} .viewsCount = data.vviews()
requestFirstPublicForwards(messageGraph, info); ? data.vviews()->v
: 0,
.forwardsCount = data.vforwards()
? data.vforwards()->v
: 0,
};
}, [](const MTPDmessageEmpty &) {
return Data::StatisticsMessageInteractionInfo();
}, [](const MTPDmessageService &) {
return Data::StatisticsMessageInteractionInfo();
});
};
auto info = result.match([&](const MTPDmessages_messages &data) {
return process(data.vmessages());
}, [&](const MTPDmessages_messagesSlice &data) {
return process(data.vmessages());
}, [&](const MTPDmessages_channelMessages &data) {
return process(data.vmessages());
}, [](const MTPDmessages_messagesNotModified &) {
return Data::StatisticsMessageInteractionInfo();
});
requestFirstPublicForwards(messageGraph, std::move(info));
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
requestFirstPublicForwards(messageGraph, {}); requestFirstPublicForwards(messageGraph, {});
}).send(); }).send();
@ -478,9 +501,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
return lifetime; return lifetime;
} }
_api.request(MTPstories_GetBoostsStatus( _api.request(MTPpremium_GetBoostsStatus(
_peer->input _peer->input
)).done([=](const MTPstories_BoostsStatus &result) { )).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data(); const auto &data = result.data();
const auto hasPremium = !!data.vpremium_audience(); const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium const auto premiumMemberCount = hasPremium
@ -530,28 +553,29 @@ void Boosts::requestBoosts(
} }
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit); constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = _api.request(MTPstories_GetBoostersList( _requestId = _api.request(MTPpremium_GetBoostsList(
MTP_flags(0),
_peer->input, _peer->input,
MTP_string(token.next), MTP_string(token.next),
token.next.isEmpty() ? kTlFirstSlice : kTlLimit token.next.isEmpty() ? kTlFirstSlice : kTlLimit
)).done([=](const MTPstories_BoostersList &result) { )).done([=](const MTPpremium_BoostsList &result) {
_requestId = 0; _requestId = 0;
const auto &data = result.data(); const auto &data = result.data();
_peer->owner().processUsers(data.vusers()); _peer->owner().processUsers(data.vusers());
auto list = std::vector<Data::Boost>(); auto list = std::vector<Data::Boost>();
list.reserve(data.vboosters().v.size()); list.reserve(data.vboosts().v.size());
for (const auto &boost : data.vboosters().v) { for (const auto &boost : data.vboosts().v) {
list.push_back({ list.push_back({
boost.data().vuser_id().v, boost.data().vuser_id().value_or_empty(),
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v), QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
}); });
} }
done(Data::BoostsListSlice{ done(Data::BoostsListSlice{
.list = std::move(list), .list = std::move(list),
.total = data.vcount().v, .total = data.vcount().v,
.allLoaded = (data.vcount().v == data.vboosters().v.size()), .allLoaded = (data.vcount().v == data.vboosts().v.size()),
.token = Data::BoostsListSlice::OffsetToken{ .token = Data::BoostsListSlice::OffsetToken{
data.vnext_offset() data.vnext_offset()
? qs(*data.vnext_offset()) ? qs(*data.vnext_offset())

View file

@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP(
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCustomEmoji: { case mtpc_messageEntityCustomEmoji: {
@ -142,6 +143,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
&& entity.type() != EntityType::StrikeOut && entity.type() != EntityType::StrikeOut
&& entity.type() != EntityType::Code // #TODO entities && entity.type() != EntityType::Code // #TODO entities
&& entity.type() != EntityType::Pre && entity.type() != EntityType::Pre
&& entity.type() != EntityType::Blockquote
&& entity.type() != EntityType::Spoiler && entity.type() != EntityType::Spoiler
&& entity.type() != EntityType::MentionName && entity.type() != EntityType::MentionName
&& entity.type() != EntityType::CustomUrl && entity.type() != EntityType::CustomUrl
@ -170,6 +172,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break; case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break;
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break; case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
case EntityType::CustomEmoji: { case EntityType::CustomEmoji: {
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) { if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {

View file

@ -2103,7 +2103,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
windows.front()->window().show(Ui::MakeInformBox(text)); windows.front()->window().show(Ui::MakeInformBox(text));
} }
} else { } else {
session().data().serviceNotification(text, d.vmedia()); session().data().serviceNotification(
text,
d.vmedia(),
d.is_invert_media());
session().api().authorizations().reload(); session().api().authorizations().reload();
} }
} break; } break;

View file

@ -2150,14 +2150,13 @@ void ApiWrap::saveDraftsToCloud() {
auto flags = MTPmessages_SaveDraft::Flags(0); auto flags = MTPmessages_SaveDraft::Flags(0);
auto &textWithTags = cloudDraft->textWithTags; auto &textWithTags = cloudDraft->textWithTags;
if (cloudDraft->previewState != Data::PreviewState::Allowed) { if (cloudDraft->webpage.removed) {
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage; flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
} else if (!cloudDraft->webpage.url.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_media;
} }
if (cloudDraft->msgId) { if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id; flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
}
if (cloudDraft->topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
} }
if (!textWithTags.tags.isEmpty()) { if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities; flags |= MTPmessages_SaveDraft::Flag::f_entities;
@ -2170,11 +2169,13 @@ void ApiWrap::saveDraftsToCloud() {
history->startSavingCloudDraft(topicRootId); history->startSavingCloudDraft(topicRootId);
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft( cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
MTP_flags(flags), MTP_flags(flags),
MTP_int(cloudDraft->msgId), ReplyToForMTP(history, cloudDraft->reply),
MTP_int(cloudDraft->topicRootId),
history->peer->input, history->peer->input,
MTP_string(textWithTags.text), MTP_string(textWithTags.text),
entities entities,
Data::WebPageForMTP(
cloudDraft->webpage,
textWithTags.text.isEmpty())
)).done([=](const MTPBool &result, const MTP::Response &response) { )).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId; const auto requestId = response.requestId;
history->finishSavingCloudDraft( history->finishSavingCloudDraft(
@ -2261,7 +2262,7 @@ void ApiWrap::gotStickerSet(
} }
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) { void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
if (page->pendingTill <= 0) { if (page->failed || !page->pendingTill) {
return; return;
} }
_webPagesPending.emplace(page, 0); _webPagesPending.emplace(page, 0);
@ -2566,7 +2567,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) { for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
if (i->second == req) { if (i->second == req) {
if (i->first->pendingTill > 0) { if (i->first->pendingTill > 0) {
i->first->pendingTill = -1; i->first->pendingTill = 0;
i->first->failed = 1;
_session->data().notifyWebPageUpdateDelayed(i->first); _session->data().notifyWebPageUpdateDelayed(i->first);
} }
i = _webPagesPending.erase(i); i = _webPagesPending.erase(i);
@ -3612,9 +3614,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true; action.generateLocal = true;
sendAction(action); sendAction(action);
const auto replyToId = action.replyTo.msgId; const auto replyTo = action.replyTo.messageId
const auto replyTo = replyToId ? peer->owner().message(action.replyTo.messageId)
? peer->owner().message(peer, replyToId)
: nullptr; : nullptr;
const auto topicRootId = replyTo const auto topicRootId = replyTo
? replyTo->topicRootId() ? replyTo->topicRootId()
@ -3642,7 +3643,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
auto &histories = history->owner().histories(); auto &histories = history->owner().histories();
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) { const auto exactWebPage = !message.webPage.url.isEmpty();
auto isFirst = true;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)
|| (isFirst && exactWebPage)) {
TextUtilities::Trim(left);
const auto isLast = left.empty();
auto newId = FullMsgId( auto newId = FullMsgId(
peer->id, peer->id,
_session->data().nextLocalMessageId()); _session->data().nextLocalMessageId());
@ -3656,26 +3663,52 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPstring msgText(MTP_string(sending.text)); MTPstring msgText(MTP_string(sending.text));
auto flags = NewMessageFlags(peer); auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMessage::Flags(0); auto sendFlags = MTPmessages_SendMessage::Flags(0);
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) { if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo; flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to; sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
} }
const auto ignoreWebPage = message.webPage.removed
|| (exactWebPage && !isLast);
const auto manualWebPage = exactWebPage
&& !ignoreWebPage
&& (message.webPage.manual || (isLast && !isFirst));
const auto replyHeader = NewMessageReplyHeader(action); const auto replyHeader = NewMessageReplyHeader(action);
MTPMessageMedia media = MTP_messageMediaEmpty(); MTPMessageMedia media = MTP_messageMediaEmpty();
if (message.webPageId == CancelledWebPageId) { if (ignoreWebPage) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage; sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
} else if (message.webPageId) { } else if (exactWebPage) {
auto page = _session->data().webpage(message.webPageId); using PageFlag = MTPDmessageMediaWebPage::Flag;
using PendingFlag = MTPDwebPagePending::Flag;
const auto &fields = message.webPage;
const auto page = _session->data().webpage(fields.id);
media = MTP_messageMediaWebPage( media = MTP_messageMediaWebPage(
MTP_flags(PageFlag()
| (manualWebPage ? PageFlag::f_manual : PageFlag())
| (fields.forceLargeMedia
? PageFlag::f_force_large_media
: PageFlag())
| (fields.forceSmallMedia
? PageFlag::f_force_small_media
: PageFlag())),
MTP_webPagePending( MTP_webPagePending(
MTP_long(page->id), MTP_flags(PendingFlag::f_url),
MTP_long(fields.id),
MTP_string(fields.url),
MTP_int(page->pendingTill))); MTP_int(page->pendingTill)));
} }
const auto anonymousPost = peer->amAnonymous(); const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options); const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags); FillMessagePostFlags(action, peer, flags);
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
if (silentPost) { if (silentPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent; sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
} }
const auto sentEntities = Api::EntitiesToMTP( const auto sentEntities = Api::EntitiesToMTP(
_session, _session,
@ -3683,11 +3716,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
Api::ConvertOption::SkipLocal); Api::ConvertOption::SkipLocal);
if (!sentEntities.v.isEmpty()) { if (!sentEntities.v.isEmpty()) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities; sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
} }
const auto clearCloudDraft = action.clearDraft; const auto clearCloudDraft = action.clearDraft;
const auto topicRootId = action.replyTo.topicRootId; const auto topicRootId = action.replyTo.topicRootId;
if (clearCloudDraft) { if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft; sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearCloudDraft(topicRootId); history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId); history->startSavingCloudDraft(topicRootId);
} }
@ -3699,6 +3734,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
: _session->userPeerId(); : _session->userPeerId();
if (sendAs) { if (sendAs) {
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as; sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
} }
const auto messagePostAuthor = peer->isBroadcast() const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name() ? _session->user()->name()
@ -3706,6 +3742,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (action.options.scheduled) { if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled; flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date; sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
} }
const auto viaBotId = UserId(); const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage( lastMessage = history->addNewLocalMessage(
@ -3719,27 +3756,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sending, sending,
media, media,
HistoryMessageMarkupData()); HistoryMessageMarkupData());
histories.sendPreparedMessage( const auto done = [=](
history, const MTPUpdates &result,
action.replyTo, const MTP::Response &response) {
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), [=](const MTPUpdates &result, const MTP::Response &response) {
if (clearCloudDraft) { if (clearCloudDraft) {
history->finishSavingCloudDraft( history->finishSavingCloudDraft(
topicRootId, topicRootId,
UnixtimeFromMsgId(response.outerMsgId)); UnixtimeFromMsgId(response.outerMsgId));
} }
}, [=](const MTP::Error &error, const MTP::Response &response) { };
const auto fail = [=](
const MTP::Error &error,
const MTP::Response &response) {
if (error.type() == u"MESSAGE_EMPTY"_q) { if (error.type() == u"MESSAGE_EMPTY"_q) {
lastMessage->destroy(); lastMessage->destroy();
} else { } else {
@ -3750,7 +3778,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
topicRootId, topicRootId,
UnixtimeFromMsgId(response.outerMsgId)); UnixtimeFromMsgId(response.outerMsgId));
} }
}); };
if (exactWebPage
&& !ignoreWebPage
&& (manualWebPage || sending.empty())) {
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
MTP_flags(mediaFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
Data::WebPageForMTP(message.webPage, true),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(message.action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
} else {
histories.sendPreparedMessage(
history,
action.replyTo,
randomId,
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
MTP_flags(sendFlags),
peer->input,
Data::Histories::ReplyToPlaceholder(),
msgText,
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
), done, fail);
}
isFirst = false;
} }
finishForwarding(action); finishForwarding(action);
@ -3815,7 +3880,7 @@ void ApiWrap::sendInlineResult(
? (*localMessageId) ? (*localMessageId)
: _session->data().nextLocalMessageId()); : _session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>(); const auto randomId = base::RandomValue<uint64>();
const auto topicRootId = action.replyTo.msgId const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId ? action.replyTo.topicRootId
: 0; : 0;

View file

@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
, _controller(controller) , _controller(controller)
, _forPeer(args.forPeer) , _forPeer(args.forPeer)
, _fromMessageId(args.fromMessageId) , _fromMessageId(args.fromMessageId)
, _chatStyle(std::make_unique<Ui::ChatStyle>()) , _chatStyle(std::make_unique<Ui::ChatStyle>(
controller->session().colorIndicesValue()))
, _serviceHistory(_controller->session().data().history( , _serviceHistory(_controller->session().data().history(
PeerData::kServiceNotificationsId)) PeerData::kServiceNotificationsId))
, _service(nullptr) , _service(nullptr)
@ -434,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons(); clearButtons();
addButton(_forPeer addButton(_forPeer
? tr::lng_background_apply_button() ? tr::lng_background_apply_button()
: tr::lng_background_apply(), [=] { apply(); }); : tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); });
if (!_forPeer && _paper.hasShareUrl()) { if (!_forPeer && _paper.hasShareUrl()) {
addLeftButton(tr::lng_background_share(), [=] { share(); }); addLeftButton(tr::lng_background_share(), [=] { share(); });

View file

@ -8,26 +8,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h" #include "boxes/gift_premium_box.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_premium.h"
#include "api/api_premium_option.h" #include "api/api_premium_option.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_media_types.h" // Data::Giveaway
#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 "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mainwidget.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/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.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/table_layout.h"
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -35,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_premium.h" #include "styles/style_premium.h"
#include <QtGui/QGuiApplication>
namespace { namespace {
constexpr auto kDiscountDivider = 5.; constexpr auto kDiscountDivider = 5.;
@ -225,6 +237,131 @@ void GiftBox(
}, box->lifetime()); }, box->lifetime());
} }
struct GiftCodeLink {
QString text;
QString link;
};
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session,
const QString &slug) {
const auto path = u"giftcode/"_q + slug;
return {
session->createInternalLink(path),
session->createInternalLinkFull(path),
};
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkCopyIcon(
not_null<QWidget*> parent) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
const auto &icon = st::giveawayGiftCodeLinkCopy;
const auto left = (raw->width() - icon.width()) / 2;
const auto top = (raw->height() - icon.height()) / 2;
icon.paint(p, left, top, raw->width());
}, raw->lifetime());
raw->resize(
st::giveawayGiftCodeLinkCopyWidth,
st::giveawayGiftCodeLinkHeight);
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
return result;
}
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int months) {
return (months < 12)
? tr::lng_premium_gift_duration_months
: tr::lng_premium_gift_duration_years;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
PeerId id) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
const auto &st = st::giveawayGiftCodeUserpic;
raw->resize(raw->width(), st.photoSize);
const auto peer = controller->session().data().peer(id);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
peer->name(),
st::giveawayGiftCodeValue);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto position = st::giveawayGiftCodeNamePosition;
label->resizeToNaturalWidth(width - position.x());
label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width);
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setTextColorOverride(st::windowActiveTextFg->c);
raw->setClickedCallback([=] {
controller->show(PrepareShortInfoBox(peer, controller));
});
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
object_ptr<Ui::RpWidget> value,
style::margins valueMargins) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel),
std::move(value),
st::giveawayGiftCodeLabelMargin,
valueMargins);
}
not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
rpl::producer<TextWithEntities> value) {
auto widget = object_ptr<Ui::FlatLabel>(
table,
std::move(value),
st::giveawayGiftCodeValue);
const auto result = widget.data();
AddTableRow(
table,
std::move(label),
std::move(widget),
st::giveawayGiftCodeValueMargin);
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
not_null<Window::SessionController*> controller,
PeerId id) {
if (!id) {
return;
}
AddTableRow(
table,
std::move(label),
MakePeerTableValue(table, controller, id),
st::giveawayGiftCodePeerMargin);
}
} // namespace } // namespace
GiftPremiumValidator::GiftPremiumValidator( GiftPremiumValidator::GiftPremiumValidator(
@ -263,3 +400,405 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
_requestId = 0; _requestId = 0;
}).send(); }).send();
} }
rpl::producer<QString> GiftDurationValue(int months) {
return GiftDurationPhrase(months)(
lt_count,
rpl::single(float64((months < 12) ? months : (months / 12))));
}
QString GiftDuration(int months) {
return GiftDurationPhrase(months)(
tr::now,
lt_count,
(months < 12) ? months : (months / 12));
}
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const QString &slug) {
struct State {
rpl::variable<Api::GiftCode> data;
rpl::variable<bool> used;
bool sent = false;
};
const auto session = &controller->session();
const auto state = box->lifetime().make_state<State>(State{});
state->data = session->api().premium().giftCodeValue(slug);
state->used = state->data.value(
) | rpl::map([=](const Api::GiftCode &data) {
return data.used;
});
box->setWidth(st::boxWideWidth);
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
const auto bar = box->setPinnedToTopContent(
object_ptr<Ui::Premium::TopBar>(
box,
st::giveawayGiftCodeCover,
nullptr,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_title(),
tr::lng_gift_link_title()),
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
true));
const auto max = st::giveawayGiftCodeTopHeight;
bar->setMaximumHeight(max);
bar->setMinimumHeight(st::infoLayerTopBarHeight);
bar->resize(bar->width(), bar->maximumHeight());
const auto link = MakeGiftCodeLink(&controller->session(), slug);
box->addRow(
Ui::MakeLinkLabel(
box,
rpl::single(link.text),
rpl::single(link.link),
box->uiShow(),
MakeLinkCopyIcon(box)),
st::giveawayGiftCodeLinkMargin);
auto table = box->addRow(
object_ptr<Ui::TableLayout>(
box,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto current = state->data.current();
AddTableRow(
table,
tr::lng_gift_link_label_from(),
controller,
current.from);
if (current.to) {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
current.to);
} else {
AddTableRow(
table,
tr::lng_gift_link_label_to(),
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
}
AddTableRow(
table,
tr::lng_gift_link_label_gift(),
tr::lng_gift_link_gift_premium(
lt_duration,
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
Ui::Text::WithEntities));
const auto reason = AddTableRow(
table,
tr::lng_gift_link_label_reason(),
(current.giveawayId
? ((current.to
? tr::lng_gift_link_reason_giveaway
: tr::lng_gift_link_reason_unclaimed)(
) | Ui::Text::ToLink())
: current.giveaway
? ((current.to
? tr::lng_gift_link_reason_giveaway
: tr::lng_gift_link_reason_unclaimed)(
Ui::Text::WithEntities
) | rpl::type_erased())
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
reason->setClickHandlerFilter([=](const auto &...) {
controller->showPeerHistory(
current.from,
Window::SectionShow::Way::Forward,
current.giveawayId);
return false;
});
if (current.date) {
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(Ui::Text::WithEntities(
langDateTime(base::unixtime::parse(current.date)))));
}
auto shareLink = tr::lng_gift_link_also_send_link(
) | rpl::map([](const QString &text) {
return Ui::Text::Link(text);
});
auto richDate = [](const Api::GiftCode &data) {
return TextWithEntities{
langDateTime(base::unixtime::parse(data.used)),
};
};
const auto footer = box->addRow(
object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_footer(
lt_date,
state->data.value() | rpl::map(richDate),
Ui::Text::WithEntities),
tr::lng_gift_link_also_send(
lt_link,
std::move(shareLink),
Ui::Text::WithEntities)),
st::giveawayGiftCodeFooter),
st::giveawayGiftCodeFooterMargin);
footer->setClickHandlerFilter([=](const auto &...) {
const auto chosen = [=](not_null<Data::Thread*> thread) {
const auto content = controller->content();
return content->shareUrl(
thread,
MakeGiftCodeLink(&controller->session(), slug).link,
QString());
};
Window::ShowChooseRecipientBox(controller, chosen);
return false;
});
const auto close = Ui::CreateChild<Ui::IconButton>(
box.get(),
st::boxTitleClose);
close->setClickedCallback([=] {
box->closeBox();
});
box->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0);
}, box->lifetime());
const auto button = box->addButton(rpl::conditional(
state->used.value(),
tr::lng_box_ok(),
tr::lng_gift_link_use()
), [=] {
if (state->used.current()) {
box->closeBox();
} else if (!state->sent) {
state->sent = true;
const auto done = crl::guard(box, [=](const QString &error) {
if (error.isEmpty()) {
auto copy = state->data.current();
copy.used = base::unixtime::now();
state->data = std::move(copy);
Ui::StartFireworks(box->parentWidget());
} else {
box->uiShow()->showToast(error);
}
});
controller->session().api().premium().applyGiftCode(slug, done);
}
});
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
}
void ResolveGiftCode(
not_null<Window::SessionController*> controller,
const QString &slug) {
const auto done = [=](Api::GiftCode code) {
if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now));
} else {
controller->show(Box(GiftCodeBox, controller, slug));
}
};
controller->session().api().premium().checkGiftCode(
slug,
crl::guard(controller, done));
}
void GiveawayInfoBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
Data::Giveaway giveaway,
Api::GiveawayInfo info) {
using State = Api::GiveawayState;
const auto finished = (info.state == State::Finished)
|| (info.state == State::Refunded);
box->setTitle((finished
? tr::lng_prizes_end_title
: tr::lng_prizes_how_title)());
const auto first = !giveaway.channels.empty()
? giveaway.channels.front()->name()
: u"channel"_q;
auto text = (finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
lt_admins,
tr::lng_prizes_admins(
tr::now,
lt_count,
giveaway.quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(giveaway.months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue);
const auto many = (giveaway.channels.size() > 1);
const auto count = info.winnersCount
? info.winnersCount
: giveaway.quantity;
auto winners = giveaway.all
? (many
? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
Ui::Text::RichLangValue)
: (many
? tr::lng_prizes_winners_new_of_many
: tr::lng_prizes_winners_new_of_one)(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_start_date,
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.startDate))),
Ui::Text::RichLangValue);
text.append("\n\n").append((finished
? tr::lng_prizes_end_when_finish
: tr::lng_prizes_how_when_finish)(
tr::now,
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
lt_winners,
winners,
Ui::Text::RichLangValue));
if (info.activatedCount > 0) {
text.append(' ').append(tr::lng_prizes_end_activated(
tr::now,
lt_count,
info.activatedCount));
}
if (!info.giftCode.isEmpty()) {
text.append("\n\n");
text.append(tr::lng_prizes_you_won(
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) {
if (info.adminChannelId) {
const auto channel = controller->session().data().channel(
info.adminChannelId);
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
tr::now,
lt_channel,
Ui::Text::Bold(channel->name()),
Ui::Text::RichLangValue));
} else if (info.tooEarlyDate) {
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
tr::now,
lt_date,
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
Ui::Text::RichLangValue));
} else if (!info.disallowedCountry.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_how_no_country(
tr::now,
Ui::Text::RichLangValue));
} else if (info.participating) {
text.append("\n\n").append((many
? tr::lng_prizes_how_yes_joined_many
: tr::lng_prizes_how_yes_joined_one)(
tr::now,
lt_channel,
Ui::Text::Bold(first),
Ui::Text::RichLangValue));
} else {
text.append("\n\n").append((many
? tr::lng_prizes_how_participate_many
: tr::lng_prizes_how_participate_one)(
tr::now,
lt_channel,
Ui::Text::Bold(first),
lt_date,
Ui::Text::Bold(langDayOfMonthFull(
base::unixtime::parse(giveaway.untilDate).date())),
Ui::Text::RichLangValue));
}
}
const auto padding = st::boxPadding;
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
rpl::single(std::move(text)),
st::boxLabel),
{ padding.left(), 0, padding.right(), padding.bottom() });
if (info.state == State::Refunded) {
const auto wrap = box->addRow(
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
box.get(),
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_prizes_cancelled(),
st::giveawayRefundedLabel),
st::giveawayRefundedPadding),
{ padding.left(), 0, padding.right(), padding.bottom() });
const auto bg = wrap->lifetime().make_state<Ui::RoundRect>(
st::boxRadius,
st::attentionBoxButton.textBgOver);
wrap->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(wrap);
bg->paint(p, wrap->rect());
}, wrap->lifetime());
}
if (const auto slug = info.giftCode; !slug.isEmpty()) {
box->addButton(tr::lng_prizes_view_prize(), [=] {
ResolveGiftCode(controller, slug);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
} else {
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
}
}
void ResolveGiveawayInfo(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway) {
const auto show = [=](Api::GiveawayInfo info) {
if (!info) {
controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
} else {
controller->show(
Box(GiveawayInfoBox, controller, giveaway, info));
}
};
controller->session().api().premium().resolveGiveawayInfo(
peer,
messageId,
crl::guard(controller, show));
}

View file

@ -11,6 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData; class UserData;
namespace Api {
struct GiftCode;
} // namespace Api
namespace Data {
struct Giveaway;
} // namespace Data
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
@ -29,3 +41,20 @@ private:
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
}; };
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
[[nodiscard]] QString GiftDuration(int months);
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const QString &slug);
void ResolveGiftCode(
not_null<Window::SessionController*> controller,
const QString &slug);
void ResolveGiveawayInfo(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway);

View file

@ -456,7 +456,7 @@ int PeerListController::descriptionTopSkipMin() const {
void PeerListBox::addSelectItem( void PeerListBox::addSelectItem(
not_null<PeerData*> peer, not_null<PeerData*> peer,
anim::type animated) { anim::type animated) {
const auto respect = _controller->respectSavedMessagesChat(); const auto respect = !_controller->savedMessagesChatStatus().isEmpty();
const auto text = (respect && peer->isSelf()) const auto text = (respect && peer->isSelf())
? tr::lng_saved_short(tr::now) ? tr::lng_saved_short(tr::now)
: (respect && peer->isRepliesChat()) : (respect && peer->isRepliesChat())
@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() {
_statusType = StatusType::LastSeen; _statusType = StatusType::LastSeen;
_statusValidTill = 0; _statusValidTill = 0;
if (auto user = peer()->asUser()) { if (auto user = peer()->asUser()) {
if (_isSavedMessagesChat) { if (!_savedMessagesStatus.isEmpty()) {
setStatusText(tr::lng_saved_forward_here(tr::now)); setStatusText(_savedMessagesStatus);
} else { } else {
auto time = base::unixtime::now(); auto time = base::unixtime::now();
setStatusText(Data::OnlineText(user, time)); setStatusText(Data::OnlineText(user, time));
@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
if (!_initialized) { if (!_initialized) {
return; return;
} }
const auto text = _isSavedMessagesChat const auto text = !_savedMessagesStatus.isEmpty()
? tr::lng_saved_messages(tr::now) ? tr::lng_saved_messages(tr::now)
: _isRepliesMessagesChat : _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now) ? tr::lng_replies_messages(tr::now)
@ -683,7 +683,7 @@ QString PeerListRow::generateName() {
} }
QString PeerListRow::generateShortName() { QString PeerListRow::generateShortName() {
return _isSavedMessagesChat return !_savedMessagesStatus.isEmpty()
? tr::lng_saved_short(tr::now) ? tr::lng_saved_short(tr::now)
: _isRepliesMessagesChat : _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now) ? tr::lng_replies_messages(tr::now)
@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback( PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
bool forceRound) { bool forceRound) {
const auto saved = _isSavedMessagesChat; const auto saved = !_savedMessagesStatus.isEmpty();
const auto replies = _isRepliesMessagesChat; const auto replies = _isRepliesMessagesChat;
const auto peer = this->peer(); const auto peer = this->peer();
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView(); auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth(
int availableWidth, int availableWidth,
int outerWidth, int outerWidth,
bool selected) { bool selected) {
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) { if (special()
|| !_savedMessagesStatus.isEmpty()
|| _isRepliesMessagesChat) {
return 0; return 0;
} }
return _bagde.drawGetWidth( return _bagde.drawGetWidth(
@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic(
auto iconBorderPen = st.checkbox.check.border->p; auto iconBorderPen = st.checkbox.check.border->p;
iconBorderPen.setWidth(st.checkbox.selectWidth); iconBorderPen.setWidth(st.checkbox.selectWidth);
if (_isSavedMessagesChat) { if (!_savedMessagesStatus.isEmpty()) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
} else if (_isRepliesMessagesChat) { } else if (_isRepliesMessagesChat) {
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2); Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
@ -1046,9 +1048,10 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
} }
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) { void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
if (_controller->respectSavedMessagesChat() && !row->special()) { const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
if (!savedMessagesStatus.isEmpty() && !row->special()) {
if (row->peer()->isSelf()) { if (row->peer()->isSelf()) {
row->setIsSavedMessagesChat(true); row->setSavedMessagesChatStatus(savedMessagesStatus);
} else if (row->peer()->isRepliesChat()) { } else if (row->peer()->isRepliesChat()) {
row->setIsRepliesMessagesChat(true); row->setIsRepliesMessagesChat(true);
} }

View file

@ -185,8 +185,8 @@ public:
void setIsSearchResult(bool isSearchResult) { void setIsSearchResult(bool isSearchResult) {
_isSearchResult = isSearchResult; _isSearchResult = isSearchResult;
} }
void setIsSavedMessagesChat(bool isSavedMessagesChat) { void setSavedMessagesChatStatus(QString savedMessagesStatus) {
_isSavedMessagesChat = isSavedMessagesChat; _savedMessagesStatus = savedMessagesStatus;
} }
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) { void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
_isRepliesMessagesChat = isRepliesMessagesChat; _isRepliesMessagesChat = isRepliesMessagesChat;
@ -278,12 +278,12 @@ private:
StatusType _statusType = StatusType::Online; StatusType _statusType = StatusType::Online;
crl::time _statusValidTill = 0; crl::time _statusValidTill = 0;
base::flat_set<QChar> _nameFirstLetters; base::flat_set<QChar> _nameFirstLetters;
QString _savedMessagesStatus;
int _absoluteIndex = -1; int _absoluteIndex = -1;
State _disabledState = State::Active; State _disabledState = State::Active;
bool _hidden : 1 = false; bool _hidden : 1 = false;
bool _initialized : 1 = false; bool _initialized : 1 = false;
bool _isSearchResult : 1 = false; bool _isSearchResult : 1 = false;
bool _isSavedMessagesChat : 1 = false;
bool _isRepliesMessagesChat : 1 = false; bool _isRepliesMessagesChat : 1 = false;
}; };
@ -517,8 +517,8 @@ public:
void peerListSearchAddRow(PeerListRowId id) override; void peerListSearchAddRow(PeerListRowId id) override;
void peerListSearchRefreshRows() override; void peerListSearchRefreshRows() override;
[[nodiscard]] virtual bool respectSavedMessagesChat() const { [[nodiscard]] virtual QString savedMessagesChatStatus() const {
return false; return QString();
} }
[[nodiscard]] virtual int customRowHeight() { [[nodiscard]] virtual int customRowHeight() {
Unexpected("PeerListController::customRowHeight."); Unexpected("PeerListController::customRowHeight.");

View file

@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() {
return count; return count;
}; };
auto added = 0; auto added = 0;
if (respectSavedMessagesChat()) { if (!savedMessagesChatStatus().isEmpty()) {
if (appendRow(session().data().history(session().user()))) { if (appendRow(session().data().history(session().user()))) {
++added; ++added;
} }
@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() {
const auto history = static_cast<const Row&>(a).history(); const auto history = static_cast<const Row&>(a).history();
return history->inChatList(); return history->inChatList();
}); });
if (respectSavedMessagesChat()) { if (!savedMessagesChatStatus().isEmpty()) {
delegate()->peerListPartitionRows([](const PeerListRow &a) { delegate()->peerListPartitionRows([](const PeerListRow &a) {
return a.peer()->isSelf(); return a.peer()->isSelf();
}); });
@ -696,6 +696,10 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now);
}
auto ChooseRecipientBoxController::createRow( auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> { not_null<History*> history) -> std::unique_ptr<Row> {
const auto peer = history->peer; const auto peer = history->peer;

View file

@ -218,9 +218,7 @@ public:
Main::Session &session() const override; Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
bool respectSavedMessagesChat() const override { QString savedMessagesChatStatus() const override;
return true;
}
protected: protected:
void prepareViewHook() override; void prepareViewHook() override;

View file

@ -45,8 +45,8 @@ public:
Main::Session &session() const override; Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
bool respectSavedMessagesChat() const override { QString savedMessagesChatStatus() const override {
return true; return tr::lng_saved_forward_here(tr::now);
} }
private: private:

View file

@ -0,0 +1,936 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_color_box.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "info/boosts/info_boosts_widget.h"
#include "info/profile/info_profile_emoji_status_panel.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "ui/boxes/boost_box.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/effects/path_shift_gradient.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/painter.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
namespace {
using namespace Settings;
constexpr auto kFakeChannelId = ChannelId(0xFFFFFFF000ULL);
constexpr auto kFakeWebPageId = WebPageId(0xFFFFFFFF00000000ULL);
constexpr auto kSelectAnimationDuration = crl::time(150);
class ColorSample final : public Ui::AbstractButton {
public:
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name);
ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected);
[[nodiscard]] uint8 index() const;
int naturalWidth() const override;
void setSelected(bool selected);
private:
void paintEvent(QPaintEvent *e) override;
std::shared_ptr<Ui::ChatStyle> _style;
Ui::Text::String _name;
uint8 _index = 0;
Ui::Animations::Simple _selectAnimation;
bool _selected = false;
bool _simple = false;
};
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
public:
PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update);
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
HistoryView::Context elementContext() override;
private:
const not_null<QWidget*> _parent;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
};
class PreviewWrap final : public Ui::RpWidget {
public:
PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> backgroundEmojiId);
~PreviewWrap();
private:
using Element = HistoryView::Element;
void paintEvent(QPaintEvent *e) override;
void initElements();
const not_null<Ui::GenericBox*> _box;
const not_null<PeerData*> _peer;
const not_null<ChannelData*> _fake;
const not_null<History*> _history;
const not_null<WebPageData*> _webpage;
const std::shared_ptr<Ui::ChatTheme> _theme;
const std::shared_ptr<Ui::ChatStyle> _style;
const std::unique_ptr<PreviewDelegate> _delegate;
const not_null<HistoryItem*> _replyToItem;
const not_null<HistoryItem*> _replyItem;
std::unique_ptr<Element> _element;
Ui::PeerUserpicView _userpic;
QPoint _position;
};
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndex,
const QString &name)
: AbstractButton(parent)
, _style(style)
, _name(st::semiboldTextStyle, name) {
std::move(
colorIndex
) | rpl::start_with_next([=](uint8 index) {
_index = index;
update();
}, lifetime());
}
ColorSample::ColorSample(
not_null<QWidget*> parent,
std::shared_ptr<Ui::ChatStyle> style,
uint8 colorIndex,
bool selected)
: AbstractButton(parent)
, _style(style)
, _index(colorIndex)
, _selected(selected)
, _simple(true) {
}
void ColorSample::setSelected(bool selected) {
if (_selected == selected) {
return;
}
_selected = selected;
_selectAnimation.start(
[=] { update(); },
_selected ? 0. : 1.,
_selected ? 1. : 0.,
kSelectAnimationDuration);
}
void ColorSample::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
auto hq = PainterHighQualityEnabler(p);
const auto colors = _style->coloredValues(false, _index);
if (!_simple && !colors.outlines[1].alpha()) {
const auto radius = height() / 2;
p.setPen(Qt::NoPen);
p.setBrush(colors.bg);
p.drawRoundedRect(rect(), radius, radius);
const auto padding = st::settingsColorSamplePadding;
p.setPen(colors.name);
p.setBrush(Qt::NoBrush);
p.setFont(st::semiboldFont);
_name.drawLeftElided(
p,
padding.left(),
padding.top(),
width() - padding.left() - padding.right(),
width(),
1,
style::al_top);
} else {
const auto size = float64(width());
const auto half = size / 2.;
const auto full = QRectF(-half, -half, size, size);
p.translate(size / 2., size / 2.);
p.setPen(Qt::NoPen);
if (colors.outlines[1].alpha()) {
p.rotate(-45.);
p.setClipRect(-size, 0, 3 * size, size);
p.setBrush(colors.outlines[1]);
p.drawEllipse(full);
p.setClipRect(-size, -size, 3 * size, size);
}
p.setBrush(colors.outlines[0]);
p.drawEllipse(full);
p.setClipping(false);
if (colors.outlines[2].alpha()) {
const auto multiplier = size / st::settingsColorSampleSize;
const auto center = st::settingsColorSampleCenter * multiplier;
const auto radius = st::settingsColorSampleCenterRadius
* multiplier;
p.setBrush(colors.outlines[2]);
p.drawRoundedRect(
QRectF(-center / 2., -center / 2., center, center),
radius,
radius);
}
const auto selected = _selectAnimation.value(_selected ? 1. : 0.);
if (selected > 0) {
const auto line = st::settingsColorRadioStroke * 1.;
const auto thickness = selected * line;
auto pen = st::boxBg->p;
pen.setWidthF(thickness);
p.setBrush(Qt::NoBrush);
p.setPen(pen);
const auto skip = 1.5 * line;
p.drawEllipse(full.marginsRemoved({ skip, skip, skip, skip }));
}
}
}
uint8 ColorSample::index() const {
return _index;
}
int ColorSample::naturalWidth() const {
if (_name.isEmpty() || _style->colorPatternIndex(_index)) {
return st::settingsColorSampleSize;
}
const auto padding = st::settingsColorSamplePadding;
return std::max(
padding.left() + _name.maxWidth() + padding.right(),
padding.top() + st::semiboldFont->height + padding.bottom());
}
PreviewWrap::PreviewWrap(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme,
not_null<PeerData*> peer,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> backgroundEmojiId)
: RpWidget(box)
, _box(box)
, _peer(peer)
, _fake(_peer->owner().channel(kFakeChannelId))
, _history(_fake->owner().history(_fake))
, _webpage(_peer->owner().webpage(
kFakeWebPageId,
WebPageType::Article,
u"internal:peer-color-webpage-preview"_q,
u"internal:peer-color-webpage-preview"_q,
tr::lng_settings_color_link_name(tr::now),
tr::lng_settings_color_link_title(tr::now),
{ tr::lng_settings_color_link_description(tr::now) },
nullptr, // photo
nullptr, // document
WebPageCollage(),
0, // duration
QString(), // author
false, // hasLargeMedia
0)) // pendingTill
, _theme(theme)
, _style(style)
, _delegate(std::make_unique<PreviewDelegate>(box, _style.get(), [=] {
update();
}))
, _replyToItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::Post),
UserId(), // via
FullReplyTo(),
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_reply(tr::now)
: tr::lng_settings_color_reply_channel(tr::now),
},
MTP_messageMediaEmpty(),
HistoryMessageMarkupData(),
uint64(0)))
, _replyItem(_history->addNewLocalMessage(
_history->nextNonHistoryEntryId(),
(MessageFlag::FakeHistoryItem
| MessageFlag::HasFromId
| MessageFlag::HasReplyInfo
| MessageFlag::Post),
UserId(), // via
FullReplyTo{ .messageId = _replyToItem->fullId() },
base::unixtime::now(), // date
_fake->id,
QString(), // postAuthor
TextWithEntities{ _peer->isSelf()
? tr::lng_settings_color_text(tr::now)
: tr::lng_settings_color_text_channel(tr::now),
},
MTP_messageMediaWebPage(
MTP_flags(0),
MTP_webPagePending(
MTP_flags(0),
MTP_long(_webpage->id),
MTPstring(),
MTP_int(0))),
HistoryMessageMarkupData(),
uint64(0)))
, _element(_replyItem->createView(_delegate.get()))
, _position(0, st::msgMargin.bottom()) {
_style->apply(_theme.get());
_fake->setName(peer->name(), QString());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
_fake->changeColorIndex(index);
update();
}, lifetime());
std::move(backgroundEmojiId) | rpl::start_with_next([=](DocumentId id) {
_fake->changeBackgroundEmojiId(id);
update();
}, lifetime());
const auto session = &_history->session();
session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> view) {
if (view == _element.get()) {
update();
}
}, lifetime());
initElements();
}
PreviewWrap::~PreviewWrap() {
_element = nullptr;
_replyItem->destroy();
_replyToItem->destroy();
}
void PreviewWrap::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
const auto clip = e->rect();
p.setClipRect(clip);
Window::SectionWidget::PaintBackground(
p,
_theme.get(),
QSize(_box->width(), _box->window()->height()),
clip);
auto context = _theme->preparePaintContext(
_style.get(),
rect(),
clip,
!window()->isActiveWindow());
p.translate(_position);
_element->draw(p, context);
if (_element->displayFromPhoto()) {
auto userpicBottom = height()
- _element->marginBottom()
- _element->marginTop();
const auto userpicTop = userpicBottom - st::msgPhotoSize;
_peer->paintUserpicLeft(
p,
_userpic,
st::historyPhotoLeft,
userpicTop,
width(),
st::msgPhotoSize);
}
}
void PreviewWrap::initElements() {
_element->initDimensions();
widthValue(
) | rpl::filter([=](int width) {
return width > st::msgMinWidth;
}) | rpl::start_with_next([=](int width) {
const auto height = _position.y()
+ _element->resizeGetHeight(width)
+ st::msgMargin.top();
resize(width, height);
}, lifetime());
}
PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update)
: _parent(parent)
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
}
bool PreviewDelegate::elementAnimationsPaused() {
return _parent->window()->isActiveWindow();
}
auto PreviewDelegate::elementPathShiftGradient()
-> not_null<Ui::PathShiftGradient*> {
return _pathGradient.get();
}
HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
void Set(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId) {
const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId();
const auto setLocal = [=](uint8 index, DocumentId emojiId) {
using UpdateFlag = Data::PeerUpdate::Flag;
peer->changeColorIndex(index);
peer->changeBackgroundEmojiId(emojiId);
peer->session().changes().peerUpdated(
peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
};
setLocal(colorIndex, backgroundEmojiId);
const auto done = [=] {
show->showToast(peer->isSelf()
? tr::lng_settings_color_changed(tr::now)
: tr::lng_settings_color_changed_channel(tr::now));
};
const auto fail = [=](const MTP::Error &error) {
setLocal(wasIndex, wasEmojiId);
show->showToast(error.type());
};
const auto send = [&](auto &&request) {
peer->session().api().request(
std::move(request)
).done(done).fail(fail).send();
};
if (peer->isSelf()) {
send(MTPaccount_UpdateColor(
MTP_flags(
MTPaccount_UpdateColor::Flag::f_background_emoji_id),
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) {
send(MTPchannels_UpdateColor(
MTP_flags(
MTPchannels_UpdateColor::Flag::f_background_emoji_id),
channel->inputChannel,
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
}
void Apply(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId,
Fn<void()> close,
Fn<void()> cancel) {
const auto session = &peer->session();
if (peer->colorIndex() == colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
show,
tr::lng_settings_color_subscribe(
tr::now,
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_as_premium_required_link(tr::now))),
Ui::Text::WithEntities),
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId);
close();
} else {
session->api().request(MTPpremium_GetBoostsStatus(
peer->input
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto required = session->account().appConfig().get<int>(
"channel_color_level_min",
5);
if (data.vlevel().v >= required) {
Set(show, peer, colorIndex, backgroundEmojiId);
close();
return;
}
const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = {
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
},
.requiredLevel = required,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
show->showToast(error.type());
cancel();
}).send();
}
}
class ColorSelector final : public Ui::RpWidget {
public:
ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback);
private:
void fillFrom(std::vector<uint8> indices);
int resizeGetHeight(int newWidth) override;
const std::shared_ptr<Ui::ChatStyle> _style;
std::vector<std::unique_ptr<ColorSample>> _samples;
const Fn<void(uint8)> _callback;
uint8 _index = 0;
};
ColorSelector::ColorSelector(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<std::vector<uint8>> indices,
uint8 index,
Fn<void(uint8)> callback)
: RpWidget(box)
, _style(style)
, _callback(std::move(callback))
, _index(index) {
std::move(
indices
) | rpl::start_with_next([=](std::vector<uint8> indices) {
fillFrom(std::move(indices));
}, lifetime());
}
void ColorSelector::fillFrom(std::vector<uint8> indices) {
auto samples = std::vector<std::unique_ptr<ColorSample>>();
const auto add = [&](uint8 index) {
auto i = ranges::find(_samples, index, &ColorSample::index);
if (i != end(_samples)) {
samples.push_back(std::move(*i));
_samples.erase(i);
} else {
samples.push_back(std::make_unique<ColorSample>(
this,
_style,
index,
index == _index));
samples.back()->show();
samples.back()->setClickedCallback([=] {
if (_index != index) {
_callback(index);
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(false);
_index = index;
ranges::find(
_samples,
_index,
&ColorSample::index
)->get()->setSelected(true);
}
});
}
};
for (const auto index : indices) {
add(index);
}
if (!ranges::contains(indices, _index)) {
add(_index);
}
_samples = std::move(samples);
if (width() > 0) {
resizeToWidth(width());
}
}
int ColorSelector::resizeGetHeight(int newWidth) {
if (newWidth <= 0) {
return 0;
}
const auto count = int(_samples.size());
const auto columns = Ui::kSimpleColorIndexCount;
const auto skip = st::settingsColorRadioSkip;
const auto size = (newWidth - skip * (columns - 1)) / float64(columns);
const auto isize = int(base::SafeRound(size));
auto top = 0;
auto left = 0.;
for (auto i = 0; i != count; ++i) {
_samples[i]->resize(isize, isize);
_samples[i]->move(int(base::SafeRound(left)), top);
left += size + skip;
if (!((i + 1) % columns)) {
top += isize + skip;
left = 0.;
}
}
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
std::shared_ptr<Ui::ChatStyle> style,
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> emojiIdValue,
Fn<void(DocumentId)> emojiIdChosen) {
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 = CreateButton(
parent,
tr::lng_settings_color_emoji(),
*st,
{});
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
struct State {
Info::Profile::EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0;
uint8 index = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index;
if (state->emoji) {
right->update();
}
}, right->lifetime());
const auto session = &show->session();
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
state->emojiId = emojiId;
state->emoji = emojiId
? session->data().customEmojiManager().create(
emojiId,
[=] { right->update(); })
: nullptr;
right->resize(
(emojiId ? 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) {
const auto colors = style->coloredValues(false, state->index);
state->emoji->paint(p, {
.textColor = colors.name,
.position = QPoint(added, (height - emojiSize) / 2),
.internal = {
.forceFirstFrame = true,
},
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(style->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 customTextColor = [=] {
return style->coloredValues(false, state->index).name;
};
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.currentBackgroundEmojiId = state->emojiId,
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
}
});
return result;
}
} // namespace
void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme) {
box->setTitle(tr::lng_settings_color_title());
box->setWidth(st::boxWideWidth);
struct State {
rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
box->addRow(object_ptr<PreviewWrap>(
box,
style,
theme,
peer,
state->index.value(),
state->emojiId.value()
), {});
const auto appConfig = &peer->session().account().appConfig();
auto indices = rpl::single(
rpl::empty
) | rpl::then(
appConfig->refreshed()
) | rpl::map([=] {
const auto list = appConfig->get<std::vector<int>>(
"peer_colors_available",
{ 0, 1, 2, 3, 4, 5, 6 });
return list | ranges::views::transform([](int i) {
return uint8(i);
}) | ranges::to_vector;
});
const auto margin = st::settingsColorRadioMargin;
const auto skip = st::settingsColorRadioSkip;
box->addRow(
object_ptr<ColorSelector>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
const auto container = box->verticalLayout();
AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel());
AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiIconButton(
container,
show,
style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
AddSkip(container, st::settingsColorSampleSkip);
AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
}
state->applying = true;
const auto index = state->index.current();
const auto emojiId = state->emojiId.current();
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;
}));
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
const auto button = AddButton(
container,
(peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color()),
st::settingsColorButton,
{ &st::menuIconChangeColors });
auto colorIndexValue = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::Color
) | rpl::map([=] {
return peer->colorIndex();
});
const auto name = peer->shortName();
const auto style = std::make_shared<Ui::ChatStyle>(
peer->session().colorIndicesValue());
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(button->lifetime()));
style->apply(theme.get());
const auto sample = Ui::CreateChild<ColorSample>(
button.get(),
style,
rpl::duplicate(colorIndexValue),
name);
sample->show();
rpl::combine(
button->widthValue(),
tr::lng_settings_theme_name_color(),
rpl::duplicate(colorIndexValue)
) | rpl::start_with_next([=](
int width,
const QString &button,
int colorIndex) {
const auto sampleSize = st::settingsColorSampleSize;
const auto available = width
- st::settingsButton.padding.left()
- (st::settingsColorButton.padding.right() - sampleSize)
- st::settingsButton.style.font->width(button)
- st::settingsButtonRightSkip;
if (style->colorPatternIndex(colorIndex)) {
sample->resize(sampleSize, sampleSize);
} else {
const auto padding = st::settingsColorSamplePadding;
const auto wantedHeight = padding.top()
+ st::semiboldFont->height
+ padding.bottom();
const auto wantedWidth = sample->naturalWidth();
sample->resize(std::min(wantedWidth, available), wantedHeight);
}
sample->update();
}, sample->lifetime());
rpl::combine(
button->sizeValue(),
sample->sizeValue(),
std::move(colorIndexValue)
) | rpl::start_with_next([=](QSize outer, QSize inner, int colorIndex) {
const auto right = st::settingsColorButton.padding.right()
- st::settingsColorSampleSkip
- st::settingsColorSampleSize
- (style->colorPatternIndex(colorIndex)
? 0
: st::settingsColorSamplePadding.right());
sample->move(
outer.width() - right - inner.width(),
(outer.height() - inner.height()) / 2);
}, sample->lifetime());
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
button->setClickedCallback([=] {
show->show(Box(EditPeerColorBox, show, peer, style, theme));
});
}

View file

@ -0,0 +1,31 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui {
class GenericBox;
class ChatStyle;
class ChatTheme;
class VerticalLayout;
} // namespace Ui
void EditPeerColorBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style = nullptr,
std::shared_ptr<Ui::ChatTheme> theme = nullptr);
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer);

View file

@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/peers/edit_peer_common.h" #include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_peer_type_box.h" #include "boxes/peers/edit_peer_type_box.h"
#include "boxes/peers/edit_peer_history_visibility_box.h" #include "boxes/peers/edit_peer_history_visibility_box.h"
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_linked_chat_box.h" #include "boxes/peers/edit_linked_chat_box.h"
#include "boxes/peers/edit_peer_requests_box.h" #include "boxes/peers/edit_peer_requests_box.h"
#include "boxes/peers/edit_peer_reactions.h" #include "boxes/peers/edit_peer_reactions.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/stickers_box.h" #include "boxes/stickers_box.h"
#include "boxes/username_box.h" #include "boxes/username_box.h"
#include "ui/boxes/single_choice_box.h" #include "ui/boxes/single_choice_box.h"
@ -302,6 +303,7 @@ private:
void fillLinkedChatButton(); void fillLinkedChatButton();
//void fillInviteLinkButton(); //void fillInviteLinkButton();
void fillForumButton(); void fillForumButton();
void fillColorIndexButton();
void fillSignaturesButton(); void fillSignaturesButton();
void fillHistoryVisibilityButton(); void fillHistoryVisibilityButton();
void fillManageSection(); void fillManageSection();
@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() {
_controls.forumToggle->setToggleLocked(locked); _controls.forumToggle->setToggleLocked(locked);
} }
void Controller::fillColorIndexButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto show = _navigation->uiShow();
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
}
void Controller::fillSignaturesButton() { void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr); Expects(_controls.buttonsLayout != nullptr);
@ -1024,74 +1033,42 @@ void Controller::fillManageSection() {
return; return;
} }
const auto canEditType = [&] { const auto canEditType = isChannel
return isChannel ? channel->amCreator()
? channel->amCreator() : chat->amCreator();
: chat->amCreator(); const auto canEditSignatures = isChannel
}(); && channel->canEditSignatures()
const auto canEditSignatures = [&] { && !channel->isMegagroup();
return isChannel const auto canEditPreHistoryHidden = isChannel
? (channel->canEditSignatures() && !channel->isMegagroup()) ? channel->canEditPreHistoryHidden()
: false; : chat->canEditPreHistoryHidden();
}();
const auto canEditPreHistoryHidden = [&] {
return isChannel
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
}();
const auto canEditForum = isChannel const auto canEditForum = isChannel
? (channel->isMegagroup() && channel->amCreator()) ? (channel->isMegagroup() && channel->amCreator())
: chat->amCreator(); : chat->amCreator();
const auto canEditPermissions = isChannel
const auto canEditPermissions = [&] { ? channel->canEditPermissions()
return isChannel : chat->canEditPermissions();
? channel->canEditPermissions() const auto canEditInviteLinks = isChannel
: chat->canEditPermissions(); ? channel->canHaveInviteLink()
}(); : chat->canHaveInviteLink();
const auto canEditInviteLinks = [&] { const auto canViewAdmins = isChannel
return isChannel ? channel->canViewAdmins()
? channel->canHaveInviteLink() : chat->amIn();
: chat->canHaveInviteLink(); const auto canViewMembers = isChannel
}(); ? channel->canViewMembers()
const auto canViewAdmins = [&] { : chat->amIn();
return isChannel const auto canViewKicked = isChannel
? channel->canViewAdmins() && (channel->isBroadcast() || channel->isGigagroup());
: chat->amIn(); const auto hasRecentActions = isChannel
}(); && (channel->hasAdminRights() || channel->amCreator());
const auto canViewMembers = [&] { const auto canEditStickers = isChannel && channel->canEditStickers();
return isChannel const auto canDeleteChannel = isChannel && channel->canDelete();
? channel->canViewMembers() const auto canEditColorIndex = isChannel
: chat->amIn(); && !channel->isMegagroup()
}(); && channel->canEditInformation();
const auto canViewKicked = [&] { const auto canViewOrEditLinkedChat = isChannel
return isChannel && (channel->linkedChat()
? (channel->isBroadcast() || channel->isGigagroup()) || (channel->isBroadcast() && channel->canEditInformation()));
: false;
}();
const auto hasRecentActions = [&] {
return isChannel
? (channel->hasAdminRights() || channel->amCreator())
: false;
}();
const auto canEditStickers = [&] {
return isChannel
? channel->canEditStickers()
: false;
}();
const auto canDeleteChannel = [&] {
return isChannel
? channel->canDelete()
: false;
}();
const auto canViewOrEditLinkedChat = [&] {
return !isChannel
? false
: channel->linkedChat()
? true
: (channel->isBroadcast() && channel->canEditInformation());
}();
AddSkip(_controls.buttonsLayout, 0); AddSkip(_controls.buttonsLayout, 0);
@ -1109,11 +1086,15 @@ void Controller::fillManageSection() {
if (canEditForum) { if (canEditForum) {
fillForumButton(); fillForumButton();
} }
if (canEditColorIndex) {
fillColorIndexButton();
}
if (canEditSignatures) { if (canEditSignatures) {
fillSignaturesButton(); fillSignaturesButton();
} }
if (canEditPreHistoryHidden if (canEditPreHistoryHidden
|| canEditForum || canEditForum
|| canEditColorIndex
|| canEditSignatures || canEditSignatures
//|| canEditInviteLinks //|| canEditInviteLinks
|| canViewOrEditLinkedChat || canViewOrEditLinkedChat

View file

@ -63,7 +63,9 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
MTPstring(), // lang code MTPstring(), // lang code
MTPEmojiStatus(), MTPEmojiStatus(),
MTPVector<MTPUsername>(), MTPVector<MTPUsername>(),
MTPint())); // stories_max_id MTPint(), // stories_max_id
MTP_int(0), // color
MTPlong())); // background_emoji_id
return peerId; return peerId;
} }
@ -71,7 +73,7 @@ AdminLog::OwnedItem GenerateItem(
not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryView::ElementDelegate*> delegate,
not_null<History*> history, not_null<History*> history,
PeerId from, PeerId from,
MsgId replyTo, FullMsgId replyTo,
const QString &text) { const QString &text) {
Expects(history->peer->isUser()); Expects(history->peer->isUser());
@ -81,7 +83,7 @@ AdminLog::OwnedItem GenerateItem(
| MessageFlag::HasFromId | MessageFlag::HasFromId
| MessageFlag::HasReplyInfo), | MessageFlag::HasReplyInfo),
UserId(), // via UserId(), // via
FullReplyTo{ .msgId = replyTo }, FullReplyTo{ .messageId = replyTo },
base::unixtime::now(), // date base::unixtime::now(), // date
from, from,
QString(), // postAuthor QString(), // postAuthor
@ -131,7 +133,8 @@ void AddMessage(
state->delegate = std::make_unique<Delegate>( state->delegate = std::make_unique<Delegate>(
controller, controller,
crl::guard(widget, [=] { widget->update(); })); crl::guard(widget, [=] { widget->update(); }));
state->style = std::make_unique<Ui::ChatStyle>(); state->style = std::make_unique<Ui::ChatStyle>(
controller->session().colorIndicesValue());
state->style->apply(controller->defaultChatTheme().get()); state->style->apply(controller->defaultChatTheme().get());
state->icons.lifetimes = std::vector<rpl::lifetime>(2); state->icons.lifetimes = std::vector<rpl::lifetime>(2);
@ -143,13 +146,13 @@ void AddMessage(
GenerateUser( GenerateUser(
history, history,
tr::lng_settings_chat_message_reply_from(tr::now)), tr::lng_settings_chat_message_reply_from(tr::now)),
0, FullMsgId(),
tr::lng_settings_chat_message_reply(tr::now)); tr::lng_settings_chat_message_reply(tr::now));
auto message = GenerateItem( auto message = GenerateItem(
state->delegate.get(), state->delegate.get(),
history, history,
history->peer->id, history->peer->id,
state->reply->data()->fullId().msg, state->reply->data()->fullId(),
tr::lng_settings_chat_message(tr::now)); tr::lng_settings_chat_message(tr::now));
const auto view = message.get(); const auto view = message.get();
state->item = std::move(message); state->item = std::move(message);

View file

@ -202,8 +202,7 @@ void Userpic::createCache(Image *image) {
{ {
auto p = QPainter(&filled); auto p = QPainter(&filled);
Ui::EmptyUserpic( Ui::EmptyUserpic(
Ui::EmptyUserpic::UserpicColor( Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()),
Data::PeerColorIndex(_peer->id)),
_peer->name() _peer->name()
).paintCircle(p, 0, 0, size, size); ).paintCircle(p, 0, 0, size, size);
} }

View file

@ -16,7 +16,7 @@ struct SendCommandRequest {
not_null<PeerData*> peer; not_null<PeerData*> peer;
QString command; QString command;
FullMsgId context; FullMsgId context;
MsgId replyTo = 0; FullReplyTo replyTo;
}; };
[[nodiscard]] QString WrapCommandInChat( [[nodiscard]] QString WrapCommandInChat(

View file

@ -662,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) {
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }}; fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
fadeRight: icon {{ "fade_horizontal", windowBg }}; fadeRight: icon {{ "fade_horizontal", windowBg }};
} }
backgroundEmojiPan: EmojiPan(defaultEmojiPan) {
padding: margins(7px, 7px, 4px, 0px);
}
inlineBotsScroll: ScrollArea(defaultSolidScroll) { inlineBotsScroll: ScrollArea(defaultSolidScroll) {
deltat: stickerPanPadding; deltat: stickerPanPadding;

View file

@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget(
, _localSetsManager( , _localSetsManager(
std::make_unique<LocalStickersManager>(&session())) std::make_unique<LocalStickersManager>(&session()))
, _customRecentFactory(std::move(descriptor.customRecentFactory)) , _customRecentFactory(std::move(descriptor.customRecentFactory))
, _customTextColor(std::move(descriptor.customTextColor))
, _overBg(st::emojiPanRadius, st().overBg) , _overBg(st::emojiPanRadius, st().overBg)
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg) , _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
, _picker(this, st()) , _picker(this, st())
@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
} }
if (_mode != Mode::RecentReactions) { if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
setupSearch(); setupSearch();
} }
@ -791,10 +792,12 @@ object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
}; };
auto result = object_ptr<StickersListFooter>(FooterDescriptor{ auto result = object_ptr<StickersListFooter>(FooterDescriptor{
.session = &session(), .session = &session(),
.customTextColor = _customTextColor,
.paused = footerPaused, .paused = footerPaused,
.parent = this, .parent = this,
.st = &st(), .st = &st(),
.features = { .stickersSettings = false }, .features = { .stickersSettings = false },
.forceFirstFrame = (_mode == Mode::BackgroundEmoji),
}); });
_footer = result; _footer = result;
@ -1027,6 +1030,14 @@ 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) {
const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({
.custom = resolveCustomRecent(fakeId),
.id = { Ui::Emoji::Find(no) },
});
_recentCustomIds.emplace(fakeId);
} else { } else {
_recent.push_back({ _recent.push_back({
.custom = resolveCustomRecent(id), .custom = resolveCustomRecent(id),
@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
void EmojiListWidget::validateEmojiPaintContext( void EmojiListWidget::validateEmojiPaintContext(
const ExpandingContext &context) { const ExpandingContext &context) {
auto value = Ui::Text::CustomEmojiPaintContext{ auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_mode == Mode::EmojiStatus .textColor = (_customTextColor
? _customTextColor()
: (_mode == Mode::EmojiStatus)
? anim::color( ? anim::color(
st::stickerPanPremium1, st::stickerPanPremium1,
st::stickerPanPremium2, st::stickerPanPremium2,
@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext(
.scale = context.progress, .scale = context.progress,
.paused = On(powerSavingFlag()) || paused(), .paused = On(powerSavingFlag()) || paused(),
.scaled = context.expanding, .scaled = context.expanding,
.internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) },
}; };
if (!_emojiPaintContext) { if (!_emojiPaintContext) {
_emojiPaintContext = std::make_unique< _emojiPaintContext = std::make_unique<
@ -1629,6 +1643,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
case Mode::TopicIcon: case Mode::TopicIcon:
Settings::ShowPremium(resolved, u"forum_topic_icon"_q); Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
break; break;
case Mode::BackgroundEmoji:
Settings::ShowPremium(resolved, u"name_color"_q);
break;
} }
} }
} }
@ -1995,7 +2012,10 @@ void EmojiListWidget::refreshCustom() {
const auto &sets = owner->stickers().sets(); const auto &sets = owner->stickers().sets();
const auto push = [&](uint64 setId, bool installed) { const auto push = [&](uint64 setId, bool installed) {
auto it = sets.find(setId); auto it = sets.find(setId);
if (it == sets.cend() || it->second->stickers.isEmpty()) { if (it == sets.cend()
|| it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji
&& !it->second->textColor())) {
return; return;
} }
const auto canRemove = !!(it->second->flags const auto canRemove = !!(it->second->flags

View file

@ -74,11 +74,13 @@ enum class EmojiListMode {
FullReactions, FullReactions,
RecentReactions, RecentReactions,
UserpicBuilder, UserpicBuilder,
BackgroundEmoji,
}; };
struct EmojiListDescriptor { struct EmojiListDescriptor {
std::shared_ptr<Show> show; std::shared_ptr<Show> show;
EmojiListMode mode = EmojiListMode::Full; EmojiListMode mode = EmojiListMode::Full;
Fn<QColor()> customTextColor;
Fn<bool()> paused; Fn<bool()> paused;
std::vector<DocumentId> customRecentList; std::vector<DocumentId> customRecentList;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>( Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
@ -386,6 +388,7 @@ private:
base::flat_map< base::flat_map<
DocumentId, DocumentId,
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent; std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
Fn<QColor()> _customTextColor;
int _customSingleSize = 0; int _customSingleSize = 0;
bool _allowWithoutPremium = false; bool _allowWithoutPremium = false;
Ui::RoundRect _overBg; Ui::RoundRect _overBg;

View file

@ -601,6 +601,12 @@ MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
_lifetime = _field->changes( _lifetime = _field->changes(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto length = _field->getTextWithTags().text.size(); const auto length = _field->getTextWithTags().text.size();
if (!length) {
_lastLength = 0;
_timer.cancel();
parse();
return;
}
const auto timeout = (std::abs(length - _lastLength) > 2) const auto timeout = (std::abs(length - _lastLength) > 2)
? 0 ? 0
: kParseLinksTimeout; : kParseLinksTimeout;
@ -642,16 +648,13 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
return QObject::eventFilter(object, event); return QObject::eventFilter(object, event);
} }
const rpl::variable<QStringList> &MessageLinksParser::list() const {
return _list;
}
void MessageLinksParser::parse() { void MessageLinksParser::parse() {
const auto &textWithTags = _field->getTextWithTags(); const auto &textWithTags = _field->getTextWithTags();
const auto &text = textWithTags.text; const auto &text = textWithTags.text;
const auto &tags = textWithTags.tags; const auto &tags = textWithTags.tags;
const auto &markdownTags = _field->getMarkdownTags(); const auto &markdownTags = _field->getMarkdownTags();
if (_disabled || text.isEmpty()) { if (_disabled || text.isEmpty()) {
_ranges = {};
_list = QStringList(); _list = QStringList();
return; return;
} }
@ -663,7 +666,7 @@ void MessageLinksParser::parse() {
|| (tag == Ui::InputField::kTagSpoiler); || (tag == Ui::InputField::kTagSpoiler);
}; };
auto ranges = QVector<LinkRange>(); _ranges.clear();
auto tag = tags.begin(); auto tag = tags.begin();
const auto tagsEnd = tags.end(); const auto tagsEnd = tags.end();
@ -672,7 +675,7 @@ void MessageLinksParser::parse() {
if (Ui::InputField::IsValidMarkdownLink(tag->id) if (Ui::InputField::IsValidMarkdownLink(tag->id)
&& !TextUtilities::IsMentionLink(tag->id)) { && !TextUtilities::IsMentionLink(tag->id)) {
ranges.push_back({ tag->offset, tag->length, tag->id }); _ranges.push_back({ tag->offset, tag->length, tag->id });
} }
++tag; ++tag;
}; };
@ -774,7 +777,7 @@ void MessageLinksParser::parse() {
continue; continue;
} }
} }
const auto range = LinkRange { const auto range = MessageLinkRange{
int(domainOffset), int(domainOffset),
static_cast<int>(p - start - domainOffset), static_cast<int>(p - start - domainOffset),
QString() QString()
@ -782,22 +785,20 @@ void MessageLinksParser::parse() {
processTagsBefore(domainOffset); processTagsBefore(domainOffset);
if (!hasTagsIntersection(range.start + range.length)) { if (!hasTagsIntersection(range.start + range.length)) {
if (markdownTagsAllow(range.start, range.length)) { if (markdownTagsAllow(range.start, range.length)) {
ranges.push_back(range); _ranges.push_back(range);
} }
} }
offset = matchOffset = p - start; offset = matchOffset = p - start;
} }
processTagsBefore(Ui::kQFixedMax); processTagsBefore(Ui::kQFixedMax);
apply(text, ranges); applyRanges(text);
} }
void MessageLinksParser::apply( void MessageLinksParser::applyRanges(const QString &text) {
const QString &text, const auto count = int(_ranges.size());
const QVector<LinkRange> &ranges) {
const auto count = int(ranges.size());
const auto current = _list.current(); const auto current = _list.current();
const auto computeLink = [&](const LinkRange &range) { const auto computeLink = [&](const MessageLinkRange &range) {
return range.custom.isEmpty() return range.custom.isEmpty()
? base::StringViewMid(text, range.start, range.length) ? base::StringViewMid(text, range.start, range.length)
: QStringView(range.custom); : QStringView(range.custom);
@ -807,7 +808,7 @@ void MessageLinksParser::apply(
return true; return true;
} }
for (auto i = 0; i != count; ++i) { for (auto i = 0; i != count; ++i) {
if (computeLink(ranges[i]) != current[i]) { if (computeLink(_ranges[i]) != current[i]) {
return true; return true;
} }
} }
@ -818,7 +819,7 @@ void MessageLinksParser::apply(
} }
auto parsed = QStringList(); auto parsed = QStringList();
parsed.reserve(count); parsed.reserve(count);
for (const auto &range : ranges) { for (const auto &range : _ranges) {
parsed.push_back(computeLink(range).toString()); parsed.push_back(computeLink(range).toString());
} }
_list = std::move(parsed); _list = std::move(parsed);

View file

@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "ui/widgets/fields/input_field.h" #include "base/qt/qt_compare.h"
#include "base/timer.h" #include "base/timer.h"
#include "chat_helpers/compose/compose_features.h" #include "chat_helpers/compose/compose_features.h"
#include "ui/widgets/fields/input_field.h"
#ifndef TDESKTOP_DISABLE_SPELLCHECK #ifndef TDESKTOP_DISABLE_SPELLCHECK
#include "boxes/dictionaries_manager.h" #include "boxes/dictionaries_manager.h"
@ -96,38 +97,42 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery(
not_null<const Ui::InputField*> field, not_null<const Ui::InputField*> field,
ChatHelpers::ComposeFeatures features); ChatHelpers::ComposeFeatures features);
class MessageLinksParser : private QObject { struct MessageLinkRange {
int start = 0;
int length = 0;
QString custom;
friend inline auto operator<=>(
const MessageLinkRange&,
const MessageLinkRange&) = default;
friend inline bool operator==(
const MessageLinkRange&,
const MessageLinkRange&) = default;
};
class MessageLinksParser final : private QObject {
public: public:
MessageLinksParser(not_null<Ui::InputField*> field); MessageLinksParser(not_null<Ui::InputField*> field);
void parseNow(); void parseNow();
void setDisabled(bool disabled); void setDisabled(bool disabled);
[[nodiscard]] const rpl::variable<QStringList> &list() const; [[nodiscard]] const rpl::variable<QStringList> &list() const {
return _list;
protected: }
bool eventFilter(QObject *object, QEvent *event) override; [[nodiscard]] const std::vector<MessageLinkRange> &ranges() const {
return _ranges;
}
private: private:
struct LinkRange { bool eventFilter(QObject *object, QEvent *event) override;
int start;
int length;
QString custom;
};
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
return (a.start == b.start)
&& (a.length == b.length)
&& (a.custom == b.custom);
}
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
return !(a == b);
}
void parse(); void parse();
void apply(const QString &text, const QVector<LinkRange> &ranges); void applyRanges(const QString &text);
not_null<Ui::InputField*> _field; not_null<Ui::InputField*> _field;
rpl::variable<QStringList> _list; rpl::variable<QStringList> _list;
std::vector<MessageLinkRange> _ranges;
int _lastLength = 0; int _lastLength = 0;
bool _disabled = false; bool _disabled = false;
base::Timer _timer; base::Timer _timer;

View file

@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
descriptor.parent, descriptor.parent,
descriptor.st ? *descriptor.st : st::defaultEmojiPan) descriptor.st ? *descriptor.st : st::defaultEmojiPan)
, _session(descriptor.session) , _session(descriptor.session)
, _paused(descriptor.paused) , _customTextColor(std::move(descriptor.customTextColor))
, _paused(std::move(descriptor.paused))
, _features(descriptor.features) , _features(descriptor.features)
, _iconState([=] { update(); }) , _iconState([=] { update(); })
, _subiconState([=] { update(); }) , _subiconState([=] { update(); })
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver) , _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) { , _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
, _forceFirstFrame(descriptor.forceFirstFrame) {
setMouseTracking(true); setMouseTracking(true);
_iconsLeft = st().iconSkip _iconsLeft = st().iconSkip
@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache(
const auto y = (st().footer - icon.pixh) / 2; const auto y = (st().footer - icon.pixh) / 2;
if (icon.custom) { if (icon.custom) {
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{ icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
.textColor = st().textFg->c, .textColor = (_customTextColor
? _customTextColor()
: st().textFg->c),
.size = QSize(icon.pixw, icon.pixh), .size = QSize(icon.pixw, icon.pixh),
.now = now, .now = now,
.scale = context.progress, .scale = context.progress,
.position = { x, y }, .position = { x, y },
.paused = paused, .paused = paused,
.scaled = context.expanding, .scaled = context.expanding,
.internal = { .forceFirstFrame = _forceFirstFrame },
}); });
} else if (icon.lottie && icon.lottie->ready()) { } else if (icon.lottie && icon.lottie->ready()) {
const auto frame = icon.lottie->frame(); const auto frame = icon.lottie->frame();
@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache(
return icons[index]; return icons[index];
}; };
const auto paintOne = [&](int left, const style::icon *icon) { const auto paintOne = [&](int left, const style::icon *icon) {
icon->paint( left += (_singleWidth - icon->width()) / 2;
p, const auto top = (st().footer - icon->height()) / 2;
left + (_singleWidth - icon->width()) / 2, if (_customTextColor) {
(st().footer - icon->height()) / 2, icon->paint(p, left, top, width(), _customTextColor());
width()); } else {
icon->paint(p, left, top, width());
}
}; };
if (_icons[info.index].setId == AllEmojiSectionSetId() if (_icons[info.index].setId == AllEmojiSectionSetId()
&& info.width > _singleWidth) { && info.width > _singleWidth) {

View file

@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter {
public: public:
struct Descriptor { struct Descriptor {
not_null<Main::Session*> session; not_null<Main::Session*> session;
Fn<QColor()> customTextColor;
Fn<bool()> paused; Fn<bool()> paused;
not_null<RpWidget*> parent; not_null<RpWidget*> parent;
const style::EmojiPan *st = nullptr; const style::EmojiPan *st = nullptr;
ComposeFeatures features; ComposeFeatures features;
bool forceFirstFrame = false;
}; };
explicit StickersListFooter(Descriptor &&descriptor); explicit StickersListFooter(Descriptor &&descriptor);
@ -269,6 +271,7 @@ private:
void clipCallback(Media::Clip::Notification notification, uint64 setId); void clipCallback(Media::Clip::Notification notification, uint64 setId);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
const Fn<QColor()> _customTextColor;
const Fn<bool()> _paused; const Fn<bool()> _paused;
const ComposeFeatures _features; const ComposeFeatures _features;
@ -303,6 +306,7 @@ private:
int _subiconsWidth = 0; int _subiconsWidth = 0;
bool _subiconsExpanded = false; bool _subiconsExpanded = false;
bool _repaintScheduled = false; bool _repaintScheduled = false;
bool _forceFirstFrame = false;
rpl::event_stream<> _openSettingsRequests; rpl::event_stream<> _openSettingsRequests;
rpl::event_stream<uint64> _setChosen; rpl::event_stream<uint64> _setChosen;

View file

@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector(
Mode mode) Mode mode)
: TabbedSelector(parent, { : TabbedSelector(parent, {
.show = std::move(show), .show = std::move(show),
.st = (mode == Mode::EmojiStatus .st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji)
? st::statusEmojiPan ? st::statusEmojiPan
: st::defaultEmojiPan), : st::defaultEmojiPan),
.level = level, .level = level,
@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector(
, _features(descriptor.features) , _features(descriptor.features)
, _show(std::move(descriptor.show)) , _show(std::move(descriptor.show))
, _level(descriptor.level) , _level(descriptor.level)
, _customTextColor(std::move(descriptor.customTextColor))
, _mode(descriptor.mode) , _mode(descriptor.mode)
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg)) , _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
, _categoriesRounding( , _categoriesRounding(
@ -512,7 +513,10 @@ 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::BackgroundEmoji
? EmojiMode::BackgroundEmoji
: EmojiMode::Full), : EmojiMode::Full),
.customTextColor = _customTextColor,
.paused = paused, .paused = paused,
.st = &_st, .st = &_st,
.features = _features, .features = _features,

View file

@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly, EmojiOnly,
MediaEditor, MediaEditor,
EmojiStatus, EmojiStatus,
BackgroundEmoji,
}; };
struct TabbedSelectorDescriptor { struct TabbedSelectorDescriptor {
@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor {
const style::EmojiPan &st; const style::EmojiPan &st;
PauseReason level = {}; PauseReason level = {};
TabbedSelectorMode mode = TabbedSelectorMode::Full; TabbedSelectorMode mode = TabbedSelectorMode::Full;
Fn<QColor()> customTextColor;
ComposeFeatures features; ComposeFeatures features;
}; };
@ -272,6 +274,7 @@ private:
const ComposeFeatures _features; const ComposeFeatures _features;
const std::shared_ptr<Show> _show; const std::shared_ptr<Show> _show;
const PauseReason _level = {}; const PauseReason _level = {};
const Fn<QColor()> _customTextColor;
Mode _mode = Mode::Full; Mode _mode = Mode::Full;
int _roundRadius = 0; int _roundRadius = 0;

View file

@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
.peer = peer, .peer = peer,
.command = _cmd, .command = _cmd,
.context = my.itemId, .context = my.itemId,
.replyTo = 0,
}); });
} }
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "api/api_chat_filters.h" #include "api/api_chat_filters.h"
#include "api/api_chat_invite.h" #include "api/api_chat_invite.h"
#include "api/api_premium.h"
#include "base/qthelp_regex.h" #include "base/qthelp_regex.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "boxes/connection_box.h" #include "boxes/connection_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h" #include "boxes/sessions_box.h"
#include "boxes/language_box.h" #include "boxes/language_box.h"
@ -348,6 +350,11 @@ bool ResolveUsernameOrPhone(
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);
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
ResolveGiftCode(controller, appnameParam);
return true;
}
// Fix t.me/s/username links. // Fix t.me/s/username links.
const auto webChannelPreviewLink = (domainParam == u"s"_q) const auto webChannelPreviewLink = (domainParam == u"s"_q)
&& !appnameParam.isEmpty(); && !appnameParam.isEmpty();
@ -637,6 +644,17 @@ bool OpenExternalLink(
context); context);
} }
bool CopyPeerId(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
if (controller) {
controller->showToast(tr::lng_text_copied(tr::now));
}
return true;
}
void ExportTestChatTheme( void ExportTestChatTheme(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) { not_null<const Data::CloudTheme*> theme) {
@ -978,6 +996,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^url:(.+)$"_q, u"^url:(.+)$"_q,
OpenExternalLink OpenExternalLink
}, },
{
u"^copy:(.+)$"_q,
CopyPeerId
}
}; };
return Result; return Result;
} }
@ -1078,7 +1100,7 @@ QString TryConvertUrlToLocal(QString url) {
"(" "("
"/?\\?|" "/?\\?|"
"/?$|" "/?$|"
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|" "/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
"/\\d+/?(\\?|$)|" "/\\d+/?(\\?|$)|"
"/s/\\d+/?(\\?|$)|" "/s/\\d+/?(\\?|$)|"
"/\\d+/\\d+/?(\\?|$)" "/\\d+/\\d+/?(\\?|$)"
@ -1103,7 +1125,7 @@ QString TryConvertUrlToLocal(QString url) {
added = u"&post="_q + postMatch->captured(1); added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { } else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&story="_q + storyMatch->captured(1); added = u"&story="_q + storyMatch->captured(1);
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) { } else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&appname="_q + appNameMatch->captured(1); added = u"&appname="_q + appNameMatch->captured(1);
} }
return base + added + (params.isEmpty() ? QString() : '&' + params); return base + added + (params.isEmpty() ? QString() : '&' + params);

View file

@ -97,7 +97,7 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"message"_q , Command::JustSendMessage }, { u"message"_q , Command::JustSendMessage },
{ u"message_silently"_q , Command::SendSilentMessage }, { u"message_silently"_q , Command::SendSilentMessage },
{ u"message_scheduled"_q , Command::ScheduleMessage }, { u"message_scheduled"_q , Command::ScheduleMessage },
{ u"mevia_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen }, { u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
// //
}; };

View file

@ -348,6 +348,10 @@ QString UiIntegration::phraseFormattingStrikeOut() {
return tr::lng_menu_formatting_strike_out(tr::now); return tr::lng_menu_formatting_strike_out(tr::now);
} }
QString UiIntegration::phraseFormattingBlockquote() {
return tr::lng_menu_formatting_blockquote(tr::now);
}
QString UiIntegration::phraseFormattingMonospace() { QString UiIntegration::phraseFormattingMonospace() {
return tr::lng_menu_formatting_monospace(tr::now); return tr::lng_menu_formatting_monospace(tr::now);
} }
@ -404,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() {
return tr::lng_bot_allow_write_confirm(tr::now); return tr::lng_bot_allow_write_confirm(tr::now);
} }
QString UiIntegration::phraseQuoteHeaderCopy() {
return tr::lng_code_block_header_copy(tr::now);
}
bool OpenGLLastCheckFailed() { bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath()); return QFile::exists(OpenGLCheckFilePath());
} }

View file

@ -77,6 +77,7 @@ public:
QString phraseFormattingItalic() override; QString phraseFormattingItalic() override;
QString phraseFormattingUnderline() override; QString phraseFormattingUnderline() override;
QString phraseFormattingStrikeOut() override; QString phraseFormattingStrikeOut() override;
QString phraseFormattingBlockquote() override;
QString phraseFormattingMonospace() override; QString phraseFormattingMonospace() override;
QString phraseFormattingSpoiler() override; QString phraseFormattingSpoiler() override;
QString phraseButtonOk() override; QString phraseButtonOk() override;
@ -91,6 +92,7 @@ public:
QString phraseBotAllowWrite() override; QString phraseBotAllowWrite() override;
QString phraseBotAllowWriteTitle() override; QString phraseBotAllowWriteTitle() override;
QString phraseBotAllowWriteConfirm() override; QString phraseBotAllowWriteConfirm() override;
QString phraseQuoteHeaderCopy() override;
}; };

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 = 4010004; constexpr auto AppVersion = 4011001;
constexpr auto AppVersionStr = "4.10.4"; constexpr auto AppVersionStr = "4.11.1";
constexpr auto AppBetaVersion = true; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -254,7 +254,7 @@ const std::array<Info, 231> FallbackList = { {
CountriesInstance::CountriesInstance() { CountriesInstance::CountriesInstance() {
} }
const std::vector<Info> &CountriesInstance::list() { const std::vector<Info> &CountriesInstance::list() const {
if (_list.empty()) { if (_list.empty()) {
_list = (FallbackList | ranges::to_vector); _list = (FallbackList | ranges::to_vector);
} }
@ -268,7 +268,7 @@ void CountriesInstance::setList(std::vector<Info> &&infos) {
_updated.fire({}); _updated.fire({});
} }
const CountriesInstance::Map &CountriesInstance::byCode() { const CountriesInstance::Map &CountriesInstance::byCode() const {
if (_byCode.empty()) { if (_byCode.empty()) {
_byCode.reserve(list().size()); _byCode.reserve(list().size());
for (const auto &entry : list()) { for (const auto &entry : list()) {
@ -280,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() {
return _byCode; return _byCode;
} }
const CountriesInstance::Map &CountriesInstance::byISO2() { const CountriesInstance::Map &CountriesInstance::byISO2() const {
if (_byISO2.empty()) { if (_byISO2.empty()) {
_byISO2.reserve(list().size()); _byISO2.reserve(list().size());
for (const auto &entry : list()) { for (const auto &entry : list()) {
@ -290,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() {
return _byISO2; return _byISO2;
} }
QString CountriesInstance::validPhoneCode(QString fullCode) { QString CountriesInstance::validPhoneCode(QString fullCode) const {
const auto &listByCode = byCode(); const auto &listByCode = byCode();
while (fullCode.length()) { while (fullCode.length()) {
const auto i = listByCode.constFind(fullCode); const auto i = listByCode.constFind(fullCode);
@ -302,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) {
return QString(); return QString();
} }
QString CountriesInstance::countryNameByISO2(const QString &iso) { QString CountriesInstance::countryNameByISO2(const QString &iso) const {
const auto &listByISO2 = byISO2(); const auto &listByISO2 = byISO2();
const auto i = listByISO2.constFind(iso); const auto i = listByISO2.constFind(iso);
return (i != listByISO2.cend()) ? (*i)->name : QString(); return (i != listByISO2.cend()) ? (*i)->name : QString();
} }
QString CountriesInstance::countryISO2ByPhone(const QString &phone) { QString CountriesInstance::countryISO2ByPhone(const QString &phone) const {
const auto &listByCode = byCode(); const auto &listByCode = byCode();
const auto code = validPhoneCode(phone); const auto code = validPhoneCode(phone);
const auto i = listByCode.find(code); const auto i = listByCode.find(code);
return (i != listByCode.cend()) ? (*i)->iso2 : QString(); return (i != listByCode.cend()) ? (*i)->iso2 : QString();
} }
FormatResult CountriesInstance::format(FormatArgs args) { QString CountriesInstance::flagEmojiByISO2(const QString &iso) const {
if (iso.size() != 2
|| iso.front() < 'A'
|| iso.front() > 'Z'
|| iso.back() < 'A'
|| iso.back() > 'Z') {
return QString();
}
auto result = QString(4, QChar(0xD83C));
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
result[3] = QChar(iso.back().unicode() - 'A' + 0xDDE6);
return result;
}
FormatResult CountriesInstance::format(FormatArgs args) const {
// Ported from TDLib. // Ported from TDLib.
if (args.phone.isEmpty()) { if (args.phone.isEmpty()) {
return FormatResult(); return FormatResult();

View file

@ -43,25 +43,26 @@ public:
using Map = QHash<QString, const Info *>; using Map = QHash<QString, const Info *>;
CountriesInstance(); CountriesInstance();
[[nodiscard]] const std::vector<Info> &list(); [[nodiscard]] const std::vector<Info> &list() const;
void setList(std::vector<Info> &&infos); void setList(std::vector<Info> &&infos);
[[nodiscard]] const Map &byCode(); [[nodiscard]] const Map &byCode() const;
[[nodiscard]] const Map &byISO2(); [[nodiscard]] const Map &byISO2() const;
[[nodiscard]] QString validPhoneCode(QString fullCode); [[nodiscard]] QString validPhoneCode(QString fullCode) const;
[[nodiscard]] QString countryNameByISO2(const QString &iso); [[nodiscard]] QString countryNameByISO2(const QString &iso) const;
[[nodiscard]] QString countryISO2ByPhone(const QString &phone); [[nodiscard]] QString countryISO2ByPhone(const QString &phone) const;
[[nodiscard]] QString flagEmojiByISO2(const QString &iso) const;
[[nodiscard]] FormatResult format(FormatArgs args); [[nodiscard]] FormatResult format(FormatArgs args) const;
[[nodiscard]] rpl::producer<> updated() const; [[nodiscard]] rpl::producer<> updated() const;
private: private:
std::vector<Info> _list; mutable std::vector<Info> _list;
Map _byCode; mutable Map _byCode;
Map _byISO2; mutable Map _byISO2;
rpl::event_stream<> _updated; rpl::event_stream<> _updated;

View file

@ -24,5 +24,4 @@ struct BotAppData {
uint64 accessHash = 0; uint64 accessHash = 0;
uint64 hash = 0; uint64 hash = 0;
bool hasSettings = false;
}; };

View file

@ -71,41 +71,43 @@ struct PeerUpdate {
FullInfo = (1ULL << 11), FullInfo = (1ULL << 11),
Usernames = (1ULL << 12), Usernames = (1ULL << 12),
TranslationDisabled = (1ULL << 13), TranslationDisabled = (1ULL << 13),
Color = (1ULL << 14),
BackgroundEmoji = (1ULL << 15),
// For users // For users
CanShareContact = (1ULL << 14), CanShareContact = (1ULL << 16),
IsContact = (1ULL << 15), IsContact = (1ULL << 17),
PhoneNumber = (1ULL << 16), PhoneNumber = (1ULL << 18),
OnlineStatus = (1ULL << 17), OnlineStatus = (1ULL << 19),
BotCommands = (1ULL << 18), BotCommands = (1ULL << 20),
BotCanBeInvited = (1ULL << 19), BotCanBeInvited = (1ULL << 21),
BotStartToken = (1ULL << 20), BotStartToken = (1ULL << 22),
CommonChats = (1ULL << 21), CommonChats = (1ULL << 23),
HasCalls = (1ULL << 22), HasCalls = (1ULL << 24),
SupportInfo = (1ULL << 23), SupportInfo = (1ULL << 25),
IsBot = (1ULL << 24), IsBot = (1ULL << 26),
EmojiStatus = (1ULL << 25), EmojiStatus = (1ULL << 27),
StoriesState = (1ULL << 26), StoriesState = (1ULL << 28),
// For chats and channels // For chats and channels
InviteLinks = (1ULL << 27), InviteLinks = (1ULL << 29),
Members = (1ULL << 28), Members = (1ULL << 30),
Admins = (1ULL << 29), Admins = (1ULL << 31),
BannedUsers = (1ULL << 30), BannedUsers = (1ULL << 32),
Rights = (1ULL << 31), Rights = (1ULL << 33),
PendingRequests = (1ULL << 32), PendingRequests = (1ULL << 34),
Reactions = (1ULL << 33), Reactions = (1ULL << 35),
// For channels // For channels
ChannelAmIn = (1ULL << 34), ChannelAmIn = (1ULL << 36),
StickersSet = (1ULL << 35), StickersSet = (1ULL << 37),
ChannelLinkedChat = (1ULL << 36), ChannelLinkedChat = (1ULL << 38),
ChannelLocation = (1ULL << 37), ChannelLocation = (1ULL << 39),
Slowmode = (1ULL << 38), Slowmode = (1ULL << 40),
GroupCall = (1ULL << 39), GroupCall = (1ULL << 41),
// For iteration // For iteration
LastUsedBit = (1ULL << 39), LastUsedBit = (1ULL << 41),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -74,22 +74,8 @@ ChatFilter ChatFilter::FromTL(
| (data.is_exclude_read() ? Flag::NoRead : Flag(0)) | (data.is_exclude_read() ? Flag::NoRead : Flag(0))
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0)); | (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
auto &&to_histories = ranges::views::transform([&]( auto &&to_histories = ranges::views::transform([&](
const MTPInputPeer &data) { const MTPInputPeer &input) {
const auto peer = data.match([&](const MTPDinputPeerUser &data) { const auto peer = Data::PeerFromInputMTP(owner, input);
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return (PeerData*)user;
}, [&](const MTPDinputPeerChat &data) {
return (PeerData*)owner->chat(data.vchat_id().v);
}, [&](const MTPDinputPeerChannel &data) {
const auto channel = owner->channel(data.vchannel_id().v);
channel->setAccessHash(data.vaccess_hash().v);
return (PeerData*)channel;
}, [&](const MTPDinputPeerSelf &data) {
return (PeerData*)owner->session().user();
}, [&](const auto &data) {
return (PeerData*)nullptr;
});
return peer ? owner->history(peer).get() : nullptr; return peer ? owner->history(peer).get() : nullptr;
}) | ranges::views::filter([](History *history) { }) | ranges::views::filter([](History *history) {
return history != nullptr; return history != nullptr;

View file

@ -12,39 +12,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_widget.h" #include "history/history_widget.h"
#include "history/history_item_components.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
namespace Data { namespace Data {
WebPageDraft WebPageDraft::FromItem(not_null<HistoryItem*> item) {
const auto previewMedia = item->media();
const auto previewPage = previewMedia
? previewMedia->webpage()
: nullptr;
using PageFlag = MediaWebPageFlag;
const auto previewFlags = previewMedia
? previewMedia->webpageFlags()
: PageFlag();
return {
.id = previewPage ? previewPage->id : 0,
.url = previewPage ? previewPage->url : QString(),
.forceLargeMedia = !!(previewFlags & PageFlag::ForceLargeMedia),
.forceSmallMedia = !!(previewFlags & PageFlag::ForceSmallMedia),
.invert = item->invertMedia(),
.manual = !!(previewFlags & PageFlag::Manual),
.removed = !previewPage,
};
}
Draft::Draft( Draft::Draft(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
MsgId msgId, FullReplyTo reply,
MsgId topicRootId,
const MessageCursor &cursor, const MessageCursor &cursor,
PreviewState previewState, WebPageDraft webpage,
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(textWithTags) : textWithTags(textWithTags)
, msgId(msgId) , reply(std::move(reply))
, topicRootId(topicRootId)
, cursor(cursor) , cursor(cursor)
, previewState(previewState) , webpage(webpage)
, saveRequestId(saveRequestId) { , saveRequestId(saveRequestId) {
} }
Draft::Draft( Draft::Draft(
not_null<const Ui::InputField*> field, not_null<const Ui::InputField*> field,
MsgId msgId, FullReplyTo reply,
MsgId topicRootId, WebPageDraft webpage,
PreviewState previewState,
mtpRequestId saveRequestId) mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags()) : textWithTags(field->getTextWithTags())
, msgId(msgId) , reply(std::move(reply))
, topicRootId(topicRootId)
, cursor(field) , cursor(field)
, previewState(previewState) { , webpage(webpage) {
} }
void ApplyPeerCloudDraft( void ApplyPeerCloudDraft(
@ -64,15 +82,32 @@ void ApplyPeerCloudDraft(
session, session,
draft.ventities().value_or_empty())) draft.ventities().value_or_empty()))
}; };
const auto replyTo = draft.vreply_to_msg_id().value_or_empty(); auto replyTo = draft.vreply_to()
? ReplyToFromMTP(history, *draft.vreply_to())
: FullReplyTo();
replyTo.topicRootId = topicRootId;
auto webpage = WebPageDraft{
.invert = draft.is_invert_media(),
.removed = draft.is_no_webpage(),
};
if (const auto media = draft.vmedia()) {
media->match([&](const MTPDmessageMediaWebPage &data) {
const auto parsed = session->data().processWebpage(
data.vwebpage());
if (!parsed->failed) {
webpage.forceLargeMedia = data.is_force_large_media();
webpage.forceSmallMedia = data.is_force_small_media();
webpage.manual = data.is_manual();
webpage.url = parsed->url;
webpage.id = parsed->id;
}
}, [](const auto &) {});
}
auto cloudDraft = std::make_unique<Draft>( auto cloudDraft = std::make_unique<Draft>(
textWithTags, textWithTags,
replyTo, replyTo,
topicRootId,
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax), MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
(draft.is_no_webpage() std::move(webpage));
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed));
cloudDraft->date = date; cloudDraft->date = date;
history->setCloudDraft(std::move(cloudDraft)); history->setCloudDraft(std::move(cloudDraft));

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/data_msg_id.h"
namespace Ui { namespace Ui {
class InputField; class InputField;
} // namespace Ui } // namespace Ui
@ -28,34 +30,40 @@ void ClearPeerCloudDraft(
MsgId topicRootId, MsgId topicRootId,
TimeId date); TimeId date);
enum class PreviewState : char { struct WebPageDraft {
Allowed, [[nodiscard]] static WebPageDraft FromItem(not_null<HistoryItem*> item);
Cancelled,
EmptyOnEdit, WebPageId id = 0;
QString url;
bool forceLargeMedia : 1 = false;
bool forceSmallMedia : 1 = false;
bool invert : 1 = false;
bool manual : 1 = false;
bool removed : 1 = false;
friend inline bool operator==(const WebPageDraft&, const WebPageDraft&)
= default;
}; };
struct Draft { struct Draft {
Draft() = default; Draft() = default;
Draft( Draft(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
MsgId msgId, FullReplyTo reply,
MsgId topicRootId,
const MessageCursor &cursor, const MessageCursor &cursor,
PreviewState previewState, WebPageDraft webpage,
mtpRequestId saveRequestId = 0); mtpRequestId saveRequestId = 0);
Draft( Draft(
not_null<const Ui::InputField*> field, not_null<const Ui::InputField*> field,
MsgId msgId, FullReplyTo reply,
MsgId topicRootId, WebPageDraft webpage,
PreviewState previewState,
mtpRequestId saveRequestId = 0); mtpRequestId saveRequestId = 0);
TimeId date = 0; TimeId date = 0;
TextWithTags textWithTags; TextWithTags textWithTags;
MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft.
MsgId topicRootId = 0;
MessageCursor cursor; MessageCursor cursor;
PreviewState previewState = PreviewState::Allowed; WebPageDraft webpage;
mtpRequestId saveRequestId = 0; mtpRequestId saveRequestId = 0;
}; };
@ -167,7 +175,8 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
[[nodiscard]] inline bool DraftIsNull(const Draft *draft) { [[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
return !draft return !draft
|| (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text)); || (!draft->reply.messageId
&& DraftStringIsEmpty(draft->textWithTags.text));
} }
[[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) { [[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
@ -179,8 +188,8 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
return false; return false;
} }
return (a->textWithTags == b->textWithTags) return (a->textWithTags == b->textWithTags)
&& (a->msgId == b->msgId) && (a->reply == b->reply)
&& (a->previewState == b->previewState); && (a->webpage == b->webpage);
} }
} // namespace Data } // namespace Data

View file

@ -123,6 +123,7 @@ void GroupCall::requestParticipants() {
return; return;
} }
} }
api().request(base::take(_participantsRequestId)).cancel();
_participantsRequestId = api().request(MTPphone_GetGroupParticipants( _participantsRequestId = api().request(MTPphone_GetGroupParticipants(
input(), input(),
MTP_vector<MTPInputPeer>(), // ids MTP_vector<MTPInputPeer>(), // ids
@ -132,8 +133,8 @@ void GroupCall::requestParticipants() {
: _nextOffset), : _nextOffset),
MTP_int(kRequestPerPage) MTP_int(kRequestPerPage)
)).done([=](const MTPphone_GroupParticipants &result) { )).done([=](const MTPphone_GroupParticipants &result) {
_participantsRequestId = 0;
result.match([&](const MTPDphone_groupParticipants &data) { result.match([&](const MTPDphone_groupParticipants &data) {
_participantsRequestId = 0;
const auto reloaded = processSavedFullCall(); const auto reloaded = processSavedFullCall();
_nextOffset = qs(data.vnext_offset()); _nextOffset = qs(data.vnext_offset());
_peer->owner().processUsers(data.vusers()); _peer->owner().processUsers(data.vusers());
@ -168,7 +169,7 @@ bool GroupCall::processSavedFullCall() {
if (!_savedFull) { if (!_savedFull) {
return false; return false;
} }
_reloadRequestId = 0; api().request(base::take(_reloadRequestId)).cancel();
_reloadLastFinished = crl::now(); _reloadLastFinished = crl::now();
processFullCallFields(*base::take(_savedFull)); processFullCallFields(*base::take(_savedFull));
return true; return true;
@ -511,10 +512,8 @@ void GroupCall::reloadIfStale() {
void GroupCall::reload() { void GroupCall::reload() {
if (_reloadRequestId || _applyingQueuedUpdates) { if (_reloadRequestId || _applyingQueuedUpdates) {
return; return;
} else if (_participantsRequestId) {
api().request(_participantsRequestId).cancel();
_participantsRequestId = 0;
} }
api().request(base::take(_participantsRequestId)).cancel();
DEBUG_LOG(("Group Call Participants: " DEBUG_LOG(("Group Call Participants: "
"Reloading with queued: %1" "Reloading with queued: %1"

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "data/data_histories.h" #include "data/data_histories.h"
#include "api/api_text_entities.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
@ -39,8 +40,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
} // namespace } // namespace
MTPInputReplyTo ReplyToForMTP( MTPInputReplyTo ReplyToForMTP(
not_null<Session*> owner, not_null<History*> history,
FullReplyTo replyTo) { FullReplyTo replyTo) {
const auto owner = &history->owner();
if (replyTo.storyId) { if (replyTo.storyId) {
if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) { if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
@ -49,18 +51,45 @@ MTPInputReplyTo ReplyToForMTP(
MTP_int(replyTo.storyId.story)); MTP_int(replyTo.storyId.story));
} }
} }
} else if (replyTo.msgId || replyTo.topicRootId) { } else if (replyTo.messageId || replyTo.topicRootId) {
const auto external = replyTo.messageId
&& (replyTo.messageId.peer != history->peer->id);
const auto quoteEntities = Api::EntitiesToMTP(
&history->session(),
replyTo.quote.entities,
Api::ConvertOption::SkipLocal);
using Flag = MTPDinputReplyToMessage::Flag; using Flag = MTPDinputReplyToMessage::Flag;
return MTP_inputReplyToMessage( return MTP_inputReplyToMessage(
(replyTo.topicRootId MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
? MTP_flags(Flag::f_top_msg_id) | (external ? Flag::f_reply_to_peer_id : Flag())
: MTP_flags(0)), | (replyTo.quote.text.isEmpty()
MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId), ? Flag()
MTP_int(replyTo.topicRootId)); : Flag::f_quote_text)
| (quoteEntities.v.isEmpty()
? Flag()
: Flag::f_quote_entities)),
MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
MTP_int(replyTo.topicRootId),
(external
? owner->peer(replyTo.messageId.peer)->input
: MTPInputPeer()),
MTP_string(replyTo.quote.text),
quoteEntities);
} }
return MTPInputReplyTo(); return MTPInputReplyTo();
} }
MTPInputMedia WebPageForMTP(
const Data::WebPageDraft &draft,
bool required) {
using Flag = MTPDinputMediaWebPage::Flag;
return MTP_inputMediaWebPage(
MTP_flags((required ? Flag() : Flag::f_optional)
| (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
| (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
MTP_string(draft.url));
}
Histories::Histories(not_null<Session*> owner) Histories::Histories(not_null<Session*> owner)
: _owner(owner) : _owner(owner)
, _readRequestsTimer([=] { sendReadRequests(); }) { , _readRequestsTimer([=] { sendReadRequests(); }) {
@ -930,7 +959,7 @@ int Histories::sendPreparedMessage(
not_null<History*> history, not_null<History*> history,
FullReplyTo replyTo, FullReplyTo replyTo,
uint64 randomId, uint64 randomId,
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message, Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done, Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail) { Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
if (isCreatingTopic(history, replyTo.topicRootId)) { if (isCreatingTopic(history, replyTo.topicRootId)) {
@ -945,7 +974,7 @@ int Histories::sendPreparedMessage(
} }
i->second.push_back({ i->second.push_back({
.randomId = randomId, .randomId = randomId,
.replyTo = replyTo.msgId, .replyTo = replyTo.messageId,
.message = std::move(message), .message = std::move(message),
.done = std::move(done), .done = std::move(done),
.fail = std::move(fail), .fail = std::move(fail),
@ -955,11 +984,12 @@ int Histories::sendPreparedMessage(
return id; return id;
} }
const auto realReplyTo = FullReplyTo{ const auto realReplyTo = FullReplyTo{
.msgId = convertTopicReplyToId(history, replyTo.msgId), .messageId = convertTopicReplyToId(history, replyTo.messageId),
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId), .quote = replyTo.quote,
.storyId = replyTo.storyId, .storyId = replyTo.storyId,
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
}; };
return v::match(message(_owner, realReplyTo), [&](const auto &request) { return v::match(message(history, realReplyTo), [&](const auto &request) {
const auto type = RequestType::Send; const auto type = RequestType::Send;
return sendRequest(history, type, [=](Fn<void()> finish) { return sendRequest(history, type, [=](Fn<void()> finish) {
const auto session = &_owner->session(); const auto session = &_owner->session();
@ -1003,7 +1033,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
sendPreparedMessage( sendPreparedMessage(
history, history,
FullReplyTo{ FullReplyTo{
.msgId = entry.replyTo, .messageId = entry.replyTo,
.topicRootId = realRoot, .topicRootId = realRoot,
}, },
entry.randomId, entry.randomId,
@ -1025,6 +1055,15 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
} }
} }
FullMsgId Histories::convertTopicReplyToId(
not_null<History*> history,
FullMsgId replyToId) const {
const auto id = (history->peer->id == replyToId.peer)
? convertTopicReplyToId(history, replyToId.msg)
: replyToId.msg;
return { replyToId.peer, id };
}
MsgId Histories::convertTopicReplyToId( MsgId Histories::convertTopicReplyToId(
not_null<History*> history, not_null<History*> history,
MsgId replyToId) const { MsgId replyToId) const {

View file

@ -25,10 +25,14 @@ namespace Data {
class Session; class Session;
class Folder; class Folder;
struct WebPageDraft;
[[nodiscard]] MTPInputReplyTo ReplyToForMTP( [[nodiscard]] MTPInputReplyTo ReplyToForMTP(
not_null<Session*> owner, not_null<History*> history,
FullReplyTo replyTo); FullReplyTo replyTo);
[[nodiscard]] MTPInputMedia WebPageForMTP(
const Data::WebPageDraft &draft,
bool required = false);
class Histories final { class Histories final {
public: public:
@ -108,7 +112,7 @@ public:
not_null<History*> history, not_null<History*> history,
FullReplyTo replyTo, FullReplyTo replyTo,
uint64 randomId, uint64 randomId,
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message, Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
Fn<void(const MTPUpdates&, const MTP::Response&)> done, Fn<void(const MTPUpdates&, const MTP::Response&)> done,
Fn<void(const MTP::Error&, const MTP::Response&)> fail); Fn<void(const MTP::Error&, const MTP::Response&)> fail);
@ -116,14 +120,17 @@ public:
}; };
template <typename RequestType, typename ...Args> template <typename RequestType, typename ...Args>
static auto PrepareMessage(const Args &...args) static auto PrepareMessage(const Args &...args)
-> Fn<Histories::PreparedMessage(not_null<Session*>, FullReplyTo)> { -> Fn<Histories::PreparedMessage(not_null<History*>, FullReplyTo)> {
return [=](not_null<Session*> owner, FullReplyTo replyTo) return [=](not_null<History*> history, FullReplyTo replyTo)
-> RequestType { -> RequestType {
return { ReplaceReplyIds(owner, args, replyTo)... }; return { ReplaceReplyIds(history, args, replyTo)... };
}; };
} }
void checkTopicCreated(FullMsgId rootId, MsgId realRoot); void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
[[nodiscard]] FullMsgId convertTopicReplyToId(
not_null<History*> history,
FullMsgId replyToId) const;
[[nodiscard]] MsgId convertTopicReplyToId( [[nodiscard]] MsgId convertTopicReplyToId(
not_null<History*> history, not_null<History*> history,
MsgId replyToId) const; MsgId replyToId) const;
@ -152,8 +159,8 @@ private:
}; };
struct DelayedByTopicMessage { struct DelayedByTopicMessage {
uint64 randomId = 0; uint64 randomId = 0;
MsgId replyTo = 0; FullMsgId replyTo;
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message; Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message;
Fn<void(const MTPUpdates&, const MTP::Response&)> done; Fn<void(const MTPUpdates&, const MTP::Response&)> done;
Fn<void(const MTP::Error&, const MTP::Response&)> fail; Fn<void(const MTP::Error&, const MTP::Response&)> fail;
int requestId = 0; int requestId = 0;
@ -169,11 +176,11 @@ private:
template <typename Arg> template <typename Arg>
static auto ReplaceReplyIds( static auto ReplaceReplyIds(
not_null<Session*> owner, not_null<History*> history,
Arg arg, Arg arg,
FullReplyTo replyTo) { FullReplyTo replyTo) {
if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) { if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
return ReplyToForMTP(owner, replyTo); return ReplyToForMTP(history, replyTo);
} else { } else {
return arg; return arg;
} }

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_contact.h" #include "history/view/media/history_view_contact.h"
#include "history/view/media/history_view_location.h" #include "history/view/media/history_view_location.h"
#include "history/view/media/history_view_game.h" #include "history/view/media/history_view_game.h"
#include "history/view/media/history_view_giveaway.h"
#include "history/view/media/history_view_invoice.h" #include "history/view/media/history_view_invoice.h"
#include "history/view/media/history_view_call.h" #include "history/view/media/history_view_call.h"
#include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_web_page.h"
@ -361,6 +362,28 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
return result; return result;
} }
Giveaway ComputeGiveawayData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data) {
auto result = Giveaway{
.untilDate = data.vuntil_date().v,
.quantity = data.vquantity().v,
.months = data.vmonths().v,
};
result.channels.reserve(data.vchannels().v.size());
const auto owner = &item->history()->owner();
for (const auto &id : data.vchannels().v) {
result.channels.push_back(owner->channel(ChannelId(id)));
}
if (const auto countries = data.vcountries_iso2()) {
result.countries.reserve(countries->v.size());
for (const auto &country : countries->v) {
result.countries.push_back(qs(country));
}
}
return result;
}
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) { Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
} }
@ -380,6 +403,10 @@ WebPageData *Media::webpage() const {
return nullptr; return nullptr;
} }
MediaWebPageFlags Media::webpageFlags() const {
return {};
}
const SharedContact *Media::sharedContact() const { const SharedContact *Media::sharedContact() const {
return nullptr; return nullptr;
} }
@ -420,6 +447,10 @@ bool Media::storyMention() const {
return false; return false;
} }
const Giveaway *Media::giveaway() const {
return nullptr;
}
bool Media::uploading() const { bool Media::uploading() const {
return false; return false;
} }
@ -1406,9 +1437,11 @@ QString MediaCall::Text(
MediaWebPage::MediaWebPage( MediaWebPage::MediaWebPage(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<WebPageData*> page) not_null<WebPageData*> page,
MediaWebPageFlags flags)
: Media(parent) : Media(parent)
, _page(page) { , _page(page)
, _flags(flags) {
parent->history()->owner().registerWebPageItem(_page, parent); parent->history()->owner().registerWebPageItem(_page, parent);
} }
@ -1417,7 +1450,7 @@ MediaWebPage::~MediaWebPage() {
} }
std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) { std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaWebPage>(parent, _page); return std::make_unique<MediaWebPage>(parent, _page, _flags);
} }
DocumentData *MediaWebPage::document() const { DocumentData *MediaWebPage::document() const {
@ -1432,6 +1465,10 @@ WebPageData *MediaWebPage::webpage() const {
return _page; return _page;
} }
MediaWebPageFlags MediaWebPage::webpageFlags() const {
return _flags;
}
bool MediaWebPage::hasReplyPreview() const { bool MediaWebPage::hasReplyPreview() const {
if (const auto document = MediaWebPage::document()) { if (const auto document = MediaWebPage::document()) {
return document->hasThumbnail() return document->hasThumbnail()
@ -1462,10 +1499,13 @@ bool MediaWebPage::replyPreviewLoaded() const {
} }
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
return { .text = options.translated auto text = options.translated
? parent()->translatedText() ? parent()->translatedText()
: parent()->originalText() : parent()->originalText();
}; if (text.empty()) {
text = Ui::Text::Colorized(_page->url);
}
return { .text = text };
} }
TextWithEntities MediaWebPage::notificationText() const { TextWithEntities MediaWebPage::notificationText() const {
@ -1496,7 +1536,7 @@ std::unique_ptr<HistoryView::Media> MediaWebPage::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::WebPage>(message, _page); return std::make_unique<HistoryView::WebPage>(message, _page, _flags);
} }
MediaGame::MediaGame( MediaGame::MediaGame(
@ -1887,21 +1927,28 @@ MediaGiftBox::MediaGiftBox(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<PeerData*> from, not_null<PeerData*> from,
int months) int months)
: MediaGiftBox(parent, from, GiftCode{ .months = months }) {
}
MediaGiftBox::MediaGiftBox(
not_null<HistoryItem*> parent,
not_null<PeerData*> from,
GiftCode data)
: Media(parent) : Media(parent)
, _from(from) , _from(from)
, _months(months) { , _data(std::move(data)) {
} }
std::unique_ptr<Media> MediaGiftBox::clone(not_null<HistoryItem*> parent) { std::unique_ptr<Media> MediaGiftBox::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiftBox>(parent, _from, _months); return std::make_unique<MediaGiftBox>(parent, _from, _data);
} }
not_null<PeerData*> MediaGiftBox::from() const { not_null<PeerData*> MediaGiftBox::from() const {
return _from; return _from;
} }
int MediaGiftBox::months() const { const GiftCode &MediaGiftBox::data() const {
return _months; return _data;
} }
TextWithEntities MediaGiftBox::notificationText() const { TextWithEntities MediaGiftBox::notificationText() const {
@ -1933,14 +1980,6 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
std::make_unique<HistoryView::PremiumGift>(message, this)); std::make_unique<HistoryView::PremiumGift>(message, this));
} }
bool MediaGiftBox::activated() const {
return _activated;
}
void MediaGiftBox::setActivated(bool activated) {
_activated = activated;
}
MediaWallPaper::MediaWallPaper( MediaWallPaper::MediaWallPaper(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
const WallPaper &paper) const WallPaper &paper)
@ -2144,4 +2183,50 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
} }
} }
MediaGiveaway::MediaGiveaway(
not_null<HistoryItem*> parent,
const Giveaway &data)
: Media(parent)
, _giveaway(data) {
}
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) {
return std::make_unique<MediaGiveaway>(parent, _giveaway);
}
const Giveaway *MediaGiveaway::giveaway() const {
return &_giveaway;
}
TextWithEntities MediaGiveaway::notificationText() const {
return {
.text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity),
};
}
QString MediaGiveaway::pinnedTextSubstring() const {
return QString::fromUtf8("\xC2\xAB")
+ notificationText().text
+ QString::fromUtf8("\xC2\xBB");
}
TextForMimeData MediaGiveaway::clipboardText() const {
return TextForMimeData();
}
bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) {
return true;
}
bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) {
return true;
}
std::unique_ptr<HistoryView::Media> MediaGiveaway::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway);
}
} // namespace Data } // namespace Data

View file

@ -90,6 +90,23 @@ struct Invoice {
bool isTest = false; bool isTest = false;
}; };
struct Giveaway {
std::vector<not_null<ChannelData*>> channels;
std::vector<QString> countries;
TimeId untilDate = 0;
int quantity = 0;
int months = 0;
bool all = false;
};
struct GiftCode {
QString slug;
ChannelData *channel = nullptr;
int months = 0;
bool viaGiveaway = false;
bool unclaimed = false;
};
class Media { class Media {
public: public:
Media(not_null<HistoryItem*> parent); Media(not_null<HistoryItem*> parent);
@ -106,6 +123,7 @@ public:
virtual DocumentData *document() const; virtual DocumentData *document() const;
virtual PhotoData *photo() const; virtual PhotoData *photo() const;
virtual WebPageData *webpage() const; virtual WebPageData *webpage() const;
virtual MediaWebPageFlags webpageFlags() const;
virtual const SharedContact *sharedContact() const; virtual const SharedContact *sharedContact() const;
virtual const Call *call() const; virtual const Call *call() const;
virtual GameData *game() const; virtual GameData *game() const;
@ -116,6 +134,7 @@ 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 bool uploading() const; virtual bool uploading() const;
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
@ -355,7 +374,8 @@ class MediaWebPage final : public Media {
public: public:
MediaWebPage( MediaWebPage(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<WebPageData*> page); not_null<WebPageData*> page,
MediaWebPageFlags flags);
~MediaWebPage(); ~MediaWebPage();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@ -363,6 +383,7 @@ public:
DocumentData *document() const override; DocumentData *document() const override;
PhotoData *photo() const override; PhotoData *photo() const override;
WebPageData *webpage() const override; WebPageData *webpage() const override;
MediaWebPageFlags webpageFlags() const override;
bool hasReplyPreview() const override; bool hasReplyPreview() const override;
Image *replyPreview() const override; Image *replyPreview() const override;
@ -381,7 +402,8 @@ public:
HistoryView::Element *replacing = nullptr) override; HistoryView::Element *replacing = nullptr) override;
private: private:
not_null<WebPageData*> _page; const not_null<WebPageData*> _page;
const MediaWebPageFlags _flags;
}; };
@ -517,14 +539,15 @@ public:
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<PeerData*> from, not_null<PeerData*> from,
int months); int months);
MediaGiftBox(
not_null<HistoryItem*> parent,
not_null<PeerData*> from,
GiftCode data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
[[nodiscard]] not_null<PeerData*> from() const; [[nodiscard]] not_null<PeerData*> from() const;
[[nodiscard]] int months() const; [[nodiscard]] const GiftCode &data() const;
[[nodiscard]] bool activated() const;
void setActivated(bool activated);
TextWithEntities notificationText() const override; TextWithEntities notificationText() const override;
QString pinnedTextSubstring() const override; QString pinnedTextSubstring() const override;
@ -539,8 +562,7 @@ public:
private: private:
not_null<PeerData*> _from; not_null<PeerData*> _from;
int _months = 0; GiftCode _data;
bool _activated = false;
}; };
@ -605,6 +627,32 @@ private:
}; };
class MediaGiveaway final : public Media {
public:
MediaGiveaway(
not_null<HistoryItem*> parent,
const Giveaway &data);
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
const Giveaway *giveaway() 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:
Giveaway _giveaway;
};
[[nodiscard]] TextForMimeData WithCaptionClipboardText( [[nodiscard]] TextForMimeData WithCaptionClipboardText(
const QString &attachType, const QString &attachType,
TextForMimeData &&caption); TextForMimeData &&caption);
@ -615,4 +663,8 @@ private:
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
[[nodiscard]] Giveaway ComputeGiveawayData(
not_null<HistoryItem*> item,
const MTPDmessageMediaGiveaway &data);
} // namespace Data } // namespace Data

View file

@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/qt/qt_compare.h"
#include "data/data_peer_id.h" #include "data/data_peer_id.h"
#include "ui/text/text_entity.h"
struct MsgId { struct MsgId {
constexpr MsgId() noexcept = default; constexpr MsgId() noexcept = default;
@ -67,21 +69,6 @@ struct FullStoryId {
friend inline bool operator==(FullStoryId, FullStoryId) = default; friend inline bool operator==(FullStoryId, FullStoryId) = default;
}; };
struct FullReplyTo {
MsgId msgId = 0;
MsgId topicRootId = 0;
FullStoryId storyId;
[[nodiscard]] bool valid() const {
return msgId || (storyId && peerIsUser(storyId.peer));
}
explicit operator bool() const {
return valid();
}
friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
};
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58)); constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
constexpr auto ClientMsgIds = (1LL << 31); constexpr auto ClientMsgIds = (1LL << 31);
constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds); constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds);
@ -169,6 +156,22 @@ struct FullMsgId {
Q_DECLARE_METATYPE(FullMsgId); Q_DECLARE_METATYPE(FullMsgId);
struct FullReplyTo {
FullMsgId messageId;
TextWithEntities quote;
FullStoryId storyId;
MsgId topicRootId = 0;
[[nodiscard]] bool valid() const {
return messageId || (storyId && peerIsUser(storyId.peer));
}
explicit operator bool() const {
return valid();
}
friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
};
struct GlobalMsgId { struct GlobalMsgId {
FullMsgId itemId; FullMsgId itemId;
uint64 sessionUniqueId = 0; uint64 sessionUniqueId = 0;

View file

@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/main_window.h" // Window::LogoNoMargin. #include "window/main_window.h" // Window::LogoNoMargin.
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/chat/chat_style.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/painter.h" #include "ui/painter.h"
@ -59,8 +60,8 @@ using UpdateFlag = Data::PeerUpdate::Flag;
namespace Data { namespace Data {
int PeerColorIndex(PeerId peerId) { uint8 DecideColorIndex(PeerId peerId) {
return Ui::EmptyUserpic::ColorIndex(peerId.value & PeerId::kChatTypeMask); return Ui::DecideColorIndex(peerId.value & PeerId::kChatTypeMask);
} }
PeerId FakePeerIdForJustName(const QString &name) { PeerId FakePeerIdForJustName(const QString &name) {
@ -125,6 +126,40 @@ AllowedReactions Parse(const MTPChatReactions &value) {
}); });
} }
PeerData *PeerFromInputMTP(
not_null<Session*> owner,
const MTPInputPeer &input) {
return input.match([&](const MTPDinputPeerUser &data) {
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return (PeerData*)user;
}, [&](const MTPDinputPeerChat &data) {
return (PeerData*)owner->chat(data.vchat_id().v);
}, [&](const MTPDinputPeerChannel &data) {
const auto channel = owner->channel(data.vchannel_id().v);
channel->setAccessHash(data.vaccess_hash().v);
return (PeerData*)channel;
}, [&](const MTPDinputPeerSelf &data) {
return (PeerData*)owner->session().user();
}, [&](const auto &data) {
return (PeerData*)nullptr;
});
}
UserData *UserFromInputMTP(
not_null<Session*> owner,
const MTPInputUser &input) {
return input.match([&](const MTPDinputUser &data) {
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return user.get();
}, [&](const MTPDinputUserSelf &data) {
return owner->session().user().get();
}, [](const auto &data) {
return (UserData*)nullptr;
});
}
} // namespace Data } // namespace Data
PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer) PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)
@ -157,7 +192,8 @@ void PeerClickHandler::onClick(ClickContext context) const {
PeerData::PeerData(not_null<Data::Session*> owner, PeerId id) PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
: id(id) : id(id)
, _owner(owner) { , _owner(owner)
, _colorIndex(Data::DecideColorIndex(id)) {
} }
Data::Session &PeerData::owner() const { Data::Session &PeerData::owner() const {
@ -230,7 +266,7 @@ not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
if (!_userpicEmpty) { if (!_userpicEmpty) {
const auto user = asUser(); const auto user = asUser();
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>( _userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(id)), Ui::EmptyUserpic::UserpicColor(colorIndex()),
((user && user->isInaccessible()) ((user && user->isInaccessible())
? Ui::EmptyUserpic::InaccessibleName() ? Ui::EmptyUserpic::InaccessibleName()
: name())); : name()));
@ -251,7 +287,7 @@ void PeerData::setUserpic(
const ImageLocation &location, const ImageLocation &location,
bool hasVideo) { bool hasVideo) {
_userpicPhotoId = photoId; _userpicPhotoId = photoId;
_userpicHasVideo = hasVideo; _userpicHasVideo = hasVideo ? 1 : 0;
_userpic.set(&session(), ImageWithLocation{ .location = location }); _userpic.set(&session(), ImageWithLocation{ .location = location });
} }
@ -389,6 +425,22 @@ QImage PeerData::generateUserpicImage(
return result; return result;
} }
ImageLocation PeerData::userpicLocation() const {
return _userpic.location();
}
bool PeerData::userpicPhotoUnknown() const {
return (_userpicPhotoId == kUnknownPhotoId);
}
PhotoId PeerData::userpicPhotoId() const {
return userpicPhotoUnknown() ? 0 : _userpicPhotoId;
}
bool PeerData::userpicHasVideo() const {
return _userpicHasVideo != 0;
}
Data::FileOrigin PeerData::userpicOrigin() const { Data::FileOrigin PeerData::userpicOrigin() const {
return Data::FileOriginPeerPhoto(id); return Data::FileOriginPeerPhoto(id);
} }
@ -428,7 +480,7 @@ void PeerData::setUserpicChecked(
bool hasVideo) { bool hasVideo) {
if (_userpicPhotoId != photoId if (_userpicPhotoId != photoId
|| _userpic.location() != location || _userpic.location() != location
|| _userpicHasVideo != hasVideo) { || _userpicHasVideo != (hasVideo ? 1 : 0)) {
const auto known = !userpicPhotoUnknown(); const auto known = !userpicPhotoUnknown();
setUserpic(photoId, location, hasVideo); setUserpic(photoId, location, hasVideo);
session().changes().peerUpdated(this, UpdateFlag::Photo); session().changes().peerUpdated(this, UpdateFlag::Photo);
@ -602,6 +654,20 @@ void PeerData::setSettings(const MTPPeerSettings &data) {
}); });
} }
bool PeerData::changeColorIndex(
const tl::conditional<MTPint> &cloudColorIndex) {
return cloudColorIndex
? changeColorIndex(cloudColorIndex->v)
: clearColorIndex();
}
bool PeerData::changeBackgroundEmojiId(
const tl::conditional<MTPlong> &cloudBackgroundEmoji) {
return changeBackgroundEmojiId(cloudBackgroundEmoji
? cloudBackgroundEmoji->v
: DocumentId());
}
void PeerData::fillNames() { void PeerData::fillNames() {
_nameWords.clear(); _nameWords.clear();
_nameFirstLetters.clear(); _nameFirstLetters.clear();
@ -818,6 +884,36 @@ QString PeerData::userName() const {
return QString(); return QString();
} }
bool PeerData::changeColorIndex(uint8 index) {
index %= Ui::kColorIndexCount;
if (_colorIndexCloud && _colorIndex == index) {
return false;
}
_colorIndexCloud = 1;
_colorIndex = index;
return true;
}
bool PeerData::clearColorIndex() {
if (!_colorIndexCloud) {
return false;
}
_colorIndexCloud = 0;
_colorIndex = Data::DecideColorIndex(id);
return true;
}
DocumentId PeerData::backgroundEmojiId() const {
return _backgroundEmojiId;
}
bool PeerData::changeBackgroundEmojiId(DocumentId id) {
if (_backgroundEmojiId == id) {
return false;
}
_backgroundEmojiId = id;
return true;
}
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

@ -39,7 +39,7 @@ class GroupCall;
struct ReactionId; struct ReactionId;
class WallPaper; class WallPaper;
[[nodiscard]] int PeerColorIndex(PeerId peerId); [[nodiscard]] uint8 DecideColorIndex(PeerId peerId);
// Must be used only for PeerColor-s. // Must be used only for PeerColor-s.
[[nodiscard]] PeerId FakePeerIdForJustName(const QString &name); [[nodiscard]] PeerId FakePeerIdForJustName(const QString &name);
@ -116,6 +116,12 @@ bool operator<(const AllowedReactions &a, const AllowedReactions &b);
bool operator==(const AllowedReactions &a, const AllowedReactions &b); bool operator==(const AllowedReactions &a, const AllowedReactions &b);
[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value); [[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
[[nodiscard]] PeerData *PeerFromInputMTP(
not_null<Session*> owner,
const MTPInputPeer &input);
[[nodiscard]] UserData *UserFromInputMTP(
not_null<Session*> owner,
const MTPInputUser &input);
} // namespace Data } // namespace Data
@ -164,6 +170,14 @@ public:
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
[[nodiscard]] Main::Account &account() const; [[nodiscard]] Main::Account &account() const;
[[nodiscard]] uint8 colorIndex() const {
return _colorIndex;
}
bool changeColorIndex(uint8 index);
bool clearColorIndex();
[[nodiscard]] DocumentId backgroundEmojiId() const;
bool changeBackgroundEmojiId(DocumentId id);
[[nodiscard]] bool isUser() const { [[nodiscard]] bool isUser() const {
return peerIsUser(id); return peerIsUser(id);
} }
@ -285,20 +299,12 @@ public:
Ui::PeerUserpicView &view, Ui::PeerUserpicView &view,
int size, int size,
std::optional<int> radius = {}) const; std::optional<int> radius = {}) const;
[[nodiscard]] ImageLocation userpicLocation() const { [[nodiscard]] ImageLocation userpicLocation() const;
return _userpic.location();
}
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL); static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
[[nodiscard]] bool userpicPhotoUnknown() const { [[nodiscard]] bool userpicPhotoUnknown() const;
return (_userpicPhotoId == kUnknownPhotoId); [[nodiscard]] PhotoId userpicPhotoId() const;
} [[nodiscard]] bool userpicHasVideo() const;
[[nodiscard]] PhotoId userpicPhotoId() const {
return userpicPhotoUnknown() ? 0 : _userpicPhotoId;
}
[[nodiscard]] bool userpicHasVideo() const {
return _userpicHasVideo;
}
[[nodiscard]] Data::FileOrigin userpicOrigin() const; [[nodiscard]] Data::FileOrigin userpicOrigin() const;
[[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const; [[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const;
@ -361,6 +367,9 @@ public:
void saveTranslationDisabled(bool disabled); void saveTranslationDisabled(bool disabled);
void setSettings(const MTPPeerSettings &data); void setSettings(const MTPPeerSettings &data);
bool changeColorIndex(const tl::conditional<MTPint> &cloudColorIndex);
bool changeBackgroundEmojiId(
const tl::conditional<MTPlong> &cloudBackgroundEmoji);
enum class BlockStatus : char { enum class BlockStatus : char {
Unknown, Unknown,
@ -453,6 +462,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;
uint64 _backgroundEmojiId = 0;
crl::time _lastFullUpdate = 0; crl::time _lastFullUpdate = 0;
QString _name; QString _name;
@ -460,14 +470,16 @@ private:
TimeId _ttlPeriod = 0; TimeId _ttlPeriod = 0;
QString _requestChatTitle;
TimeId _requestChatDate = 0;
Settings _settings = PeerSettings(PeerSetting::Unknown); Settings _settings = PeerSettings(PeerSetting::Unknown);
BlockStatus _blockStatus = BlockStatus::Unknown; BlockStatus _blockStatus = BlockStatus::Unknown;
LoadedStatus _loadedStatus = LoadedStatus::Not; LoadedStatus _loadedStatus = LoadedStatus::Not;
TranslationFlag _translationFlag = TranslationFlag::Unknown; TranslationFlag _translationFlag = TranslationFlag::Unknown;
bool _userpicHasVideo = false; uint8 _colorIndex : 6 = 0;
uint8 _colorIndexCloud : 1 = 0;
QString _requestChatTitle; uint8 _userpicHasVideo : 1 = 0;
TimeId _requestChatDate = 0;
QString _about; QString _about;
QString _themeEmoticon; QString _themeEmoticon;

View file

@ -40,14 +40,14 @@ void ReplyPreview::prepare(
if (h <= 0) h = 1; if (h <= 0) h = 1;
auto thumbSize = (w > h) auto thumbSize = (w > h)
? QSize( ? QSize(
w * st::msgReplyBarSize.height() / h, w * st::historyReplyPreview / h,
st::msgReplyBarSize.height()) st::historyReplyPreview)
: QSize( : QSize(
st::msgReplyBarSize.height(), st::historyReplyPreview,
h * st::msgReplyBarSize.height() / w); h * st::historyReplyPreview / w);
thumbSize *= style::DevicePixelRatio(); thumbSize *= style::DevicePixelRatio();
options |= Option::TransparentBackground; options |= Option::TransparentBackground;
auto outerSize = st::msgReplyBarSize.height(); auto outerSize = st::historyReplyPreview;
auto original = spoiler auto original = spoiler
? image->original().scaled( ? image->original().scaled(
{ 40, 40 }, { 40, 40 },

View file

@ -716,6 +716,19 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
if (canShareThisContact != result->canShareThisContactFast()) { if (canShareThisContact != result->canShareThisContactFast()) {
flags |= UpdateFlag::CanShareContact; flags |= UpdateFlag::CanShareContact;
} }
auto decorationsUpdated = false;
if (result->changeColorIndex(data.vcolor())) {
flags |= UpdateFlag::Color;
decorationsUpdated = true;
}
if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) {
flags |= UpdateFlag::BackgroundEmoji;
decorationsUpdated = true;
}
if (decorationsUpdated && result->isMinimalLoaded()) {
_peerDecorationsUpdated.fire_copy(result);
}
}); });
if (minimal) { if (minimal) {
@ -990,6 +1003,18 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) { if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) {
flags |= UpdateFlag::GroupCall; flags |= UpdateFlag::GroupCall;
} }
auto decorationsUpdated = false;
if (result->changeColorIndex(data.vcolor())) {
flags |= UpdateFlag::Color;
decorationsUpdated = true;
}
if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) {
flags |= UpdateFlag::BackgroundEmoji;
decorationsUpdated = true;
}
if (decorationsUpdated && result->isMinimalLoaded()) {
_peerDecorationsUpdated.fire_copy(result);
}
}, [&](const MTPDchannelForbidden &data) { }, [&](const MTPDchannelForbidden &data) {
const auto channel = result->asChannel(); const auto channel = result->asChannel();
@ -3319,8 +3344,10 @@ not_null<WebPageData*> Session::processWebpage(const MTPWebPage &data) {
return processWebpage(data.c_webPage()); return processWebpage(data.c_webPage());
case mtpc_webPageEmpty: { case mtpc_webPageEmpty: {
const auto result = webpage(data.c_webPageEmpty().vid().v); const auto result = webpage(data.c_webPageEmpty().vid().v);
result->type = WebPageType::None;
if (result->pendingTill > 0) { if (result->pendingTill > 0) {
result->pendingTill = -1; // failed result->pendingTill = 0;
result->failed = 1;
notifyWebPageUpdateDelayed(result); notifyWebPageUpdateDelayed(result);
} }
return result; return result;
@ -3341,12 +3368,13 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPage &data) {
return result; return result;
} }
not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) { not_null<WebPageData*> Session::processWebpage(
const MTPDwebPagePending &data) {
constexpr auto kDefaultPendingTimeout = 60; constexpr auto kDefaultPendingTimeout = 60;
const auto result = webpage(data.vid().v); const auto result = webpage(data.vid().v);
webpageApplyFields( webpageApplyFields(
result, result,
WebPageType::Article, WebPageType::None,
QString(), QString(),
QString(), QString(),
QString(), QString(),
@ -3358,6 +3386,7 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
WebPageCollage(), WebPageCollage(),
0, 0,
QString(), QString(),
false,
data.vdate().v data.vdate().v
? data.vdate().v ? data.vdate().v
: (base::unixtime::now() + kDefaultPendingTimeout)); : (base::unixtime::now() + kDefaultPendingTimeout));
@ -3381,6 +3410,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage(), WebPageCollage(),
0, 0,
QString(), QString(),
false,
TimeId(0)); TimeId(0));
} }
@ -3397,6 +3427,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage &&collage, WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia,
TimeId pendingTill) { TimeId pendingTill) {
const auto result = webpage(id); const auto result = webpage(id);
webpageApplyFields( webpageApplyFields(
@ -3413,6 +3444,7 @@ not_null<WebPageData*> Session::webpage(
std::move(collage), std::move(collage),
duration, duration,
author, author,
hasLargeMedia,
pendingTill); pendingTill);
return result; return result;
} }
@ -3512,6 +3544,7 @@ void Session::webpageApplyFields(
WebPageCollage(this, data), WebPageCollage(this, data),
data.vduration().value_or_empty(), data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()), qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
pendingTill); pendingTill);
} }
@ -3529,6 +3562,7 @@ void Session::webpageApplyFields(
WebPageCollage &&collage, WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia,
TimeId pendingTill) { TimeId pendingTill) {
const auto requestPending = (!page->pendingTill && pendingTill > 0); const auto requestPending = (!page->pendingTill && pendingTill > 0);
const auto changed = page->applyChanges( const auto changed = page->applyChanges(
@ -3544,6 +3578,7 @@ void Session::webpageApplyFields(
std::move(collage), std::move(collage),
duration, duration,
author, author,
hasLargeMedia,
pendingTill); pendingTill);
if (requestPending) { if (requestPending) {
_session->api().requestWebPageDelayed(page); _session->api().requestWebPageDelayed(page);
@ -4377,7 +4412,8 @@ auto Session::dialogsRowReplacements() const
void Session::serviceNotification( void Session::serviceNotification(
const TextWithEntities &message, const TextWithEntities &message,
const MTPMessageMedia &media) { const MTPMessageMedia &media,
bool invertMedia) {
const auto date = base::unixtime::now(); const auto date = base::unixtime::now();
if (!peerLoaded(PeerData::kServiceNotificationsId)) { if (!peerLoaded(PeerData::kServiceNotificationsId)) {
processUser(MTP_user( processUser(MTP_user(
@ -4400,25 +4436,32 @@ void Session::serviceNotification(
MTPstring(), // lang_code MTPstring(), // lang_code
MTPEmojiStatus(), MTPEmojiStatus(),
MTPVector<MTPUsername>(), MTPVector<MTPUsername>(),
MTPint())); // stories_max_id MTPint(), // stories_max_id
MTP_int(0), // color
MTPlong())); // background_emoji_id
} }
const auto history = this->history(PeerData::kServiceNotificationsId); const auto history = this->history(PeerData::kServiceNotificationsId);
const auto insert = [=] {
insertCheckedServiceNotification(message, media, date, invertMedia);
};
if (!history->folderKnown()) { if (!history->folderKnown()) {
histories().requestDialogEntry(history, [=] { histories().requestDialogEntry(history, insert);
insertCheckedServiceNotification(message, media, date);
});
} else { } else {
insertCheckedServiceNotification(message, media, date); insert();
} }
} }
void Session::insertCheckedServiceNotification( void Session::insertCheckedServiceNotification(
const TextWithEntities &message, const TextWithEntities &message,
const MTPMessageMedia &media, const MTPMessageMedia &media,
TimeId date) { TimeId date,
bool invertMedia) {
const auto flags = MTPDmessage::Flag::f_entities const auto flags = MTPDmessage::Flag::f_entities
| MTPDmessage::Flag::f_from_id | MTPDmessage::Flag::f_from_id
| MTPDmessage::Flag::f_media; | MTPDmessage::Flag::f_media
| (invertMedia
? MTPDmessage::Flag::f_invert_media
: MTPDmessage::Flag());
const auto localFlags = MessageFlag::ClientSideUnread const auto localFlags = MessageFlag::ClientSideUnread
| MessageFlag::Local; | MessageFlag::Local;
auto sending = TextWithEntities(), left = message; auto sending = TextWithEntities(), left = message;
@ -4562,6 +4605,10 @@ auto Session::webViewResultSent() const -> rpl::producer<WebViewResultSent> {
return _webViewResultSent.events(); return _webViewResultSent.events();
} }
rpl::producer<not_null<PeerData*>> Session::peerDecorationsUpdated() const {
return _peerDecorationsUpdated.events();
}
void Session::clearLocalStorage() { void Session::clearLocalStorage() {
_cache->close(); _cache->close();
_cache->clear(); _cache->clear();

View file

@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Image; class Image;
class HistoryItem; class HistoryItem;
struct WebPageCollage; struct WebPageCollage;
enum class WebPageType; enum class WebPageType : uint8;
enum class NewMessageType; enum class NewMessageType;
namespace HistoryView { namespace HistoryView {
@ -562,6 +562,7 @@ public:
WebPageCollage &&collage, WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia,
TimeId pendingTill); TimeId pendingTill);
[[nodiscard]] not_null<GameData*> game(GameId id); [[nodiscard]] not_null<GameData*> game(GameId id);
@ -704,7 +705,8 @@ public:
void serviceNotification( void serviceNotification(
const TextWithEntities &message, const TextWithEntities &message,
const MTPMessageMedia &media = MTP_messageMediaEmpty()); const MTPMessageMedia &media = MTP_messageMediaEmpty(),
bool invertMedia = false);
void setMimeForwardIds(MessageIdsList &&list); void setMimeForwardIds(MessageIdsList &&list);
MessageIdsList takeMimeForwardIds(); MessageIdsList takeMimeForwardIds();
@ -725,6 +727,9 @@ public:
void webViewResultSent(WebViewResultSent &&sent); void webViewResultSent(WebViewResultSent &&sent);
[[nodiscard]] rpl::producer<WebViewResultSent> webViewResultSent() const; [[nodiscard]] rpl::producer<WebViewResultSent> webViewResultSent() const;
[[nodiscard]] auto peerDecorationsUpdated() const
-> rpl::producer<not_null<PeerData*>>;
void clearLocalStorage(); void clearLocalStorage();
private: private:
@ -824,6 +829,7 @@ private:
WebPageCollage &&collage, WebPageCollage &&collage,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia,
TimeId pendingTill); TimeId pendingTill);
void gameApplyFields( void gameApplyFields(
@ -846,7 +852,8 @@ private:
void insertCheckedServiceNotification( void insertCheckedServiceNotification(
const TextWithEntities &message, const TextWithEntities &message,
const MTPMessageMedia &media, const MTPMessageMedia &media,
TimeId date); TimeId date,
bool invertMedia);
void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash); void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash);
void highlightProcessDone(uint64 processId); void highlightProcessDone(uint64 processId);
@ -1009,6 +1016,8 @@ private:
rpl::event_stream<WebViewResultSent> _webViewResultSent; rpl::event_stream<WebViewResultSent> _webViewResultSent;
rpl::event_stream<not_null<PeerData*>> _peerDecorationsUpdated;
Groups _groups; Groups _groups;
const std::unique_ptr<ChatFilters> _chatsFilters; const std::unique_ptr<ChatFilters> _chatsFilters;
std::unique_ptr<ScheduledMessages> _scheduledMessages; std::unique_ptr<ScheduledMessages> _scheduledMessages;

View file

@ -246,65 +246,78 @@ enum class MessageFlag : uint64 {
MentionsMe = (1ULL << 15), MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1ULL << 16), IsOrWasScheduled = (1ULL << 16),
NoForwards = (1ULL << 17), NoForwards = (1ULL << 17),
InvertMedia = (1ULL << 18),
// Needs to return back to inline mode. // Needs to return back to inline mode.
HasSwitchInlineButton = (1ULL << 18), HasSwitchInlineButton = (1ULL << 19),
// For "shared links" indexing. // For "shared links" indexing.
HasTextLinks = (1ULL << 19), HasTextLinks = (1ULL << 20),
// Group / channel create or migrate service message. // Group / channel create or migrate service message.
IsGroupEssential = (1ULL << 20), IsGroupEssential = (1ULL << 21),
// Edited media is generated on the client // Edited media is generated on the client
// and should not update media from server. // and should not update media from server.
IsLocalUpdateMedia = (1ULL << 21), IsLocalUpdateMedia = (1ULL << 22),
// Sent from inline bot, need to re-set media when sent. // Sent from inline bot, need to re-set media when sent.
FromInlineBot = (1ULL << 22), FromInlineBot = (1ULL << 23),
// Generated on the client side and should be unread. // Generated on the client side and should be unread.
ClientSideUnread = (1ULL << 23), ClientSideUnread = (1ULL << 24),
// In a supergroup. // In a supergroup.
HasAdminBadge = (1ULL << 24), HasAdminBadge = (1ULL << 25),
// Outgoing message that is being sent. // Outgoing message that is being sent.
BeingSent = (1ULL << 25), BeingSent = (1ULL << 26),
// Outgoing message and failed to be sent. // Outgoing message and failed to be sent.
SendingFailed = (1ULL << 26), SendingFailed = (1ULL << 27),
// No media and only a several emoji or an only custom emoji text. // No media and only a several emoji or an only custom emoji text.
SpecialOnlyEmoji = (1ULL << 27), SpecialOnlyEmoji = (1ULL << 28),
// Message existing in the message history. // Message existing in the message history.
HistoryEntry = (1ULL << 28), HistoryEntry = (1ULL << 29),
// Local message, not existing on the server. // Local message, not existing on the server.
Local = (1ULL << 29), Local = (1ULL << 30),
// Fake message for some UI element. // Fake message for some UI element.
FakeHistoryItem = (1ULL << 30), FakeHistoryItem = (1ULL << 31),
// Contact sign-up message, notification should be skipped for Silent. // Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1ULL << 31), IsContactSignUp = (1ULL << 32),
// Optimization for item text custom emoji repainting. // Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32), CustomEmojiRepainting = (1ULL << 33),
// Profile photo suggestion, views have special media type. // Profile photo suggestion, views have special media type.
IsUserpicSuggestion = (1ULL << 33), IsUserpicSuggestion = (1ULL << 34),
OnlyEmojiAndSpaces = (1ULL << 34), OnlyEmojiAndSpaces = (1ULL << 35),
OnlyEmojiAndSpacesSet = (1ULL << 35), OnlyEmojiAndSpacesSet = (1ULL << 36),
// Fake message with bot cover and information. // Fake message with bot cover and information.
FakeBotAbout = (1ULL << 36), FakeBotAbout = (1ULL << 37),
StoryItem = (1ULL << 37), StoryItem = (1ULL << 38),
InHighlightProcess = (1ULL << 38), InHighlightProcess = (1ULL << 39),
// If not set then we need to refresh _displayFrom value.
DisplayFromChecked = (1ULL << 40),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;
enum class MediaWebPageFlag : uint8 {
ForceLargeMedia = (1 << 0),
ForceSmallMedia = (1 << 1),
Manual = (1 << 2),
Safe = (1 << 3),
};
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
using MediaWebPageFlags = base::flags<MediaWebPageFlag>;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "lang/lang_keys.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/text/text_entity.h" #include "ui/text/text_entity.h"
@ -224,9 +225,10 @@ bool WebPageData::applyChanges(
WebPageCollage &&newCollage, WebPageCollage &&newCollage,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill) { int newPendingTill) {
if (newPendingTill != 0 if (newPendingTill != 0
&& (!url.isEmpty() || pendingTill < 0) && (!url.isEmpty() || failed)
&& (!pendingTill && (!pendingTill
|| pendingTill == newPendingTill || pendingTill == newPendingTill
|| newPendingTill < -1)) { || newPendingTill < -1)) {
@ -252,6 +254,15 @@ bool WebPageData::applyChanges(
} }
return QString(); return QString();
}(); }();
const auto hasSiteName = !resultSiteName.isEmpty() ? 1 : 0;
const auto hasTitle = !resultTitle.isEmpty() ? 1 : 0;
const auto hasDescription = !newDescription.text.isEmpty() ? 1 : 0;
if (newDocument
|| !newCollage.items.empty()
|| !newPhoto
|| (hasSiteName + hasTitle + hasDescription < 2)) {
newHasLargeMedia = false;
}
if (type == newType if (type == newType
&& url == resultUrl && url == resultUrl
@ -265,6 +276,7 @@ bool WebPageData::applyChanges(
&& collage.items == newCollage.items && collage.items == newCollage.items
&& duration == newDuration && duration == newDuration
&& author == resultAuthor && author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
&& pendingTill == newPendingTill) { && pendingTill == newPendingTill) {
return false; return false;
} }
@ -272,6 +284,7 @@ bool WebPageData::applyChanges(
_owner->session().api().clearWebPageRequest(this); _owner->session().api().clearWebPageRequest(this);
} }
type = newType; type = newType;
hasLargeMedia = newHasLargeMedia ? 1 : 0;
url = resultUrl; url = resultUrl;
displayUrl = resultDisplayUrl; displayUrl = resultDisplayUrl;
siteName = resultSiteName; siteName = resultSiteName;
@ -343,3 +356,38 @@ void WebPageData::ApplyChanges(
} }
session->data().sendWebPageGamePollNotifications(); session->data().sendWebPageGamePollNotifications();
} }
QString WebPageData::displayedSiteName() const {
return (document && document->isWallPaper())
? tr::lng_media_chat_background(tr::now)
: (document && document->isTheme())
? tr::lng_media_color_theme(tr::now)
: siteName;
}
bool WebPageData::computeDefaultSmallMedia() const {
if (!collage.items.empty()) {
return false;
} else if (siteName.isEmpty()
&& title.isEmpty()
&& description.empty()
&& author.isEmpty()) {
return false;
} else if (!document
&& photo
&& type != WebPageType::Photo
&& type != WebPageType::Document
&& type != WebPageType::Story
&& type != WebPageType::Video) {
if (type == WebPageType::Profile) {
return true;
} else if (siteName == u"Twitter"_q
|| siteName == u"Facebook"_q
|| type == WebPageType::ArticleWithIV) {
return false;
} else {
return true;
}
}
return false;
}

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/flags.h"
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_document.h" #include "data/data_document.h"
@ -16,7 +17,9 @@ namespace Data {
class Session; class Session;
} // namespace Data } // namespace Data
enum class WebPageType { enum class WebPageType : uint8 {
None,
Message, Message,
Group, Group,
@ -44,8 +47,7 @@ enum class WebPageType {
VoiceChat, VoiceChat,
Livestream, Livestream,
}; };
[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type);
WebPageType ParseWebPageType(const MTPDwebPage &type);
struct WebPageCollage { struct WebPageCollage {
using Item = std::variant<PhotoData*, DocumentData*>; using Item = std::variant<PhotoData*, DocumentData*>;
@ -78,6 +80,7 @@ struct WebPageData {
WebPageCollage &&newCollage, WebPageCollage &&newCollage,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill); int newPendingTill);
static void ApplyChanges( static void ApplyChanges(
@ -85,21 +88,26 @@ struct WebPageData {
ChannelData *channel, ChannelData *channel,
const MTPmessages_Messages &result); const MTPmessages_Messages &result);
WebPageId id = 0; [[nodiscard]] QString displayedSiteName() const;
WebPageType type = WebPageType::Article; [[nodiscard]] bool computeDefaultSmallMedia() const;
const WebPageId id = 0;
WebPageType type = WebPageType::None;
QString url; QString url;
QString displayUrl; QString displayUrl;
QString siteName; QString siteName;
QString title; QString title;
TextWithEntities description; TextWithEntities description;
FullStoryId storyId; FullStoryId storyId;
int duration = 0;
QString author; QString author;
PhotoData *photo = nullptr; PhotoData *photo = nullptr;
DocumentData *document = nullptr; DocumentData *document = nullptr;
WebPageCollage collage; WebPageCollage collage;
int pendingTill = 0; int duration = 0;
int version = 0; TimeId pendingTill = 0;
uint32 version : 30 = 0;
uint32 hasLargeMedia : 1 = 0;
uint32 failed : 1 = 0;
private: private:
void replaceDocumentGoodThumbnail(); void replaceDocumentGoodThumbnail();

View file

@ -53,7 +53,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
| (data.is_masks() ? Flag::Masks : Flag()) | (data.is_masks() ? Flag::Masks : Flag())
| (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());
} }
StickersSet::StickersSet( StickersSet::StickersSet(
@ -108,6 +109,10 @@ StickersType StickersSet::type() const {
: StickersType::Stickers; : StickersType::Stickers;
} }
bool StickersSet::textColor() const {
return flags & StickersSetFlag::TextColor;
}
void StickersSet::setThumbnail(const ImageWithLocation &data) { void StickersSet::setThumbnail(const ImageWithLocation &data) {
Data::UpdateCloudFile( Data::UpdateCloudFile(
_thumbnail, _thumbnail,

View file

@ -57,6 +57,7 @@ enum class StickersSetFlag {
Special = (1 << 7), Special = (1 << 7),
Webm = (1 << 8), Webm = (1 << 8),
Emoji = (1 << 9), Emoji = (1 << 9),
TextColor = (1 << 10),
}; };
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>;
@ -84,6 +85,7 @@ public:
[[nodiscard]] MTPInputStickerSet mtpInput() const; [[nodiscard]] MTPInputStickerSet mtpInput() const;
[[nodiscard]] StickerSetIdentifier identifier() const; [[nodiscard]] StickerSetIdentifier identifier() const;
[[nodiscard]] StickersType type() const; [[nodiscard]] StickersType type() const;
[[nodiscard]] bool textColor() const;
void setThumbnail(const ImageWithLocation &data); void setThumbnail(const ImageWithLocation &data);

View file

@ -108,10 +108,9 @@ struct EntryState {
Key key; Key key;
Section section = Section::History; Section section = Section::History;
FilterId filterId = 0; FilterId filterId = 0;
MsgId rootId = 0; FullReplyTo currentReplyTo;
MsgId currentReplyToId = 0;
friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept friend inline auto operator<=>(EntryState, EntryState) noexcept
= default; = default;
}; };

View file

@ -54,17 +54,16 @@ QString PrepareStoryFileName(
} // namespace } // namespace
int PeerColorIndex(BareId bareId) { uint8 PeerColorIndex(BareId bareId) {
const auto index = bareId % 7; const uint8 map[] = { 0, 7, 4, 1, 6, 3, 5 };
const int map[] = { 0, 7, 4, 1, 6, 3, 5 }; return map[bareId % base::array_size(map)];
return map[index];
} }
BareId PeerToBareId(PeerId peerId) { BareId PeerToBareId(PeerId peerId) {
return (peerId.value & PeerId::kChatTypeMask); return (peerId.value & PeerId::kChatTypeMask);
} }
int PeerColorIndex(PeerId peerId) { uint8 PeerColorIndex(PeerId peerId) {
return PeerColorIndex(PeerToBareId(peerId)); return PeerColorIndex(PeerToBareId(peerId));
} }
@ -78,7 +77,7 @@ BareId StringBarePeerId(const Utf8String &data) {
return result; return result;
} }
int ApplicationColorIndex(int applicationId) { uint8 ApplicationColorIndex(int applicationId) {
static const auto official = std::map<int, int> { static const auto official = std::map<int, int> {
{ 1, 0 }, // iOS { 1, 0 }, // iOS
{ 7, 0 }, // iOS X { 7, 0 }, // iOS X
@ -576,6 +575,18 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
return result; return result;
} }
Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
auto result = Giveaway{
.untilDate = data.vuntil_date().v,
.quantity = data.vquantity().v,
.months = data.vmonths().v,
};
for (const auto &id : data.vchannels().v) {
result.channels.push_back(ChannelId(id));
}
return result;
}
UserpicsSlice ParseUserpicsSlice( UserpicsSlice ParseUserpicsSlice(
const MTPVector<MTPPhoto> &data, const MTPVector<MTPPhoto> &data,
int baseIndex) { int baseIndex) {
@ -755,6 +766,8 @@ ContactInfo ParseContactInfo(const MTPUser &data) {
auto result = ContactInfo(); auto result = ContactInfo();
data.match([&](const MTPDuser &data) { data.match([&](const MTPDuser &data) {
result.userId = data.vid().v; result.userId = data.vid().v;
result.colorIndex = data.vcolor().value_or(
PeerColorIndex(result.userId));
if (const auto firstName = data.vfirst_name()) { if (const auto firstName = data.vfirst_name()) {
result.firstName = ParseString(*firstName); result.firstName = ParseString(*firstName);
} }
@ -766,15 +779,13 @@ ContactInfo ParseContactInfo(const MTPUser &data) {
} }
}, [&](const MTPDuserEmpty &data) { }, [&](const MTPDuserEmpty &data) {
result.userId = data.vid().v; result.userId = data.vid().v;
result.colorIndex = PeerColorIndex(result.userId);
}); });
return result; return result;
} }
int ContactColorIndex(const ContactInfo &data) { uint8 ContactColorIndex(const ContactInfo &data) {
if (data.userId != 0) { return data.colorIndex;
return PeerColorIndex(data.userId.bare);
}
return PeerColorIndex(StringBarePeerId(data.phoneNumber));
} }
PeerId User::id() const { PeerId User::id() const {
@ -786,6 +797,8 @@ User ParseUser(const MTPUser &data) {
result.info = ParseContactInfo(data); result.info = ParseContactInfo(data);
data.match([&](const MTPDuser &data) { data.match([&](const MTPDuser &data) {
result.bareId = data.vid().v; result.bareId = data.vid().v;
result.colorIndex = data.vcolor().value_or(
PeerColorIndex(result.bareId));
if (const auto username = data.vusername()) { if (const auto username = data.vusername()) {
result.username = ParseString(*username); result.username = ParseString(*username);
} }
@ -840,6 +853,8 @@ Chat ParseChat(const MTPChat &data) {
result.input = MTP_inputPeerChat(MTP_long(result.bareId)); result.input = MTP_inputPeerChat(MTP_long(result.bareId));
}, [&](const MTPDchannel &data) { }, [&](const MTPDchannel &data) {
result.bareId = data.vid().v; result.bareId = data.vid().v;
result.colorIndex = data.vcolor().value_or(
PeerColorIndex(result.bareId));
result.isBroadcast = data.is_broadcast(); result.isBroadcast = data.is_broadcast();
result.isSupergroup = data.is_megagroup(); result.isSupergroup = data.is_megagroup();
result.title = ParseString(data.vtitle()); result.title = ParseString(data.vtitle());
@ -923,6 +938,15 @@ MTPInputPeer Peer::input() const {
Unexpected("Variant in Peer::id."); Unexpected("Variant in Peer::id.");
} }
uint8 Peer::colorIndex() const {
if (const auto user = this->user()) {
return user->colorIndex;
} else if (const auto chat = this->chat()) {
return chat->colorIndex;
}
Unexpected("Variant in Peer::colorIndex.");
}
std::map<PeerId, Peer> ParsePeersLists( std::map<PeerId, Peer> ParsePeersLists(
const MTPVector<MTPUser> &users, const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats) { const MTPVector<MTPChat> &chats) {
@ -1057,7 +1081,9 @@ Media ParseMedia(
}, [](const MTPDmessageMediaDice &data) { }, [](const MTPDmessageMediaDice &data) {
// #TODO dice // #TODO dice
}, [](const MTPDmessageMediaStory &data) { }, [](const MTPDmessageMediaStory &data) {
// #TODO stories export // #TODO export stories
}, [&](const MTPDmessageMediaGiveaway &data) {
result.content = ParseGiveaway(data);
}, [](const MTPDmessageMediaEmpty &data) {}); }, [](const MTPDmessageMediaEmpty &data) {});
return result; return result;
} }
@ -1298,6 +1324,19 @@ ServiceAction ParseServiceAction(
content.peerId = ParsePeerId(data.vpeer()); content.peerId = ParsePeerId(data.vpeer());
content.buttonId = data.vbutton_id().v; content.buttonId = data.vbutton_id().v;
result.content = content; result.content = content;
}, [&](const MTPDmessageActionGiftCode &data) {
auto content = ActionGiftCode();
content.boostPeerId = data.vboost_peer()
? peerFromMTP(*data.vboost_peer())
: PeerId();
content.viaGiveaway = data.is_via_giveaway();
content.unclaimed = data.is_unclaimed();
content.months = data.vmonths().v;
content.code = data.vslug().v;
result.content = content;
}, [&](const MTPDmessageActionGiveawayLaunch &) {
auto content = ActionGiveawayLaunch();
result.content = content;
}, [](const MTPDmessageActionEmpty &data) {}); }, [](const MTPDmessageActionEmpty &data) {});
return result; return result;
} }
@ -1358,15 +1397,19 @@ Message ParseMessage(
} }
if (const auto replyTo = data.vreply_to()) { if (const auto replyTo = data.vreply_to()) {
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
result.replyToMsgId = data.vreply_to_msg_id().v; if (const auto replyToMsg = data.vreply_to_msg_id()) {
result.replyToPeerId = data.vreply_to_peer_id() result.replyToMsgId = replyToMsg->v;
? ParsePeerId(*data.vreply_to_peer_id()) result.replyToPeerId = data.vreply_to_peer_id()
: 0; ? ParsePeerId(*data.vreply_to_peer_id())
if (result.replyToPeerId == result.peerId) { : 0;
result.replyToPeerId = 0; if (result.replyToPeerId == result.peerId) {
result.replyToPeerId = 0;
}
} else {
// #TODO export replies
} }
}, [&](const MTPDmessageReplyStoryHeader &data) { }, [&](const MTPDmessageReplyStoryHeader &data) {
// #TODO stories export // #TODO export stories
}); });
} }
} }
@ -1410,12 +1453,16 @@ Message ParseMessage(
} }
if (const auto replyTo = data.vreply_to()) { if (const auto replyTo = data.vreply_to()) {
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
result.replyToMsgId = data.vreply_to_msg_id().v; if (const auto replyToMsg = data.vreply_to_msg_id()) {
result.replyToPeerId = data.vreply_to_peer_id() result.replyToMsgId = replyToMsg->v;
? ParsePeerId(*data.vreply_to_peer_id()) result.replyToPeerId = data.vreply_to_peer_id()
: PeerId(0); ? ParsePeerId(*data.vreply_to_peer_id())
: PeerId(0);
} else {
// #TODO export replies
}
}, [&](const MTPDmessageReplyStoryHeader &data) { }, [&](const MTPDmessageReplyStoryHeader &data) {
// #TODO stories export // #TODO export stories
}); });
} }
if (const auto viaBotId = data.vvia_bot_id()) { if (const auto viaBotId = data.vvia_bot_id()) {
@ -1507,6 +1554,8 @@ ContactsList ParseContactsList(const MTPVector<MTPSavedContact> &data) {
info.lastName = ParseString(data.vlast_name()); info.lastName = ParseString(data.vlast_name());
info.phoneNumber = ParseString(data.vphone()); info.phoneNumber = ParseString(data.vphone());
info.date = data.vdate().v; info.date = data.vdate().v;
info.colorIndex = PeerColorIndex(
StringBarePeerId(info.phoneNumber));
return info; return info;
}); });
result.list.push_back(std::move(info)); result.list.push_back(std::move(info));
@ -1724,6 +1773,7 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
info.lastName = peer.user() info.lastName = peer.user()
? peer.user()->info.lastName ? peer.user()->info.lastName
: Utf8String(); : Utf8String();
info.colorIndex = peer.colorIndex();
info.input = peer.input(); info.input = peer.input();
info.migratedToChannelId = peer.chat() info.migratedToChannelId = peer.chat()
? peer.chat()->migratedToChannelId ? peer.chat()->migratedToChannelId

View file

@ -24,10 +24,10 @@ namespace Data {
using Utf8String = QByteArray; using Utf8String = QByteArray;
int PeerColorIndex(BareId bareId); uint8 PeerColorIndex(BareId bareId);
BareId PeerToBareId(PeerId peerId); BareId PeerToBareId(PeerId peerId);
int PeerColorIndex(PeerId peerId); uint8 PeerColorIndex(PeerId peerId);
int ApplicationColorIndex(int applicationId); uint8 ApplicationColorIndex(int applicationId);
int DomainApplicationId(const Utf8String &data); int DomainApplicationId(const Utf8String &data);
Utf8String ParseString(const MTPstring &data); Utf8String ParseString(const MTPstring &data);
@ -108,12 +108,13 @@ struct ContactInfo {
Utf8String lastName; Utf8String lastName;
Utf8String phoneNumber; Utf8String phoneNumber;
TimeId date = 0; TimeId date = 0;
uint8 colorIndex = 0;
Utf8String name() const; Utf8String name() const;
}; };
ContactInfo ParseContactInfo(const MTPUser &data); ContactInfo ParseContactInfo(const MTPUser &data);
int ContactColorIndex(const ContactInfo &data); uint8 ContactColorIndex(const ContactInfo &data);
struct Photo { struct Photo {
uint64 id = 0; uint64 id = 0;
@ -196,6 +197,13 @@ struct Poll {
bool closed = false; bool closed = false;
}; };
struct Giveaway {
std::vector<ChannelId> channels;
TimeId untilDate = 0;
int quantity = 0;
int months = 0;
};
struct UserpicsSlice { struct UserpicsSlice {
std::vector<Photo> list; std::vector<Photo> list;
}; };
@ -210,6 +218,7 @@ struct User {
BareId bareId = 0; BareId bareId = 0;
ContactInfo info; ContactInfo info;
Utf8String username; Utf8String username;
uint8 colorIndex = 0;
bool isBot = false; bool isBot = false;
bool isSelf = false; bool isSelf = false;
bool isReplies = false; bool isReplies = false;
@ -229,6 +238,7 @@ struct Chat {
ChannelId migratedToChannelId = 0; ChannelId migratedToChannelId = 0;
Utf8String title; Utf8String title;
Utf8String username; Utf8String username;
uint8 colorIndex = 0;
bool isBroadcast = false; bool isBroadcast = false;
bool isSupergroup = false; bool isSupergroup = false;
@ -242,6 +252,7 @@ struct Peer {
PeerId id() const; PeerId id() const;
Utf8String name() const; Utf8String name() const;
MTPInputPeer input() const; MTPInputPeer input() const;
uint8 colorIndex() const;
const User *user() const; const User *user() const;
const Chat *chat() const; const Chat *chat() const;
@ -325,6 +336,7 @@ struct Media {
Game, Game,
Invoice, Invoice,
Poll, Poll,
Giveaway,
UnsupportedMedia> content; UnsupportedMedia> content;
TimeId ttl = 0; TimeId ttl = 0;
@ -527,11 +539,22 @@ struct ActionSetChatWallPaper {
struct ActionSetSameChatWallPaper { struct ActionSetSameChatWallPaper {
}; };
struct ActionGiftCode {
QByteArray code;
PeerId boostPeerId = 0;
int months = 0;
bool viaGiveaway = false;
bool unclaimed = false;
};
struct ActionRequestedPeer { struct ActionRequestedPeer {
PeerId peerId = 0; PeerId peerId = 0;
int buttonId = 0; int buttonId = 0;
}; };
struct ActionGiveawayLaunch {
};
struct ServiceAction { struct ServiceAction {
std::variant< std::variant<
v::null_t, v::null_t,
@ -570,7 +593,9 @@ struct ServiceAction {
ActionSuggestProfilePhoto, ActionSuggestProfilePhoto,
ActionRequestedPeer, ActionRequestedPeer,
ActionSetChatWallPaper, ActionSetChatWallPaper,
ActionSetSameChatWallPaper> content; ActionSetSameChatWallPaper,
ActionGiftCode,
ActionGiveawayLaunch> content;
}; };
ServiceAction ParseServiceAction( ServiceAction ParseServiceAction(
@ -726,6 +751,7 @@ struct DialogInfo {
int32 topMessageId = 0; int32 topMessageId = 0;
TimeId topMessageDate = 0; TimeId topMessageDate = 0;
PeerId peerId = 0; PeerId peerId = 0;
uint8 colorIndex = 0;
MTPInputPeer migratedFromInput = MTP_inputPeerEmpty(); MTPInputPeer migratedFromInput = MTP_inputPeerEmpty();
ChannelId migratedToChannelId = 0; ChannelId migratedToChannelId = 0;

View file

@ -25,7 +25,7 @@ constexpr auto kPersonalUserpicSize = 90;
constexpr auto kEntryUserpicSize = 48; constexpr auto kEntryUserpicSize = 48;
constexpr auto kServiceMessagePhotoSize = 60; constexpr auto kServiceMessagePhotoSize = 60;
constexpr auto kHistoryUserpicSize = 42; constexpr auto kHistoryUserpicSize = 42;
constexpr auto kSavedMessagesColorIndex = 3; constexpr auto kSavedMessagesColorIndex = uint8(3);
constexpr auto kJoinWithinSeconds = 900; constexpr auto kJoinWithinSeconds = 900;
constexpr auto kPhotoMaxWidth = 520; constexpr auto kPhotoMaxWidth = 520;
constexpr auto kPhotoMaxHeight = 520; constexpr auto kPhotoMaxHeight = 520;
@ -351,7 +351,7 @@ QByteArray FormatTimeText(TimeId date) {
namespace details { namespace details {
struct UserpicData { struct UserpicData {
int colorIndex = 0; uint8 colorIndex = 0;
int pixelSize = 0; int pixelSize = 0;
QString imageLink; QString imageLink;
QString largeLink; QString largeLink;
@ -611,6 +611,9 @@ private:
const Data::Photo &data, const Data::Photo &data,
const QString &basePath); const QString &basePath);
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data); [[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
[[nodiscard]] QByteArray pushGiveaway(
const PeersMap &peers,
const Data::Giveaway &data);
File _file; File _file;
QByteArray _composedStart; QByteArray _composedStart;
@ -988,7 +991,7 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
result.append(popTag()); result.append(popTag());
if (photo) { if (photo) {
auto userpic = UserpicData(); auto userpic = UserpicData();
userpic.colorIndex = Data::PeerColorIndex(dialog.peerId); userpic.colorIndex = dialog.colorIndex;
userpic.firstName = dialog.name; userpic.firstName = dialog.name;
userpic.lastName = dialog.lastName; userpic.lastName = dialog.lastName;
userpic.pixelSize = kServiceMessagePhotoSize; userpic.pixelSize = kServiceMessagePhotoSize;
@ -1276,6 +1279,24 @@ auto HtmlWriter::Wrap::pushMessage(
+ " set " + " set "
+ wrapReplyToLink("the same background") + wrapReplyToLink("the same background")
+ " for this chat"; + " for this chat";
}, [&](const ActionGiftCode &data) {
return data.unclaimed
? ("This is an unclaimed Telegram Premium for "
+ NumberToString(data.months)
+ (data.months > 1 ? " months" : "month")
+ " prize in a giveaway organized by a channel.")
: data.viaGiveaway
? ("You won a Telegram Premium for "
+ NumberToString(data.months)
+ (data.months > 1 ? " months" : "month")
+ " prize in a giveaway organized by a channel.")
: ("You've received a Telegram Premium for "
+ NumberToString(data.months)
+ (data.months > 1 ? " months" : "month")
+ " gift from a channel.");
}, [&](const ActionGiveawayLaunch &data) {
return serviceFrom + " just started a giveaway "
"of Telegram Premium subscriptions to its followers.";
}, [](v::null_t) { return QByteArray(); }); }, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) { if (!serviceText.isEmpty()) {
@ -1459,8 +1480,9 @@ QByteArray HtmlWriter::Wrap::pushMedia(
if (!data.classes.isEmpty()) { if (!data.classes.isEmpty()) {
return pushGenericMedia(data); return pushGenericMedia(data);
} }
using namespace Data;
const auto &content = message.media.content; const auto &content = message.media.content;
if (const auto document = std::get_if<Data::Document>(&content)) { if (const auto document = std::get_if<Document>(&content)) {
Assert(!message.media.ttl); Assert(!message.media.ttl);
if (document->isSticker) { if (document->isSticker) {
return pushStickerMedia(*document, basePath); return pushStickerMedia(*document, basePath);
@ -1470,11 +1492,13 @@ QByteArray HtmlWriter::Wrap::pushMedia(
return pushVideoFileMedia(*document, basePath); return pushVideoFileMedia(*document, basePath);
} }
Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia."); Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia.");
} else if (const auto photo = std::get_if<Data::Photo>(&content)) { } else if (const auto photo = std::get_if<Photo>(&content)) {
Assert(!message.media.ttl); Assert(!message.media.ttl);
return pushPhotoMedia(*photo, basePath); return pushPhotoMedia(*photo, basePath);
} else if (const auto poll = std::get_if<Data::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)) {
return pushGiveaway(peers, *giveaway);
} }
Assert(v::is_null(content)); Assert(v::is_null(content));
return QByteArray(); return QByteArray();
@ -1796,6 +1820,52 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) {
return result; return result;
} }
QByteArray HtmlWriter::Wrap::pushGiveaway(
const PeersMap &peers,
const Data::Giveaway &data) {
auto result = pushDiv("media_wrap clearfix");
result.append(pushDiv("media_giveaway"));
result.append(pushDiv("section_title bold"));
result.append(SerializeString("Giveaway Prizes"));
result.append(popTag());
result.append(pushDiv("section_body"));
result.append("<b>"
+ Data::NumberToString(data.quantity)
+ "</b> "
+ SerializeString((data.quantity > 1)
? "Telegram Premium Subscriptions"
: "Telegram Premium Subscription")
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
+ (data.months > 1 ? "months." : "month."));
result.append(popTag());
result.append(pushDiv("section_title bold"));
result.append(SerializeString("Participants"));
result.append(popTag());
result.append(pushDiv("section_body"));
auto channels = QByteArrayList();
for (const auto &channel : data.channels) {
channels.append("<b>" + peers.wrapPeerName(channel) + "</b>");
}
result.append(SerializeString((channels.size() > 1)
? "All subscribers of those channels: "
: "All subscribers of the channel: ")
+ channels.join(", "));
result.append(popTag());
result.append(pushDiv("section_title bold"));
result.append(SerializeString("Winners Selection Date"));
result.append(popTag());
result.append(pushDiv("section_body"));
result.append(Data::FormatDateTime(data.untilDate));
result.append(popTag());
result.append(popTag());
result.append(popTag());
return result;
}
MediaData HtmlWriter::Wrap::prepareMediaData( MediaData HtmlWriter::Wrap::prepareMediaData(
const Data::Message &message, const Data::Message &message,
const QString &basePath, const QString &basePath,
@ -1954,6 +2024,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 UnsupportedMedia &data) { }, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message."); Unexpected("Unsupported message.");
}, [](v::null_t) {}); }, [](v::null_t) {});
@ -2104,7 +2175,7 @@ Result HtmlWriter::start(
Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) { Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_summary != nullptr); Expects(_summary != nullptr);
_selfColorIndex = Data::PeerColorIndex(data.user.info.userId); _selfColorIndex = data.user.info.colorIndex;
if (_settings.types & Settings::Type::Userpics) { if (_settings.types & Settings::Type::Userpics) {
_delayedPersonalInfo = std::make_unique<Data::PersonalInfo>(data); _delayedPersonalInfo = std::make_unique<Data::PersonalInfo>(data);
return Result::Success(); return Result::Success();

View file

@ -150,7 +150,7 @@ private:
bool _summaryNeedDivider = false; bool _summaryNeedDivider = false;
bool _haveSections = false; bool _haveSections = false;
int _selfColorIndex = 0; uint8 _selfColorIndex = 0;
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo; std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
int _userpicsCount = 0; int _userpicsCount = 0;

View file

@ -287,11 +287,12 @@ QByteArray SerializeMessage(
} }
const auto push = [&](const QByteArray &key, const auto &value) { const auto push = [&](const QByteArray &key, const auto &value) {
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(value)>>) { using V = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<V, bool>) {
pushBare(key, value ? "true" : "false");
} else if constexpr (std::is_arithmetic_v<V>) {
pushBare(key, Data::NumberToString(value)); pushBare(key, Data::NumberToString(value));
} else if constexpr (std::is_same_v< } else if constexpr (std::is_same_v<V, PeerId>) {
std::decay_t<decltype(value)>,
PeerId>) {
if (const auto chat = peerToChat(value)) { if (const auto chat = peerToChat(value)) {
pushBare( pushBare(
key, key,
@ -592,6 +593,17 @@ QByteArray SerializeMessage(
pushAction("requested_peer"); pushAction("requested_peer");
push("button_id", data.buttonId); push("button_id", data.buttonId);
push("peer_id", data.peerId.value); push("peer_id", data.peerId.value);
}, [&](const ActionGiftCode &data) {
pushAction("gift_code_prize");
push("gift_code", data.code);
if (data.boostPeerId) {
push("boost_peer_id", data.boostPeerId);
}
push("months", data.months);
push("unclaimed", data.unclaimed);
push("via_giveaway", data.viaGiveaway);
}, [&](const ActionGiveawayLaunch &data) {
pushAction("giveaway_launch");
}, [&](const ActionSetChatWallPaper &data) { }, [&](const ActionSetChatWallPaper &data) {
pushActor(); pushActor();
pushAction("set_chat_wallpaper"); pushAction("set_chat_wallpaper");
@ -738,6 +750,22 @@ QByteArray SerializeMessage(
{ "total_voters", NumberToString(data.totalVotes) }, { "total_voters", NumberToString(data.totalVotes) },
{ "answers", serialized } { "answers", serialized }
})); }));
}, [&](const Giveaway &data) {
context.nesting.push_back(Context::kObject);
const auto channels = ranges::views::all(
data.channels
) | ranges::views::transform([&](ChannelId id) {
return NumberToString(id.bare);
}) | ranges::to_vector;
const auto serialized = SerializeArray(context, channels);
context.nesting.pop_back();
push("giveaway_information", SerializeObject(context, {
{ "quantity", NumberToString(data.quantity) },
{ "months", NumberToString(data.months) },
{ "until_date", SerializeDate(data.untilDate) },
{ "channels", serialized },
}));
}, [](const UnsupportedMedia &data) { }, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message."); Unexpected("Unsupported message.");
}, [](v::null_t) {}); }, [](v::null_t) {});

View file

@ -659,7 +659,7 @@ not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {
return _pathGradient.get(); return _pathGradient.get();
} }
void InnerWidget::elementReplyTo(const FullMsgId &to) { void InnerWidget::elementReplyTo(const FullReplyTo &to) {
} }
void InnerWidget::elementStartInteraction(not_null<const Element*> view) { void InnerWidget::elementStartInteraction(not_null<const Element*> view) {

View file

@ -127,7 +127,7 @@ public:
void elementHandleViaClick(not_null<UserData*> bot) override; void elementHandleViaClick(not_null<UserData*> bot) override;
bool elementIsChatWide() override; bool elementIsChatWide() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override; not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
void elementReplyTo(const FullMsgId &to) override; void elementReplyTo(const FullReplyTo &to) override;
void elementStartInteraction( void elementStartInteraction(
not_null<const HistoryView::Element*> view) override; not_null<const HistoryView::Element*> view) override;
void elementStartPremium( void elementStartPremium(

View file

@ -767,6 +767,8 @@ void GenerateItems(
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic; using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic; using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam; using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor;
using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji;
const auto session = &history->session(); const auto session = &history->session();
const auto id = event.vid().v; const auto id = event.vid().v;
@ -1815,6 +1817,54 @@ void GenerateItems(
addSimpleServiceMessage(text); addSimpleServiceMessage(text);
}; };
const auto createChangeColor = [&](const LogChangeColor &data) {
const auto text = tr::lng_admin_log_change_color(
tr::now,
lt_from,
fromLinkText,
lt_previous,
{ '#' + QString::number(data.vprev_value().v + 1) },
lt_color,
{ '#' + QString::number(data.vnew_value().v + 1) },
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) {
const auto was = data.vprev_value().v;
const auto now = data.vnew_value().v;
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,
lt_from,
fromLinkText,
lt_previous,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(was)),
lt_emoji,
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(now)),
Ui::Text::WithEntities);
addSimpleServiceMessage(text);
};
action.match( action.match(
createChangeTitle, createChangeTitle,
createChangeAbout, createChangeAbout,
@ -1858,7 +1908,9 @@ void GenerateItems(
createEditTopic, createEditTopic,
createDeleteTopic, createDeleteTopic,
createPinTopic, createPinTopic,
createToggleAntiSpam); createToggleAntiSpam,
createChangeColor,
createChangeBackgroundEmoji);
} }
} // namespace AdminLog } // namespace AdminLog

View file

@ -181,8 +181,7 @@ void History::takeLocalDraft(not_null<History*> from) {
&& !_drafts.contains(Data::DraftKey::Local(topicRootId))) { && !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
// Edit and reply to drafts can't migrate. // Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically. // Cloud drafts do not migrate automatically.
draft->msgId = 0; draft->reply = FullReplyTo();
setLocalDraft(std::move(draft)); setLocalDraft(std::move(draft));
} }
from->clearLocalDraft(topicRootId); from->clearLocalDraft(topicRootId);
@ -198,6 +197,7 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
return; return;
} }
draft->reply.topicRootId = topicRootId;
auto existing = localDraft(topicRootId); auto existing = localDraft(topicRootId);
if (Data::DraftIsNull(existing) if (Data::DraftIsNull(existing)
|| !existing->date || !existing->date
@ -205,17 +205,15 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
if (!existing) { if (!existing) {
setLocalDraft(std::make_unique<Data::Draft>( setLocalDraft(std::make_unique<Data::Draft>(
draft->textWithTags, draft->textWithTags,
draft->msgId, draft->reply,
topicRootId,
draft->cursor, draft->cursor,
draft->previewState)); draft->webpage));
existing = localDraft(topicRootId); existing = localDraft(topicRootId);
} else if (existing != draft) { } else if (existing != draft) {
existing->textWithTags = draft->textWithTags; existing->textWithTags = draft->textWithTags;
existing->msgId = draft->msgId; existing->reply = draft->reply;
existing->topicRootId = draft->topicRootId;
existing->cursor = draft->cursor; existing->cursor = draft->cursor;
existing->previewState = draft->previewState; existing->webpage = draft->webpage;
} }
existing->date = draft->date; existing->date = draft->date;
} }
@ -281,28 +279,29 @@ Data::Draft *History::createCloudDraft(
if (Data::DraftIsNull(fromDraft)) { if (Data::DraftIsNull(fromDraft)) {
setCloudDraft(std::make_unique<Data::Draft>( setCloudDraft(std::make_unique<Data::Draft>(
TextWithTags(), TextWithTags(),
0, FullReplyTo{ .topicRootId = topicRootId },
topicRootId,
MessageCursor(), MessageCursor(),
Data::PreviewState::Allowed)); Data::WebPageDraft()));
cloudDraft(topicRootId)->date = TimeId(0); cloudDraft(topicRootId)->date = TimeId(0);
} else { } else {
auto existing = cloudDraft(topicRootId); auto existing = cloudDraft(topicRootId);
if (!existing) { if (!existing) {
auto reply = fromDraft->reply;
reply.topicRootId = topicRootId;
setCloudDraft(std::make_unique<Data::Draft>( setCloudDraft(std::make_unique<Data::Draft>(
fromDraft->textWithTags, fromDraft->textWithTags,
fromDraft->msgId, reply,
topicRootId,
fromDraft->cursor, fromDraft->cursor,
fromDraft->previewState)); fromDraft->webpage));
existing = cloudDraft(topicRootId); existing = cloudDraft(topicRootId);
} else if (existing != fromDraft) { } else if (existing != fromDraft) {
existing->textWithTags = fromDraft->textWithTags; existing->textWithTags = fromDraft->textWithTags;
existing->msgId = fromDraft->msgId; existing->reply = fromDraft->reply;
existing->cursor = fromDraft->cursor; existing->cursor = fromDraft->cursor;
existing->previewState = fromDraft->previewState; existing->webpage = fromDraft->webpage;
} }
existing->date = base::unixtime::now(); existing->date = base::unixtime::now();
existing->reply.topicRootId = topicRootId;
} }
if (const auto thread = threadFor(topicRootId)) { if (const auto thread = threadFor(topicRootId)) {
@ -1185,8 +1184,8 @@ void History::applyServiceChanges(
}, [&](const MTPDmessageActionPinMessage &data) { }, [&](const MTPDmessageActionPinMessage &data) {
if (replyTo) { if (replyTo) {
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
const auto id = data.vreply_to_msg_id().v; const auto id = data.vreply_to_msg_id().value_or_empty();
if (item) { if (id && item) {
session().storage().add(Storage::SharedMediaAddSlice( session().storage().add(Storage::SharedMediaAddSlice(
peer->id, peer->id,
MsgId(0), MsgId(0),

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