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_participants_box.cpp
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_info_box.cpp
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_search.cpp
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.h
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_button.cpp
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.h
history/view/media/history_view_contact.cpp
@ -744,6 +750,8 @@ PRIVATE
history/view/media/history_view_game.h
history/view/media/history_view_gif.cpp
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.h
history/view/media/history_view_large_emoji.cpp
@ -883,20 +891,6 @@ PRIVATE
history/history_view_highlight_manager.h
history/history_widget.cpp
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.h
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_widget.cpp
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.h
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_try_now" = "Try now";
"lng_code_block_header_copy" = "copy";
"lng_status_service_notifications" = "service notifications";
"lng_status_support" = "support";
"lng_status_bot" = "bot";
@ -402,6 +404,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dlg_search_from" = "From: {user}";
"lng_settings_save" = "Save";
"lng_settings_apply" = "Apply";
"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.";
@ -770,11 +773,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_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_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_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_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";
@ -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_text2" = "I can't even take you seriously right now.";
"lng_background_bad_link" = "This background link appears to be invalid.";
"lng_background_apply" = "Apply";
"lng_background_share" = "Share";
"lng_background_link_copied" = "Link copied to clipboard";
"lng_background_blur" = "Blurred";
@ -1142,6 +1166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mute_box_title" = "Mute notifications for...";
"lng_preview_loading" = "Getting Link Info...";
"lng_preview_cant" = "Could not generate preview for this link.";
"lng_profile_settings_section" = "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_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_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#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_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_limit1#one" = "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_short" = "Save";
"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_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_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"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_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_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?";
@ -2546,6 +2715,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_bot_title" = "Edit bot";
"lng_edit_sign_messages" = "Sign messages";
"lng_edit_group" = "Edit group";
"lng_edit_channel_color" = "Change name color";
"lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create";
@ -2674,6 +2844,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_underline" = "Underline";
"lng_menu_formatting_strike_out" = "Strike-through";
"lng_menu_formatting_blockquote" = "Quote";
"lng_menu_formatting_monospace" = "Monospace";
"lng_menu_formatting_spoiler" = "Spoiler";
"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_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_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_antispam_enabled" = "{from} enabled 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_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}";

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.10.4.0" />
Version="4.11.1.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

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

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,10,4,0
PRODUCTVERSION 4,10,4,0
FILEVERSION 4,11,1,0
PRODUCTVERSION 4,11,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.10.4.0"
VALUE "FileVersion", "4.11.1.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.10.4.0"
VALUE "ProductVersion", "4.11.1.0"
END
END
BLOCK "VarFileInfo"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,6 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
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)
: _session(&api->session())
@ -183,6 +198,115 @@ void Premium::reloadCloudSet() {
}).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 {
return _subscriptionOptions;
}

View file

@ -18,6 +18,49 @@ class Session;
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 {
public:
explicit Premium(not_null<ApiWrap*> api);
@ -40,6 +83,19 @@ public:
[[nodiscard]] int64 monthlyAmount() 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
-> const Data::SubscriptionOptions &;
@ -71,6 +127,16 @@ private:
int64 _monthlyAmount = 0;
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;
};

View file

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

View file

@ -434,19 +434,42 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
const auto requestPrivateForwards = [=](
const Data::StatisticalGraph &messageGraph) {
_api.request(MTPstats_GetBroadcastStats(
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
_channel->inputChannel
)).done([=](const MTPstats_BroadcastStats &result) {
const auto channelStats = ChannelStatisticsFromTL(result.data());
auto info = Data::StatisticsMessageInteractionInfo();
for (const auto &r : channelStats.recentMessageInteractions) {
if (r.messageId == _fullId.msg) {
info = r;
break;
}
}
requestFirstPublicForwards(messageGraph, info);
_api.request(MTPchannels_GetMessages(
_channel->inputChannel,
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(_fullId.msg))))
).done([=](const MTPmessages_Messages &result) {
const auto process = [&](const MTPVector<MTPMessage> &messages) {
const auto &message = messages.v.front();
return message.match([&](const MTPDmessage &data) {
return Data::StatisticsMessageInteractionInfo{
.messageId = IdFromMessage(message),
.viewsCount = data.vviews()
? 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) {
requestFirstPublicForwards(messageGraph, {});
}).send();
@ -478,9 +501,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
return lifetime;
}
_api.request(MTPstories_GetBoostsStatus(
_api.request(MTPpremium_GetBoostsStatus(
_peer->input
)).done([=](const MTPstories_BoostsStatus &result) {
)).done([=](const MTPpremium_BoostsStatus &result) {
const auto &data = result.data();
const auto hasPremium = !!data.vpremium_audience();
const auto premiumMemberCount = hasPremium
@ -530,28 +553,29 @@ void Boosts::requestBoosts(
}
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = _api.request(MTPstories_GetBoostersList(
_requestId = _api.request(MTPpremium_GetBoostsList(
MTP_flags(0),
_peer->input,
MTP_string(token.next),
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
)).done([=](const MTPstories_BoostersList &result) {
)).done([=](const MTPpremium_BoostsList &result) {
_requestId = 0;
const auto &data = result.data();
_peer->owner().processUsers(data.vusers());
auto list = std::vector<Data::Boost>();
list.reserve(data.vboosters().v.size());
for (const auto &boost : data.vboosters().v) {
list.reserve(data.vboosts().v.size());
for (const auto &boost : data.vboosts().v) {
list.push_back({
boost.data().vuser_id().v,
boost.data().vuser_id().value_or_empty(),
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
});
}
done(Data::BoostsListSlice{
.list = std::move(list),
.total = data.vcount().v,
.allLoaded = (data.vcount().v == data.vboosters().v.size()),
.allLoaded = (data.vcount().v == data.vboosts().v.size()),
.token = Data::BoostsListSlice::OffsetToken{
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_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_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_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCustomEmoji: {
@ -142,6 +143,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
&& entity.type() != EntityType::StrikeOut
&& entity.type() != EntityType::Code // #TODO entities
&& entity.type() != EntityType::Pre
&& entity.type() != EntityType::Blockquote
&& entity.type() != EntityType::Spoiler
&& entity.type() != EntityType::MentionName
&& entity.type() != EntityType::CustomUrl
@ -170,6 +172,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
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::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::CustomEmoji: {
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));
}
} else {
session().data().serviceNotification(text, d.vmedia());
session().data().serviceNotification(
text,
d.vmedia(),
d.is_invert_media());
session().api().authorizations().reload();
}
} break;

View file

@ -2150,14 +2150,13 @@ void ApiWrap::saveDraftsToCloud() {
auto flags = MTPmessages_SaveDraft::Flags(0);
auto &textWithTags = cloudDraft->textWithTags;
if (cloudDraft->previewState != Data::PreviewState::Allowed) {
if (cloudDraft->webpage.removed) {
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
} else if (!cloudDraft->webpage.url.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_media;
}
if (cloudDraft->msgId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
}
if (cloudDraft->topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
}
if (!textWithTags.tags.isEmpty()) {
flags |= MTPmessages_SaveDraft::Flag::f_entities;
@ -2170,11 +2169,13 @@ void ApiWrap::saveDraftsToCloud() {
history->startSavingCloudDraft(topicRootId);
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
MTP_flags(flags),
MTP_int(cloudDraft->msgId),
MTP_int(cloudDraft->topicRootId),
ReplyToForMTP(history, cloudDraft->reply),
history->peer->input,
MTP_string(textWithTags.text),
entities
entities,
Data::WebPageForMTP(
cloudDraft->webpage,
textWithTags.text.isEmpty())
)).done([=](const MTPBool &result, const MTP::Response &response) {
const auto requestId = response.requestId;
history->finishSavingCloudDraft(
@ -2261,7 +2262,7 @@ void ApiWrap::gotStickerSet(
}
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
if (page->pendingTill <= 0) {
if (page->failed || !page->pendingTill) {
return;
}
_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();) {
if (i->second == req) {
if (i->first->pendingTill > 0) {
i->first->pendingTill = -1;
i->first->pendingTill = 0;
i->first->failed = 1;
_session->data().notifyWebPageUpdateDelayed(i->first);
}
i = _webPagesPending.erase(i);
@ -3612,9 +3614,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true;
sendAction(action);
const auto replyToId = action.replyTo.msgId;
const auto replyTo = replyToId
? peer->owner().message(peer, replyToId)
const auto replyTo = action.replyTo.messageId
? peer->owner().message(action.replyTo.messageId)
: nullptr;
const auto topicRootId = replyTo
? replyTo->topicRootId()
@ -3642,7 +3643,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
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(
peer->id,
_session->data().nextLocalMessageId());
@ -3656,26 +3663,52 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPstring msgText(MTP_string(sending.text));
auto flags = NewMessageFlags(peer);
auto sendFlags = MTPmessages_SendMessage::Flags(0);
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
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);
MTPMessageMedia media = MTP_messageMediaEmpty();
if (message.webPageId == CancelledWebPageId) {
if (ignoreWebPage) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
} else if (message.webPageId) {
auto page = _session->data().webpage(message.webPageId);
} else if (exactWebPage) {
using PageFlag = MTPDmessageMediaWebPage::Flag;
using PendingFlag = MTPDwebPagePending::Flag;
const auto &fields = message.webPage;
const auto page = _session->data().webpage(fields.id);
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_long(page->id),
MTP_flags(PendingFlag::f_url),
MTP_long(fields.id),
MTP_string(fields.url),
MTP_int(page->pendingTill)));
}
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
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) {
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto sentEntities = Api::EntitiesToMTP(
_session,
@ -3683,11 +3716,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
Api::ConvertOption::SkipLocal);
if (!sentEntities.v.isEmpty()) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
const auto clearCloudDraft = action.clearDraft;
const auto topicRootId = action.replyTo.topicRootId;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
history->clearCloudDraft(topicRootId);
history->startSavingCloudDraft(topicRootId);
}
@ -3699,6 +3734,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
: _session->userPeerId();
if (sendAs) {
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
}
const auto messagePostAuthor = peer->isBroadcast()
? _session->user()->name()
@ -3706,6 +3742,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
const auto viaBotId = UserId();
lastMessage = history->addNewLocalMessage(
@ -3719,27 +3756,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sending,
media,
HistoryMessageMarkupData());
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())
), [=](const MTPUpdates &result, const MTP::Response &response) {
const auto done = [=](
const MTPUpdates &result,
const MTP::Response &response) {
if (clearCloudDraft) {
history->finishSavingCloudDraft(
topicRootId,
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) {
lastMessage->destroy();
} else {
@ -3750,7 +3778,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
topicRootId,
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);
@ -3815,7 +3880,7 @@ void ApiWrap::sendInlineResult(
? (*localMessageId)
: _session->data().nextLocalMessageId());
const auto randomId = base::RandomValue<uint64>();
const auto topicRootId = action.replyTo.msgId
const auto topicRootId = action.replyTo.messageId
? action.replyTo.topicRootId
: 0;

View file

@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
, _controller(controller)
, _forPeer(args.forPeer)
, _fromMessageId(args.fromMessageId)
, _chatStyle(std::make_unique<Ui::ChatStyle>())
, _chatStyle(std::make_unique<Ui::ChatStyle>(
controller->session().colorIndicesValue()))
, _serviceHistory(_controller->session().data().history(
PeerData::kServiceNotificationsId))
, _service(nullptr)
@ -434,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons();
addButton(_forPeer
? tr::lng_background_apply_button()
: tr::lng_background_apply(), [=] { apply(); });
: tr::lng_settings_apply(), [=] { apply(); });
addButton(tr::lng_cancel(), [=] { closeBox(); });
if (!_forPeer && _paper.hasShareUrl()) {
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 "apiwrap.h"
#include "api/api_premium.h"
#include "api/api_premium_option.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.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_session.h"
#include "data/data_subscription_option.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "settings/settings_premium.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
#include "ui/boxes/boost_box.h" // StartFireworks.
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.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 "styles/style_boxes.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_premium.h"
#include <QtGui/QGuiApplication>
namespace {
constexpr auto kDiscountDivider = 5.;
@ -225,6 +237,131 @@ void GiftBox(
}, 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
GiftPremiumValidator::GiftPremiumValidator(
@ -263,3 +400,405 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
_requestId = 0;
}).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;
namespace Api {
struct GiftCode;
} // namespace Api
namespace Data {
struct Giveaway;
} // namespace Data
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
@ -29,3 +41,20 @@ private:
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(
not_null<PeerData*> peer,
anim::type animated) {
const auto respect = _controller->respectSavedMessagesChat();
const auto respect = !_controller->savedMessagesChatStatus().isEmpty();
const auto text = (respect && peer->isSelf())
? tr::lng_saved_short(tr::now)
: (respect && peer->isRepliesChat())
@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() {
_statusType = StatusType::LastSeen;
_statusValidTill = 0;
if (auto user = peer()->asUser()) {
if (_isSavedMessagesChat) {
setStatusText(tr::lng_saved_forward_here(tr::now));
if (!_savedMessagesStatus.isEmpty()) {
setStatusText(_savedMessagesStatus);
} else {
auto time = base::unixtime::now();
setStatusText(Data::OnlineText(user, time));
@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
if (!_initialized) {
return;
}
const auto text = _isSavedMessagesChat
const auto text = !_savedMessagesStatus.isEmpty()
? tr::lng_saved_messages(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
@ -683,7 +683,7 @@ QString PeerListRow::generateName() {
}
QString PeerListRow::generateShortName() {
return _isSavedMessagesChat
return !_savedMessagesStatus.isEmpty()
? tr::lng_saved_short(tr::now)
: _isRepliesMessagesChat
? tr::lng_replies_messages(tr::now)
@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
bool forceRound) {
const auto saved = _isSavedMessagesChat;
const auto saved = !_savedMessagesStatus.isEmpty();
const auto replies = _isRepliesMessagesChat;
const auto peer = this->peer();
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth(
int availableWidth,
int outerWidth,
bool selected) {
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) {
if (special()
|| !_savedMessagesStatus.isEmpty()
|| _isRepliesMessagesChat) {
return 0;
}
return _bagde.drawGetWidth(
@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic(
auto iconBorderPen = st.checkbox.check.border->p;
iconBorderPen.setWidth(st.checkbox.selectWidth);
if (_isSavedMessagesChat) {
if (!_savedMessagesStatus.isEmpty()) {
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
} else if (_isRepliesMessagesChat) {
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) {
if (_controller->respectSavedMessagesChat() && !row->special()) {
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
if (!savedMessagesStatus.isEmpty() && !row->special()) {
if (row->peer()->isSelf()) {
row->setIsSavedMessagesChat(true);
row->setSavedMessagesChatStatus(savedMessagesStatus);
} else if (row->peer()->isRepliesChat()) {
row->setIsRepliesMessagesChat(true);
}

View file

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

View file

@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() {
return count;
};
auto added = 0;
if (respectSavedMessagesChat()) {
if (!savedMessagesChatStatus().isEmpty()) {
if (appendRow(session().data().history(session().user()))) {
++added;
}
@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() {
const auto history = static_cast<const Row&>(a).history();
return history->inChatList();
});
if (respectSavedMessagesChat()) {
if (!savedMessagesChatStatus().isEmpty()) {
delegate()->peerListPartitionRows([](const PeerListRow &a) {
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(
not_null<History*> history) -> std::unique_ptr<Row> {
const auto peer = history->peer;

View file

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

View file

@ -45,8 +45,8 @@ public:
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
bool respectSavedMessagesChat() const override {
return true;
QString savedMessagesChatStatus() const override {
return tr::lng_saved_forward_here(tr::now);
}
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 "boxes/add_contact_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_peer_color_box.h"
#include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_peer_type_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_peer_requests_box.h"
#include "boxes/peers/edit_peer_reactions.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/stickers_box.h"
#include "boxes/username_box.h"
#include "ui/boxes/single_choice_box.h"
@ -302,6 +303,7 @@ private:
void fillLinkedChatButton();
//void fillInviteLinkButton();
void fillForumButton();
void fillColorIndexButton();
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() {
_controls.forumToggle->setToggleLocked(locked);
}
void Controller::fillColorIndexButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto show = _navigation->uiShow();
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
}
void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr);
@ -1024,74 +1033,42 @@ void Controller::fillManageSection() {
return;
}
const auto canEditType = [&] {
return isChannel
? channel->amCreator()
: chat->amCreator();
}();
const auto canEditSignatures = [&] {
return isChannel
? (channel->canEditSignatures() && !channel->isMegagroup())
: false;
}();
const auto canEditPreHistoryHidden = [&] {
return isChannel
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
}();
const auto canEditType = isChannel
? channel->amCreator()
: chat->amCreator();
const auto canEditSignatures = isChannel
&& channel->canEditSignatures()
&& !channel->isMegagroup();
const auto canEditPreHistoryHidden = isChannel
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
const auto canEditForum = isChannel
? (channel->isMegagroup() && channel->amCreator())
: chat->amCreator();
const auto canEditPermissions = [&] {
return isChannel
? channel->canEditPermissions()
: chat->canEditPermissions();
}();
const auto canEditInviteLinks = [&] {
return isChannel
? channel->canHaveInviteLink()
: chat->canHaveInviteLink();
}();
const auto canViewAdmins = [&] {
return isChannel
? channel->canViewAdmins()
: chat->amIn();
}();
const auto canViewMembers = [&] {
return isChannel
? channel->canViewMembers()
: chat->amIn();
}();
const auto canViewKicked = [&] {
return isChannel
? (channel->isBroadcast() || channel->isGigagroup())
: 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());
}();
const auto canEditPermissions = isChannel
? channel->canEditPermissions()
: chat->canEditPermissions();
const auto canEditInviteLinks = isChannel
? channel->canHaveInviteLink()
: chat->canHaveInviteLink();
const auto canViewAdmins = isChannel
? channel->canViewAdmins()
: chat->amIn();
const auto canViewMembers = isChannel
? channel->canViewMembers()
: chat->amIn();
const auto canViewKicked = isChannel
&& (channel->isBroadcast() || channel->isGigagroup());
const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator());
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel
&& !channel->isMegagroup()
&& channel->canEditInformation();
const auto canViewOrEditLinkedChat = isChannel
&& (channel->linkedChat()
|| (channel->isBroadcast() && channel->canEditInformation()));
AddSkip(_controls.buttonsLayout, 0);
@ -1109,11 +1086,15 @@ void Controller::fillManageSection() {
if (canEditForum) {
fillForumButton();
}
if (canEditColorIndex) {
fillColorIndexButton();
}
if (canEditSignatures) {
fillSignaturesButton();
}
if (canEditPreHistoryHidden
|| canEditForum
|| canEditColorIndex
|| canEditSignatures
//|| canEditInviteLinks
|| canViewOrEditLinkedChat

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
.peer = peer,
.command = _cmd,
.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_chat_filters.h"
#include "api/api_chat_invite.h"
#include "api/api_premium.h"
#include "base/qthelp_regex.h"
#include "base/qthelp_url.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 "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
@ -348,6 +350,11 @@ bool ResolveUsernameOrPhone(
const auto domainParam = params.value(u"domain"_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.
const auto webChannelPreviewLink = (domainParam == u"s"_q)
&& !appnameParam.isEmpty();
@ -637,6 +644,17 @@ bool OpenExternalLink(
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(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@ -978,6 +996,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^url:(.+)$"_q,
OpenExternalLink
},
{
u"^copy:(.+)$"_q,
CopyPeerId
}
};
return Result;
}
@ -1078,7 +1100,7 @@ QString TryConvertUrlToLocal(QString url) {
"("
"/?\\?|"
"/?$|"
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|"
"/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
"/\\d+/?(\\?|$)|"
"/s/\\d+/?(\\?|$)|"
"/\\d+/\\d+/?(\\?|$)"
@ -1103,7 +1125,7 @@ QString TryConvertUrlToLocal(QString url) {
added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
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);
}
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_silently"_q , Command::SendSilentMessage },
{ 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);
}
QString UiIntegration::phraseFormattingBlockquote() {
return tr::lng_menu_formatting_blockquote(tr::now);
}
QString UiIntegration::phraseFormattingMonospace() {
return tr::lng_menu_formatting_monospace(tr::now);
}
@ -404,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() {
return tr::lng_bot_allow_write_confirm(tr::now);
}
QString UiIntegration::phraseQuoteHeaderCopy() {
return tr::lng_code_block_header_copy(tr::now);
}
bool OpenGLLastCheckFailed() {
return QFile::exists(OpenGLCheckFilePath());
}

View file

@ -77,6 +77,7 @@ public:
QString phraseFormattingItalic() override;
QString phraseFormattingUnderline() override;
QString phraseFormattingStrikeOut() override;
QString phraseFormattingBlockquote() override;
QString phraseFormattingMonospace() override;
QString phraseFormattingSpoiler() override;
QString phraseButtonOk() override;
@ -91,6 +92,7 @@ public:
QString phraseBotAllowWrite() override;
QString phraseBotAllowWriteTitle() 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 AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4010004;
constexpr auto AppVersionStr = "4.10.4";
constexpr auto AppBetaVersion = true;
constexpr auto AppVersion = 4011001;
constexpr auto AppVersionStr = "4.11.1";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -254,7 +254,7 @@ const std::array<Info, 231> FallbackList = { {
CountriesInstance::CountriesInstance() {
}
const std::vector<Info> &CountriesInstance::list() {
const std::vector<Info> &CountriesInstance::list() const {
if (_list.empty()) {
_list = (FallbackList | ranges::to_vector);
}
@ -268,7 +268,7 @@ void CountriesInstance::setList(std::vector<Info> &&infos) {
_updated.fire({});
}
const CountriesInstance::Map &CountriesInstance::byCode() {
const CountriesInstance::Map &CountriesInstance::byCode() const {
if (_byCode.empty()) {
_byCode.reserve(list().size());
for (const auto &entry : list()) {
@ -280,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() {
return _byCode;
}
const CountriesInstance::Map &CountriesInstance::byISO2() {
const CountriesInstance::Map &CountriesInstance::byISO2() const {
if (_byISO2.empty()) {
_byISO2.reserve(list().size());
for (const auto &entry : list()) {
@ -290,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() {
return _byISO2;
}
QString CountriesInstance::validPhoneCode(QString fullCode) {
QString CountriesInstance::validPhoneCode(QString fullCode) const {
const auto &listByCode = byCode();
while (fullCode.length()) {
const auto i = listByCode.constFind(fullCode);
@ -302,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) {
return QString();
}
QString CountriesInstance::countryNameByISO2(const QString &iso) {
QString CountriesInstance::countryNameByISO2(const QString &iso) const {
const auto &listByISO2 = byISO2();
const auto i = listByISO2.constFind(iso);
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 code = validPhoneCode(phone);
const auto i = listByCode.find(code);
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.
if (args.phone.isEmpty()) {
return FormatResult();

View file

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

View file

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

View file

@ -71,41 +71,43 @@ struct PeerUpdate {
FullInfo = (1ULL << 11),
Usernames = (1ULL << 12),
TranslationDisabled = (1ULL << 13),
Color = (1ULL << 14),
BackgroundEmoji = (1ULL << 15),
// For users
CanShareContact = (1ULL << 14),
IsContact = (1ULL << 15),
PhoneNumber = (1ULL << 16),
OnlineStatus = (1ULL << 17),
BotCommands = (1ULL << 18),
BotCanBeInvited = (1ULL << 19),
BotStartToken = (1ULL << 20),
CommonChats = (1ULL << 21),
HasCalls = (1ULL << 22),
SupportInfo = (1ULL << 23),
IsBot = (1ULL << 24),
EmojiStatus = (1ULL << 25),
StoriesState = (1ULL << 26),
CanShareContact = (1ULL << 16),
IsContact = (1ULL << 17),
PhoneNumber = (1ULL << 18),
OnlineStatus = (1ULL << 19),
BotCommands = (1ULL << 20),
BotCanBeInvited = (1ULL << 21),
BotStartToken = (1ULL << 22),
CommonChats = (1ULL << 23),
HasCalls = (1ULL << 24),
SupportInfo = (1ULL << 25),
IsBot = (1ULL << 26),
EmojiStatus = (1ULL << 27),
StoriesState = (1ULL << 28),
// For chats and channels
InviteLinks = (1ULL << 27),
Members = (1ULL << 28),
Admins = (1ULL << 29),
BannedUsers = (1ULL << 30),
Rights = (1ULL << 31),
PendingRequests = (1ULL << 32),
Reactions = (1ULL << 33),
InviteLinks = (1ULL << 29),
Members = (1ULL << 30),
Admins = (1ULL << 31),
BannedUsers = (1ULL << 32),
Rights = (1ULL << 33),
PendingRequests = (1ULL << 34),
Reactions = (1ULL << 35),
// For channels
ChannelAmIn = (1ULL << 34),
StickersSet = (1ULL << 35),
ChannelLinkedChat = (1ULL << 36),
ChannelLocation = (1ULL << 37),
Slowmode = (1ULL << 38),
GroupCall = (1ULL << 39),
ChannelAmIn = (1ULL << 36),
StickersSet = (1ULL << 37),
ChannelLinkedChat = (1ULL << 38),
ChannelLocation = (1ULL << 39),
Slowmode = (1ULL << 40),
GroupCall = (1ULL << 41),
// For iteration
LastUsedBit = (1ULL << 39),
LastUsedBit = (1ULL << 41),
};
using Flags = base::flags<Flag>;
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_archived() ? Flag::NoArchived : Flag(0));
auto &&to_histories = ranges::views::transform([&](
const MTPInputPeer &data) {
const auto peer = data.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;
});
const MTPInputPeer &input) {
const auto peer = Data::PeerFromInputMTP(owner, input);
return peer ? owner->history(peer).get() : nullptr;
}) | ranges::views::filter([](History *history) {
return history != nullptr;

View file

@ -12,39 +12,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/message_field.h"
#include "history/history.h"
#include "history/history_widget.h"
#include "history/history_item_components.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "mainwidget.h"
#include "storage/localstorage.h"
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(
const TextWithTags &textWithTags,
MsgId msgId,
MsgId topicRootId,
FullReplyTo reply,
const MessageCursor &cursor,
PreviewState previewState,
WebPageDraft webpage,
mtpRequestId saveRequestId)
: textWithTags(textWithTags)
, msgId(msgId)
, topicRootId(topicRootId)
, reply(std::move(reply))
, cursor(cursor)
, previewState(previewState)
, webpage(webpage)
, saveRequestId(saveRequestId) {
}
Draft::Draft(
not_null<const Ui::InputField*> field,
MsgId msgId,
MsgId topicRootId,
PreviewState previewState,
FullReplyTo reply,
WebPageDraft webpage,
mtpRequestId saveRequestId)
: textWithTags(field->getTextWithTags())
, msgId(msgId)
, topicRootId(topicRootId)
, reply(std::move(reply))
, cursor(field)
, previewState(previewState) {
, webpage(webpage) {
}
void ApplyPeerCloudDraft(
@ -64,15 +82,32 @@ void ApplyPeerCloudDraft(
session,
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>(
textWithTags,
replyTo,
topicRootId,
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
(draft.is_no_webpage()
? Data::PreviewState::Cancelled
: Data::PreviewState::Allowed));
std::move(webpage));
cloudDraft->date = date;
history->setCloudDraft(std::move(cloudDraft));

View file

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

View file

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

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_histories.h"
#include "api/api_text_entities.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@ -39,8 +40,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
} // namespace
MTPInputReplyTo ReplyToForMTP(
not_null<Session*> owner,
not_null<History*> history,
FullReplyTo replyTo) {
const auto owner = &history->owner();
if (replyTo.storyId) {
if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
if (const auto user = peer->asUser()) {
@ -49,18 +51,45 @@ MTPInputReplyTo ReplyToForMTP(
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;
return MTP_inputReplyToMessage(
(replyTo.topicRootId
? MTP_flags(Flag::f_top_msg_id)
: MTP_flags(0)),
MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId),
MTP_int(replyTo.topicRootId));
MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
| (external ? Flag::f_reply_to_peer_id : Flag())
| (replyTo.quote.text.isEmpty()
? Flag()
: 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();
}
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)
: _owner(owner)
, _readRequestsTimer([=] { sendReadRequests(); }) {
@ -930,7 +959,7 @@ int Histories::sendPreparedMessage(
not_null<History*> history,
FullReplyTo replyTo,
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 MTP::Error&, const MTP::Response&)> fail) {
if (isCreatingTopic(history, replyTo.topicRootId)) {
@ -945,7 +974,7 @@ int Histories::sendPreparedMessage(
}
i->second.push_back({
.randomId = randomId,
.replyTo = replyTo.msgId,
.replyTo = replyTo.messageId,
.message = std::move(message),
.done = std::move(done),
.fail = std::move(fail),
@ -955,11 +984,12 @@ int Histories::sendPreparedMessage(
return id;
}
const auto realReplyTo = FullReplyTo{
.msgId = convertTopicReplyToId(history, replyTo.msgId),
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
.messageId = convertTopicReplyToId(history, replyTo.messageId),
.quote = replyTo.quote,
.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;
return sendRequest(history, type, [=](Fn<void()> finish) {
const auto session = &_owner->session();
@ -1003,7 +1033,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
sendPreparedMessage(
history,
FullReplyTo{
.msgId = entry.replyTo,
.messageId = entry.replyTo,
.topicRootId = realRoot,
},
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(
not_null<History*> history,
MsgId replyToId) const {

View file

@ -25,10 +25,14 @@ namespace Data {
class Session;
class Folder;
struct WebPageDraft;
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
not_null<Session*> owner,
not_null<History*> history,
FullReplyTo replyTo);
[[nodiscard]] MTPInputMedia WebPageForMTP(
const Data::WebPageDraft &draft,
bool required = false);
class Histories final {
public:
@ -108,7 +112,7 @@ public:
not_null<History*> history,
FullReplyTo replyTo,
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 MTP::Error&, const MTP::Response&)> fail);
@ -116,14 +120,17 @@ public:
};
template <typename RequestType, typename ...Args>
static auto PrepareMessage(const Args &...args)
-> Fn<Histories::PreparedMessage(not_null<Session*>, FullReplyTo)> {
return [=](not_null<Session*> owner, FullReplyTo replyTo)
-> Fn<Histories::PreparedMessage(not_null<History*>, FullReplyTo)> {
return [=](not_null<History*> history, FullReplyTo replyTo)
-> RequestType {
return { ReplaceReplyIds(owner, args, replyTo)... };
return { ReplaceReplyIds(history, args, replyTo)... };
};
}
void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
[[nodiscard]] FullMsgId convertTopicReplyToId(
not_null<History*> history,
FullMsgId replyToId) const;
[[nodiscard]] MsgId convertTopicReplyToId(
not_null<History*> history,
MsgId replyToId) const;
@ -152,8 +159,8 @@ private:
};
struct DelayedByTopicMessage {
uint64 randomId = 0;
MsgId replyTo = 0;
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message;
FullMsgId replyTo;
Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message;
Fn<void(const MTPUpdates&, const MTP::Response&)> done;
Fn<void(const MTP::Error&, const MTP::Response&)> fail;
int requestId = 0;
@ -169,11 +176,11 @@ private:
template <typename Arg>
static auto ReplaceReplyIds(
not_null<Session*> owner,
not_null<History*> history,
Arg arg,
FullReplyTo replyTo) {
if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
return ReplyToForMTP(owner, replyTo);
return ReplyToForMTP(history, replyTo);
} else {
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_location.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_call.h"
#include "history/view/media/history_view_web_page.h"
@ -361,6 +362,28 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
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) {
}
@ -380,6 +403,10 @@ WebPageData *Media::webpage() const {
return nullptr;
}
MediaWebPageFlags Media::webpageFlags() const {
return {};
}
const SharedContact *Media::sharedContact() const {
return nullptr;
}
@ -420,6 +447,10 @@ bool Media::storyMention() const {
return false;
}
const Giveaway *Media::giveaway() const {
return nullptr;
}
bool Media::uploading() const {
return false;
}
@ -1406,9 +1437,11 @@ QString MediaCall::Text(
MediaWebPage::MediaWebPage(
not_null<HistoryItem*> parent,
not_null<WebPageData*> page)
not_null<WebPageData*> page,
MediaWebPageFlags flags)
: Media(parent)
, _page(page) {
, _page(page)
, _flags(flags) {
parent->history()->owner().registerWebPageItem(_page, parent);
}
@ -1417,7 +1450,7 @@ MediaWebPage::~MediaWebPage() {
}
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 {
@ -1432,6 +1465,10 @@ WebPageData *MediaWebPage::webpage() const {
return _page;
}
MediaWebPageFlags MediaWebPage::webpageFlags() const {
return _flags;
}
bool MediaWebPage::hasReplyPreview() const {
if (const auto document = MediaWebPage::document()) {
return document->hasThumbnail()
@ -1462,10 +1499,13 @@ bool MediaWebPage::replyPreviewLoaded() const {
}
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
return { .text = options.translated
auto text = options.translated
? parent()->translatedText()
: parent()->originalText()
};
: parent()->originalText();
if (text.empty()) {
text = Ui::Text::Colorized(_page->url);
}
return { .text = text };
}
TextWithEntities MediaWebPage::notificationText() const {
@ -1496,7 +1536,7 @@ std::unique_ptr<HistoryView::Media> MediaWebPage::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::WebPage>(message, _page);
return std::make_unique<HistoryView::WebPage>(message, _page, _flags);
}
MediaGame::MediaGame(
@ -1887,21 +1927,28 @@ MediaGiftBox::MediaGiftBox(
not_null<HistoryItem*> parent,
not_null<PeerData*> from,
int months)
: MediaGiftBox(parent, from, GiftCode{ .months = months }) {
}
MediaGiftBox::MediaGiftBox(
not_null<HistoryItem*> parent,
not_null<PeerData*> from,
GiftCode data)
: Media(parent)
, _from(from)
, _months(months) {
, _data(std::move(data)) {
}
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 {
return _from;
}
int MediaGiftBox::months() const {
return _months;
const GiftCode &MediaGiftBox::data() const {
return _data;
}
TextWithEntities MediaGiftBox::notificationText() const {
@ -1933,14 +1980,6 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
std::make_unique<HistoryView::PremiumGift>(message, this));
}
bool MediaGiftBox::activated() const {
return _activated;
}
void MediaGiftBox::setActivated(bool activated) {
_activated = activated;
}
MediaWallPaper::MediaWallPaper(
not_null<HistoryItem*> parent,
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

View file

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

View file

@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/qt/qt_compare.h"
#include "data/data_peer_id.h"
#include "ui/text/text_entity.h"
struct MsgId {
constexpr MsgId() noexcept = default;
@ -67,21 +69,6 @@ struct FullStoryId {
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 ClientMsgIds = (1LL << 31);
constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds);
@ -169,6 +156,22 @@ struct 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 {
FullMsgId itemId;
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/main_window.h" // Window::LogoNoMargin.
#include "ui/image/image.h"
#include "ui/chat/chat_style.h"
#include "ui/empty_userpic.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
@ -59,8 +60,8 @@ using UpdateFlag = Data::PeerUpdate::Flag;
namespace Data {
int PeerColorIndex(PeerId peerId) {
return Ui::EmptyUserpic::ColorIndex(peerId.value & PeerId::kChatTypeMask);
uint8 DecideColorIndex(PeerId peerId) {
return Ui::DecideColorIndex(peerId.value & PeerId::kChatTypeMask);
}
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
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)
: id(id)
, _owner(owner) {
, _owner(owner)
, _colorIndex(Data::DecideColorIndex(id)) {
}
Data::Session &PeerData::owner() const {
@ -230,7 +266,7 @@ not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
if (!_userpicEmpty) {
const auto user = asUser();
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(id)),
Ui::EmptyUserpic::UserpicColor(colorIndex()),
((user && user->isInaccessible())
? Ui::EmptyUserpic::InaccessibleName()
: name()));
@ -251,7 +287,7 @@ void PeerData::setUserpic(
const ImageLocation &location,
bool hasVideo) {
_userpicPhotoId = photoId;
_userpicHasVideo = hasVideo;
_userpicHasVideo = hasVideo ? 1 : 0;
_userpic.set(&session(), ImageWithLocation{ .location = location });
}
@ -389,6 +425,22 @@ QImage PeerData::generateUserpicImage(
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 {
return Data::FileOriginPeerPhoto(id);
}
@ -428,7 +480,7 @@ void PeerData::setUserpicChecked(
bool hasVideo) {
if (_userpicPhotoId != photoId
|| _userpic.location() != location
|| _userpicHasVideo != hasVideo) {
|| _userpicHasVideo != (hasVideo ? 1 : 0)) {
const auto known = !userpicPhotoUnknown();
setUserpic(photoId, location, hasVideo);
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() {
_nameWords.clear();
_nameFirstLetters.clear();
@ -818,6 +884,36 @@ QString PeerData::userName() const {
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 {
if (const auto user = asUser()) {
return (user->flags() & UserDataFlag::Self);

View file

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

View file

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

View file

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

View file

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

View file

@ -246,65 +246,78 @@ enum class MessageFlag : uint64 {
MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1ULL << 16),
NoForwards = (1ULL << 17),
InvertMedia = (1ULL << 18),
// Needs to return back to inline mode.
HasSwitchInlineButton = (1ULL << 18),
HasSwitchInlineButton = (1ULL << 19),
// For "shared links" indexing.
HasTextLinks = (1ULL << 19),
HasTextLinks = (1ULL << 20),
// Group / channel create or migrate service message.
IsGroupEssential = (1ULL << 20),
IsGroupEssential = (1ULL << 21),
// Edited media is generated on the client
// and should not update media from server.
IsLocalUpdateMedia = (1ULL << 21),
IsLocalUpdateMedia = (1ULL << 22),
// 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.
ClientSideUnread = (1ULL << 23),
ClientSideUnread = (1ULL << 24),
// In a supergroup.
HasAdminBadge = (1ULL << 24),
HasAdminBadge = (1ULL << 25),
// Outgoing message that is being sent.
BeingSent = (1ULL << 25),
BeingSent = (1ULL << 26),
// 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.
SpecialOnlyEmoji = (1ULL << 27),
SpecialOnlyEmoji = (1ULL << 28),
// Message existing in the message history.
HistoryEntry = (1ULL << 28),
HistoryEntry = (1ULL << 29),
// Local message, not existing on the server.
Local = (1ULL << 29),
Local = (1ULL << 30),
// Fake message for some UI element.
FakeHistoryItem = (1ULL << 30),
FakeHistoryItem = (1ULL << 31),
// Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1ULL << 31),
IsContactSignUp = (1ULL << 32),
// Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32),
CustomEmojiRepainting = (1ULL << 33),
// Profile photo suggestion, views have special media type.
IsUserpicSuggestion = (1ULL << 33),
IsUserpicSuggestion = (1ULL << 34),
OnlyEmojiAndSpaces = (1ULL << 34),
OnlyEmojiAndSpacesSet = (1ULL << 35),
OnlyEmojiAndSpaces = (1ULL << 35),
OnlyEmojiAndSpacesSet = (1ULL << 36),
// 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; }
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_channel.h"
#include "data/data_document.h"
#include "lang/lang_keys.h"
#include "ui/image/image.h"
#include "ui/text/text_entity.h"
@ -224,9 +225,10 @@ bool WebPageData::applyChanges(
WebPageCollage &&newCollage,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill) {
if (newPendingTill != 0
&& (!url.isEmpty() || pendingTill < 0)
&& (!url.isEmpty() || failed)
&& (!pendingTill
|| pendingTill == newPendingTill
|| newPendingTill < -1)) {
@ -252,6 +254,15 @@ bool WebPageData::applyChanges(
}
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
&& url == resultUrl
@ -265,6 +276,7 @@ bool WebPageData::applyChanges(
&& collage.items == newCollage.items
&& duration == newDuration
&& author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
&& pendingTill == newPendingTill) {
return false;
}
@ -272,6 +284,7 @@ bool WebPageData::applyChanges(
_owner->session().api().clearWebPageRequest(this);
}
type = newType;
hasLargeMedia = newHasLargeMedia ? 1 : 0;
url = resultUrl;
displayUrl = resultDisplayUrl;
siteName = resultSiteName;
@ -343,3 +356,38 @@ void WebPageData::ApplyChanges(
}
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
#include "base/flags.h"
#include "data/data_photo.h"
#include "data/data_document.h"
@ -16,7 +17,9 @@ namespace Data {
class Session;
} // namespace Data
enum class WebPageType {
enum class WebPageType : uint8 {
None,
Message,
Group,
@ -44,8 +47,7 @@ enum class WebPageType {
VoiceChat,
Livestream,
};
WebPageType ParseWebPageType(const MTPDwebPage &type);
[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type);
struct WebPageCollage {
using Item = std::variant<PhotoData*, DocumentData*>;
@ -78,6 +80,7 @@ struct WebPageData {
WebPageCollage &&newCollage,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
int newPendingTill);
static void ApplyChanges(
@ -85,21 +88,26 @@ struct WebPageData {
ChannelData *channel,
const MTPmessages_Messages &result);
WebPageId id = 0;
WebPageType type = WebPageType::Article;
[[nodiscard]] QString displayedSiteName() const;
[[nodiscard]] bool computeDefaultSmallMedia() const;
const WebPageId id = 0;
WebPageType type = WebPageType::None;
QString url;
QString displayUrl;
QString siteName;
QString title;
TextWithEntities description;
FullStoryId storyId;
int duration = 0;
QString author;
PhotoData *photo = nullptr;
DocumentData *document = nullptr;
WebPageCollage collage;
int pendingTill = 0;
int version = 0;
int duration = 0;
TimeId pendingTill = 0;
uint32 version : 30 = 0;
uint32 hasLargeMedia : 1 = 0;
uint32 failed : 1 = 0;
private:
void replaceDocumentGoodThumbnail();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,7 @@ constexpr auto kPersonalUserpicSize = 90;
constexpr auto kEntryUserpicSize = 48;
constexpr auto kServiceMessagePhotoSize = 60;
constexpr auto kHistoryUserpicSize = 42;
constexpr auto kSavedMessagesColorIndex = 3;
constexpr auto kSavedMessagesColorIndex = uint8(3);
constexpr auto kJoinWithinSeconds = 900;
constexpr auto kPhotoMaxWidth = 520;
constexpr auto kPhotoMaxHeight = 520;
@ -351,7 +351,7 @@ QByteArray FormatTimeText(TimeId date) {
namespace details {
struct UserpicData {
int colorIndex = 0;
uint8 colorIndex = 0;
int pixelSize = 0;
QString imageLink;
QString largeLink;
@ -611,6 +611,9 @@ private:
const Data::Photo &data,
const QString &basePath);
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
[[nodiscard]] QByteArray pushGiveaway(
const PeersMap &peers,
const Data::Giveaway &data);
File _file;
QByteArray _composedStart;
@ -988,7 +991,7 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
result.append(popTag());
if (photo) {
auto userpic = UserpicData();
userpic.colorIndex = Data::PeerColorIndex(dialog.peerId);
userpic.colorIndex = dialog.colorIndex;
userpic.firstName = dialog.name;
userpic.lastName = dialog.lastName;
userpic.pixelSize = kServiceMessagePhotoSize;
@ -1276,6 +1279,24 @@ auto HtmlWriter::Wrap::pushMessage(
+ " set "
+ wrapReplyToLink("the same background")
+ " 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(); });
if (!serviceText.isEmpty()) {
@ -1459,8 +1480,9 @@ QByteArray HtmlWriter::Wrap::pushMedia(
if (!data.classes.isEmpty()) {
return pushGenericMedia(data);
}
using namespace Data;
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);
if (document->isSticker) {
return pushStickerMedia(*document, basePath);
@ -1470,11 +1492,13 @@ QByteArray HtmlWriter::Wrap::pushMedia(
return pushVideoFileMedia(*document, basePath);
}
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);
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);
} else if (const auto giveaway = std::get_if<Giveaway>(&content)) {
return pushGiveaway(peers, *giveaway);
}
Assert(v::is_null(content));
return QByteArray();
@ -1796,6 +1820,52 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) {
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(
const Data::Message &message,
const QString &basePath,
@ -1954,6 +2024,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
result.description = data.description;
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
}, [](const Poll &data) {
}, [](const Giveaway &data) {
}, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message.");
}, [](v::null_t) {});
@ -2104,7 +2175,7 @@ Result HtmlWriter::start(
Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_summary != nullptr);
_selfColorIndex = Data::PeerColorIndex(data.user.info.userId);
_selfColorIndex = data.user.info.colorIndex;
if (_settings.types & Settings::Type::Userpics) {
_delayedPersonalInfo = std::make_unique<Data::PersonalInfo>(data);
return Result::Success();

View file

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

View file

@ -287,11 +287,12 @@ QByteArray SerializeMessage(
}
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));
} else if constexpr (std::is_same_v<
std::decay_t<decltype(value)>,
PeerId>) {
} else if constexpr (std::is_same_v<V, PeerId>) {
if (const auto chat = peerToChat(value)) {
pushBare(
key,
@ -592,6 +593,17 @@ QByteArray SerializeMessage(
pushAction("requested_peer");
push("button_id", data.buttonId);
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) {
pushActor();
pushAction("set_chat_wallpaper");
@ -738,6 +750,22 @@ QByteArray SerializeMessage(
{ "total_voters", NumberToString(data.totalVotes) },
{ "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) {
Unexpected("Unsupported message.");
}, [](v::null_t) {});

View file

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

View file

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

View file

@ -767,6 +767,8 @@ void GenerateItems(
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor;
using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji;
const auto session = &history->session();
const auto id = event.vid().v;
@ -1815,6 +1817,54 @@ void GenerateItems(
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(
createChangeTitle,
createChangeAbout,
@ -1858,7 +1908,9 @@ void GenerateItems(
createEditTopic,
createDeleteTopic,
createPinTopic,
createToggleAntiSpam);
createToggleAntiSpam,
createChangeColor,
createChangeBackgroundEmoji);
}
} // namespace AdminLog

View file

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

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