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
|
@ -265,6 +265,8 @@ PRIVATE
|
||||||
boxes/peers/edit_participant_box.h
|
boxes/peers/edit_participant_box.h
|
||||||
boxes/peers/edit_participants_box.cpp
|
boxes/peers/edit_participants_box.cpp
|
||||||
boxes/peers/edit_participants_box.h
|
boxes/peers/edit_participants_box.h
|
||||||
|
boxes/peers/edit_peer_color_box.cpp
|
||||||
|
boxes/peers/edit_peer_color_box.h
|
||||||
boxes/peers/edit_peer_common.h
|
boxes/peers/edit_peer_common.h
|
||||||
boxes/peers/edit_peer_info_box.cpp
|
boxes/peers/edit_peer_info_box.cpp
|
||||||
boxes/peers/edit_peer_info_box.h
|
boxes/peers/edit_peer_info_box.h
|
||||||
|
@ -718,6 +720,8 @@ PRIVATE
|
||||||
history/view/controls/history_view_compose_controls.h
|
history/view/controls/history_view_compose_controls.h
|
||||||
history/view/controls/history_view_compose_search.cpp
|
history/view/controls/history_view_compose_search.cpp
|
||||||
history/view/controls/history_view_compose_search.h
|
history/view/controls/history_view_compose_search.h
|
||||||
|
history/view/controls/history_view_draft_options.cpp
|
||||||
|
history/view/controls/history_view_draft_options.h
|
||||||
history/view/controls/history_view_forward_panel.cpp
|
history/view/controls/history_view_forward_panel.cpp
|
||||||
history/view/controls/history_view_forward_panel.h
|
history/view/controls/history_view_forward_panel.h
|
||||||
history/view/controls/history_view_ttl_button.cpp
|
history/view/controls/history_view_ttl_button.cpp
|
||||||
|
@ -726,6 +730,8 @@ PRIVATE
|
||||||
history/view/controls/history_view_voice_record_bar.h
|
history/view/controls/history_view_voice_record_bar.h
|
||||||
history/view/controls/history_view_voice_record_button.cpp
|
history/view/controls/history_view_voice_record_button.cpp
|
||||||
history/view/controls/history_view_voice_record_button.h
|
history/view/controls/history_view_voice_record_button.h
|
||||||
|
history/view/controls/history_view_webpage_processor.cpp
|
||||||
|
history/view/controls/history_view_webpage_processor.h
|
||||||
history/view/media/history_view_call.cpp
|
history/view/media/history_view_call.cpp
|
||||||
history/view/media/history_view_call.h
|
history/view/media/history_view_call.h
|
||||||
history/view/media/history_view_contact.cpp
|
history/view/media/history_view_contact.cpp
|
||||||
|
@ -744,6 +750,8 @@ PRIVATE
|
||||||
history/view/media/history_view_game.h
|
history/view/media/history_view_game.h
|
||||||
history/view/media/history_view_gif.cpp
|
history/view/media/history_view_gif.cpp
|
||||||
history/view/media/history_view_gif.h
|
history/view/media/history_view_gif.h
|
||||||
|
history/view/media/history_view_giveaway.cpp
|
||||||
|
history/view/media/history_view_giveaway.h
|
||||||
history/view/media/history_view_invoice.cpp
|
history/view/media/history_view_invoice.cpp
|
||||||
history/view/media/history_view_invoice.h
|
history/view/media/history_view_invoice.h
|
||||||
history/view/media/history_view_large_emoji.cpp
|
history/view/media/history_view_large_emoji.cpp
|
||||||
|
@ -883,20 +891,6 @@ PRIVATE
|
||||||
history/history_view_highlight_manager.h
|
history/history_view_highlight_manager.h
|
||||||
history/history_widget.cpp
|
history/history_widget.cpp
|
||||||
history/history_widget.h
|
history/history_widget.h
|
||||||
info/info_content_widget.cpp
|
|
||||||
info/info_content_widget.h
|
|
||||||
info/info_controller.cpp
|
|
||||||
info/info_controller.h
|
|
||||||
info/info_layer_widget.cpp
|
|
||||||
info/info_layer_widget.h
|
|
||||||
info/info_memento.cpp
|
|
||||||
info/info_memento.h
|
|
||||||
info/info_section_widget.cpp
|
|
||||||
info/info_section_widget.h
|
|
||||||
info/info_top_bar.cpp
|
|
||||||
info/info_top_bar.h
|
|
||||||
info/info_wrap_widget.cpp
|
|
||||||
info/info_wrap_widget.h
|
|
||||||
info/boosts/info_boosts_inner_widget.cpp
|
info/boosts/info_boosts_inner_widget.cpp
|
||||||
info/boosts/info_boosts_inner_widget.h
|
info/boosts/info_boosts_inner_widget.h
|
||||||
info/boosts/info_boosts_widget.cpp
|
info/boosts/info_boosts_widget.cpp
|
||||||
|
@ -983,6 +977,20 @@ PRIVATE
|
||||||
info/userpic/info_userpic_emoji_builder_preview.h
|
info/userpic/info_userpic_emoji_builder_preview.h
|
||||||
info/userpic/info_userpic_emoji_builder_widget.cpp
|
info/userpic/info_userpic_emoji_builder_widget.cpp
|
||||||
info/userpic/info_userpic_emoji_builder_widget.h
|
info/userpic/info_userpic_emoji_builder_widget.h
|
||||||
|
info/info_content_widget.cpp
|
||||||
|
info/info_content_widget.h
|
||||||
|
info/info_controller.cpp
|
||||||
|
info/info_controller.h
|
||||||
|
info/info_layer_widget.cpp
|
||||||
|
info/info_layer_widget.h
|
||||||
|
info/info_memento.cpp
|
||||||
|
info/info_memento.h
|
||||||
|
info/info_section_widget.cpp
|
||||||
|
info/info_section_widget.h
|
||||||
|
info/info_top_bar.cpp
|
||||||
|
info/info_top_bar.h
|
||||||
|
info/info_wrap_widget.cpp
|
||||||
|
info/info_wrap_widget.h
|
||||||
inline_bots/bot_attach_web_view.cpp
|
inline_bots/bot_attach_web_view.cpp
|
||||||
inline_bots/bot_attach_web_view.h
|
inline_bots/bot_attach_web_view.h
|
||||||
inline_bots/inline_bot_layout_internal.cpp
|
inline_bots/inline_bot_layout_internal.cpp
|
||||||
|
|
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 1.9 KiB |
BIN
Telegram/Resources/icons/menu/boosts.png
Normal file
After Width: | Height: | Size: 589 B |
BIN
Telegram/Resources/icons/menu/boosts@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/boosts@3x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/link_above.png
Normal file
After Width: | Height: | Size: 450 B |
BIN
Telegram/Resources/icons/menu/link_above@2x.png
Normal file
After Width: | Height: | Size: 725 B |
BIN
Telegram/Resources/icons/menu/link_above@3x.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
Telegram/Resources/icons/menu/link_below.png
Normal file
After Width: | Height: | Size: 454 B |
BIN
Telegram/Resources/icons/menu/link_below@2x.png
Normal file
After Width: | Height: | Size: 761 B |
BIN
Telegram/Resources/icons/menu/link_below@3x.png
Normal file
After Width: | Height: | Size: 1,017 B |
BIN
Telegram/Resources/icons/menu/link_enlarge.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
Telegram/Resources/icons/menu/link_enlarge@2x.png
Normal file
After Width: | Height: | Size: 965 B |
BIN
Telegram/Resources/icons/menu/link_enlarge@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/menu/link_shrink.png
Normal file
After Width: | Height: | Size: 542 B |
BIN
Telegram/Resources/icons/menu/link_shrink@2x.png
Normal file
After Width: | Height: | Size: 991 B |
BIN
Telegram/Resources/icons/menu/link_shrink@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -129,6 +129,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_reconnecting#other" = "Reconnect in {count} s...";
|
"lng_reconnecting#other" = "Reconnect in {count} s...";
|
||||||
"lng_reconnecting_try_now" = "Try now";
|
"lng_reconnecting_try_now" = "Try now";
|
||||||
|
|
||||||
|
"lng_code_block_header_copy" = "copy";
|
||||||
|
|
||||||
"lng_status_service_notifications" = "service notifications";
|
"lng_status_service_notifications" = "service notifications";
|
||||||
"lng_status_support" = "support";
|
"lng_status_support" = "support";
|
||||||
"lng_status_bot" = "bot";
|
"lng_status_bot" = "bot";
|
||||||
|
@ -402,6 +404,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_dlg_search_from" = "From: {user}";
|
"lng_dlg_search_from" = "From: {user}";
|
||||||
|
|
||||||
"lng_settings_save" = "Save";
|
"lng_settings_save" = "Save";
|
||||||
|
"lng_settings_apply" = "Apply";
|
||||||
|
|
||||||
"lng_username_title" = "Username";
|
"lng_username_title" = "Username";
|
||||||
"lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.";
|
"lng_username_description1" = "You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.";
|
||||||
|
@ -770,11 +773,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_clear_payment_info_clear" = "Clear";
|
"lng_clear_payment_info_clear" = "Clear";
|
||||||
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
|
"lng_clear_payment_info_confirm" = "Delete your shipping info and instruct all payment providers to remove your saved credit cards? Note that Telegram never stores your credit card data.";
|
||||||
|
|
||||||
|
"lng_settings_theme_settings" = "Theme settings";
|
||||||
|
"lng_settings_theme_name_color" = "Your name color";
|
||||||
"lng_settings_auto_night_mode" = "Auto-Night mode";
|
"lng_settings_auto_night_mode" = "Auto-Night mode";
|
||||||
"lng_settings_auto_night_enabled" = "Match the system settings";
|
"lng_settings_auto_night_mode_off" = "Off";
|
||||||
|
"lng_settings_auto_night_mode_on" = "System";
|
||||||
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
|
"lng_settings_auto_night_warning" = "You have enabled auto-night mode. If you want to change the dark mode settings, you'll need to disable it first.";
|
||||||
"lng_settings_auto_night_disable" = "Disable";
|
"lng_settings_auto_night_disable" = "Disable";
|
||||||
|
|
||||||
|
"lng_settings_color_title" = "Color preview";
|
||||||
|
"lng_settings_color_reply" = "Reply to your message";
|
||||||
|
"lng_settings_color_reply_channel" = "Reply to your channel message";
|
||||||
|
"lng_settings_color_text" = "Your name and replies to your messages will be shown in the selected color.";
|
||||||
|
"lng_settings_color_text_channel" = "The name of the channel and replies to its messages will be shown in the selected color.";
|
||||||
|
"lng_settings_color_link_name" = "Telegram";
|
||||||
|
"lng_settings_color_link_title" = "Link Preview";
|
||||||
|
"lng_settings_color_link_description" = "Your selected color will also tint the link preview.";
|
||||||
|
"lng_settings_color_about" = "You can choose a color to tint your name, the links you send, and replies to your messages.";
|
||||||
|
"lng_settings_color_about_channel" = "You can choose a color to tint your channel's name, the links it sends, and replies to its messages.";
|
||||||
|
"lng_settings_color_emoji" = "Add icons to replies";
|
||||||
|
"lng_settings_color_emoji_remove" = "Remove icon";
|
||||||
|
"lng_settings_color_emoji_off" = "Off";
|
||||||
|
"lng_settings_color_emoji_about" = "Make replies to your messages stand out by adding custom patterns to them.";
|
||||||
|
"lng_settings_color_emoji_about_channel" = "Make replies to your channel's messages stand out by adding custom patterns to them.";
|
||||||
|
"lng_settings_color_subscribe" = "Subscribe to {link} to choose a custom color for your name.";
|
||||||
|
"lng_settings_color_changed" = "Your name color has been updated!";
|
||||||
|
"lng_settings_color_changed_channel" = "Your channel color has been updated!";
|
||||||
|
|
||||||
"lng_suggest_hide_new_title" = "Hide new chats?";
|
"lng_suggest_hide_new_title" = "Hide new chats?";
|
||||||
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
|
"lng_suggest_hide_new_about" = "You are receiving lots of new chats from users who are not in your Contact List.\n\nDo you want to have such chats **automatically muted** and **archived**?";
|
||||||
"lng_suggest_hide_new_to_settings" = "Go to Settings";
|
"lng_suggest_hide_new_to_settings" = "Go to Settings";
|
||||||
|
@ -802,7 +827,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
|
"lng_background_text1" = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!";
|
||||||
"lng_background_text2" = "I can't even take you seriously right now.";
|
"lng_background_text2" = "I can't even take you seriously right now.";
|
||||||
"lng_background_bad_link" = "This background link appears to be invalid.";
|
"lng_background_bad_link" = "This background link appears to be invalid.";
|
||||||
"lng_background_apply" = "Apply";
|
|
||||||
"lng_background_share" = "Share";
|
"lng_background_share" = "Share";
|
||||||
"lng_background_link_copied" = "Link copied to clipboard";
|
"lng_background_link_copied" = "Link copied to clipboard";
|
||||||
"lng_background_blur" = "Blurred";
|
"lng_background_blur" = "Blurred";
|
||||||
|
@ -1142,6 +1166,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_mute_box_title" = "Mute notifications for...";
|
"lng_mute_box_title" = "Mute notifications for...";
|
||||||
|
|
||||||
"lng_preview_loading" = "Getting Link Info...";
|
"lng_preview_loading" = "Getting Link Info...";
|
||||||
|
"lng_preview_cant" = "Could not generate preview for this link.";
|
||||||
|
|
||||||
"lng_profile_settings_section" = "Settings";
|
"lng_profile_settings_section" = "Settings";
|
||||||
"lng_profile_bot_settings" = "Bot Settings";
|
"lng_profile_bot_settings" = "Bot Settings";
|
||||||
|
@ -1640,6 +1665,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_action_story_mention_button" = "View Story";
|
"lng_action_story_mention_button" = "View Story";
|
||||||
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
|
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
|
||||||
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
|
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
|
||||||
|
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
|
||||||
|
|
||||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||||
|
@ -2034,6 +2060,129 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
||||||
"lng_boost_now_replace" = "Replace";
|
"lng_boost_now_replace" = "Replace";
|
||||||
|
|
||||||
|
"lng_boost_channel_title_color" = "Enable colors";
|
||||||
|
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||||
|
"lng_boost_channel_needs_level_color#other" = "Your channel needs to reach **Level {count}** to change channel color.";
|
||||||
|
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
|
||||||
|
"lng_boost_channel_ask_button" = "Copy Link";
|
||||||
|
"lng_boost_channel_or" = "or";
|
||||||
|
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||||
|
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||||
|
|
||||||
|
"lng_giveaway_new_title" = "Boosts via Gifts";
|
||||||
|
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
|
||||||
|
"lng_giveaway_create_option" = "Create Giveaway";
|
||||||
|
"lng_giveaway_create_subtitle" = "winners are chosen randomly";
|
||||||
|
"lng_giveaway_award_option" = "Award Specific Users";
|
||||||
|
"lng_giveaway_award_subtitle" = "Select recipients >";
|
||||||
|
"lng_giveaway_award_chosen#one" = "{count} recipient >";
|
||||||
|
"lng_giveaway_award_chosen#other" = "{count} recipients >";
|
||||||
|
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
|
||||||
|
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
|
||||||
|
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
|
||||||
|
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
|
||||||
|
"lng_giveaway_channels_title" = "Channels included in the giveaway";
|
||||||
|
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
|
||||||
|
"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts";
|
||||||
|
"lng_giveaway_channels_add" = "Add Channel";
|
||||||
|
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
|
||||||
|
"lng_giveaway_users_title" = "Users eligible for the giveaway";
|
||||||
|
"lng_giveaway_users_all" = "All subscribers";
|
||||||
|
"lng_giveaway_users_new" = "Only new subscribers";
|
||||||
|
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
|
||||||
|
"lng_giveaway_start" = "Start Giveaway";
|
||||||
|
"lng_giveaway_award" = "Gift Premium";
|
||||||
|
"lng_giveaway_date_title" = "Date when giveaway ends";
|
||||||
|
"lng_giveaway_date" = "Date and Time";
|
||||||
|
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
|
||||||
|
"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
|
||||||
|
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
|
||||||
|
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
|
||||||
|
"lng_giveaway_duration_price" = "{price} x {amount}";
|
||||||
|
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
|
||||||
|
"lng_giveaway_duration_about_link" = "here";
|
||||||
|
"lng_giveaway_date_select" = "Select Date and Time";
|
||||||
|
"lng_giveaway_date_confirm" = "Confirm";
|
||||||
|
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
|
||||||
|
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
|
||||||
|
"lng_giveaway_recipients_save" = "Save Recipients";
|
||||||
|
"lng_giveaway_recipients_deselect" = "Deselect All";
|
||||||
|
|
||||||
|
"lng_prize_title" = "Congratulations!";
|
||||||
|
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
|
||||||
|
"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}.";
|
||||||
|
"lng_prize_gift_about" = "You've received a gift from {channel}.";
|
||||||
|
"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}.";
|
||||||
|
"lng_prize_open" = "Open Gift Link";
|
||||||
|
"lng_prize_unclaimed_title" = "Unclaimed Prize";
|
||||||
|
"lng_prize_unclaimed_about" = "You have an unclaimed prize from a giveaway by {channel}.";
|
||||||
|
"lng_prize_unclaimed_duration" = "This prize is a **Telegram Premium** subscription {duration}.";
|
||||||
|
|
||||||
|
"lng_prizes_title#one" = "Giveaway Prize";
|
||||||
|
"lng_prizes_title#other" = "Giveaway Prizes";
|
||||||
|
"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}.";
|
||||||
|
"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}.";
|
||||||
|
"lng_prizes_participants" = "Participants";
|
||||||
|
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
|
||||||
|
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
|
||||||
|
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
|
||||||
|
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
|
||||||
|
"lng_prizes_countries" = "from {countries}";
|
||||||
|
"lng_prizes_countries_and_one" = "{countries}, {country}";
|
||||||
|
"lng_prizes_countries_and_last" = "{countries} and {country}";
|
||||||
|
"lng_prizes_date" = "Winners Selection Date";
|
||||||
|
"lng_prizes_how_works" = "Learn more";
|
||||||
|
"lng_prizes_how_title" = "About this giveaway";
|
||||||
|
"lng_prizes_end_title" = "Giveaway ended";
|
||||||
|
"lng_prizes_how_text" = "This giveaway is sponsored by {admins}.";
|
||||||
|
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
|
||||||
|
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
|
||||||
|
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
|
||||||
|
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
|
||||||
|
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
|
||||||
|
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
|
||||||
|
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
|
||||||
|
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}.";
|
||||||
|
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}.";
|
||||||
|
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels.";
|
||||||
|
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels.";
|
||||||
|
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}";
|
||||||
|
"lng_prizes_winners_new_of_one#other" = "{count} random users that joined {channel} after {start_date}";
|
||||||
|
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
|
||||||
|
"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}";
|
||||||
|
"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}.";
|
||||||
|
"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}.";
|
||||||
|
"lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel}).";
|
||||||
|
"lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started.";
|
||||||
|
"lng_prizes_how_no_country" = "You are not eligible to participate in this giveaway, because your country is not included in the terms of the giveaway.";
|
||||||
|
"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}.";
|
||||||
|
"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels).";
|
||||||
|
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
|
||||||
|
"lng_prizes_view_prize" = "View my prize";
|
||||||
|
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway.";
|
||||||
|
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||||
|
"lng_prizes_badge" = "x{amount}";
|
||||||
|
|
||||||
|
"lng_gift_link_title" = "Gift Link";
|
||||||
|
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
|
||||||
|
"lng_gift_link_label_from" = "From";
|
||||||
|
"lng_gift_link_label_to" = "To";
|
||||||
|
"lng_gift_link_label_to_unclaimed" = "No recipient";
|
||||||
|
"lng_gift_link_label_gift" = "Gift";
|
||||||
|
"lng_gift_link_gift_premium" = "Telegram Premium {duration}";
|
||||||
|
"lng_gift_link_label_reason" = "Reason";
|
||||||
|
"lng_gift_link_reason_giveaway" = "Giveaway";
|
||||||
|
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
|
||||||
|
"lng_gift_link_reason_chosen" = "You were selected by the channel";
|
||||||
|
"lng_gift_link_label_date" = "Date";
|
||||||
|
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
|
||||||
|
"lng_gift_link_also_send_link" = "send this link";
|
||||||
|
"lng_gift_link_use" = "Use Link";
|
||||||
|
"lng_gift_link_used_title" = "Used Gift Link";
|
||||||
|
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
|
||||||
|
"lng_gift_link_used_footer" = "This link was used on {date}.";
|
||||||
|
"lng_gift_link_expired" = "Gift code link expired";
|
||||||
|
|
||||||
"lng_accounts_limit_title" = "Limit Reached";
|
"lng_accounts_limit_title" = "Limit Reached";
|
||||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||||
|
@ -2213,6 +2362,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_saved_messages" = "Saved Messages";
|
"lng_saved_messages" = "Saved Messages";
|
||||||
"lng_saved_short" = "Save";
|
"lng_saved_short" = "Save";
|
||||||
"lng_saved_forward_here" = "Forward messages here for quick access";
|
"lng_saved_forward_here" = "Forward messages here for quick access";
|
||||||
|
"lng_saved_quote_here" = "Quote here to save";
|
||||||
|
|
||||||
"lng_scheduled_messages" = "Scheduled Messages";
|
"lng_scheduled_messages" = "Scheduled Messages";
|
||||||
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
|
"lng_scheduled_messages_empty" = "No scheduled messages here yet...";
|
||||||
|
@ -2420,6 +2570,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_attached_stickers" = "Attached Stickers";
|
"lng_context_attached_stickers" = "Attached Stickers";
|
||||||
"lng_context_to_msg" = "Go To Message";
|
"lng_context_to_msg" = "Go To Message";
|
||||||
"lng_context_reply_msg" = "Reply";
|
"lng_context_reply_msg" = "Reply";
|
||||||
|
"lng_context_quote_and_reply" = "Quote & Reply";
|
||||||
"lng_context_edit_msg" = "Edit";
|
"lng_context_edit_msg" = "Edit";
|
||||||
"lng_context_forward_msg" = "Forward Message";
|
"lng_context_forward_msg" = "Forward Message";
|
||||||
"lng_context_send_now_msg" = "Send now";
|
"lng_context_send_now_msg" = "Send now";
|
||||||
|
@ -2524,6 +2675,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_inline_switch_choose" = "Choose conversation...";
|
"lng_inline_switch_choose" = "Choose conversation...";
|
||||||
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
|
||||||
|
|
||||||
|
"lng_reply_in_another_title" = "Reply in...";
|
||||||
|
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||||
|
"lng_reply_show_in_chat" = "Show in Chat";
|
||||||
|
"lng_reply_remove" = "Do Not Reply";
|
||||||
|
"lng_reply_about_quote" = "You can select specific part to quote.";
|
||||||
|
"lng_reply_options_header" = "Reply to Message";
|
||||||
|
"lng_reply_options_quote" = "Update Quote";
|
||||||
|
"lng_reply_header_short" = "Reply";
|
||||||
|
"lng_reply_quote_selected" = "Quote Selected";
|
||||||
|
"lng_link_options_header" = "Link Preview Settings";
|
||||||
|
"lng_link_header_short" = "Link";
|
||||||
|
"lng_link_move_up" = "Move Up";
|
||||||
|
"lng_link_move_down" = "Move Down";
|
||||||
|
"lng_link_shrink_photo" = "Shrink Photo";
|
||||||
|
"lng_link_enlarge_photo" = "Enlarge Photo";
|
||||||
|
"lng_link_remove" = "Do Not Preview";
|
||||||
|
"lng_link_about_choose" = "Click on a link to generate its preview.";
|
||||||
|
|
||||||
"lng_share_cant" = "Sorry, no way to share here :(";
|
"lng_share_cant" = "Sorry, no way to share here :(";
|
||||||
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
|
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
|
||||||
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";
|
||||||
|
@ -2546,6 +2715,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_edit_bot_title" = "Edit bot";
|
"lng_edit_bot_title" = "Edit bot";
|
||||||
"lng_edit_sign_messages" = "Sign messages";
|
"lng_edit_sign_messages" = "Sign messages";
|
||||||
"lng_edit_group" = "Edit group";
|
"lng_edit_group" = "Edit group";
|
||||||
|
"lng_edit_channel_color" = "Change name color";
|
||||||
"lng_edit_self_title" = "Edit your name";
|
"lng_edit_self_title" = "Edit your name";
|
||||||
"lng_confirm_contact_data" = "New Contact";
|
"lng_confirm_contact_data" = "New Contact";
|
||||||
"lng_add_contact" = "Create";
|
"lng_add_contact" = "Create";
|
||||||
|
@ -2674,6 +2844,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_menu_formatting_italic" = "Italic";
|
"lng_menu_formatting_italic" = "Italic";
|
||||||
"lng_menu_formatting_underline" = "Underline";
|
"lng_menu_formatting_underline" = "Underline";
|
||||||
"lng_menu_formatting_strike_out" = "Strike-through";
|
"lng_menu_formatting_strike_out" = "Strike-through";
|
||||||
|
"lng_menu_formatting_blockquote" = "Quote";
|
||||||
"lng_menu_formatting_monospace" = "Monospace";
|
"lng_menu_formatting_monospace" = "Monospace";
|
||||||
"lng_menu_formatting_spoiler" = "Spoiler";
|
"lng_menu_formatting_spoiler" = "Spoiler";
|
||||||
"lng_menu_formatting_link_create" = "Create link";
|
"lng_menu_formatting_link_create" = "Create link";
|
||||||
|
@ -2686,7 +2857,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_formatting_link_create" = "Create";
|
"lng_formatting_link_create" = "Create";
|
||||||
|
|
||||||
"lng_text_copied" = "Text copied to clipboard.";
|
"lng_text_copied" = "Text copied to clipboard.";
|
||||||
"lng_code_copied" = "Code copied to clipboard.";
|
"lng_code_copied" = "Block copied to clipboard.";
|
||||||
|
|
||||||
"lng_spellchecker_submenu" = "Spelling";
|
"lng_spellchecker_submenu" = "Spelling";
|
||||||
"lng_spellchecker_add" = "Add to Dictionary";
|
"lng_spellchecker_add" = "Add to Dictionary";
|
||||||
|
@ -3352,6 +3523,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
|
"lng_admin_log_participant_volume_channel" = "{from} changed live stream volume for {user} to {percent}";
|
||||||
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
|
"lng_admin_log_antispam_enabled" = "{from} enabled aggressive anti-spam";
|
||||||
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
|
"lng_admin_log_antispam_disabled" = "{from} disabled aggressive anti-spam";
|
||||||
|
"lng_admin_log_change_color" = "{from} changed channel color from {previous} to {color}";
|
||||||
|
"lng_admin_log_set_background_emoji" = "{from} set channel background emoji to {emoji}";
|
||||||
|
"lng_admin_log_change_background_emoji" = "{from} changed channel background emoji from {previous} to {emoji}";
|
||||||
|
"lng_admin_log_removed_background_emoji" = "{from} removed channel background emoji {emoji}";
|
||||||
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
"lng_admin_log_user_with_username" = "{name} ({mention})";
|
||||||
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
|
"lng_admin_log_messages_ttl_set" = "{from} enabled messages auto-delete after {duration}";
|
||||||
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
|
"lng_admin_log_messages_ttl_changed" = "{from} changed messages auto-delete period from {previous} to {duration}";
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="4.10.4.0" />
|
Version="4.11.1.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,10,4,0
|
FILEVERSION 4,11,1,0
|
||||||
PRODUCTVERSION 4,10,4,0
|
PRODUCTVERSION 4,11,1,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "4.10.4.0"
|
VALUE "FileVersion", "4.11.1.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.10.4.0"
|
VALUE "ProductVersion", "4.11.1.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,10,4,0
|
FILEVERSION 4,11,1,0
|
||||||
PRODUCTVERSION 4,10,4,0
|
PRODUCTVERSION 4,11,1,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "4.10.4.0"
|
VALUE "FileVersion", "4.11.1.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.10.4.0"
|
VALUE "ProductVersion", "4.11.1.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -169,9 +169,7 @@ void SendBotCallbackData(
|
||||||
void HideSingleUseKeyboard(
|
void HideSingleUseKeyboard(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<HistoryItem*> item) {
|
not_null<HistoryItem*> item) {
|
||||||
controller->content()->hideSingleUseKeyboard(
|
controller->content()->hideSingleUseKeyboard(item->fullId());
|
||||||
item->history()->peer,
|
|
||||||
item->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -312,12 +310,14 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||||
case ButtonType::Default: {
|
case ButtonType::Default: {
|
||||||
// Copy string before passing it to the sending method
|
// Copy string before passing it to the sending method
|
||||||
// because the original button can be destroyed inside.
|
// because the original button can be destroyed inside.
|
||||||
const auto replyTo = item->isRegular() ? item->id : 0;
|
const auto replyTo = item->isRegular()
|
||||||
|
? item->fullId()
|
||||||
|
: FullMsgId();
|
||||||
controller->content()->sendBotCommand({
|
controller->content()->sendBotCommand({
|
||||||
.peer = item->history()->peer,
|
.peer = item->history()->peer,
|
||||||
.command = QString(button->text),
|
.command = QString(button->text),
|
||||||
.context = item->fullId(),
|
.context = item->fullId(),
|
||||||
.replyTo = replyTo,
|
.replyTo = { replyTo },
|
||||||
});
|
});
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
@ -363,7 +363,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||||
|
|
||||||
case ButtonType::RequestPhone: {
|
case ButtonType::RequestPhone: {
|
||||||
HideSingleUseKeyboard(controller, item);
|
HideSingleUseKeyboard(controller, item);
|
||||||
const auto itemId = item->id;
|
const auto itemId = item->fullId();
|
||||||
const auto topicRootId = item->topicRootId();
|
const auto topicRootId = item->topicRootId();
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
controller->show(Ui::MakeConfirmBox({
|
controller->show(Ui::MakeConfirmBox({
|
||||||
|
@ -376,7 +376,7 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||||
auto action = Api::SendAction(history);
|
auto action = Api::SendAction(history);
|
||||||
action.clearDraft = false;
|
action.clearDraft = false;
|
||||||
action.replyTo = {
|
action.replyTo = {
|
||||||
.msgId = itemId,
|
.messageId = itemId,
|
||||||
.topicRootId = topicRootId,
|
.topicRootId = topicRootId,
|
||||||
};
|
};
|
||||||
history->session().api().shareContact(
|
history->session().api().shareContact(
|
||||||
|
@ -397,13 +397,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
||||||
chosen |= PollData::Flag::Quiz;
|
chosen |= PollData::Flag::Quiz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto replyToId = MsgId(0);
|
const auto replyTo = FullReplyTo();
|
||||||
const auto topicRootId = MsgId(0);
|
|
||||||
Window::PeerMenuCreatePoll(
|
Window::PeerMenuCreatePoll(
|
||||||
controller,
|
controller,
|
||||||
item->history()->peer,
|
item->history()->peer,
|
||||||
replyToId,
|
replyTo,
|
||||||
topicRootId,
|
|
||||||
chosen,
|
chosen,
|
||||||
disabled);
|
disabled);
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -19,8 +19,8 @@ SendAction::SendAction(
|
||||||
SendOptions options)
|
SendOptions options)
|
||||||
: history(thread->owningHistory())
|
: history(thread->owningHistory())
|
||||||
, options(options)
|
, options(options)
|
||||||
, replyTo({ .msgId = thread->topicRootId() }) {
|
, replyTo({ .messageId = { history->peer->id, thread->topicRootId() } }) {
|
||||||
replyTo.topicRootId = replyTo.msgId;
|
replyTo.topicRootId = replyTo.messageId.msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendOptions DefaultSendWhenOnlineOptions() {
|
SendOptions DefaultSendWhenOnlineOptions() {
|
||||||
|
@ -31,7 +31,7 @@ SendOptions DefaultSendWhenOnlineOptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPInputReplyTo SendAction::mtpReplyTo() const {
|
MTPInputReplyTo SendAction::mtpReplyTo() const {
|
||||||
return Data::ReplyToForMTP(&history->owner(), replyTo);
|
return Data::ReplyToForMTP(history, replyTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_drafts.h"
|
||||||
|
|
||||||
class History;
|
class History;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -22,7 +24,6 @@ struct SendOptions {
|
||||||
TimeId scheduled = 0;
|
TimeId scheduled = 0;
|
||||||
bool silent = false;
|
bool silent = false;
|
||||||
bool handleSupportSwitch = false;
|
bool handleSupportSwitch = false;
|
||||||
bool removeWebPageId = false;
|
|
||||||
bool hideViaBot = false;
|
bool hideViaBot = false;
|
||||||
};
|
};
|
||||||
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
[[nodiscard]] SendOptions DefaultSendWhenOnlineOptions();
|
||||||
|
@ -54,7 +55,7 @@ struct MessageToSend {
|
||||||
|
|
||||||
SendAction action;
|
SendAction action;
|
||||||
TextWithTags textWithTags;
|
TextWithTags textWithTags;
|
||||||
WebPageId webPageId = 0;
|
Data::WebPageDraft webPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RemoteFileInfo {
|
struct RemoteFileInfo {
|
||||||
|
|
|
@ -11,8 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_media.h"
|
#include "api/api_media.h"
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
|
#include "data/data_histories.h"
|
||||||
#include "data/data_scheduled_messages.h"
|
#include "data/data_scheduled_messages.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_web_page.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
@ -45,6 +47,7 @@ template <typename DoneCallback, typename FailCallback>
|
||||||
mtpRequestId EditMessage(
|
mtpRequestId EditMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities &textWithEntities,
|
const TextWithEntities &textWithEntities,
|
||||||
|
Data::WebPageDraft webpage,
|
||||||
SendOptions options,
|
SendOptions options,
|
||||||
DoneCallback &&done,
|
DoneCallback &&done,
|
||||||
FailCallback &&fail,
|
FailCallback &&fail,
|
||||||
|
@ -65,15 +68,21 @@ mtpRequestId EditMessage(
|
||||||
|
|
||||||
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
const auto emptyFlag = MTPmessages_EditMessage::Flag(0);
|
||||||
const auto flags = emptyFlag
|
const auto flags = emptyFlag
|
||||||
| (!text.isEmpty() || media
|
| ((!text.isEmpty() || media)
|
||||||
? MTPmessages_EditMessage::Flag::f_message
|
? MTPmessages_EditMessage::Flag::f_message
|
||||||
: emptyFlag)
|
: emptyFlag)
|
||||||
| ((media && inputMedia.has_value())
|
| ((media && inputMedia.has_value())
|
||||||
? MTPmessages_EditMessage::Flag::f_media
|
? MTPmessages_EditMessage::Flag::f_media
|
||||||
: emptyFlag)
|
: emptyFlag)
|
||||||
| (options.removeWebPageId
|
| (webpage.removed
|
||||||
? MTPmessages_EditMessage::Flag::f_no_webpage
|
? MTPmessages_EditMessage::Flag::f_no_webpage
|
||||||
: emptyFlag)
|
: emptyFlag)
|
||||||
|
| ((!webpage.removed && !webpage.url.isEmpty())
|
||||||
|
? MTPmessages_EditMessage::Flag::f_media
|
||||||
|
: emptyFlag)
|
||||||
|
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|
||||||
|
? MTPmessages_EditMessage::Flag::f_invert_media
|
||||||
|
: emptyFlag)
|
||||||
| (!sentEntities.v.isEmpty()
|
| (!sentEntities.v.isEmpty()
|
||||||
? MTPmessages_EditMessage::Flag::f_entities
|
? MTPmessages_EditMessage::Flag::f_entities
|
||||||
: emptyFlag)
|
: emptyFlag)
|
||||||
|
@ -89,7 +98,7 @@ mtpRequestId EditMessage(
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
MTP_int(id),
|
MTP_int(id),
|
||||||
MTP_string(text),
|
MTP_string(text),
|
||||||
inputMedia.value_or(MTPInputMedia()),
|
inputMedia.value_or(Data::WebPageForMTP(webpage, text.isEmpty())),
|
||||||
MTPReplyMarkup(),
|
MTPReplyMarkup(),
|
||||||
sentEntities,
|
sentEntities,
|
||||||
MTP_int(options.scheduled)
|
MTP_int(options.scheduled)
|
||||||
|
@ -133,9 +142,15 @@ mtpRequestId EditMessage(
|
||||||
FailCallback &&fail,
|
FailCallback &&fail,
|
||||||
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
|
std::optional<MTPInputMedia> inputMedia = std::nullopt) {
|
||||||
const auto &text = item->originalText();
|
const auto &text = item->originalText();
|
||||||
|
const auto webpage = (!item->media() || !item->media()->webpage())
|
||||||
|
? Data::WebPageDraft{ .removed = true }
|
||||||
|
: Data::WebPageDraft{
|
||||||
|
.id = item->media()->webpage()->id,
|
||||||
|
};
|
||||||
return EditMessage(
|
return EditMessage(
|
||||||
item,
|
item,
|
||||||
text,
|
text,
|
||||||
|
webpage,
|
||||||
options,
|
options,
|
||||||
std::forward<DoneCallback>(done),
|
std::forward<DoneCallback>(done),
|
||||||
std::forward<FailCallback>(fail),
|
std::forward<FailCallback>(fail),
|
||||||
|
@ -216,12 +231,19 @@ mtpRequestId EditCaption(
|
||||||
SendOptions options,
|
SendOptions options,
|
||||||
Fn<void()> done,
|
Fn<void()> done,
|
||||||
Fn<void(const QString &)> fail) {
|
Fn<void(const QString &)> fail) {
|
||||||
return EditMessage(item, caption, options, done, fail);
|
return EditMessage(
|
||||||
|
item,
|
||||||
|
caption,
|
||||||
|
Data::WebPageDraft(),
|
||||||
|
options,
|
||||||
|
done,
|
||||||
|
fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
mtpRequestId EditTextMessage(
|
mtpRequestId EditTextMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities &caption,
|
const TextWithEntities &caption,
|
||||||
|
Data::WebPageDraft webpage,
|
||||||
SendOptions options,
|
SendOptions options,
|
||||||
Fn<void(mtpRequestId requestId)> done,
|
Fn<void(mtpRequestId requestId)> done,
|
||||||
Fn<void(const QString &, mtpRequestId requestId)> fail) {
|
Fn<void(const QString &, mtpRequestId requestId)> fail) {
|
||||||
|
@ -229,7 +251,7 @@ mtpRequestId EditTextMessage(
|
||||||
applyUpdates();
|
applyUpdates();
|
||||||
done(id);
|
done(id);
|
||||||
};
|
};
|
||||||
return EditMessage(item, caption, options, callback, fail);
|
return EditMessage(item, caption, webpage, options, callback, fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct WebPageDraft;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace MTP {
|
namespace MTP {
|
||||||
class Error;
|
class Error;
|
||||||
} // namespace MTP
|
} // namespace MTP
|
||||||
|
@ -48,6 +52,7 @@ mtpRequestId EditCaption(
|
||||||
mtpRequestId EditTextMessage(
|
mtpRequestId EditTextMessage(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities &caption,
|
const TextWithEntities &caption,
|
||||||
|
Data::WebPageDraft webpage,
|
||||||
SendOptions options,
|
SendOptions options,
|
||||||
Fn<void(mtpRequestId requestId)> done,
|
Fn<void(mtpRequestId requestId)> done,
|
||||||
Fn<void(const QString &error, mtpRequestId requestId)> fail);
|
Fn<void(const QString &error, mtpRequestId requestId)> fail);
|
||||||
|
|
|
@ -510,40 +510,57 @@ void PeerPhoto::requestUserPhotos(
|
||||||
_userPhotosRequests.emplace(user, requestId);
|
_userPhotosRequests.emplace(user, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
|
||||||
|
switch (type) {
|
||||||
|
case EmojiListType::Profile: return _profileEmojiList;
|
||||||
|
case EmojiListType::Group: return _groupEmojiList;
|
||||||
|
case EmojiListType::Background: return _backgroundEmojiList;
|
||||||
|
}
|
||||||
|
Unexpected("Type in PeerPhoto::emojiList.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PeerPhoto::emojiList(EmojiListType type) const
|
||||||
|
-> const EmojiListData & {
|
||||||
|
return const_cast<PeerPhoto*>(this)->emojiList(type);
|
||||||
|
}
|
||||||
|
|
||||||
void PeerPhoto::requestEmojiList(EmojiListType type) {
|
void PeerPhoto::requestEmojiList(EmojiListType type) {
|
||||||
if (_requestIdEmojiList) {
|
auto &list = emojiList(type);
|
||||||
|
if (list.requestId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto isGroup = (type == EmojiListType::Group);
|
const auto send = [&](auto &&request) {
|
||||||
const auto d = [=](const MTPEmojiList &result) {
|
return _api.request(
|
||||||
_requestIdEmojiList = 0;
|
std::move(request)
|
||||||
result.match([](const MTPDemojiListNotModified &data) {
|
).done([=](const MTPEmojiList &result) {
|
||||||
}, [&](const MTPDemojiList &data) {
|
auto &list = emojiList(type);
|
||||||
auto &list = isGroup ? _profileEmojiList : _groupEmojiList;
|
list.requestId = 0;
|
||||||
list = ranges::views::all(
|
result.match([](const MTPDemojiListNotModified &data) {
|
||||||
data.vdocument_id().v
|
}, [&](const MTPDemojiList &data) {
|
||||||
) | ranges::views::transform(&MTPlong::v) | ranges::to_vector;
|
list.list = ranges::views::all(
|
||||||
});
|
data.vdocument_id().v
|
||||||
|
) | ranges::views::transform(
|
||||||
|
&MTPlong::v
|
||||||
|
) | ranges::to_vector;
|
||||||
|
});
|
||||||
|
}).fail([=] {
|
||||||
|
emojiList(type).requestId = 0;
|
||||||
|
}).send();
|
||||||
};
|
};
|
||||||
const auto f = [=] { _requestIdEmojiList = 0; };
|
list.requestId = (type == EmojiListType::Profile)
|
||||||
_requestIdEmojiList = isGroup
|
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
|
||||||
? _api.request(
|
: (type == EmojiListType::Group)
|
||||||
MTPaccount_GetDefaultGroupPhotoEmojis()
|
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
|
||||||
).done(d).fail(f).send()
|
: send(MTPaccount_GetDefaultBackgroundEmojis());
|
||||||
: _api.request(
|
|
||||||
MTPaccount_GetDefaultProfilePhotoEmojis()
|
|
||||||
).done(d).fail(f).send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
|
rpl::producer<PeerPhoto::EmojiList> PeerPhoto::emojiListValue(
|
||||||
EmojiListType type) {
|
EmojiListType type) {
|
||||||
auto &list = (type == EmojiListType::Group)
|
auto &list = emojiList(type);
|
||||||
? _profileEmojiList
|
if (list.list.current().empty() && !list.requestId) {
|
||||||
: _groupEmojiList;
|
|
||||||
if (list.current().empty() && !_requestIdEmojiList) {
|
|
||||||
requestEmojiList(type);
|
requestEmojiList(type);
|
||||||
}
|
}
|
||||||
return list.value();
|
return list.list.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-personal photo in case a personal photo is set.
|
// Non-personal photo in case a personal photo is set.
|
||||||
|
|
|
@ -31,6 +31,7 @@ public:
|
||||||
enum class EmojiListType {
|
enum class EmojiListType {
|
||||||
Profile,
|
Profile,
|
||||||
Group,
|
Group,
|
||||||
|
Background,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UserPhoto {
|
struct UserPhoto {
|
||||||
|
@ -73,6 +74,10 @@ private:
|
||||||
Suggestion,
|
Suggestion,
|
||||||
Fallback,
|
Fallback,
|
||||||
};
|
};
|
||||||
|
struct EmojiListData {
|
||||||
|
rpl::variable<EmojiList> list;
|
||||||
|
mtpRequestId requestId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
void ready(
|
void ready(
|
||||||
const FullMsgId &msgId,
|
const FullMsgId &msgId,
|
||||||
|
@ -84,6 +89,9 @@ private:
|
||||||
UploadType type,
|
UploadType type,
|
||||||
Fn<void()> done);
|
Fn<void()> done);
|
||||||
|
|
||||||
|
[[nodiscard]] EmojiListData &emojiList(EmojiListType type);
|
||||||
|
[[nodiscard]] const EmojiListData &emojiList(EmojiListType type) const;
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
@ -101,9 +109,9 @@ private:
|
||||||
not_null<UserData*>,
|
not_null<UserData*>,
|
||||||
not_null<PhotoData*>> _nonPersonalPhotos;
|
not_null<PhotoData*>> _nonPersonalPhotos;
|
||||||
|
|
||||||
mtpRequestId _requestIdEmojiList = 0;
|
EmojiListData _profileEmojiList;
|
||||||
rpl::variable<EmojiList> _profileEmojiList;
|
EmojiListData _groupEmojiList;
|
||||||
rpl::variable<EmojiList> _groupEmojiList;
|
EmojiListData _backgroundEmojiList;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ void Polls::create(
|
||||||
|
|
||||||
const auto history = action.history;
|
const auto history = action.history;
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
const auto topicRootId = action.replyTo.msgId
|
const auto topicRootId = action.replyTo.messageId
|
||||||
? action.replyTo.topicRootId
|
? action.replyTo.topicRootId
|
||||||
: 0;
|
: 0;
|
||||||
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
||||||
|
|
|
@ -17,6 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
|
||||||
|
return {
|
||||||
|
.from = peerFromMTP(data.vfrom_id()),
|
||||||
|
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
|
||||||
|
.giveawayId = data.vgiveaway_msg_id().value_or_empty(),
|
||||||
|
.date = data.vdate().v,
|
||||||
|
.used = data.vused_date().value_or_empty(),
|
||||||
|
.months = data.vmonths().v,
|
||||||
|
.giveaway = data.is_via_giveaway(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Premium::Premium(not_null<ApiWrap*> api)
|
Premium::Premium(not_null<ApiWrap*> api)
|
||||||
: _session(&api->session())
|
: _session(&api->session())
|
||||||
|
@ -183,6 +198,115 @@ void Premium::reloadCloudSet() {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Premium::checkGiftCode(
|
||||||
|
const QString &slug,
|
||||||
|
Fn<void(GiftCode)> done) {
|
||||||
|
if (_giftCodeRequestId) {
|
||||||
|
if (_giftCodeSlug == slug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_api.request(_giftCodeRequestId).cancel();
|
||||||
|
}
|
||||||
|
_giftCodeSlug = slug;
|
||||||
|
_giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode(
|
||||||
|
MTP_string(slug)
|
||||||
|
)).done([=](const MTPpayments_CheckedGiftCode &result) {
|
||||||
|
_giftCodeRequestId = 0;
|
||||||
|
|
||||||
|
const auto &data = result.data();
|
||||||
|
_session->data().processUsers(data.vusers());
|
||||||
|
_session->data().processChats(data.vchats());
|
||||||
|
done(updateGiftCode(slug, Parse(data)));
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
_giftCodeRequestId = 0;
|
||||||
|
|
||||||
|
done(updateGiftCode(slug, {}));
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
GiftCode Premium::updateGiftCode(
|
||||||
|
const QString &slug,
|
||||||
|
const GiftCode &code) {
|
||||||
|
auto &now = _giftCodes[slug];
|
||||||
|
if (now != code) {
|
||||||
|
now = code;
|
||||||
|
_giftCodeUpdated.fire_copy(slug);
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
|
||||||
|
return _giftCodeUpdated.events_starting_with_copy(
|
||||||
|
slug
|
||||||
|
) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] {
|
||||||
|
const auto i = _giftCodes.find(slug);
|
||||||
|
return (i != end(_giftCodes)) ? i->second : GiftCode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Premium::applyGiftCode(const QString &slug, Fn<void(QString)> done) {
|
||||||
|
_api.request(MTPpayments_ApplyGiftCode(
|
||||||
|
MTP_string(slug)
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
_session->api().applyUpdates(result);
|
||||||
|
done({});
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
done(error.type());
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Premium::resolveGiveawayInfo(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
Fn<void(GiveawayInfo)> done) {
|
||||||
|
Expects(done != nullptr);
|
||||||
|
|
||||||
|
_giveawayInfoDone = std::move(done);
|
||||||
|
if (_giveawayInfoRequestId) {
|
||||||
|
if (_giveawayInfoPeer == peer
|
||||||
|
&& _giveawayInfoMessageId == messageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_api.request(_giveawayInfoRequestId).cancel();
|
||||||
|
}
|
||||||
|
_giveawayInfoPeer = peer;
|
||||||
|
_giveawayInfoMessageId = messageId;
|
||||||
|
_giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo(
|
||||||
|
_giveawayInfoPeer->input,
|
||||||
|
MTP_int(_giveawayInfoMessageId.bare)
|
||||||
|
)).done([=](const MTPpayments_GiveawayInfo &result) {
|
||||||
|
_giveawayInfoRequestId = 0;
|
||||||
|
|
||||||
|
auto info = GiveawayInfo();
|
||||||
|
result.match([&](const MTPDpayments_giveawayInfo &data) {
|
||||||
|
info.participating = data.is_participating();
|
||||||
|
info.state = data.is_preparing_results()
|
||||||
|
? GiveawayState::Preparing
|
||||||
|
: GiveawayState::Running;
|
||||||
|
info.adminChannelId = data.vadmin_disallowed_chat_id()
|
||||||
|
? ChannelId(*data.vadmin_disallowed_chat_id())
|
||||||
|
: ChannelId();
|
||||||
|
info.disallowedCountry = qs(
|
||||||
|
data.vdisallowed_country().value_or_empty());
|
||||||
|
info.tooEarlyDate
|
||||||
|
= data.vjoined_too_early_date().value_or_empty();
|
||||||
|
info.startDate = data.vstart_date().v;
|
||||||
|
}, [&](const MTPDpayments_giveawayInfoResults &data) {
|
||||||
|
info.state = data.is_refunded()
|
||||||
|
? GiveawayState::Refunded
|
||||||
|
: GiveawayState::Finished;
|
||||||
|
info.giftCode = qs(data.vgift_code_slug().value_or_empty());
|
||||||
|
info.activatedCount = data.vactivated_count().v;
|
||||||
|
info.finishDate = data.vfinish_date().v;
|
||||||
|
info.startDate = data.vstart_date().v;
|
||||||
|
});
|
||||||
|
_giveawayInfoDone(std::move(info));
|
||||||
|
}).fail([=] {
|
||||||
|
_giveawayInfoRequestId = 0;
|
||||||
|
_giveawayInfoDone({});
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
|
||||||
return _subscriptionOptions;
|
return _subscriptionOptions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,49 @@ class Session;
|
||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
|
struct GiftCode {
|
||||||
|
PeerId from = 0;
|
||||||
|
PeerId to = 0;
|
||||||
|
MsgId giveawayId = 0;
|
||||||
|
TimeId date = 0;
|
||||||
|
TimeId used = 0; // 0 if not used.
|
||||||
|
int months = 0;
|
||||||
|
bool giveaway = false;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return months != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend inline bool operator==(
|
||||||
|
const GiftCode&,
|
||||||
|
const GiftCode&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GiveawayState {
|
||||||
|
Invalid,
|
||||||
|
Running,
|
||||||
|
Preparing,
|
||||||
|
Finished,
|
||||||
|
Refunded,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GiveawayInfo {
|
||||||
|
QString giftCode;
|
||||||
|
QString disallowedCountry;
|
||||||
|
ChannelId adminChannelId = 0;
|
||||||
|
GiveawayState state = GiveawayState::Invalid;
|
||||||
|
TimeId tooEarlyDate = 0;
|
||||||
|
TimeId finishDate = 0;
|
||||||
|
TimeId startDate = 0;
|
||||||
|
int winnersCount = 0;
|
||||||
|
int activatedCount = 0;
|
||||||
|
bool participating = false;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return state != GiveawayState::Invalid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Premium final {
|
class Premium final {
|
||||||
public:
|
public:
|
||||||
explicit Premium(not_null<ApiWrap*> api);
|
explicit Premium(not_null<ApiWrap*> api);
|
||||||
|
@ -40,6 +83,19 @@ public:
|
||||||
[[nodiscard]] int64 monthlyAmount() const;
|
[[nodiscard]] int64 monthlyAmount() const;
|
||||||
[[nodiscard]] QString monthlyCurrency() const;
|
[[nodiscard]] QString monthlyCurrency() const;
|
||||||
|
|
||||||
|
void checkGiftCode(
|
||||||
|
const QString &slug,
|
||||||
|
Fn<void(GiftCode)> done);
|
||||||
|
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
|
||||||
|
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
|
||||||
|
const QString &slug) const;
|
||||||
|
void applyGiftCode(const QString &slug, Fn<void(QString)> done);
|
||||||
|
|
||||||
|
void resolveGiveawayInfo(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
Fn<void(GiveawayInfo)> done);
|
||||||
|
|
||||||
[[nodiscard]] auto subscriptionOptions() const
|
[[nodiscard]] auto subscriptionOptions() const
|
||||||
-> const Data::SubscriptionOptions &;
|
-> const Data::SubscriptionOptions &;
|
||||||
|
|
||||||
|
@ -71,6 +127,16 @@ private:
|
||||||
int64 _monthlyAmount = 0;
|
int64 _monthlyAmount = 0;
|
||||||
QString _monthlyCurrency;
|
QString _monthlyCurrency;
|
||||||
|
|
||||||
|
mtpRequestId _giftCodeRequestId = 0;
|
||||||
|
QString _giftCodeSlug;
|
||||||
|
base::flat_map<QString, GiftCode> _giftCodes;
|
||||||
|
rpl::event_stream<QString> _giftCodeUpdated;
|
||||||
|
|
||||||
|
mtpRequestId _giveawayInfoRequestId = 0;
|
||||||
|
PeerData *_giveawayInfoPeer = nullptr;
|
||||||
|
MsgId _giveawayInfoMessageId = 0;
|
||||||
|
Fn<void(GiveawayInfo)> _giveawayInfoDone;
|
||||||
|
|
||||||
Data::SubscriptionOptions _subscriptionOptions;
|
Data::SubscriptionOptions _subscriptionOptions;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -368,9 +368,9 @@ void SendConfirmedFile(
|
||||||
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
const auto histories = &session->data().histories();
|
const auto histories = &session->data().histories();
|
||||||
file->to.replyTo.msgId = histories->convertTopicReplyToId(
|
file->to.replyTo.messageId = histories->convertTopicReplyToId(
|
||||||
history,
|
history,
|
||||||
file->to.replyTo.msgId);
|
file->to.replyTo.messageId);
|
||||||
file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
|
file->to.replyTo.topicRootId = histories->convertTopicReplyToId(
|
||||||
history,
|
history,
|
||||||
file->to.replyTo.topicRootId);
|
file->to.replyTo.topicRootId);
|
||||||
|
|
|
@ -434,19 +434,42 @@ void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||||
|
|
||||||
const auto requestPrivateForwards = [=](
|
const auto requestPrivateForwards = [=](
|
||||||
const Data::StatisticalGraph &messageGraph) {
|
const Data::StatisticalGraph &messageGraph) {
|
||||||
_api.request(MTPstats_GetBroadcastStats(
|
_api.request(MTPchannels_GetMessages(
|
||||||
MTP_flags(MTPstats_GetBroadcastStats::Flags(0)),
|
_channel->inputChannel,
|
||||||
_channel->inputChannel
|
MTP_vector<MTPInputMessage>(
|
||||||
)).done([=](const MTPstats_BroadcastStats &result) {
|
1,
|
||||||
const auto channelStats = ChannelStatisticsFromTL(result.data());
|
MTP_inputMessageID(MTP_int(_fullId.msg))))
|
||||||
auto info = Data::StatisticsMessageInteractionInfo();
|
).done([=](const MTPmessages_Messages &result) {
|
||||||
for (const auto &r : channelStats.recentMessageInteractions) {
|
const auto process = [&](const MTPVector<MTPMessage> &messages) {
|
||||||
if (r.messageId == _fullId.msg) {
|
const auto &message = messages.v.front();
|
||||||
info = r;
|
return message.match([&](const MTPDmessage &data) {
|
||||||
break;
|
return Data::StatisticsMessageInteractionInfo{
|
||||||
}
|
.messageId = IdFromMessage(message),
|
||||||
}
|
.viewsCount = data.vviews()
|
||||||
requestFirstPublicForwards(messageGraph, info);
|
? data.vviews()->v
|
||||||
|
: 0,
|
||||||
|
.forwardsCount = data.vforwards()
|
||||||
|
? data.vforwards()->v
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
}, [](const MTPDmessageEmpty &) {
|
||||||
|
return Data::StatisticsMessageInteractionInfo();
|
||||||
|
}, [](const MTPDmessageService &) {
|
||||||
|
return Data::StatisticsMessageInteractionInfo();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto info = result.match([&](const MTPDmessages_messages &data) {
|
||||||
|
return process(data.vmessages());
|
||||||
|
}, [&](const MTPDmessages_messagesSlice &data) {
|
||||||
|
return process(data.vmessages());
|
||||||
|
}, [&](const MTPDmessages_channelMessages &data) {
|
||||||
|
return process(data.vmessages());
|
||||||
|
}, [](const MTPDmessages_messagesNotModified &) {
|
||||||
|
return Data::StatisticsMessageInteractionInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
requestFirstPublicForwards(messageGraph, std::move(info));
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
requestFirstPublicForwards(messageGraph, {});
|
requestFirstPublicForwards(messageGraph, {});
|
||||||
}).send();
|
}).send();
|
||||||
|
@ -478,9 +501,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
||||||
return lifetime;
|
return lifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
_api.request(MTPstories_GetBoostsStatus(
|
_api.request(MTPpremium_GetBoostsStatus(
|
||||||
_peer->input
|
_peer->input
|
||||||
)).done([=](const MTPstories_BoostsStatus &result) {
|
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||||
const auto &data = result.data();
|
const auto &data = result.data();
|
||||||
const auto hasPremium = !!data.vpremium_audience();
|
const auto hasPremium = !!data.vpremium_audience();
|
||||||
const auto premiumMemberCount = hasPremium
|
const auto premiumMemberCount = hasPremium
|
||||||
|
@ -530,28 +553,29 @@ void Boosts::requestBoosts(
|
||||||
}
|
}
|
||||||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||||
_requestId = _api.request(MTPstories_GetBoostersList(
|
_requestId = _api.request(MTPpremium_GetBoostsList(
|
||||||
|
MTP_flags(0),
|
||||||
_peer->input,
|
_peer->input,
|
||||||
MTP_string(token.next),
|
MTP_string(token.next),
|
||||||
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
|
token.next.isEmpty() ? kTlFirstSlice : kTlLimit
|
||||||
)).done([=](const MTPstories_BoostersList &result) {
|
)).done([=](const MTPpremium_BoostsList &result) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
|
|
||||||
const auto &data = result.data();
|
const auto &data = result.data();
|
||||||
_peer->owner().processUsers(data.vusers());
|
_peer->owner().processUsers(data.vusers());
|
||||||
|
|
||||||
auto list = std::vector<Data::Boost>();
|
auto list = std::vector<Data::Boost>();
|
||||||
list.reserve(data.vboosters().v.size());
|
list.reserve(data.vboosts().v.size());
|
||||||
for (const auto &boost : data.vboosters().v) {
|
for (const auto &boost : data.vboosts().v) {
|
||||||
list.push_back({
|
list.push_back({
|
||||||
boost.data().vuser_id().v,
|
boost.data().vuser_id().value_or_empty(),
|
||||||
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
|
QDateTime::fromSecsSinceEpoch(boost.data().vexpires().v),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
done(Data::BoostsListSlice{
|
done(Data::BoostsListSlice{
|
||||||
.list = std::move(list),
|
.list = std::move(list),
|
||||||
.total = data.vcount().v,
|
.total = data.vcount().v,
|
||||||
.allLoaded = (data.vcount().v == data.vboosters().v.size()),
|
.allLoaded = (data.vcount().v == data.vboosts().v.size()),
|
||||||
.token = Data::BoostsListSlice::OffsetToken{
|
.token = Data::BoostsListSlice::OffsetToken{
|
||||||
data.vnext_offset()
|
data.vnext_offset()
|
||||||
? qs(*data.vnext_offset())
|
? qs(*data.vnext_offset())
|
||||||
|
|
|
@ -114,6 +114,7 @@ EntitiesInText EntitiesFromMTP(
|
||||||
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
|
||||||
|
case mtpc_messageEntityBlockquote: { auto &d = entity.c_messageEntityBlockquote(); result.push_back({ EntityType::Blockquote, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
|
case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
|
||||||
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
|
||||||
case mtpc_messageEntityCustomEmoji: {
|
case mtpc_messageEntityCustomEmoji: {
|
||||||
|
@ -142,6 +143,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
&& entity.type() != EntityType::StrikeOut
|
&& entity.type() != EntityType::StrikeOut
|
||||||
&& entity.type() != EntityType::Code // #TODO entities
|
&& entity.type() != EntityType::Code // #TODO entities
|
||||||
&& entity.type() != EntityType::Pre
|
&& entity.type() != EntityType::Pre
|
||||||
|
&& entity.type() != EntityType::Blockquote
|
||||||
&& entity.type() != EntityType::Spoiler
|
&& entity.type() != EntityType::Spoiler
|
||||||
&& entity.type() != EntityType::MentionName
|
&& entity.type() != EntityType::MentionName
|
||||||
&& entity.type() != EntityType::CustomUrl
|
&& entity.type() != EntityType::CustomUrl
|
||||||
|
@ -170,6 +172,7 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
|
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
|
||||||
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
|
||||||
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
|
||||||
|
case EntityType::Blockquote: v.push_back(MTP_messageEntityBlockquote(offset, length)); break;
|
||||||
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
|
||||||
case EntityType::CustomEmoji: {
|
case EntityType::CustomEmoji: {
|
||||||
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {
|
if (const auto valid = CustomEmojiEntity(offset, length, entity.data())) {
|
||||||
|
|
|
@ -2103,7 +2103,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
|
||||||
windows.front()->window().show(Ui::MakeInformBox(text));
|
windows.front()->window().show(Ui::MakeInformBox(text));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session().data().serviceNotification(text, d.vmedia());
|
session().data().serviceNotification(
|
||||||
|
text,
|
||||||
|
d.vmedia(),
|
||||||
|
d.is_invert_media());
|
||||||
session().api().authorizations().reload();
|
session().api().authorizations().reload();
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -2150,14 +2150,13 @@ void ApiWrap::saveDraftsToCloud() {
|
||||||
|
|
||||||
auto flags = MTPmessages_SaveDraft::Flags(0);
|
auto flags = MTPmessages_SaveDraft::Flags(0);
|
||||||
auto &textWithTags = cloudDraft->textWithTags;
|
auto &textWithTags = cloudDraft->textWithTags;
|
||||||
if (cloudDraft->previewState != Data::PreviewState::Allowed) {
|
if (cloudDraft->webpage.removed) {
|
||||||
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
|
flags |= MTPmessages_SaveDraft::Flag::f_no_webpage;
|
||||||
|
} else if (!cloudDraft->webpage.url.isEmpty()) {
|
||||||
|
flags |= MTPmessages_SaveDraft::Flag::f_media;
|
||||||
}
|
}
|
||||||
if (cloudDraft->msgId) {
|
if (cloudDraft->reply.messageId || cloudDraft->reply.topicRootId) {
|
||||||
flags |= MTPmessages_SaveDraft::Flag::f_reply_to_msg_id;
|
flags |= MTPmessages_SaveDraft::Flag::f_reply_to;
|
||||||
}
|
|
||||||
if (cloudDraft->topicRootId) {
|
|
||||||
flags |= MTPmessages_SaveDraft::Flag::f_top_msg_id;
|
|
||||||
}
|
}
|
||||||
if (!textWithTags.tags.isEmpty()) {
|
if (!textWithTags.tags.isEmpty()) {
|
||||||
flags |= MTPmessages_SaveDraft::Flag::f_entities;
|
flags |= MTPmessages_SaveDraft::Flag::f_entities;
|
||||||
|
@ -2170,11 +2169,13 @@ void ApiWrap::saveDraftsToCloud() {
|
||||||
history->startSavingCloudDraft(topicRootId);
|
history->startSavingCloudDraft(topicRootId);
|
||||||
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
|
cloudDraft->saveRequestId = request(MTPmessages_SaveDraft(
|
||||||
MTP_flags(flags),
|
MTP_flags(flags),
|
||||||
MTP_int(cloudDraft->msgId),
|
ReplyToForMTP(history, cloudDraft->reply),
|
||||||
MTP_int(cloudDraft->topicRootId),
|
|
||||||
history->peer->input,
|
history->peer->input,
|
||||||
MTP_string(textWithTags.text),
|
MTP_string(textWithTags.text),
|
||||||
entities
|
entities,
|
||||||
|
Data::WebPageForMTP(
|
||||||
|
cloudDraft->webpage,
|
||||||
|
textWithTags.text.isEmpty())
|
||||||
)).done([=](const MTPBool &result, const MTP::Response &response) {
|
)).done([=](const MTPBool &result, const MTP::Response &response) {
|
||||||
const auto requestId = response.requestId;
|
const auto requestId = response.requestId;
|
||||||
history->finishSavingCloudDraft(
|
history->finishSavingCloudDraft(
|
||||||
|
@ -2261,7 +2262,7 @@ void ApiWrap::gotStickerSet(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
|
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
|
||||||
if (page->pendingTill <= 0) {
|
if (page->failed || !page->pendingTill) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_webPagesPending.emplace(page, 0);
|
_webPagesPending.emplace(page, 0);
|
||||||
|
@ -2566,7 +2567,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &resu
|
||||||
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
||||||
if (i->second == req) {
|
if (i->second == req) {
|
||||||
if (i->first->pendingTill > 0) {
|
if (i->first->pendingTill > 0) {
|
||||||
i->first->pendingTill = -1;
|
i->first->pendingTill = 0;
|
||||||
|
i->first->failed = 1;
|
||||||
_session->data().notifyWebPageUpdateDelayed(i->first);
|
_session->data().notifyWebPageUpdateDelayed(i->first);
|
||||||
}
|
}
|
||||||
i = _webPagesPending.erase(i);
|
i = _webPagesPending.erase(i);
|
||||||
|
@ -3612,9 +3614,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
action.generateLocal = true;
|
action.generateLocal = true;
|
||||||
sendAction(action);
|
sendAction(action);
|
||||||
|
|
||||||
const auto replyToId = action.replyTo.msgId;
|
const auto replyTo = action.replyTo.messageId
|
||||||
const auto replyTo = replyToId
|
? peer->owner().message(action.replyTo.messageId)
|
||||||
? peer->owner().message(peer, replyToId)
|
|
||||||
: nullptr;
|
: nullptr;
|
||||||
const auto topicRootId = replyTo
|
const auto topicRootId = replyTo
|
||||||
? replyTo->topicRootId()
|
? replyTo->topicRootId()
|
||||||
|
@ -3642,7 +3643,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
|
|
||||||
auto &histories = history->owner().histories();
|
auto &histories = history->owner().histories();
|
||||||
|
|
||||||
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
|
const auto exactWebPage = !message.webPage.url.isEmpty();
|
||||||
|
auto isFirst = true;
|
||||||
|
while (TextUtilities::CutPart(sending, left, MaxMessageSize)
|
||||||
|
|| (isFirst && exactWebPage)) {
|
||||||
|
TextUtilities::Trim(left);
|
||||||
|
const auto isLast = left.empty();
|
||||||
|
|
||||||
auto newId = FullMsgId(
|
auto newId = FullMsgId(
|
||||||
peer->id,
|
peer->id,
|
||||||
_session->data().nextLocalMessageId());
|
_session->data().nextLocalMessageId());
|
||||||
|
@ -3656,26 +3663,52 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
MTPstring msgText(MTP_string(sending.text));
|
MTPstring msgText(MTP_string(sending.text));
|
||||||
auto flags = NewMessageFlags(peer);
|
auto flags = NewMessageFlags(peer);
|
||||||
auto sendFlags = MTPmessages_SendMessage::Flags(0);
|
auto sendFlags = MTPmessages_SendMessage::Flags(0);
|
||||||
|
auto mediaFlags = MTPmessages_SendMedia::Flags(0);
|
||||||
if (action.replyTo) {
|
if (action.replyTo) {
|
||||||
flags |= MessageFlag::HasReplyInfo;
|
flags |= MessageFlag::HasReplyInfo;
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||||
}
|
}
|
||||||
|
const auto ignoreWebPage = message.webPage.removed
|
||||||
|
|| (exactWebPage && !isLast);
|
||||||
|
const auto manualWebPage = exactWebPage
|
||||||
|
&& !ignoreWebPage
|
||||||
|
&& (message.webPage.manual || (isLast && !isFirst));
|
||||||
const auto replyHeader = NewMessageReplyHeader(action);
|
const auto replyHeader = NewMessageReplyHeader(action);
|
||||||
MTPMessageMedia media = MTP_messageMediaEmpty();
|
MTPMessageMedia media = MTP_messageMediaEmpty();
|
||||||
if (message.webPageId == CancelledWebPageId) {
|
if (ignoreWebPage) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||||
} else if (message.webPageId) {
|
} else if (exactWebPage) {
|
||||||
auto page = _session->data().webpage(message.webPageId);
|
using PageFlag = MTPDmessageMediaWebPage::Flag;
|
||||||
|
using PendingFlag = MTPDwebPagePending::Flag;
|
||||||
|
const auto &fields = message.webPage;
|
||||||
|
const auto page = _session->data().webpage(fields.id);
|
||||||
media = MTP_messageMediaWebPage(
|
media = MTP_messageMediaWebPage(
|
||||||
|
MTP_flags(PageFlag()
|
||||||
|
| (manualWebPage ? PageFlag::f_manual : PageFlag())
|
||||||
|
| (fields.forceLargeMedia
|
||||||
|
? PageFlag::f_force_large_media
|
||||||
|
: PageFlag())
|
||||||
|
| (fields.forceSmallMedia
|
||||||
|
? PageFlag::f_force_small_media
|
||||||
|
: PageFlag())),
|
||||||
MTP_webPagePending(
|
MTP_webPagePending(
|
||||||
MTP_long(page->id),
|
MTP_flags(PendingFlag::f_url),
|
||||||
|
MTP_long(fields.id),
|
||||||
|
MTP_string(fields.url),
|
||||||
MTP_int(page->pendingTill)));
|
MTP_int(page->pendingTill)));
|
||||||
}
|
}
|
||||||
const auto anonymousPost = peer->amAnonymous();
|
const auto anonymousPost = peer->amAnonymous();
|
||||||
const auto silentPost = ShouldSendSilent(peer, action.options);
|
const auto silentPost = ShouldSendSilent(peer, action.options);
|
||||||
FillMessagePostFlags(action, peer, flags);
|
FillMessagePostFlags(action, peer, flags);
|
||||||
|
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
|
||||||
|
flags |= MessageFlag::InvertMedia;
|
||||||
|
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
|
||||||
|
}
|
||||||
if (silentPost) {
|
if (silentPost) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
||||||
}
|
}
|
||||||
const auto sentEntities = Api::EntitiesToMTP(
|
const auto sentEntities = Api::EntitiesToMTP(
|
||||||
_session,
|
_session,
|
||||||
|
@ -3683,11 +3716,13 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
Api::ConvertOption::SkipLocal);
|
Api::ConvertOption::SkipLocal);
|
||||||
if (!sentEntities.v.isEmpty()) {
|
if (!sentEntities.v.isEmpty()) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||||
}
|
}
|
||||||
const auto clearCloudDraft = action.clearDraft;
|
const auto clearCloudDraft = action.clearDraft;
|
||||||
const auto topicRootId = action.replyTo.topicRootId;
|
const auto topicRootId = action.replyTo.topicRootId;
|
||||||
if (clearCloudDraft) {
|
if (clearCloudDraft) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||||
history->clearCloudDraft(topicRootId);
|
history->clearCloudDraft(topicRootId);
|
||||||
history->startSavingCloudDraft(topicRootId);
|
history->startSavingCloudDraft(topicRootId);
|
||||||
}
|
}
|
||||||
|
@ -3699,6 +3734,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
: _session->userPeerId();
|
: _session->userPeerId();
|
||||||
if (sendAs) {
|
if (sendAs) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_send_as;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_send_as;
|
||||||
}
|
}
|
||||||
const auto messagePostAuthor = peer->isBroadcast()
|
const auto messagePostAuthor = peer->isBroadcast()
|
||||||
? _session->user()->name()
|
? _session->user()->name()
|
||||||
|
@ -3706,6 +3742,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
if (action.options.scheduled) {
|
if (action.options.scheduled) {
|
||||||
flags |= MessageFlag::IsOrWasScheduled;
|
flags |= MessageFlag::IsOrWasScheduled;
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
|
||||||
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
|
||||||
}
|
}
|
||||||
const auto viaBotId = UserId();
|
const auto viaBotId = UserId();
|
||||||
lastMessage = history->addNewLocalMessage(
|
lastMessage = history->addNewLocalMessage(
|
||||||
|
@ -3719,27 +3756,18 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
sending,
|
sending,
|
||||||
media,
|
media,
|
||||||
HistoryMessageMarkupData());
|
HistoryMessageMarkupData());
|
||||||
histories.sendPreparedMessage(
|
const auto done = [=](
|
||||||
history,
|
const MTPUpdates &result,
|
||||||
action.replyTo,
|
const MTP::Response &response) {
|
||||||
randomId,
|
|
||||||
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
|
|
||||||
MTP_flags(sendFlags),
|
|
||||||
peer->input,
|
|
||||||
Data::Histories::ReplyToPlaceholder(),
|
|
||||||
msgText,
|
|
||||||
MTP_long(randomId),
|
|
||||||
MTPReplyMarkup(),
|
|
||||||
sentEntities,
|
|
||||||
MTP_int(action.options.scheduled),
|
|
||||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
|
||||||
), [=](const MTPUpdates &result, const MTP::Response &response) {
|
|
||||||
if (clearCloudDraft) {
|
if (clearCloudDraft) {
|
||||||
history->finishSavingCloudDraft(
|
history->finishSavingCloudDraft(
|
||||||
topicRootId,
|
topicRootId,
|
||||||
UnixtimeFromMsgId(response.outerMsgId));
|
UnixtimeFromMsgId(response.outerMsgId));
|
||||||
}
|
}
|
||||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
};
|
||||||
|
const auto fail = [=](
|
||||||
|
const MTP::Error &error,
|
||||||
|
const MTP::Response &response) {
|
||||||
if (error.type() == u"MESSAGE_EMPTY"_q) {
|
if (error.type() == u"MESSAGE_EMPTY"_q) {
|
||||||
lastMessage->destroy();
|
lastMessage->destroy();
|
||||||
} else {
|
} else {
|
||||||
|
@ -3750,7 +3778,44 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
topicRootId,
|
topicRootId,
|
||||||
UnixtimeFromMsgId(response.outerMsgId));
|
UnixtimeFromMsgId(response.outerMsgId));
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
if (exactWebPage
|
||||||
|
&& !ignoreWebPage
|
||||||
|
&& (manualWebPage || sending.empty())) {
|
||||||
|
histories.sendPreparedMessage(
|
||||||
|
history,
|
||||||
|
action.replyTo,
|
||||||
|
randomId,
|
||||||
|
Data::Histories::PrepareMessage<MTPmessages_SendMedia>(
|
||||||
|
MTP_flags(mediaFlags),
|
||||||
|
peer->input,
|
||||||
|
Data::Histories::ReplyToPlaceholder(),
|
||||||
|
Data::WebPageForMTP(message.webPage, true),
|
||||||
|
msgText,
|
||||||
|
MTP_long(randomId),
|
||||||
|
MTPReplyMarkup(),
|
||||||
|
sentEntities,
|
||||||
|
MTP_int(message.action.options.scheduled),
|
||||||
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
||||||
|
), done, fail);
|
||||||
|
} else {
|
||||||
|
histories.sendPreparedMessage(
|
||||||
|
history,
|
||||||
|
action.replyTo,
|
||||||
|
randomId,
|
||||||
|
Data::Histories::PrepareMessage<MTPmessages_SendMessage>(
|
||||||
|
MTP_flags(sendFlags),
|
||||||
|
peer->input,
|
||||||
|
Data::Histories::ReplyToPlaceholder(),
|
||||||
|
msgText,
|
||||||
|
MTP_long(randomId),
|
||||||
|
MTPReplyMarkup(),
|
||||||
|
sentEntities,
|
||||||
|
MTP_int(action.options.scheduled),
|
||||||
|
(sendAs ? sendAs->input : MTP_inputPeerEmpty())
|
||||||
|
), done, fail);
|
||||||
|
}
|
||||||
|
isFirst = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
finishForwarding(action);
|
finishForwarding(action);
|
||||||
|
@ -3815,7 +3880,7 @@ void ApiWrap::sendInlineResult(
|
||||||
? (*localMessageId)
|
? (*localMessageId)
|
||||||
: _session->data().nextLocalMessageId());
|
: _session->data().nextLocalMessageId());
|
||||||
const auto randomId = base::RandomValue<uint64>();
|
const auto randomId = base::RandomValue<uint64>();
|
||||||
const auto topicRootId = action.replyTo.msgId
|
const auto topicRootId = action.replyTo.messageId
|
||||||
? action.replyTo.topicRootId
|
? action.replyTo.topicRootId
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,8 @@ BackgroundPreviewBox::BackgroundPreviewBox(
|
||||||
, _controller(controller)
|
, _controller(controller)
|
||||||
, _forPeer(args.forPeer)
|
, _forPeer(args.forPeer)
|
||||||
, _fromMessageId(args.fromMessageId)
|
, _fromMessageId(args.fromMessageId)
|
||||||
, _chatStyle(std::make_unique<Ui::ChatStyle>())
|
, _chatStyle(std::make_unique<Ui::ChatStyle>(
|
||||||
|
controller->session().colorIndicesValue()))
|
||||||
, _serviceHistory(_controller->session().data().history(
|
, _serviceHistory(_controller->session().data().history(
|
||||||
PeerData::kServiceNotificationsId))
|
PeerData::kServiceNotificationsId))
|
||||||
, _service(nullptr)
|
, _service(nullptr)
|
||||||
|
@ -434,7 +435,7 @@ void BackgroundPreviewBox::rebuildButtons(bool dark) {
|
||||||
clearButtons();
|
clearButtons();
|
||||||
addButton(_forPeer
|
addButton(_forPeer
|
||||||
? tr::lng_background_apply_button()
|
? tr::lng_background_apply_button()
|
||||||
: tr::lng_background_apply(), [=] { apply(); });
|
: tr::lng_settings_apply(), [=] { apply(); });
|
||||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||||
if (!_forPeer && _paper.hasShareUrl()) {
|
if (!_forPeer && _paper.hasShareUrl()) {
|
||||||
addLeftButton(tr::lng_background_share(), [=] { share(); });
|
addLeftButton(tr::lng_background_share(), [=] { share(); });
|
||||||
|
|
|
@ -8,26 +8,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/gift_premium_box.h"
|
#include "boxes/gift_premium_box.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_premium.h"
|
||||||
#include "api/api_premium_option.h"
|
#include "api/api_premium_option.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
#include "base/weak_ptr.h"
|
#include "base/weak_ptr.h"
|
||||||
|
#include "boxes/peers/prepare_short_info_box.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_media_types.h" // Data::Giveaway
|
||||||
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_subscription_option.h"
|
#include "data/data_subscription_option.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "mainwidget.h"
|
||||||
#include "settings/settings_premium.h"
|
#include "settings/settings_premium.h"
|
||||||
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
#include "ui/basic_click_handlers.h" // UrlClickHandler::Open.
|
||||||
|
#include "ui/boxes/boost_box.h" // StartFireworks.
|
||||||
#include "ui/controls/userpic_button.h"
|
#include "ui/controls/userpic_button.h"
|
||||||
#include "ui/effects/premium_graphics.h"
|
#include "ui/effects/premium_graphics.h"
|
||||||
#include "ui/effects/premium_stars_colored.h"
|
#include "ui/effects/premium_stars_colored.h"
|
||||||
|
#include "ui/effects/premium_top_bar.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/text/format_values.h"
|
#include "ui/text/format_values.h"
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
#include "ui/widgets/gradient_round_button.h"
|
#include "ui/widgets/gradient_round_button.h"
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
|
#include "ui/wrap/table_layout.h"
|
||||||
|
#include "window/window_peer_menu.h" // ShowChooseRecipientBox.
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
|
@ -35,6 +45,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_premium.h"
|
#include "styles/style_premium.h"
|
||||||
|
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kDiscountDivider = 5.;
|
constexpr auto kDiscountDivider = 5.;
|
||||||
|
@ -225,6 +237,131 @@ void GiftBox(
|
||||||
}, box->lifetime());
|
}, box->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GiftCodeLink {
|
||||||
|
QString text;
|
||||||
|
QString link;
|
||||||
|
};
|
||||||
|
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
const QString &slug) {
|
||||||
|
const auto path = u"giftcode/"_q + slug;
|
||||||
|
return {
|
||||||
|
session->createInternalLink(path),
|
||||||
|
session->createInternalLinkFull(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkCopyIcon(
|
||||||
|
not_null<QWidget*> parent) {
|
||||||
|
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||||
|
const auto raw = result.data();
|
||||||
|
|
||||||
|
raw->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(raw);
|
||||||
|
const auto &icon = st::giveawayGiftCodeLinkCopy;
|
||||||
|
const auto left = (raw->width() - icon.width()) / 2;
|
||||||
|
const auto top = (raw->height() - icon.height()) / 2;
|
||||||
|
icon.paint(p, left, top, raw->width());
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->resize(
|
||||||
|
st::giveawayGiftCodeLinkCopyWidth,
|
||||||
|
st::giveawayGiftCodeLinkHeight);
|
||||||
|
|
||||||
|
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] tr::phrase<lngtag_count> GiftDurationPhrase(int months) {
|
||||||
|
return (months < 12)
|
||||||
|
? tr::lng_premium_gift_duration_months
|
||||||
|
: tr::lng_premium_gift_duration_years;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
PeerId id) {
|
||||||
|
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||||
|
const auto raw = result.data();
|
||||||
|
|
||||||
|
const auto &st = st::giveawayGiftCodeUserpic;
|
||||||
|
raw->resize(raw->width(), st.photoSize);
|
||||||
|
|
||||||
|
const auto peer = controller->session().data().peer(id);
|
||||||
|
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
peer->name(),
|
||||||
|
st::giveawayGiftCodeValue);
|
||||||
|
raw->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
const auto position = st::giveawayGiftCodeNamePosition;
|
||||||
|
label->resizeToNaturalWidth(width - position.x());
|
||||||
|
label->moveToLeft(position.x(), position.y(), width);
|
||||||
|
const auto top = (raw->height() - userpic->height()) / 2;
|
||||||
|
userpic->moveToLeft(0, top, width);
|
||||||
|
}, label->lifetime());
|
||||||
|
|
||||||
|
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||||
|
|
||||||
|
raw->setClickedCallback([=] {
|
||||||
|
controller->show(PrepareShortInfoBox(peer, controller));
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTableRow(
|
||||||
|
not_null<Ui::TableLayout*> table,
|
||||||
|
rpl::producer<QString> label,
|
||||||
|
object_ptr<Ui::RpWidget> value,
|
||||||
|
style::margins valueMargins) {
|
||||||
|
table->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
table,
|
||||||
|
std::move(label),
|
||||||
|
st::giveawayGiftCodeLabel),
|
||||||
|
std::move(value),
|
||||||
|
st::giveawayGiftCodeLabelMargin,
|
||||||
|
valueMargins);
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<Ui::FlatLabel*> AddTableRow(
|
||||||
|
not_null<Ui::TableLayout*> table,
|
||||||
|
rpl::producer<QString> label,
|
||||||
|
rpl::producer<TextWithEntities> value) {
|
||||||
|
auto widget = object_ptr<Ui::FlatLabel>(
|
||||||
|
table,
|
||||||
|
std::move(value),
|
||||||
|
st::giveawayGiftCodeValue);
|
||||||
|
const auto result = widget.data();
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
std::move(label),
|
||||||
|
std::move(widget),
|
||||||
|
st::giveawayGiftCodeValueMargin);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddTableRow(
|
||||||
|
not_null<Ui::TableLayout*> table,
|
||||||
|
rpl::producer<QString> label,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
PeerId id) {
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
std::move(label),
|
||||||
|
MakePeerTableValue(table, controller, id),
|
||||||
|
st::giveawayGiftCodePeerMargin);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
GiftPremiumValidator::GiftPremiumValidator(
|
GiftPremiumValidator::GiftPremiumValidator(
|
||||||
|
@ -263,3 +400,405 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
|
||||||
_requestId = 0;
|
_requestId = 0;
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<QString> GiftDurationValue(int months) {
|
||||||
|
return GiftDurationPhrase(months)(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(float64((months < 12) ? months : (months / 12))));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GiftDuration(int months) {
|
||||||
|
return GiftDurationPhrase(months)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
(months < 12) ? months : (months / 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GiftCodeBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
const QString &slug) {
|
||||||
|
struct State {
|
||||||
|
rpl::variable<Api::GiftCode> data;
|
||||||
|
rpl::variable<bool> used;
|
||||||
|
bool sent = false;
|
||||||
|
};
|
||||||
|
const auto session = &controller->session();
|
||||||
|
const auto state = box->lifetime().make_state<State>(State{});
|
||||||
|
state->data = session->api().premium().giftCodeValue(slug);
|
||||||
|
state->used = state->data.value(
|
||||||
|
) | rpl::map([=](const Api::GiftCode &data) {
|
||||||
|
return data.used;
|
||||||
|
});
|
||||||
|
|
||||||
|
box->setWidth(st::boxWideWidth);
|
||||||
|
box->setStyle(st::giveawayGiftCodeBox);
|
||||||
|
box->setNoContentMargin(true);
|
||||||
|
|
||||||
|
const auto bar = box->setPinnedToTopContent(
|
||||||
|
object_ptr<Ui::Premium::TopBar>(
|
||||||
|
box,
|
||||||
|
st::giveawayGiftCodeCover,
|
||||||
|
nullptr,
|
||||||
|
rpl::conditional(
|
||||||
|
state->used.value(),
|
||||||
|
tr::lng_gift_link_used_title(),
|
||||||
|
tr::lng_gift_link_title()),
|
||||||
|
rpl::conditional(
|
||||||
|
state->used.value(),
|
||||||
|
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
|
||||||
|
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
|
||||||
|
true));
|
||||||
|
|
||||||
|
const auto max = st::giveawayGiftCodeTopHeight;
|
||||||
|
bar->setMaximumHeight(max);
|
||||||
|
bar->setMinimumHeight(st::infoLayerTopBarHeight);
|
||||||
|
|
||||||
|
bar->resize(bar->width(), bar->maximumHeight());
|
||||||
|
|
||||||
|
const auto link = MakeGiftCodeLink(&controller->session(), slug);
|
||||||
|
box->addRow(
|
||||||
|
Ui::MakeLinkLabel(
|
||||||
|
box,
|
||||||
|
rpl::single(link.text),
|
||||||
|
rpl::single(link.link),
|
||||||
|
box->uiShow(),
|
||||||
|
MakeLinkCopyIcon(box)),
|
||||||
|
st::giveawayGiftCodeLinkMargin);
|
||||||
|
|
||||||
|
auto table = box->addRow(
|
||||||
|
object_ptr<Ui::TableLayout>(
|
||||||
|
box,
|
||||||
|
st::giveawayGiftCodeTable),
|
||||||
|
st::giveawayGiftCodeTableMargin);
|
||||||
|
const auto current = state->data.current();
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_from(),
|
||||||
|
controller,
|
||||||
|
current.from);
|
||||||
|
if (current.to) {
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_to(),
|
||||||
|
controller,
|
||||||
|
current.to);
|
||||||
|
} else {
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_to(),
|
||||||
|
tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
|
||||||
|
}
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_gift(),
|
||||||
|
tr::lng_gift_link_gift_premium(
|
||||||
|
lt_duration,
|
||||||
|
GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
|
||||||
|
Ui::Text::WithEntities));
|
||||||
|
const auto reason = AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_reason(),
|
||||||
|
(current.giveawayId
|
||||||
|
? ((current.to
|
||||||
|
? tr::lng_gift_link_reason_giveaway
|
||||||
|
: tr::lng_gift_link_reason_unclaimed)(
|
||||||
|
) | Ui::Text::ToLink())
|
||||||
|
: current.giveaway
|
||||||
|
? ((current.to
|
||||||
|
? tr::lng_gift_link_reason_giveaway
|
||||||
|
: tr::lng_gift_link_reason_unclaimed)(
|
||||||
|
Ui::Text::WithEntities
|
||||||
|
) | rpl::type_erased())
|
||||||
|
: tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
|
||||||
|
reason->setClickHandlerFilter([=](const auto &...) {
|
||||||
|
controller->showPeerHistory(
|
||||||
|
current.from,
|
||||||
|
Window::SectionShow::Way::Forward,
|
||||||
|
current.giveawayId);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (current.date) {
|
||||||
|
AddTableRow(
|
||||||
|
table,
|
||||||
|
tr::lng_gift_link_label_date(),
|
||||||
|
rpl::single(Ui::Text::WithEntities(
|
||||||
|
langDateTime(base::unixtime::parse(current.date)))));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shareLink = tr::lng_gift_link_also_send_link(
|
||||||
|
) | rpl::map([](const QString &text) {
|
||||||
|
return Ui::Text::Link(text);
|
||||||
|
});
|
||||||
|
auto richDate = [](const Api::GiftCode &data) {
|
||||||
|
return TextWithEntities{
|
||||||
|
langDateTime(base::unixtime::parse(data.used)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const auto footer = box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
rpl::conditional(
|
||||||
|
state->used.value(),
|
||||||
|
tr::lng_gift_link_used_footer(
|
||||||
|
lt_date,
|
||||||
|
state->data.value() | rpl::map(richDate),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
tr::lng_gift_link_also_send(
|
||||||
|
lt_link,
|
||||||
|
std::move(shareLink),
|
||||||
|
Ui::Text::WithEntities)),
|
||||||
|
st::giveawayGiftCodeFooter),
|
||||||
|
st::giveawayGiftCodeFooterMargin);
|
||||||
|
footer->setClickHandlerFilter([=](const auto &...) {
|
||||||
|
const auto chosen = [=](not_null<Data::Thread*> thread) {
|
||||||
|
const auto content = controller->content();
|
||||||
|
return content->shareUrl(
|
||||||
|
thread,
|
||||||
|
MakeGiftCodeLink(&controller->session(), slug).link,
|
||||||
|
QString());
|
||||||
|
};
|
||||||
|
Window::ShowChooseRecipientBox(controller, chosen);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||||
|
box.get(),
|
||||||
|
st::boxTitleClose);
|
||||||
|
close->setClickedCallback([=] {
|
||||||
|
box->closeBox();
|
||||||
|
});
|
||||||
|
box->widthValue(
|
||||||
|
) | rpl::start_with_next([=](int width) {
|
||||||
|
close->moveToRight(0, 0);
|
||||||
|
}, box->lifetime());
|
||||||
|
|
||||||
|
const auto button = box->addButton(rpl::conditional(
|
||||||
|
state->used.value(),
|
||||||
|
tr::lng_box_ok(),
|
||||||
|
tr::lng_gift_link_use()
|
||||||
|
), [=] {
|
||||||
|
if (state->used.current()) {
|
||||||
|
box->closeBox();
|
||||||
|
} else if (!state->sent) {
|
||||||
|
state->sent = true;
|
||||||
|
const auto done = crl::guard(box, [=](const QString &error) {
|
||||||
|
if (error.isEmpty()) {
|
||||||
|
auto copy = state->data.current();
|
||||||
|
copy.used = base::unixtime::now();
|
||||||
|
state->data = std::move(copy);
|
||||||
|
|
||||||
|
Ui::StartFireworks(box->parentWidget());
|
||||||
|
} else {
|
||||||
|
box->uiShow()->showToast(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controller->session().api().premium().applyGiftCode(slug, done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
|
||||||
|
const auto buttonWidth = st::boxWideWidth
|
||||||
|
- buttonPadding.left()
|
||||||
|
- buttonPadding.right();
|
||||||
|
button->widthValue() | rpl::filter([=] {
|
||||||
|
return (button->widthNoMargins() != buttonWidth);
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
button->resizeToWidth(buttonWidth);
|
||||||
|
}, button->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolveGiftCode(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
const QString &slug) {
|
||||||
|
const auto done = [=](Api::GiftCode code) {
|
||||||
|
if (!code) {
|
||||||
|
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
||||||
|
} else {
|
||||||
|
controller->show(Box(GiftCodeBox, controller, slug));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
controller->session().api().premium().checkGiftCode(
|
||||||
|
slug,
|
||||||
|
crl::guard(controller, done));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GiveawayInfoBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
Data::Giveaway giveaway,
|
||||||
|
Api::GiveawayInfo info) {
|
||||||
|
using State = Api::GiveawayState;
|
||||||
|
const auto finished = (info.state == State::Finished)
|
||||||
|
|| (info.state == State::Refunded);
|
||||||
|
|
||||||
|
box->setTitle((finished
|
||||||
|
? tr::lng_prizes_end_title
|
||||||
|
: tr::lng_prizes_how_title)());
|
||||||
|
|
||||||
|
const auto first = !giveaway.channels.empty()
|
||||||
|
? giveaway.channels.front()->name()
|
||||||
|
: u"channel"_q;
|
||||||
|
auto text = (finished
|
||||||
|
? tr::lng_prizes_end_text
|
||||||
|
: tr::lng_prizes_how_text)(
|
||||||
|
tr::now,
|
||||||
|
lt_admins,
|
||||||
|
tr::lng_prizes_admins(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
giveaway.quantity,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(first),
|
||||||
|
lt_duration,
|
||||||
|
TextWithEntities{ GiftDuration(giveaway.months) },
|
||||||
|
Ui::Text::RichLangValue),
|
||||||
|
Ui::Text::RichLangValue);
|
||||||
|
const auto many = (giveaway.channels.size() > 1);
|
||||||
|
const auto count = info.winnersCount
|
||||||
|
? info.winnersCount
|
||||||
|
: giveaway.quantity;
|
||||||
|
auto winners = giveaway.all
|
||||||
|
? (many
|
||||||
|
? tr::lng_prizes_winners_all_of_many
|
||||||
|
: tr::lng_prizes_winners_all_of_one)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
count,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(first),
|
||||||
|
Ui::Text::RichLangValue)
|
||||||
|
: (many
|
||||||
|
? tr::lng_prizes_winners_new_of_many
|
||||||
|
: tr::lng_prizes_winners_new_of_one)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
count,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(first),
|
||||||
|
lt_start_date,
|
||||||
|
Ui::Text::Bold(
|
||||||
|
langDateTime(base::unixtime::parse(info.startDate))),
|
||||||
|
Ui::Text::RichLangValue);
|
||||||
|
text.append("\n\n").append((finished
|
||||||
|
? tr::lng_prizes_end_when_finish
|
||||||
|
: tr::lng_prizes_how_when_finish)(
|
||||||
|
tr::now,
|
||||||
|
lt_date,
|
||||||
|
Ui::Text::Bold(langDayOfMonthFull(
|
||||||
|
base::unixtime::parse(giveaway.untilDate).date())),
|
||||||
|
lt_winners,
|
||||||
|
winners,
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
if (info.activatedCount > 0) {
|
||||||
|
text.append(' ').append(tr::lng_prizes_end_activated(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
info.activatedCount));
|
||||||
|
}
|
||||||
|
if (!info.giftCode.isEmpty()) {
|
||||||
|
text.append("\n\n");
|
||||||
|
text.append(tr::lng_prizes_you_won(
|
||||||
|
tr::now,
|
||||||
|
lt_cup,
|
||||||
|
QString::fromUtf8("\xf0\x9f\x8f\x86")));
|
||||||
|
} else if (info.state == State::Finished) {
|
||||||
|
text.append("\n\n");
|
||||||
|
text.append(tr::lng_prizes_you_didnt(tr::now));
|
||||||
|
} else if (info.state == State::Preparing) {
|
||||||
|
|
||||||
|
} else if (info.state != State::Refunded) {
|
||||||
|
if (info.adminChannelId) {
|
||||||
|
const auto channel = controller->session().data().channel(
|
||||||
|
info.adminChannelId);
|
||||||
|
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
|
||||||
|
tr::now,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(channel->name()),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else if (info.tooEarlyDate) {
|
||||||
|
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
|
||||||
|
tr::now,
|
||||||
|
lt_date,
|
||||||
|
Ui::Text::Bold(
|
||||||
|
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else if (!info.disallowedCountry.isEmpty()) {
|
||||||
|
text.append("\n\n").append(tr::lng_prizes_how_no_country(
|
||||||
|
tr::now,
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else if (info.participating) {
|
||||||
|
text.append("\n\n").append((many
|
||||||
|
? tr::lng_prizes_how_yes_joined_many
|
||||||
|
: tr::lng_prizes_how_yes_joined_one)(
|
||||||
|
tr::now,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(first),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
} else {
|
||||||
|
text.append("\n\n").append((many
|
||||||
|
? tr::lng_prizes_how_participate_many
|
||||||
|
: tr::lng_prizes_how_participate_one)(
|
||||||
|
tr::now,
|
||||||
|
lt_channel,
|
||||||
|
Ui::Text::Bold(first),
|
||||||
|
lt_date,
|
||||||
|
Ui::Text::Bold(langDayOfMonthFull(
|
||||||
|
base::unixtime::parse(giveaway.untilDate).date())),
|
||||||
|
Ui::Text::RichLangValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto padding = st::boxPadding;
|
||||||
|
box->addRow(
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box.get(),
|
||||||
|
rpl::single(std::move(text)),
|
||||||
|
st::boxLabel),
|
||||||
|
{ padding.left(), 0, padding.right(), padding.bottom() });
|
||||||
|
|
||||||
|
if (info.state == State::Refunded) {
|
||||||
|
const auto wrap = box->addRow(
|
||||||
|
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||||
|
box.get(),
|
||||||
|
object_ptr<Ui::FlatLabel>(
|
||||||
|
box.get(),
|
||||||
|
tr::lng_prizes_cancelled(),
|
||||||
|
st::giveawayRefundedLabel),
|
||||||
|
st::giveawayRefundedPadding),
|
||||||
|
{ padding.left(), 0, padding.right(), padding.bottom() });
|
||||||
|
const auto bg = wrap->lifetime().make_state<Ui::RoundRect>(
|
||||||
|
st::boxRadius,
|
||||||
|
st::attentionBoxButton.textBgOver);
|
||||||
|
wrap->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(wrap);
|
||||||
|
bg->paint(p, wrap->rect());
|
||||||
|
}, wrap->lifetime());
|
||||||
|
}
|
||||||
|
if (const auto slug = info.giftCode; !slug.isEmpty()) {
|
||||||
|
box->addButton(tr::lng_prizes_view_prize(), [=] {
|
||||||
|
ResolveGiftCode(controller, slug);
|
||||||
|
});
|
||||||
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||||
|
} else {
|
||||||
|
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolveGiveawayInfo(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
Data::Giveaway giveaway) {
|
||||||
|
const auto show = [=](Api::GiveawayInfo info) {
|
||||||
|
if (!info) {
|
||||||
|
controller->showToast(
|
||||||
|
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||||
|
} else {
|
||||||
|
controller->show(
|
||||||
|
Box(GiveawayInfoBox, controller, giveaway, info));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
controller->session().api().premium().resolveGiveawayInfo(
|
||||||
|
peer,
|
||||||
|
messageId,
|
||||||
|
crl::guard(controller, show));
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class UserData;
|
class UserData;
|
||||||
|
|
||||||
|
namespace Api {
|
||||||
|
struct GiftCode;
|
||||||
|
} // namespace Api
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct Giveaway;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class GenericBox;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -29,3 +41,20 @@ private:
|
||||||
mtpRequestId _requestId = 0;
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||||
|
[[nodiscard]] QString GiftDuration(int months);
|
||||||
|
|
||||||
|
void GiftCodeBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
const QString &slug);
|
||||||
|
void ResolveGiftCode(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
const QString &slug);
|
||||||
|
|
||||||
|
void ResolveGiveawayInfo(
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
MsgId messageId,
|
||||||
|
Data::Giveaway giveaway);
|
||||||
|
|
|
@ -456,7 +456,7 @@ int PeerListController::descriptionTopSkipMin() const {
|
||||||
void PeerListBox::addSelectItem(
|
void PeerListBox::addSelectItem(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
anim::type animated) {
|
anim::type animated) {
|
||||||
const auto respect = _controller->respectSavedMessagesChat();
|
const auto respect = !_controller->savedMessagesChatStatus().isEmpty();
|
||||||
const auto text = (respect && peer->isSelf())
|
const auto text = (respect && peer->isSelf())
|
||||||
? tr::lng_saved_short(tr::now)
|
? tr::lng_saved_short(tr::now)
|
||||||
: (respect && peer->isRepliesChat())
|
: (respect && peer->isRepliesChat())
|
||||||
|
@ -579,8 +579,8 @@ void PeerListRow::refreshStatus() {
|
||||||
_statusType = StatusType::LastSeen;
|
_statusType = StatusType::LastSeen;
|
||||||
_statusValidTill = 0;
|
_statusValidTill = 0;
|
||||||
if (auto user = peer()->asUser()) {
|
if (auto user = peer()->asUser()) {
|
||||||
if (_isSavedMessagesChat) {
|
if (!_savedMessagesStatus.isEmpty()) {
|
||||||
setStatusText(tr::lng_saved_forward_here(tr::now));
|
setStatusText(_savedMessagesStatus);
|
||||||
} else {
|
} else {
|
||||||
auto time = base::unixtime::now();
|
auto time = base::unixtime::now();
|
||||||
setStatusText(Data::OnlineText(user, time));
|
setStatusText(Data::OnlineText(user, time));
|
||||||
|
@ -613,7 +613,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||||
if (!_initialized) {
|
if (!_initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto text = _isSavedMessagesChat
|
const auto text = !_savedMessagesStatus.isEmpty()
|
||||||
? tr::lng_saved_messages(tr::now)
|
? tr::lng_saved_messages(tr::now)
|
||||||
: _isRepliesMessagesChat
|
: _isRepliesMessagesChat
|
||||||
? tr::lng_replies_messages(tr::now)
|
? tr::lng_replies_messages(tr::now)
|
||||||
|
@ -683,7 +683,7 @@ QString PeerListRow::generateName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PeerListRow::generateShortName() {
|
QString PeerListRow::generateShortName() {
|
||||||
return _isSavedMessagesChat
|
return !_savedMessagesStatus.isEmpty()
|
||||||
? tr::lng_saved_short(tr::now)
|
? tr::lng_saved_short(tr::now)
|
||||||
: _isRepliesMessagesChat
|
: _isRepliesMessagesChat
|
||||||
? tr::lng_replies_messages(tr::now)
|
? tr::lng_replies_messages(tr::now)
|
||||||
|
@ -699,7 +699,7 @@ Ui::PeerUserpicView &PeerListRow::ensureUserpicView() {
|
||||||
|
|
||||||
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
||||||
bool forceRound) {
|
bool forceRound) {
|
||||||
const auto saved = _isSavedMessagesChat;
|
const auto saved = !_savedMessagesStatus.isEmpty();
|
||||||
const auto replies = _isRepliesMessagesChat;
|
const auto replies = _isRepliesMessagesChat;
|
||||||
const auto peer = this->peer();
|
const auto peer = this->peer();
|
||||||
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
|
auto userpic = saved ? Ui::PeerUserpicView() : ensureUserpicView();
|
||||||
|
@ -745,7 +745,9 @@ int PeerListRow::paintNameIconGetWidth(
|
||||||
int availableWidth,
|
int availableWidth,
|
||||||
int outerWidth,
|
int outerWidth,
|
||||||
bool selected) {
|
bool selected) {
|
||||||
if (special() || _isSavedMessagesChat || _isRepliesMessagesChat) {
|
if (special()
|
||||||
|
|| !_savedMessagesStatus.isEmpty()
|
||||||
|
|| _isRepliesMessagesChat) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return _bagde.drawGetWidth(
|
return _bagde.drawGetWidth(
|
||||||
|
@ -855,7 +857,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||||
auto iconBorderPen = st.checkbox.check.border->p;
|
auto iconBorderPen = st.checkbox.check.border->p;
|
||||||
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
||||||
|
|
||||||
if (_isSavedMessagesChat) {
|
if (!_savedMessagesStatus.isEmpty()) {
|
||||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||||
} else if (_isRepliesMessagesChat) {
|
} else if (_isRepliesMessagesChat) {
|
||||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||||
|
@ -1046,9 +1048,10 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||||
if (_controller->respectSavedMessagesChat() && !row->special()) {
|
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
|
||||||
|
if (!savedMessagesStatus.isEmpty() && !row->special()) {
|
||||||
if (row->peer()->isSelf()) {
|
if (row->peer()->isSelf()) {
|
||||||
row->setIsSavedMessagesChat(true);
|
row->setSavedMessagesChatStatus(savedMessagesStatus);
|
||||||
} else if (row->peer()->isRepliesChat()) {
|
} else if (row->peer()->isRepliesChat()) {
|
||||||
row->setIsRepliesMessagesChat(true);
|
row->setIsRepliesMessagesChat(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,8 +185,8 @@ public:
|
||||||
void setIsSearchResult(bool isSearchResult) {
|
void setIsSearchResult(bool isSearchResult) {
|
||||||
_isSearchResult = isSearchResult;
|
_isSearchResult = isSearchResult;
|
||||||
}
|
}
|
||||||
void setIsSavedMessagesChat(bool isSavedMessagesChat) {
|
void setSavedMessagesChatStatus(QString savedMessagesStatus) {
|
||||||
_isSavedMessagesChat = isSavedMessagesChat;
|
_savedMessagesStatus = savedMessagesStatus;
|
||||||
}
|
}
|
||||||
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
||||||
_isRepliesMessagesChat = isRepliesMessagesChat;
|
_isRepliesMessagesChat = isRepliesMessagesChat;
|
||||||
|
@ -278,12 +278,12 @@ private:
|
||||||
StatusType _statusType = StatusType::Online;
|
StatusType _statusType = StatusType::Online;
|
||||||
crl::time _statusValidTill = 0;
|
crl::time _statusValidTill = 0;
|
||||||
base::flat_set<QChar> _nameFirstLetters;
|
base::flat_set<QChar> _nameFirstLetters;
|
||||||
|
QString _savedMessagesStatus;
|
||||||
int _absoluteIndex = -1;
|
int _absoluteIndex = -1;
|
||||||
State _disabledState = State::Active;
|
State _disabledState = State::Active;
|
||||||
bool _hidden : 1 = false;
|
bool _hidden : 1 = false;
|
||||||
bool _initialized : 1 = false;
|
bool _initialized : 1 = false;
|
||||||
bool _isSearchResult : 1 = false;
|
bool _isSearchResult : 1 = false;
|
||||||
bool _isSavedMessagesChat : 1 = false;
|
|
||||||
bool _isRepliesMessagesChat : 1 = false;
|
bool _isRepliesMessagesChat : 1 = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -517,8 +517,8 @@ public:
|
||||||
void peerListSearchAddRow(PeerListRowId id) override;
|
void peerListSearchAddRow(PeerListRowId id) override;
|
||||||
void peerListSearchRefreshRows() override;
|
void peerListSearchRefreshRows() override;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool respectSavedMessagesChat() const {
|
[[nodiscard]] virtual QString savedMessagesChatStatus() const {
|
||||||
return false;
|
return QString();
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual int customRowHeight() {
|
[[nodiscard]] virtual int customRowHeight() {
|
||||||
Unexpected("PeerListController::customRowHeight.");
|
Unexpected("PeerListController::customRowHeight.");
|
||||||
|
|
|
@ -313,7 +313,7 @@ void ChatsListBoxController::rebuildRows() {
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
auto added = 0;
|
auto added = 0;
|
||||||
if (respectSavedMessagesChat()) {
|
if (!savedMessagesChatStatus().isEmpty()) {
|
||||||
if (appendRow(session().data().history(session().user()))) {
|
if (appendRow(session().data().history(session().user()))) {
|
||||||
++added;
|
++added;
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,7 @@ void ChatsListBoxController::rebuildRows() {
|
||||||
const auto history = static_cast<const Row&>(a).history();
|
const auto history = static_cast<const Row&>(a).history();
|
||||||
return history->inChatList();
|
return history->inChatList();
|
||||||
});
|
});
|
||||||
if (respectSavedMessagesChat()) {
|
if (!savedMessagesChatStatus().isEmpty()) {
|
||||||
delegate()->peerListPartitionRows([](const PeerListRow &a) {
|
delegate()->peerListPartitionRows([](const PeerListRow &a) {
|
||||||
return a.peer()->isSelf();
|
return a.peer()->isSelf();
|
||||||
});
|
});
|
||||||
|
@ -696,6 +696,10 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
|
||||||
|
return tr::lng_saved_forward_here(tr::now);
|
||||||
|
}
|
||||||
|
|
||||||
auto ChooseRecipientBoxController::createRow(
|
auto ChooseRecipientBoxController::createRow(
|
||||||
not_null<History*> history) -> std::unique_ptr<Row> {
|
not_null<History*> history) -> std::unique_ptr<Row> {
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
|
|
|
@ -218,9 +218,7 @@ public:
|
||||||
Main::Session &session() const override;
|
Main::Session &session() const override;
|
||||||
void rowClicked(not_null<PeerListRow*> row) override;
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
|
||||||
bool respectSavedMessagesChat() const override {
|
QString savedMessagesChatStatus() const override;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void prepareViewHook() override;
|
void prepareViewHook() override;
|
||||||
|
|
|
@ -45,8 +45,8 @@ public:
|
||||||
Main::Session &session() const override;
|
Main::Session &session() const override;
|
||||||
void rowClicked(not_null<PeerListRow*> row) override;
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
|
||||||
bool respectSavedMessagesChat() const override {
|
QString savedMessagesChatStatus() const override {
|
||||||
return true;
|
return tr::lng_saved_forward_here(tr::now);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
936
Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
Normal 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));
|
||||||
|
});
|
||||||
|
}
|
31
Telegram/SourceFiles/boxes/peers/edit_peer_color_box.h
Normal 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);
|
|
@ -13,8 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "boxes/add_contact_box.h"
|
#include "boxes/add_contact_box.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "boxes/peer_list_controllers.h"
|
|
||||||
#include "boxes/peers/edit_participants_box.h"
|
#include "boxes/peers/edit_participants_box.h"
|
||||||
|
#include "boxes/peers/edit_peer_color_box.h"
|
||||||
#include "boxes/peers/edit_peer_common.h"
|
#include "boxes/peers/edit_peer_common.h"
|
||||||
#include "boxes/peers/edit_peer_type_box.h"
|
#include "boxes/peers/edit_peer_type_box.h"
|
||||||
#include "boxes/peers/edit_peer_history_visibility_box.h"
|
#include "boxes/peers/edit_peer_history_visibility_box.h"
|
||||||
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "boxes/peers/edit_linked_chat_box.h"
|
#include "boxes/peers/edit_linked_chat_box.h"
|
||||||
#include "boxes/peers/edit_peer_requests_box.h"
|
#include "boxes/peers/edit_peer_requests_box.h"
|
||||||
#include "boxes/peers/edit_peer_reactions.h"
|
#include "boxes/peers/edit_peer_reactions.h"
|
||||||
|
#include "boxes/peer_list_controllers.h"
|
||||||
#include "boxes/stickers_box.h"
|
#include "boxes/stickers_box.h"
|
||||||
#include "boxes/username_box.h"
|
#include "boxes/username_box.h"
|
||||||
#include "ui/boxes/single_choice_box.h"
|
#include "ui/boxes/single_choice_box.h"
|
||||||
|
@ -302,6 +303,7 @@ private:
|
||||||
void fillLinkedChatButton();
|
void fillLinkedChatButton();
|
||||||
//void fillInviteLinkButton();
|
//void fillInviteLinkButton();
|
||||||
void fillForumButton();
|
void fillForumButton();
|
||||||
|
void fillColorIndexButton();
|
||||||
void fillSignaturesButton();
|
void fillSignaturesButton();
|
||||||
void fillHistoryVisibilityButton();
|
void fillHistoryVisibilityButton();
|
||||||
void fillManageSection();
|
void fillManageSection();
|
||||||
|
@ -905,6 +907,13 @@ void Controller::refreshForumToggleLocked() {
|
||||||
_controls.forumToggle->setToggleLocked(locked);
|
_controls.forumToggle->setToggleLocked(locked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::fillColorIndexButton() {
|
||||||
|
Expects(_controls.buttonsLayout != nullptr);
|
||||||
|
|
||||||
|
const auto show = _navigation->uiShow();
|
||||||
|
AddPeerColorButton(_controls.buttonsLayout, show, _peer);
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::fillSignaturesButton() {
|
void Controller::fillSignaturesButton() {
|
||||||
Expects(_controls.buttonsLayout != nullptr);
|
Expects(_controls.buttonsLayout != nullptr);
|
||||||
|
|
||||||
|
@ -1024,74 +1033,42 @@ void Controller::fillManageSection() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto canEditType = [&] {
|
const auto canEditType = isChannel
|
||||||
return isChannel
|
? channel->amCreator()
|
||||||
? channel->amCreator()
|
: chat->amCreator();
|
||||||
: chat->amCreator();
|
const auto canEditSignatures = isChannel
|
||||||
}();
|
&& channel->canEditSignatures()
|
||||||
const auto canEditSignatures = [&] {
|
&& !channel->isMegagroup();
|
||||||
return isChannel
|
const auto canEditPreHistoryHidden = isChannel
|
||||||
? (channel->canEditSignatures() && !channel->isMegagroup())
|
? channel->canEditPreHistoryHidden()
|
||||||
: false;
|
: chat->canEditPreHistoryHidden();
|
||||||
}();
|
|
||||||
const auto canEditPreHistoryHidden = [&] {
|
|
||||||
return isChannel
|
|
||||||
? channel->canEditPreHistoryHidden()
|
|
||||||
: chat->canEditPreHistoryHidden();
|
|
||||||
}();
|
|
||||||
const auto canEditForum = isChannel
|
const auto canEditForum = isChannel
|
||||||
? (channel->isMegagroup() && channel->amCreator())
|
? (channel->isMegagroup() && channel->amCreator())
|
||||||
: chat->amCreator();
|
: chat->amCreator();
|
||||||
|
const auto canEditPermissions = isChannel
|
||||||
const auto canEditPermissions = [&] {
|
? channel->canEditPermissions()
|
||||||
return isChannel
|
: chat->canEditPermissions();
|
||||||
? channel->canEditPermissions()
|
const auto canEditInviteLinks = isChannel
|
||||||
: chat->canEditPermissions();
|
? channel->canHaveInviteLink()
|
||||||
}();
|
: chat->canHaveInviteLink();
|
||||||
const auto canEditInviteLinks = [&] {
|
const auto canViewAdmins = isChannel
|
||||||
return isChannel
|
? channel->canViewAdmins()
|
||||||
? channel->canHaveInviteLink()
|
: chat->amIn();
|
||||||
: chat->canHaveInviteLink();
|
const auto canViewMembers = isChannel
|
||||||
}();
|
? channel->canViewMembers()
|
||||||
const auto canViewAdmins = [&] {
|
: chat->amIn();
|
||||||
return isChannel
|
const auto canViewKicked = isChannel
|
||||||
? channel->canViewAdmins()
|
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||||
: chat->amIn();
|
const auto hasRecentActions = isChannel
|
||||||
}();
|
&& (channel->hasAdminRights() || channel->amCreator());
|
||||||
const auto canViewMembers = [&] {
|
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||||
return isChannel
|
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||||
? channel->canViewMembers()
|
const auto canEditColorIndex = isChannel
|
||||||
: chat->amIn();
|
&& !channel->isMegagroup()
|
||||||
}();
|
&& channel->canEditInformation();
|
||||||
const auto canViewKicked = [&] {
|
const auto canViewOrEditLinkedChat = isChannel
|
||||||
return isChannel
|
&& (channel->linkedChat()
|
||||||
? (channel->isBroadcast() || channel->isGigagroup())
|
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||||
: false;
|
|
||||||
}();
|
|
||||||
const auto hasRecentActions = [&] {
|
|
||||||
return isChannel
|
|
||||||
? (channel->hasAdminRights() || channel->amCreator())
|
|
||||||
: false;
|
|
||||||
}();
|
|
||||||
|
|
||||||
const auto canEditStickers = [&] {
|
|
||||||
return isChannel
|
|
||||||
? channel->canEditStickers()
|
|
||||||
: false;
|
|
||||||
}();
|
|
||||||
const auto canDeleteChannel = [&] {
|
|
||||||
return isChannel
|
|
||||||
? channel->canDelete()
|
|
||||||
: false;
|
|
||||||
}();
|
|
||||||
|
|
||||||
const auto canViewOrEditLinkedChat = [&] {
|
|
||||||
return !isChannel
|
|
||||||
? false
|
|
||||||
: channel->linkedChat()
|
|
||||||
? true
|
|
||||||
: (channel->isBroadcast() && channel->canEditInformation());
|
|
||||||
}();
|
|
||||||
|
|
||||||
AddSkip(_controls.buttonsLayout, 0);
|
AddSkip(_controls.buttonsLayout, 0);
|
||||||
|
|
||||||
|
@ -1109,11 +1086,15 @@ void Controller::fillManageSection() {
|
||||||
if (canEditForum) {
|
if (canEditForum) {
|
||||||
fillForumButton();
|
fillForumButton();
|
||||||
}
|
}
|
||||||
|
if (canEditColorIndex) {
|
||||||
|
fillColorIndexButton();
|
||||||
|
}
|
||||||
if (canEditSignatures) {
|
if (canEditSignatures) {
|
||||||
fillSignaturesButton();
|
fillSignaturesButton();
|
||||||
}
|
}
|
||||||
if (canEditPreHistoryHidden
|
if (canEditPreHistoryHidden
|
||||||
|| canEditForum
|
|| canEditForum
|
||||||
|
|| canEditColorIndex
|
||||||
|| canEditSignatures
|
|| canEditSignatures
|
||||||
//|| canEditInviteLinks
|
//|| canEditInviteLinks
|
||||||
|| canViewOrEditLinkedChat
|
|| canViewOrEditLinkedChat
|
||||||
|
|
|
@ -63,7 +63,9 @@ PeerId GenerateUser(not_null<History*> history, const QString &name) {
|
||||||
MTPstring(), // lang code
|
MTPstring(), // lang code
|
||||||
MTPEmojiStatus(),
|
MTPEmojiStatus(),
|
||||||
MTPVector<MTPUsername>(),
|
MTPVector<MTPUsername>(),
|
||||||
MTPint())); // stories_max_id
|
MTPint(), // stories_max_id
|
||||||
|
MTP_int(0), // color
|
||||||
|
MTPlong())); // background_emoji_id
|
||||||
return peerId;
|
return peerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +73,7 @@ AdminLog::OwnedItem GenerateItem(
|
||||||
not_null<HistoryView::ElementDelegate*> delegate,
|
not_null<HistoryView::ElementDelegate*> delegate,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
PeerId from,
|
PeerId from,
|
||||||
MsgId replyTo,
|
FullMsgId replyTo,
|
||||||
const QString &text) {
|
const QString &text) {
|
||||||
Expects(history->peer->isUser());
|
Expects(history->peer->isUser());
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ AdminLog::OwnedItem GenerateItem(
|
||||||
| MessageFlag::HasFromId
|
| MessageFlag::HasFromId
|
||||||
| MessageFlag::HasReplyInfo),
|
| MessageFlag::HasReplyInfo),
|
||||||
UserId(), // via
|
UserId(), // via
|
||||||
FullReplyTo{ .msgId = replyTo },
|
FullReplyTo{ .messageId = replyTo },
|
||||||
base::unixtime::now(), // date
|
base::unixtime::now(), // date
|
||||||
from,
|
from,
|
||||||
QString(), // postAuthor
|
QString(), // postAuthor
|
||||||
|
@ -131,7 +133,8 @@ void AddMessage(
|
||||||
state->delegate = std::make_unique<Delegate>(
|
state->delegate = std::make_unique<Delegate>(
|
||||||
controller,
|
controller,
|
||||||
crl::guard(widget, [=] { widget->update(); }));
|
crl::guard(widget, [=] { widget->update(); }));
|
||||||
state->style = std::make_unique<Ui::ChatStyle>();
|
state->style = std::make_unique<Ui::ChatStyle>(
|
||||||
|
controller->session().colorIndicesValue());
|
||||||
state->style->apply(controller->defaultChatTheme().get());
|
state->style->apply(controller->defaultChatTheme().get());
|
||||||
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
|
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
|
||||||
|
|
||||||
|
@ -143,13 +146,13 @@ void AddMessage(
|
||||||
GenerateUser(
|
GenerateUser(
|
||||||
history,
|
history,
|
||||||
tr::lng_settings_chat_message_reply_from(tr::now)),
|
tr::lng_settings_chat_message_reply_from(tr::now)),
|
||||||
0,
|
FullMsgId(),
|
||||||
tr::lng_settings_chat_message_reply(tr::now));
|
tr::lng_settings_chat_message_reply(tr::now));
|
||||||
auto message = GenerateItem(
|
auto message = GenerateItem(
|
||||||
state->delegate.get(),
|
state->delegate.get(),
|
||||||
history,
|
history,
|
||||||
history->peer->id,
|
history->peer->id,
|
||||||
state->reply->data()->fullId().msg,
|
state->reply->data()->fullId(),
|
||||||
tr::lng_settings_chat_message(tr::now));
|
tr::lng_settings_chat_message(tr::now));
|
||||||
const auto view = message.get();
|
const auto view = message.get();
|
||||||
state->item = std::move(message);
|
state->item = std::move(message);
|
||||||
|
|
|
@ -202,8 +202,7 @@ void Userpic::createCache(Image *image) {
|
||||||
{
|
{
|
||||||
auto p = QPainter(&filled);
|
auto p = QPainter(&filled);
|
||||||
Ui::EmptyUserpic(
|
Ui::EmptyUserpic(
|
||||||
Ui::EmptyUserpic::UserpicColor(
|
Ui::EmptyUserpic::UserpicColor(_peer->colorIndex()),
|
||||||
Data::PeerColorIndex(_peer->id)),
|
|
||||||
_peer->name()
|
_peer->name()
|
||||||
).paintCircle(p, 0, 0, size, size);
|
).paintCircle(p, 0, 0, size, size);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ struct SendCommandRequest {
|
||||||
not_null<PeerData*> peer;
|
not_null<PeerData*> peer;
|
||||||
QString command;
|
QString command;
|
||||||
FullMsgId context;
|
FullMsgId context;
|
||||||
MsgId replyTo = 0;
|
FullReplyTo replyTo;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] QString WrapCommandInChat(
|
[[nodiscard]] QString WrapCommandInChat(
|
||||||
|
|
|
@ -662,6 +662,9 @@ statusEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||||
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
|
fadeLeft: icon {{ "fade_horizontal-flip_horizontal", windowBg }};
|
||||||
fadeRight: icon {{ "fade_horizontal", windowBg }};
|
fadeRight: icon {{ "fade_horizontal", windowBg }};
|
||||||
}
|
}
|
||||||
|
backgroundEmojiPan: EmojiPan(defaultEmojiPan) {
|
||||||
|
padding: margins(7px, 7px, 4px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
|
inlineBotsScroll: ScrollArea(defaultSolidScroll) {
|
||||||
deltat: stickerPanPadding;
|
deltat: stickerPanPadding;
|
||||||
|
|
|
@ -467,6 +467,7 @@ EmojiListWidget::EmojiListWidget(
|
||||||
, _localSetsManager(
|
, _localSetsManager(
|
||||||
std::make_unique<LocalStickersManager>(&session()))
|
std::make_unique<LocalStickersManager>(&session()))
|
||||||
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
, _customRecentFactory(std::move(descriptor.customRecentFactory))
|
||||||
|
, _customTextColor(std::move(descriptor.customTextColor))
|
||||||
, _overBg(st::emojiPanRadius, st().overBg)
|
, _overBg(st::emojiPanRadius, st().overBg)
|
||||||
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
, _collapsedBg(st::emojiPanExpand.height / 2, st().headerFg)
|
||||||
, _picker(this, st())
|
, _picker(this, st())
|
||||||
|
@ -476,7 +477,7 @@ EmojiListWidget::EmojiListWidget(
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mode != Mode::RecentReactions) {
|
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
|
||||||
setupSearch();
|
setupSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,10 +792,12 @@ object_ptr<TabbedSelector::InnerFooter> EmojiListWidget::createFooter() {
|
||||||
};
|
};
|
||||||
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
|
auto result = object_ptr<StickersListFooter>(FooterDescriptor{
|
||||||
.session = &session(),
|
.session = &session(),
|
||||||
|
.customTextColor = _customTextColor,
|
||||||
.paused = footerPaused,
|
.paused = footerPaused,
|
||||||
.parent = this,
|
.parent = this,
|
||||||
.st = &st(),
|
.st = &st(),
|
||||||
.features = { .stickersSettings = false },
|
.features = { .stickersSettings = false },
|
||||||
|
.forceFirstFrame = (_mode == Mode::BackgroundEmoji),
|
||||||
});
|
});
|
||||||
_footer = result;
|
_footer = result;
|
||||||
|
|
||||||
|
@ -1027,6 +1030,14 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
||||||
if (!id && _mode == Mode::EmojiStatus) {
|
if (!id && _mode == Mode::EmojiStatus) {
|
||||||
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
|
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
|
||||||
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
|
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
|
||||||
|
} else if (!id && _mode == Mode::BackgroundEmoji) {
|
||||||
|
const auto fakeId = DocumentId(5246772116543512028ULL);
|
||||||
|
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
|
||||||
|
_recent.push_back({
|
||||||
|
.custom = resolveCustomRecent(fakeId),
|
||||||
|
.id = { Ui::Emoji::Find(no) },
|
||||||
|
});
|
||||||
|
_recentCustomIds.emplace(fakeId);
|
||||||
} else {
|
} else {
|
||||||
_recent.push_back({
|
_recent.push_back({
|
||||||
.custom = resolveCustomRecent(id),
|
.custom = resolveCustomRecent(id),
|
||||||
|
@ -1188,7 +1199,9 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) {
|
||||||
void EmojiListWidget::validateEmojiPaintContext(
|
void EmojiListWidget::validateEmojiPaintContext(
|
||||||
const ExpandingContext &context) {
|
const ExpandingContext &context) {
|
||||||
auto value = Ui::Text::CustomEmojiPaintContext{
|
auto value = Ui::Text::CustomEmojiPaintContext{
|
||||||
.textColor = (_mode == Mode::EmojiStatus
|
.textColor = (_customTextColor
|
||||||
|
? _customTextColor()
|
||||||
|
: (_mode == Mode::EmojiStatus)
|
||||||
? anim::color(
|
? anim::color(
|
||||||
st::stickerPanPremium1,
|
st::stickerPanPremium1,
|
||||||
st::stickerPanPremium2,
|
st::stickerPanPremium2,
|
||||||
|
@ -1199,6 +1212,7 @@ void EmojiListWidget::validateEmojiPaintContext(
|
||||||
.scale = context.progress,
|
.scale = context.progress,
|
||||||
.paused = On(powerSavingFlag()) || paused(),
|
.paused = On(powerSavingFlag()) || paused(),
|
||||||
.scaled = context.expanding,
|
.scaled = context.expanding,
|
||||||
|
.internal = { .forceFirstFrame = (_mode == Mode::BackgroundEmoji) },
|
||||||
};
|
};
|
||||||
if (!_emojiPaintContext) {
|
if (!_emojiPaintContext) {
|
||||||
_emojiPaintContext = std::make_unique<
|
_emojiPaintContext = std::make_unique<
|
||||||
|
@ -1629,6 +1643,9 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
case Mode::TopicIcon:
|
case Mode::TopicIcon:
|
||||||
Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
|
Settings::ShowPremium(resolved, u"forum_topic_icon"_q);
|
||||||
break;
|
break;
|
||||||
|
case Mode::BackgroundEmoji:
|
||||||
|
Settings::ShowPremium(resolved, u"name_color"_q);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1995,7 +2012,10 @@ void EmojiListWidget::refreshCustom() {
|
||||||
const auto &sets = owner->stickers().sets();
|
const auto &sets = owner->stickers().sets();
|
||||||
const auto push = [&](uint64 setId, bool installed) {
|
const auto push = [&](uint64 setId, bool installed) {
|
||||||
auto it = sets.find(setId);
|
auto it = sets.find(setId);
|
||||||
if (it == sets.cend() || it->second->stickers.isEmpty()) {
|
if (it == sets.cend()
|
||||||
|
|| it->second->stickers.isEmpty()
|
||||||
|
|| (_mode == Mode::BackgroundEmoji
|
||||||
|
&& !it->second->textColor())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto canRemove = !!(it->second->flags
|
const auto canRemove = !!(it->second->flags
|
||||||
|
|
|
@ -74,11 +74,13 @@ enum class EmojiListMode {
|
||||||
FullReactions,
|
FullReactions,
|
||||||
RecentReactions,
|
RecentReactions,
|
||||||
UserpicBuilder,
|
UserpicBuilder,
|
||||||
|
BackgroundEmoji,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EmojiListDescriptor {
|
struct EmojiListDescriptor {
|
||||||
std::shared_ptr<Show> show;
|
std::shared_ptr<Show> show;
|
||||||
EmojiListMode mode = EmojiListMode::Full;
|
EmojiListMode mode = EmojiListMode::Full;
|
||||||
|
Fn<QColor()> customTextColor;
|
||||||
Fn<bool()> paused;
|
Fn<bool()> paused;
|
||||||
std::vector<DocumentId> customRecentList;
|
std::vector<DocumentId> customRecentList;
|
||||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||||
|
@ -386,6 +388,7 @@ private:
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
DocumentId,
|
DocumentId,
|
||||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||||
|
Fn<QColor()> _customTextColor;
|
||||||
int _customSingleSize = 0;
|
int _customSingleSize = 0;
|
||||||
bool _allowWithoutPremium = false;
|
bool _allowWithoutPremium = false;
|
||||||
Ui::RoundRect _overBg;
|
Ui::RoundRect _overBg;
|
||||||
|
|
|
@ -601,6 +601,12 @@ MessageLinksParser::MessageLinksParser(not_null<Ui::InputField*> field)
|
||||||
_lifetime = _field->changes(
|
_lifetime = _field->changes(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto length = _field->getTextWithTags().text.size();
|
const auto length = _field->getTextWithTags().text.size();
|
||||||
|
if (!length) {
|
||||||
|
_lastLength = 0;
|
||||||
|
_timer.cancel();
|
||||||
|
parse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto timeout = (std::abs(length - _lastLength) > 2)
|
const auto timeout = (std::abs(length - _lastLength) > 2)
|
||||||
? 0
|
? 0
|
||||||
: kParseLinksTimeout;
|
: kParseLinksTimeout;
|
||||||
|
@ -642,16 +648,13 @@ bool MessageLinksParser::eventFilter(QObject *object, QEvent *event) {
|
||||||
return QObject::eventFilter(object, event);
|
return QObject::eventFilter(object, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpl::variable<QStringList> &MessageLinksParser::list() const {
|
|
||||||
return _list;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageLinksParser::parse() {
|
void MessageLinksParser::parse() {
|
||||||
const auto &textWithTags = _field->getTextWithTags();
|
const auto &textWithTags = _field->getTextWithTags();
|
||||||
const auto &text = textWithTags.text;
|
const auto &text = textWithTags.text;
|
||||||
const auto &tags = textWithTags.tags;
|
const auto &tags = textWithTags.tags;
|
||||||
const auto &markdownTags = _field->getMarkdownTags();
|
const auto &markdownTags = _field->getMarkdownTags();
|
||||||
if (_disabled || text.isEmpty()) {
|
if (_disabled || text.isEmpty()) {
|
||||||
|
_ranges = {};
|
||||||
_list = QStringList();
|
_list = QStringList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -663,7 +666,7 @@ void MessageLinksParser::parse() {
|
||||||
|| (tag == Ui::InputField::kTagSpoiler);
|
|| (tag == Ui::InputField::kTagSpoiler);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ranges = QVector<LinkRange>();
|
_ranges.clear();
|
||||||
|
|
||||||
auto tag = tags.begin();
|
auto tag = tags.begin();
|
||||||
const auto tagsEnd = tags.end();
|
const auto tagsEnd = tags.end();
|
||||||
|
@ -672,7 +675,7 @@ void MessageLinksParser::parse() {
|
||||||
|
|
||||||
if (Ui::InputField::IsValidMarkdownLink(tag->id)
|
if (Ui::InputField::IsValidMarkdownLink(tag->id)
|
||||||
&& !TextUtilities::IsMentionLink(tag->id)) {
|
&& !TextUtilities::IsMentionLink(tag->id)) {
|
||||||
ranges.push_back({ tag->offset, tag->length, tag->id });
|
_ranges.push_back({ tag->offset, tag->length, tag->id });
|
||||||
}
|
}
|
||||||
++tag;
|
++tag;
|
||||||
};
|
};
|
||||||
|
@ -774,7 +777,7 @@ void MessageLinksParser::parse() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto range = LinkRange {
|
const auto range = MessageLinkRange{
|
||||||
int(domainOffset),
|
int(domainOffset),
|
||||||
static_cast<int>(p - start - domainOffset),
|
static_cast<int>(p - start - domainOffset),
|
||||||
QString()
|
QString()
|
||||||
|
@ -782,22 +785,20 @@ void MessageLinksParser::parse() {
|
||||||
processTagsBefore(domainOffset);
|
processTagsBefore(domainOffset);
|
||||||
if (!hasTagsIntersection(range.start + range.length)) {
|
if (!hasTagsIntersection(range.start + range.length)) {
|
||||||
if (markdownTagsAllow(range.start, range.length)) {
|
if (markdownTagsAllow(range.start, range.length)) {
|
||||||
ranges.push_back(range);
|
_ranges.push_back(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset = matchOffset = p - start;
|
offset = matchOffset = p - start;
|
||||||
}
|
}
|
||||||
processTagsBefore(Ui::kQFixedMax);
|
processTagsBefore(Ui::kQFixedMax);
|
||||||
|
|
||||||
apply(text, ranges);
|
applyRanges(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLinksParser::apply(
|
void MessageLinksParser::applyRanges(const QString &text) {
|
||||||
const QString &text,
|
const auto count = int(_ranges.size());
|
||||||
const QVector<LinkRange> &ranges) {
|
|
||||||
const auto count = int(ranges.size());
|
|
||||||
const auto current = _list.current();
|
const auto current = _list.current();
|
||||||
const auto computeLink = [&](const LinkRange &range) {
|
const auto computeLink = [&](const MessageLinkRange &range) {
|
||||||
return range.custom.isEmpty()
|
return range.custom.isEmpty()
|
||||||
? base::StringViewMid(text, range.start, range.length)
|
? base::StringViewMid(text, range.start, range.length)
|
||||||
: QStringView(range.custom);
|
: QStringView(range.custom);
|
||||||
|
@ -807,7 +808,7 @@ void MessageLinksParser::apply(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (auto i = 0; i != count; ++i) {
|
for (auto i = 0; i != count; ++i) {
|
||||||
if (computeLink(ranges[i]) != current[i]) {
|
if (computeLink(_ranges[i]) != current[i]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -818,7 +819,7 @@ void MessageLinksParser::apply(
|
||||||
}
|
}
|
||||||
auto parsed = QStringList();
|
auto parsed = QStringList();
|
||||||
parsed.reserve(count);
|
parsed.reserve(count);
|
||||||
for (const auto &range : ranges) {
|
for (const auto &range : _ranges) {
|
||||||
parsed.push_back(computeLink(range).toString());
|
parsed.push_back(computeLink(range).toString());
|
||||||
}
|
}
|
||||||
_list = std::move(parsed);
|
_list = std::move(parsed);
|
||||||
|
|
|
@ -7,9 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widgets/fields/input_field.h"
|
#include "base/qt/qt_compare.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "chat_helpers/compose/compose_features.h"
|
#include "chat_helpers/compose/compose_features.h"
|
||||||
|
#include "ui/widgets/fields/input_field.h"
|
||||||
|
|
||||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||||
#include "boxes/dictionaries_manager.h"
|
#include "boxes/dictionaries_manager.h"
|
||||||
|
@ -96,38 +97,42 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery(
|
||||||
not_null<const Ui::InputField*> field,
|
not_null<const Ui::InputField*> field,
|
||||||
ChatHelpers::ComposeFeatures features);
|
ChatHelpers::ComposeFeatures features);
|
||||||
|
|
||||||
class MessageLinksParser : private QObject {
|
struct MessageLinkRange {
|
||||||
|
int start = 0;
|
||||||
|
int length = 0;
|
||||||
|
QString custom;
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
const MessageLinkRange&,
|
||||||
|
const MessageLinkRange&) = default;
|
||||||
|
friend inline bool operator==(
|
||||||
|
const MessageLinkRange&,
|
||||||
|
const MessageLinkRange&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessageLinksParser final : private QObject {
|
||||||
public:
|
public:
|
||||||
MessageLinksParser(not_null<Ui::InputField*> field);
|
MessageLinksParser(not_null<Ui::InputField*> field);
|
||||||
|
|
||||||
void parseNow();
|
void parseNow();
|
||||||
void setDisabled(bool disabled);
|
void setDisabled(bool disabled);
|
||||||
|
|
||||||
[[nodiscard]] const rpl::variable<QStringList> &list() const;
|
[[nodiscard]] const rpl::variable<QStringList> &list() const {
|
||||||
|
return _list;
|
||||||
protected:
|
}
|
||||||
bool eventFilter(QObject *object, QEvent *event) override;
|
[[nodiscard]] const std::vector<MessageLinkRange> &ranges() const {
|
||||||
|
return _ranges;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LinkRange {
|
bool eventFilter(QObject *object, QEvent *event) override;
|
||||||
int start;
|
|
||||||
int length;
|
|
||||||
QString custom;
|
|
||||||
};
|
|
||||||
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
|
|
||||||
return (a.start == b.start)
|
|
||||||
&& (a.length == b.length)
|
|
||||||
&& (a.custom == b.custom);
|
|
||||||
}
|
|
||||||
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse();
|
void parse();
|
||||||
void apply(const QString &text, const QVector<LinkRange> &ranges);
|
void applyRanges(const QString &text);
|
||||||
|
|
||||||
not_null<Ui::InputField*> _field;
|
not_null<Ui::InputField*> _field;
|
||||||
rpl::variable<QStringList> _list;
|
rpl::variable<QStringList> _list;
|
||||||
|
std::vector<MessageLinkRange> _ranges;
|
||||||
int _lastLength = 0;
|
int _lastLength = 0;
|
||||||
bool _disabled = false;
|
bool _disabled = false;
|
||||||
base::Timer _timer;
|
base::Timer _timer;
|
||||||
|
|
|
@ -291,12 +291,14 @@ StickersListFooter::StickersListFooter(Descriptor &&descriptor)
|
||||||
descriptor.parent,
|
descriptor.parent,
|
||||||
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
|
descriptor.st ? *descriptor.st : st::defaultEmojiPan)
|
||||||
, _session(descriptor.session)
|
, _session(descriptor.session)
|
||||||
, _paused(descriptor.paused)
|
, _customTextColor(std::move(descriptor.customTextColor))
|
||||||
|
, _paused(std::move(descriptor.paused))
|
||||||
, _features(descriptor.features)
|
, _features(descriptor.features)
|
||||||
, _iconState([=] { update(); })
|
, _iconState([=] { update(); })
|
||||||
, _subiconState([=] { update(); })
|
, _subiconState([=] { update(); })
|
||||||
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
|
, _selectionBg(st::emojiPanRadius, st().categoriesBgOver)
|
||||||
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver) {
|
, _subselectionBg(st().iconArea / 2, st().categoriesBgOver)
|
||||||
|
, _forceFirstFrame(descriptor.forceFirstFrame) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
_iconsLeft = st().iconSkip
|
_iconsLeft = st().iconSkip
|
||||||
|
@ -1345,13 +1347,16 @@ void StickersListFooter::paintSetIconToCache(
|
||||||
const auto y = (st().footer - icon.pixh) / 2;
|
const auto y = (st().footer - icon.pixh) / 2;
|
||||||
if (icon.custom) {
|
if (icon.custom) {
|
||||||
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
|
icon.custom->paint(p, Ui::Text::CustomEmoji::Context{
|
||||||
.textColor = st().textFg->c,
|
.textColor = (_customTextColor
|
||||||
|
? _customTextColor()
|
||||||
|
: st().textFg->c),
|
||||||
.size = QSize(icon.pixw, icon.pixh),
|
.size = QSize(icon.pixw, icon.pixh),
|
||||||
.now = now,
|
.now = now,
|
||||||
.scale = context.progress,
|
.scale = context.progress,
|
||||||
.position = { x, y },
|
.position = { x, y },
|
||||||
.paused = paused,
|
.paused = paused,
|
||||||
.scaled = context.expanding,
|
.scaled = context.expanding,
|
||||||
|
.internal = { .forceFirstFrame = _forceFirstFrame },
|
||||||
});
|
});
|
||||||
} else if (icon.lottie && icon.lottie->ready()) {
|
} else if (icon.lottie && icon.lottie->ready()) {
|
||||||
const auto frame = icon.lottie->frame();
|
const auto frame = icon.lottie->frame();
|
||||||
|
@ -1428,11 +1433,13 @@ void StickersListFooter::paintSetIconToCache(
|
||||||
return icons[index];
|
return icons[index];
|
||||||
};
|
};
|
||||||
const auto paintOne = [&](int left, const style::icon *icon) {
|
const auto paintOne = [&](int left, const style::icon *icon) {
|
||||||
icon->paint(
|
left += (_singleWidth - icon->width()) / 2;
|
||||||
p,
|
const auto top = (st().footer - icon->height()) / 2;
|
||||||
left + (_singleWidth - icon->width()) / 2,
|
if (_customTextColor) {
|
||||||
(st().footer - icon->height()) / 2,
|
icon->paint(p, left, top, width(), _customTextColor());
|
||||||
width());
|
} else {
|
||||||
|
icon->paint(p, left, top, width());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (_icons[info.index].setId == AllEmojiSectionSetId()
|
if (_icons[info.index].setId == AllEmojiSectionSetId()
|
||||||
&& info.width > _singleWidth) {
|
&& info.width > _singleWidth) {
|
||||||
|
|
|
@ -115,10 +115,12 @@ class StickersListFooter final : public TabbedSelector::InnerFooter {
|
||||||
public:
|
public:
|
||||||
struct Descriptor {
|
struct Descriptor {
|
||||||
not_null<Main::Session*> session;
|
not_null<Main::Session*> session;
|
||||||
|
Fn<QColor()> customTextColor;
|
||||||
Fn<bool()> paused;
|
Fn<bool()> paused;
|
||||||
not_null<RpWidget*> parent;
|
not_null<RpWidget*> parent;
|
||||||
const style::EmojiPan *st = nullptr;
|
const style::EmojiPan *st = nullptr;
|
||||||
ComposeFeatures features;
|
ComposeFeatures features;
|
||||||
|
bool forceFirstFrame = false;
|
||||||
};
|
};
|
||||||
explicit StickersListFooter(Descriptor &&descriptor);
|
explicit StickersListFooter(Descriptor &&descriptor);
|
||||||
|
|
||||||
|
@ -269,6 +271,7 @@ private:
|
||||||
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
void clipCallback(Media::Clip::Notification notification, uint64 setId);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
const Fn<QColor()> _customTextColor;
|
||||||
const Fn<bool()> _paused;
|
const Fn<bool()> _paused;
|
||||||
const ComposeFeatures _features;
|
const ComposeFeatures _features;
|
||||||
|
|
||||||
|
@ -303,6 +306,7 @@ private:
|
||||||
int _subiconsWidth = 0;
|
int _subiconsWidth = 0;
|
||||||
bool _subiconsExpanded = false;
|
bool _subiconsExpanded = false;
|
||||||
bool _repaintScheduled = false;
|
bool _repaintScheduled = false;
|
||||||
|
bool _forceFirstFrame = false;
|
||||||
|
|
||||||
rpl::event_stream<> _openSettingsRequests;
|
rpl::event_stream<> _openSettingsRequests;
|
||||||
rpl::event_stream<uint64> _setChosen;
|
rpl::event_stream<uint64> _setChosen;
|
||||||
|
|
|
@ -331,7 +331,7 @@ TabbedSelector::TabbedSelector(
|
||||||
Mode mode)
|
Mode mode)
|
||||||
: TabbedSelector(parent, {
|
: TabbedSelector(parent, {
|
||||||
.show = std::move(show),
|
.show = std::move(show),
|
||||||
.st = (mode == Mode::EmojiStatus
|
.st = ((mode == Mode::EmojiStatus || mode == Mode::BackgroundEmoji)
|
||||||
? st::statusEmojiPan
|
? st::statusEmojiPan
|
||||||
: st::defaultEmojiPan),
|
: st::defaultEmojiPan),
|
||||||
.level = level,
|
.level = level,
|
||||||
|
@ -347,6 +347,7 @@ TabbedSelector::TabbedSelector(
|
||||||
, _features(descriptor.features)
|
, _features(descriptor.features)
|
||||||
, _show(std::move(descriptor.show))
|
, _show(std::move(descriptor.show))
|
||||||
, _level(descriptor.level)
|
, _level(descriptor.level)
|
||||||
|
, _customTextColor(std::move(descriptor.customTextColor))
|
||||||
, _mode(descriptor.mode)
|
, _mode(descriptor.mode)
|
||||||
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
|
, _panelRounding(Ui::PrepareCornerPixmaps(st::emojiPanRadius, _st.bg))
|
||||||
, _categoriesRounding(
|
, _categoriesRounding(
|
||||||
|
@ -512,7 +513,10 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
|
||||||
.show = _show,
|
.show = _show,
|
||||||
.mode = (_mode == Mode::EmojiStatus
|
.mode = (_mode == Mode::EmojiStatus
|
||||||
? EmojiMode::EmojiStatus
|
? EmojiMode::EmojiStatus
|
||||||
|
: _mode == Mode::BackgroundEmoji
|
||||||
|
? EmojiMode::BackgroundEmoji
|
||||||
: EmojiMode::Full),
|
: EmojiMode::Full),
|
||||||
|
.customTextColor = _customTextColor,
|
||||||
.paused = paused,
|
.paused = paused,
|
||||||
.st = &_st,
|
.st = &_st,
|
||||||
.features = _features,
|
.features = _features,
|
||||||
|
|
|
@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
|
||||||
EmojiOnly,
|
EmojiOnly,
|
||||||
MediaEditor,
|
MediaEditor,
|
||||||
EmojiStatus,
|
EmojiStatus,
|
||||||
|
BackgroundEmoji,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TabbedSelectorDescriptor {
|
struct TabbedSelectorDescriptor {
|
||||||
|
@ -88,6 +89,7 @@ struct TabbedSelectorDescriptor {
|
||||||
const style::EmojiPan &st;
|
const style::EmojiPan &st;
|
||||||
PauseReason level = {};
|
PauseReason level = {};
|
||||||
TabbedSelectorMode mode = TabbedSelectorMode::Full;
|
TabbedSelectorMode mode = TabbedSelectorMode::Full;
|
||||||
|
Fn<QColor()> customTextColor;
|
||||||
ComposeFeatures features;
|
ComposeFeatures features;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -272,6 +274,7 @@ private:
|
||||||
const ComposeFeatures _features;
|
const ComposeFeatures _features;
|
||||||
const std::shared_ptr<Show> _show;
|
const std::shared_ptr<Show> _show;
|
||||||
const PauseReason _level = {};
|
const PauseReason _level = {};
|
||||||
|
const Fn<QColor()> _customTextColor;
|
||||||
|
|
||||||
Mode _mode = Mode::Full;
|
Mode _mode = Mode::Full;
|
||||||
int _roundRadius = 0;
|
int _roundRadius = 0;
|
||||||
|
|
|
@ -308,7 +308,6 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
|
||||||
.peer = peer,
|
.peer = peer,
|
||||||
.command = _cmd,
|
.command = _cmd,
|
||||||
.context = my.itemId,
|
.context = my.itemId,
|
||||||
.replyTo = 0,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_text_entities.h"
|
#include "api/api_text_entities.h"
|
||||||
#include "api/api_chat_filters.h"
|
#include "api/api_chat_filters.h"
|
||||||
#include "api/api_chat_invite.h"
|
#include "api/api_chat_invite.h"
|
||||||
|
#include "api/api_premium.h"
|
||||||
#include "base/qthelp_regex.h"
|
#include "base/qthelp_regex.h"
|
||||||
#include "base/qthelp_url.h"
|
#include "base/qthelp_url.h"
|
||||||
#include "lang/lang_cloud_manager.h"
|
#include "lang/lang_cloud_manager.h"
|
||||||
|
@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
#include "boxes/connection_box.h"
|
#include "boxes/connection_box.h"
|
||||||
|
#include "boxes/gift_premium_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
#include "boxes/sessions_box.h"
|
#include "boxes/sessions_box.h"
|
||||||
#include "boxes/language_box.h"
|
#include "boxes/language_box.h"
|
||||||
|
@ -348,6 +350,11 @@ bool ResolveUsernameOrPhone(
|
||||||
const auto domainParam = params.value(u"domain"_q);
|
const auto domainParam = params.value(u"domain"_q);
|
||||||
const auto appnameParam = params.value(u"appname"_q);
|
const auto appnameParam = params.value(u"appname"_q);
|
||||||
|
|
||||||
|
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
|
||||||
|
ResolveGiftCode(controller, appnameParam);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Fix t.me/s/username links.
|
// Fix t.me/s/username links.
|
||||||
const auto webChannelPreviewLink = (domainParam == u"s"_q)
|
const auto webChannelPreviewLink = (domainParam == u"s"_q)
|
||||||
&& !appnameParam.isEmpty();
|
&& !appnameParam.isEmpty();
|
||||||
|
@ -637,6 +644,17 @@ bool OpenExternalLink(
|
||||||
context);
|
context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CopyPeerId(
|
||||||
|
Window::SessionController *controller,
|
||||||
|
const Match &match,
|
||||||
|
const QVariant &context) {
|
||||||
|
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
|
||||||
|
if (controller) {
|
||||||
|
controller->showToast(tr::lng_text_copied(tr::now));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ExportTestChatTheme(
|
void ExportTestChatTheme(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<const Data::CloudTheme*> theme) {
|
not_null<const Data::CloudTheme*> theme) {
|
||||||
|
@ -978,6 +996,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
||||||
u"^url:(.+)$"_q,
|
u"^url:(.+)$"_q,
|
||||||
OpenExternalLink
|
OpenExternalLink
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
u"^copy:(.+)$"_q,
|
||||||
|
CopyPeerId
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
@ -1078,7 +1100,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||||
"("
|
"("
|
||||||
"/?\\?|"
|
"/?\\?|"
|
||||||
"/?$|"
|
"/?$|"
|
||||||
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|"
|
"/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
|
||||||
"/\\d+/?(\\?|$)|"
|
"/\\d+/?(\\?|$)|"
|
||||||
"/s/\\d+/?(\\?|$)|"
|
"/s/\\d+/?(\\?|$)|"
|
||||||
"/\\d+/\\d+/?(\\?|$)"
|
"/\\d+/\\d+/?(\\?|$)"
|
||||||
|
@ -1103,7 +1125,7 @@ QString TryConvertUrlToLocal(QString url) {
|
||||||
added = u"&post="_q + postMatch->captured(1);
|
added = u"&post="_q + postMatch->captured(1);
|
||||||
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||||
added = u"&story="_q + storyMatch->captured(1);
|
added = u"&story="_q + storyMatch->captured(1);
|
||||||
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||||
added = u"&appname="_q + appNameMatch->captured(1);
|
added = u"&appname="_q + appNameMatch->captured(1);
|
||||||
}
|
}
|
||||||
return base + added + (params.isEmpty() ? QString() : '&' + params);
|
return base + added + (params.isEmpty() ? QString() : '&' + params);
|
||||||
|
|
|
@ -97,7 +97,7 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
||||||
{ u"message"_q , Command::JustSendMessage },
|
{ u"message"_q , Command::JustSendMessage },
|
||||||
{ u"message_silently"_q , Command::SendSilentMessage },
|
{ u"message_silently"_q , Command::SendSilentMessage },
|
||||||
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
{ u"message_scheduled"_q , Command::ScheduleMessage },
|
||||||
{ u"mevia_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
|
{ u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
|
||||||
//
|
//
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -348,6 +348,10 @@ QString UiIntegration::phraseFormattingStrikeOut() {
|
||||||
return tr::lng_menu_formatting_strike_out(tr::now);
|
return tr::lng_menu_formatting_strike_out(tr::now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString UiIntegration::phraseFormattingBlockquote() {
|
||||||
|
return tr::lng_menu_formatting_blockquote(tr::now);
|
||||||
|
}
|
||||||
|
|
||||||
QString UiIntegration::phraseFormattingMonospace() {
|
QString UiIntegration::phraseFormattingMonospace() {
|
||||||
return tr::lng_menu_formatting_monospace(tr::now);
|
return tr::lng_menu_formatting_monospace(tr::now);
|
||||||
}
|
}
|
||||||
|
@ -404,6 +408,10 @@ QString UiIntegration::phraseBotAllowWriteConfirm() {
|
||||||
return tr::lng_bot_allow_write_confirm(tr::now);
|
return tr::lng_bot_allow_write_confirm(tr::now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString UiIntegration::phraseQuoteHeaderCopy() {
|
||||||
|
return tr::lng_code_block_header_copy(tr::now);
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenGLLastCheckFailed() {
|
bool OpenGLLastCheckFailed() {
|
||||||
return QFile::exists(OpenGLCheckFilePath());
|
return QFile::exists(OpenGLCheckFilePath());
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ public:
|
||||||
QString phraseFormattingItalic() override;
|
QString phraseFormattingItalic() override;
|
||||||
QString phraseFormattingUnderline() override;
|
QString phraseFormattingUnderline() override;
|
||||||
QString phraseFormattingStrikeOut() override;
|
QString phraseFormattingStrikeOut() override;
|
||||||
|
QString phraseFormattingBlockquote() override;
|
||||||
QString phraseFormattingMonospace() override;
|
QString phraseFormattingMonospace() override;
|
||||||
QString phraseFormattingSpoiler() override;
|
QString phraseFormattingSpoiler() override;
|
||||||
QString phraseButtonOk() override;
|
QString phraseButtonOk() override;
|
||||||
|
@ -91,6 +92,7 @@ public:
|
||||||
QString phraseBotAllowWrite() override;
|
QString phraseBotAllowWrite() override;
|
||||||
QString phraseBotAllowWriteTitle() override;
|
QString phraseBotAllowWriteTitle() override;
|
||||||
QString phraseBotAllowWriteConfirm() override;
|
QString phraseBotAllowWriteConfirm() override;
|
||||||
|
QString phraseQuoteHeaderCopy() override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 4010004;
|
constexpr auto AppVersion = 4011001;
|
||||||
constexpr auto AppVersionStr = "4.10.4";
|
constexpr auto AppVersionStr = "4.11.1";
|
||||||
constexpr auto AppBetaVersion = true;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -254,7 +254,7 @@ const std::array<Info, 231> FallbackList = { {
|
||||||
CountriesInstance::CountriesInstance() {
|
CountriesInstance::CountriesInstance() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Info> &CountriesInstance::list() {
|
const std::vector<Info> &CountriesInstance::list() const {
|
||||||
if (_list.empty()) {
|
if (_list.empty()) {
|
||||||
_list = (FallbackList | ranges::to_vector);
|
_list = (FallbackList | ranges::to_vector);
|
||||||
}
|
}
|
||||||
|
@ -268,7 +268,7 @@ void CountriesInstance::setList(std::vector<Info> &&infos) {
|
||||||
_updated.fire({});
|
_updated.fire({});
|
||||||
}
|
}
|
||||||
|
|
||||||
const CountriesInstance::Map &CountriesInstance::byCode() {
|
const CountriesInstance::Map &CountriesInstance::byCode() const {
|
||||||
if (_byCode.empty()) {
|
if (_byCode.empty()) {
|
||||||
_byCode.reserve(list().size());
|
_byCode.reserve(list().size());
|
||||||
for (const auto &entry : list()) {
|
for (const auto &entry : list()) {
|
||||||
|
@ -280,7 +280,7 @@ const CountriesInstance::Map &CountriesInstance::byCode() {
|
||||||
return _byCode;
|
return _byCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CountriesInstance::Map &CountriesInstance::byISO2() {
|
const CountriesInstance::Map &CountriesInstance::byISO2() const {
|
||||||
if (_byISO2.empty()) {
|
if (_byISO2.empty()) {
|
||||||
_byISO2.reserve(list().size());
|
_byISO2.reserve(list().size());
|
||||||
for (const auto &entry : list()) {
|
for (const auto &entry : list()) {
|
||||||
|
@ -290,7 +290,7 @@ const CountriesInstance::Map &CountriesInstance::byISO2() {
|
||||||
return _byISO2;
|
return _byISO2;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CountriesInstance::validPhoneCode(QString fullCode) {
|
QString CountriesInstance::validPhoneCode(QString fullCode) const {
|
||||||
const auto &listByCode = byCode();
|
const auto &listByCode = byCode();
|
||||||
while (fullCode.length()) {
|
while (fullCode.length()) {
|
||||||
const auto i = listByCode.constFind(fullCode);
|
const auto i = listByCode.constFind(fullCode);
|
||||||
|
@ -302,20 +302,34 @@ QString CountriesInstance::validPhoneCode(QString fullCode) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CountriesInstance::countryNameByISO2(const QString &iso) {
|
QString CountriesInstance::countryNameByISO2(const QString &iso) const {
|
||||||
const auto &listByISO2 = byISO2();
|
const auto &listByISO2 = byISO2();
|
||||||
const auto i = listByISO2.constFind(iso);
|
const auto i = listByISO2.constFind(iso);
|
||||||
return (i != listByISO2.cend()) ? (*i)->name : QString();
|
return (i != listByISO2.cend()) ? (*i)->name : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CountriesInstance::countryISO2ByPhone(const QString &phone) {
|
QString CountriesInstance::countryISO2ByPhone(const QString &phone) const {
|
||||||
const auto &listByCode = byCode();
|
const auto &listByCode = byCode();
|
||||||
const auto code = validPhoneCode(phone);
|
const auto code = validPhoneCode(phone);
|
||||||
const auto i = listByCode.find(code);
|
const auto i = listByCode.find(code);
|
||||||
return (i != listByCode.cend()) ? (*i)->iso2 : QString();
|
return (i != listByCode.cend()) ? (*i)->iso2 : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatResult CountriesInstance::format(FormatArgs args) {
|
QString CountriesInstance::flagEmojiByISO2(const QString &iso) const {
|
||||||
|
if (iso.size() != 2
|
||||||
|
|| iso.front() < 'A'
|
||||||
|
|| iso.front() > 'Z'
|
||||||
|
|| iso.back() < 'A'
|
||||||
|
|| iso.back() > 'Z') {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
auto result = QString(4, QChar(0xD83C));
|
||||||
|
result[1] = QChar(iso.front().unicode() - 'A' + 0xDDE6);
|
||||||
|
result[3] = QChar(iso.back().unicode() - 'A' + 0xDDE6);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatResult CountriesInstance::format(FormatArgs args) const {
|
||||||
// Ported from TDLib.
|
// Ported from TDLib.
|
||||||
if (args.phone.isEmpty()) {
|
if (args.phone.isEmpty()) {
|
||||||
return FormatResult();
|
return FormatResult();
|
||||||
|
|
|
@ -43,25 +43,26 @@ public:
|
||||||
using Map = QHash<QString, const Info *>;
|
using Map = QHash<QString, const Info *>;
|
||||||
|
|
||||||
CountriesInstance();
|
CountriesInstance();
|
||||||
[[nodiscard]] const std::vector<Info> &list();
|
[[nodiscard]] const std::vector<Info> &list() const;
|
||||||
void setList(std::vector<Info> &&infos);
|
void setList(std::vector<Info> &&infos);
|
||||||
|
|
||||||
[[nodiscard]] const Map &byCode();
|
[[nodiscard]] const Map &byCode() const;
|
||||||
[[nodiscard]] const Map &byISO2();
|
[[nodiscard]] const Map &byISO2() const;
|
||||||
|
|
||||||
[[nodiscard]] QString validPhoneCode(QString fullCode);
|
[[nodiscard]] QString validPhoneCode(QString fullCode) const;
|
||||||
[[nodiscard]] QString countryNameByISO2(const QString &iso);
|
[[nodiscard]] QString countryNameByISO2(const QString &iso) const;
|
||||||
[[nodiscard]] QString countryISO2ByPhone(const QString &phone);
|
[[nodiscard]] QString countryISO2ByPhone(const QString &phone) const;
|
||||||
|
[[nodiscard]] QString flagEmojiByISO2(const QString &iso) const;
|
||||||
|
|
||||||
[[nodiscard]] FormatResult format(FormatArgs args);
|
[[nodiscard]] FormatResult format(FormatArgs args) const;
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<> updated() const;
|
[[nodiscard]] rpl::producer<> updated() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Info> _list;
|
mutable std::vector<Info> _list;
|
||||||
|
|
||||||
Map _byCode;
|
mutable Map _byCode;
|
||||||
Map _byISO2;
|
mutable Map _byISO2;
|
||||||
|
|
||||||
rpl::event_stream<> _updated;
|
rpl::event_stream<> _updated;
|
||||||
|
|
||||||
|
|
|
@ -24,5 +24,4 @@ struct BotAppData {
|
||||||
|
|
||||||
uint64 accessHash = 0;
|
uint64 accessHash = 0;
|
||||||
uint64 hash = 0;
|
uint64 hash = 0;
|
||||||
bool hasSettings = false;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,41 +71,43 @@ struct PeerUpdate {
|
||||||
FullInfo = (1ULL << 11),
|
FullInfo = (1ULL << 11),
|
||||||
Usernames = (1ULL << 12),
|
Usernames = (1ULL << 12),
|
||||||
TranslationDisabled = (1ULL << 13),
|
TranslationDisabled = (1ULL << 13),
|
||||||
|
Color = (1ULL << 14),
|
||||||
|
BackgroundEmoji = (1ULL << 15),
|
||||||
|
|
||||||
// For users
|
// For users
|
||||||
CanShareContact = (1ULL << 14),
|
CanShareContact = (1ULL << 16),
|
||||||
IsContact = (1ULL << 15),
|
IsContact = (1ULL << 17),
|
||||||
PhoneNumber = (1ULL << 16),
|
PhoneNumber = (1ULL << 18),
|
||||||
OnlineStatus = (1ULL << 17),
|
OnlineStatus = (1ULL << 19),
|
||||||
BotCommands = (1ULL << 18),
|
BotCommands = (1ULL << 20),
|
||||||
BotCanBeInvited = (1ULL << 19),
|
BotCanBeInvited = (1ULL << 21),
|
||||||
BotStartToken = (1ULL << 20),
|
BotStartToken = (1ULL << 22),
|
||||||
CommonChats = (1ULL << 21),
|
CommonChats = (1ULL << 23),
|
||||||
HasCalls = (1ULL << 22),
|
HasCalls = (1ULL << 24),
|
||||||
SupportInfo = (1ULL << 23),
|
SupportInfo = (1ULL << 25),
|
||||||
IsBot = (1ULL << 24),
|
IsBot = (1ULL << 26),
|
||||||
EmojiStatus = (1ULL << 25),
|
EmojiStatus = (1ULL << 27),
|
||||||
StoriesState = (1ULL << 26),
|
StoriesState = (1ULL << 28),
|
||||||
|
|
||||||
// For chats and channels
|
// For chats and channels
|
||||||
InviteLinks = (1ULL << 27),
|
InviteLinks = (1ULL << 29),
|
||||||
Members = (1ULL << 28),
|
Members = (1ULL << 30),
|
||||||
Admins = (1ULL << 29),
|
Admins = (1ULL << 31),
|
||||||
BannedUsers = (1ULL << 30),
|
BannedUsers = (1ULL << 32),
|
||||||
Rights = (1ULL << 31),
|
Rights = (1ULL << 33),
|
||||||
PendingRequests = (1ULL << 32),
|
PendingRequests = (1ULL << 34),
|
||||||
Reactions = (1ULL << 33),
|
Reactions = (1ULL << 35),
|
||||||
|
|
||||||
// For channels
|
// For channels
|
||||||
ChannelAmIn = (1ULL << 34),
|
ChannelAmIn = (1ULL << 36),
|
||||||
StickersSet = (1ULL << 35),
|
StickersSet = (1ULL << 37),
|
||||||
ChannelLinkedChat = (1ULL << 36),
|
ChannelLinkedChat = (1ULL << 38),
|
||||||
ChannelLocation = (1ULL << 37),
|
ChannelLocation = (1ULL << 39),
|
||||||
Slowmode = (1ULL << 38),
|
Slowmode = (1ULL << 40),
|
||||||
GroupCall = (1ULL << 39),
|
GroupCall = (1ULL << 41),
|
||||||
|
|
||||||
// For iteration
|
// For iteration
|
||||||
LastUsedBit = (1ULL << 39),
|
LastUsedBit = (1ULL << 41),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
|
|
@ -74,22 +74,8 @@ ChatFilter ChatFilter::FromTL(
|
||||||
| (data.is_exclude_read() ? Flag::NoRead : Flag(0))
|
| (data.is_exclude_read() ? Flag::NoRead : Flag(0))
|
||||||
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
|
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
|
||||||
auto &&to_histories = ranges::views::transform([&](
|
auto &&to_histories = ranges::views::transform([&](
|
||||||
const MTPInputPeer &data) {
|
const MTPInputPeer &input) {
|
||||||
const auto peer = data.match([&](const MTPDinputPeerUser &data) {
|
const auto peer = Data::PeerFromInputMTP(owner, input);
|
||||||
const auto user = owner->user(data.vuser_id().v);
|
|
||||||
user->setAccessHash(data.vaccess_hash().v);
|
|
||||||
return (PeerData*)user;
|
|
||||||
}, [&](const MTPDinputPeerChat &data) {
|
|
||||||
return (PeerData*)owner->chat(data.vchat_id().v);
|
|
||||||
}, [&](const MTPDinputPeerChannel &data) {
|
|
||||||
const auto channel = owner->channel(data.vchannel_id().v);
|
|
||||||
channel->setAccessHash(data.vaccess_hash().v);
|
|
||||||
return (PeerData*)channel;
|
|
||||||
}, [&](const MTPDinputPeerSelf &data) {
|
|
||||||
return (PeerData*)owner->session().user();
|
|
||||||
}, [&](const auto &data) {
|
|
||||||
return (PeerData*)nullptr;
|
|
||||||
});
|
|
||||||
return peer ? owner->history(peer).get() : nullptr;
|
return peer ? owner->history(peer).get() : nullptr;
|
||||||
}) | ranges::views::filter([](History *history) {
|
}) | ranges::views::filter([](History *history) {
|
||||||
return history != nullptr;
|
return history != nullptr;
|
||||||
|
|
|
@ -12,39 +12,57 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_widget.h"
|
#include "history/history_widget.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_web_page.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
|
WebPageDraft WebPageDraft::FromItem(not_null<HistoryItem*> item) {
|
||||||
|
const auto previewMedia = item->media();
|
||||||
|
const auto previewPage = previewMedia
|
||||||
|
? previewMedia->webpage()
|
||||||
|
: nullptr;
|
||||||
|
using PageFlag = MediaWebPageFlag;
|
||||||
|
const auto previewFlags = previewMedia
|
||||||
|
? previewMedia->webpageFlags()
|
||||||
|
: PageFlag();
|
||||||
|
return {
|
||||||
|
.id = previewPage ? previewPage->id : 0,
|
||||||
|
.url = previewPage ? previewPage->url : QString(),
|
||||||
|
.forceLargeMedia = !!(previewFlags & PageFlag::ForceLargeMedia),
|
||||||
|
.forceSmallMedia = !!(previewFlags & PageFlag::ForceSmallMedia),
|
||||||
|
.invert = item->invertMedia(),
|
||||||
|
.manual = !!(previewFlags & PageFlag::Manual),
|
||||||
|
.removed = !previewPage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Draft::Draft(
|
Draft::Draft(
|
||||||
const TextWithTags &textWithTags,
|
const TextWithTags &textWithTags,
|
||||||
MsgId msgId,
|
FullReplyTo reply,
|
||||||
MsgId topicRootId,
|
|
||||||
const MessageCursor &cursor,
|
const MessageCursor &cursor,
|
||||||
PreviewState previewState,
|
WebPageDraft webpage,
|
||||||
mtpRequestId saveRequestId)
|
mtpRequestId saveRequestId)
|
||||||
: textWithTags(textWithTags)
|
: textWithTags(textWithTags)
|
||||||
, msgId(msgId)
|
, reply(std::move(reply))
|
||||||
, topicRootId(topicRootId)
|
|
||||||
, cursor(cursor)
|
, cursor(cursor)
|
||||||
, previewState(previewState)
|
, webpage(webpage)
|
||||||
, saveRequestId(saveRequestId) {
|
, saveRequestId(saveRequestId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Draft::Draft(
|
Draft::Draft(
|
||||||
not_null<const Ui::InputField*> field,
|
not_null<const Ui::InputField*> field,
|
||||||
MsgId msgId,
|
FullReplyTo reply,
|
||||||
MsgId topicRootId,
|
WebPageDraft webpage,
|
||||||
PreviewState previewState,
|
|
||||||
mtpRequestId saveRequestId)
|
mtpRequestId saveRequestId)
|
||||||
: textWithTags(field->getTextWithTags())
|
: textWithTags(field->getTextWithTags())
|
||||||
, msgId(msgId)
|
, reply(std::move(reply))
|
||||||
, topicRootId(topicRootId)
|
|
||||||
, cursor(field)
|
, cursor(field)
|
||||||
, previewState(previewState) {
|
, webpage(webpage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyPeerCloudDraft(
|
void ApplyPeerCloudDraft(
|
||||||
|
@ -64,15 +82,32 @@ void ApplyPeerCloudDraft(
|
||||||
session,
|
session,
|
||||||
draft.ventities().value_or_empty()))
|
draft.ventities().value_or_empty()))
|
||||||
};
|
};
|
||||||
const auto replyTo = draft.vreply_to_msg_id().value_or_empty();
|
auto replyTo = draft.vreply_to()
|
||||||
|
? ReplyToFromMTP(history, *draft.vreply_to())
|
||||||
|
: FullReplyTo();
|
||||||
|
replyTo.topicRootId = topicRootId;
|
||||||
|
auto webpage = WebPageDraft{
|
||||||
|
.invert = draft.is_invert_media(),
|
||||||
|
.removed = draft.is_no_webpage(),
|
||||||
|
};
|
||||||
|
if (const auto media = draft.vmedia()) {
|
||||||
|
media->match([&](const MTPDmessageMediaWebPage &data) {
|
||||||
|
const auto parsed = session->data().processWebpage(
|
||||||
|
data.vwebpage());
|
||||||
|
if (!parsed->failed) {
|
||||||
|
webpage.forceLargeMedia = data.is_force_large_media();
|
||||||
|
webpage.forceSmallMedia = data.is_force_small_media();
|
||||||
|
webpage.manual = data.is_manual();
|
||||||
|
webpage.url = parsed->url;
|
||||||
|
webpage.id = parsed->id;
|
||||||
|
}
|
||||||
|
}, [](const auto &) {});
|
||||||
|
}
|
||||||
auto cloudDraft = std::make_unique<Draft>(
|
auto cloudDraft = std::make_unique<Draft>(
|
||||||
textWithTags,
|
textWithTags,
|
||||||
replyTo,
|
replyTo,
|
||||||
topicRootId,
|
|
||||||
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
|
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
|
||||||
(draft.is_no_webpage()
|
std::move(webpage));
|
||||||
? Data::PreviewState::Cancelled
|
|
||||||
: Data::PreviewState::Allowed));
|
|
||||||
cloudDraft->date = date;
|
cloudDraft->date = date;
|
||||||
|
|
||||||
history->setCloudDraft(std::move(cloudDraft));
|
history->setCloudDraft(std::move(cloudDraft));
|
||||||
|
|
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "data/data_msg_id.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class InputField;
|
class InputField;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -28,34 +30,40 @@ void ClearPeerCloudDraft(
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
TimeId date);
|
TimeId date);
|
||||||
|
|
||||||
enum class PreviewState : char {
|
struct WebPageDraft {
|
||||||
Allowed,
|
[[nodiscard]] static WebPageDraft FromItem(not_null<HistoryItem*> item);
|
||||||
Cancelled,
|
|
||||||
EmptyOnEdit,
|
WebPageId id = 0;
|
||||||
|
QString url;
|
||||||
|
bool forceLargeMedia : 1 = false;
|
||||||
|
bool forceSmallMedia : 1 = false;
|
||||||
|
bool invert : 1 = false;
|
||||||
|
bool manual : 1 = false;
|
||||||
|
bool removed : 1 = false;
|
||||||
|
|
||||||
|
friend inline bool operator==(const WebPageDraft&, const WebPageDraft&)
|
||||||
|
= default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Draft {
|
struct Draft {
|
||||||
Draft() = default;
|
Draft() = default;
|
||||||
Draft(
|
Draft(
|
||||||
const TextWithTags &textWithTags,
|
const TextWithTags &textWithTags,
|
||||||
MsgId msgId,
|
FullReplyTo reply,
|
||||||
MsgId topicRootId,
|
|
||||||
const MessageCursor &cursor,
|
const MessageCursor &cursor,
|
||||||
PreviewState previewState,
|
WebPageDraft webpage,
|
||||||
mtpRequestId saveRequestId = 0);
|
mtpRequestId saveRequestId = 0);
|
||||||
Draft(
|
Draft(
|
||||||
not_null<const Ui::InputField*> field,
|
not_null<const Ui::InputField*> field,
|
||||||
MsgId msgId,
|
FullReplyTo reply,
|
||||||
MsgId topicRootId,
|
WebPageDraft webpage,
|
||||||
PreviewState previewState,
|
|
||||||
mtpRequestId saveRequestId = 0);
|
mtpRequestId saveRequestId = 0);
|
||||||
|
|
||||||
TimeId date = 0;
|
TimeId date = 0;
|
||||||
TextWithTags textWithTags;
|
TextWithTags textWithTags;
|
||||||
MsgId msgId = 0; // replyToId for message draft, editMsgId for edit draft
|
FullReplyTo reply; // reply.messageId.msg is editMsgId for edit draft.
|
||||||
MsgId topicRootId = 0;
|
|
||||||
MessageCursor cursor;
|
MessageCursor cursor;
|
||||||
PreviewState previewState = PreviewState::Allowed;
|
WebPageDraft webpage;
|
||||||
mtpRequestId saveRequestId = 0;
|
mtpRequestId saveRequestId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,7 +175,8 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
|
||||||
|
|
||||||
[[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
|
[[nodiscard]] inline bool DraftIsNull(const Draft *draft) {
|
||||||
return !draft
|
return !draft
|
||||||
|| (!draft->msgId && DraftStringIsEmpty(draft->textWithTags.text));
|
|| (!draft->reply.messageId
|
||||||
|
&& DraftStringIsEmpty(draft->textWithTags.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
|
[[nodiscard]] inline bool DraftsAreEqual(const Draft *a, const Draft *b) {
|
||||||
|
@ -179,8 +188,8 @@ using HistoryDrafts = base::flat_map<DraftKey, std::unique_ptr<Draft>>;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (a->textWithTags == b->textWithTags)
|
return (a->textWithTags == b->textWithTags)
|
||||||
&& (a->msgId == b->msgId)
|
&& (a->reply == b->reply)
|
||||||
&& (a->previewState == b->previewState);
|
&& (a->webpage == b->webpage);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -123,6 +123,7 @@ void GroupCall::requestParticipants() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
api().request(base::take(_participantsRequestId)).cancel();
|
||||||
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
|
_participantsRequestId = api().request(MTPphone_GetGroupParticipants(
|
||||||
input(),
|
input(),
|
||||||
MTP_vector<MTPInputPeer>(), // ids
|
MTP_vector<MTPInputPeer>(), // ids
|
||||||
|
@ -132,8 +133,8 @@ void GroupCall::requestParticipants() {
|
||||||
: _nextOffset),
|
: _nextOffset),
|
||||||
MTP_int(kRequestPerPage)
|
MTP_int(kRequestPerPage)
|
||||||
)).done([=](const MTPphone_GroupParticipants &result) {
|
)).done([=](const MTPphone_GroupParticipants &result) {
|
||||||
|
_participantsRequestId = 0;
|
||||||
result.match([&](const MTPDphone_groupParticipants &data) {
|
result.match([&](const MTPDphone_groupParticipants &data) {
|
||||||
_participantsRequestId = 0;
|
|
||||||
const auto reloaded = processSavedFullCall();
|
const auto reloaded = processSavedFullCall();
|
||||||
_nextOffset = qs(data.vnext_offset());
|
_nextOffset = qs(data.vnext_offset());
|
||||||
_peer->owner().processUsers(data.vusers());
|
_peer->owner().processUsers(data.vusers());
|
||||||
|
@ -168,7 +169,7 @@ bool GroupCall::processSavedFullCall() {
|
||||||
if (!_savedFull) {
|
if (!_savedFull) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_reloadRequestId = 0;
|
api().request(base::take(_reloadRequestId)).cancel();
|
||||||
_reloadLastFinished = crl::now();
|
_reloadLastFinished = crl::now();
|
||||||
processFullCallFields(*base::take(_savedFull));
|
processFullCallFields(*base::take(_savedFull));
|
||||||
return true;
|
return true;
|
||||||
|
@ -511,10 +512,8 @@ void GroupCall::reloadIfStale() {
|
||||||
void GroupCall::reload() {
|
void GroupCall::reload() {
|
||||||
if (_reloadRequestId || _applyingQueuedUpdates) {
|
if (_reloadRequestId || _applyingQueuedUpdates) {
|
||||||
return;
|
return;
|
||||||
} else if (_participantsRequestId) {
|
|
||||||
api().request(_participantsRequestId).cancel();
|
|
||||||
_participantsRequestId = 0;
|
|
||||||
}
|
}
|
||||||
|
api().request(base::take(_participantsRequestId)).cancel();
|
||||||
|
|
||||||
DEBUG_LOG(("Group Call Participants: "
|
DEBUG_LOG(("Group Call Participants: "
|
||||||
"Reloading with queued: %1"
|
"Reloading with queued: %1"
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "data/data_histories.h"
|
#include "data/data_histories.h"
|
||||||
|
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
|
@ -39,8 +40,9 @@ constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MTPInputReplyTo ReplyToForMTP(
|
MTPInputReplyTo ReplyToForMTP(
|
||||||
not_null<Session*> owner,
|
not_null<History*> history,
|
||||||
FullReplyTo replyTo) {
|
FullReplyTo replyTo) {
|
||||||
|
const auto owner = &history->owner();
|
||||||
if (replyTo.storyId) {
|
if (replyTo.storyId) {
|
||||||
if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
|
if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
|
||||||
if (const auto user = peer->asUser()) {
|
if (const auto user = peer->asUser()) {
|
||||||
|
@ -49,18 +51,45 @@ MTPInputReplyTo ReplyToForMTP(
|
||||||
MTP_int(replyTo.storyId.story));
|
MTP_int(replyTo.storyId.story));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (replyTo.msgId || replyTo.topicRootId) {
|
} else if (replyTo.messageId || replyTo.topicRootId) {
|
||||||
|
const auto external = replyTo.messageId
|
||||||
|
&& (replyTo.messageId.peer != history->peer->id);
|
||||||
|
const auto quoteEntities = Api::EntitiesToMTP(
|
||||||
|
&history->session(),
|
||||||
|
replyTo.quote.entities,
|
||||||
|
Api::ConvertOption::SkipLocal);
|
||||||
using Flag = MTPDinputReplyToMessage::Flag;
|
using Flag = MTPDinputReplyToMessage::Flag;
|
||||||
return MTP_inputReplyToMessage(
|
return MTP_inputReplyToMessage(
|
||||||
(replyTo.topicRootId
|
MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
|
||||||
? MTP_flags(Flag::f_top_msg_id)
|
| (external ? Flag::f_reply_to_peer_id : Flag())
|
||||||
: MTP_flags(0)),
|
| (replyTo.quote.text.isEmpty()
|
||||||
MTP_int(replyTo.msgId ? replyTo.msgId : replyTo.topicRootId),
|
? Flag()
|
||||||
MTP_int(replyTo.topicRootId));
|
: Flag::f_quote_text)
|
||||||
|
| (quoteEntities.v.isEmpty()
|
||||||
|
? Flag()
|
||||||
|
: Flag::f_quote_entities)),
|
||||||
|
MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
|
||||||
|
MTP_int(replyTo.topicRootId),
|
||||||
|
(external
|
||||||
|
? owner->peer(replyTo.messageId.peer)->input
|
||||||
|
: MTPInputPeer()),
|
||||||
|
MTP_string(replyTo.quote.text),
|
||||||
|
quoteEntities);
|
||||||
}
|
}
|
||||||
return MTPInputReplyTo();
|
return MTPInputReplyTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MTPInputMedia WebPageForMTP(
|
||||||
|
const Data::WebPageDraft &draft,
|
||||||
|
bool required) {
|
||||||
|
using Flag = MTPDinputMediaWebPage::Flag;
|
||||||
|
return MTP_inputMediaWebPage(
|
||||||
|
MTP_flags((required ? Flag() : Flag::f_optional)
|
||||||
|
| (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
|
||||||
|
| (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
|
||||||
|
MTP_string(draft.url));
|
||||||
|
}
|
||||||
|
|
||||||
Histories::Histories(not_null<Session*> owner)
|
Histories::Histories(not_null<Session*> owner)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
, _readRequestsTimer([=] { sendReadRequests(); }) {
|
, _readRequestsTimer([=] { sendReadRequests(); }) {
|
||||||
|
@ -930,7 +959,7 @@ int Histories::sendPreparedMessage(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
FullReplyTo replyTo,
|
FullReplyTo replyTo,
|
||||||
uint64 randomId,
|
uint64 randomId,
|
||||||
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message,
|
Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
|
||||||
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
|
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
|
||||||
Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
|
Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
|
||||||
if (isCreatingTopic(history, replyTo.topicRootId)) {
|
if (isCreatingTopic(history, replyTo.topicRootId)) {
|
||||||
|
@ -945,7 +974,7 @@ int Histories::sendPreparedMessage(
|
||||||
}
|
}
|
||||||
i->second.push_back({
|
i->second.push_back({
|
||||||
.randomId = randomId,
|
.randomId = randomId,
|
||||||
.replyTo = replyTo.msgId,
|
.replyTo = replyTo.messageId,
|
||||||
.message = std::move(message),
|
.message = std::move(message),
|
||||||
.done = std::move(done),
|
.done = std::move(done),
|
||||||
.fail = std::move(fail),
|
.fail = std::move(fail),
|
||||||
|
@ -955,11 +984,12 @@ int Histories::sendPreparedMessage(
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
const auto realReplyTo = FullReplyTo{
|
const auto realReplyTo = FullReplyTo{
|
||||||
.msgId = convertTopicReplyToId(history, replyTo.msgId),
|
.messageId = convertTopicReplyToId(history, replyTo.messageId),
|
||||||
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
|
.quote = replyTo.quote,
|
||||||
.storyId = replyTo.storyId,
|
.storyId = replyTo.storyId,
|
||||||
|
.topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
|
||||||
};
|
};
|
||||||
return v::match(message(_owner, realReplyTo), [&](const auto &request) {
|
return v::match(message(history, realReplyTo), [&](const auto &request) {
|
||||||
const auto type = RequestType::Send;
|
const auto type = RequestType::Send;
|
||||||
return sendRequest(history, type, [=](Fn<void()> finish) {
|
return sendRequest(history, type, [=](Fn<void()> finish) {
|
||||||
const auto session = &_owner->session();
|
const auto session = &_owner->session();
|
||||||
|
@ -1003,7 +1033,7 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
|
||||||
sendPreparedMessage(
|
sendPreparedMessage(
|
||||||
history,
|
history,
|
||||||
FullReplyTo{
|
FullReplyTo{
|
||||||
.msgId = entry.replyTo,
|
.messageId = entry.replyTo,
|
||||||
.topicRootId = realRoot,
|
.topicRootId = realRoot,
|
||||||
},
|
},
|
||||||
entry.randomId,
|
entry.randomId,
|
||||||
|
@ -1025,6 +1055,15 @@ void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FullMsgId Histories::convertTopicReplyToId(
|
||||||
|
not_null<History*> history,
|
||||||
|
FullMsgId replyToId) const {
|
||||||
|
const auto id = (history->peer->id == replyToId.peer)
|
||||||
|
? convertTopicReplyToId(history, replyToId.msg)
|
||||||
|
: replyToId.msg;
|
||||||
|
return { replyToId.peer, id };
|
||||||
|
}
|
||||||
|
|
||||||
MsgId Histories::convertTopicReplyToId(
|
MsgId Histories::convertTopicReplyToId(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId replyToId) const {
|
MsgId replyToId) const {
|
||||||
|
|
|
@ -25,10 +25,14 @@ namespace Data {
|
||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
class Folder;
|
class Folder;
|
||||||
|
struct WebPageDraft;
|
||||||
|
|
||||||
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
|
[[nodiscard]] MTPInputReplyTo ReplyToForMTP(
|
||||||
not_null<Session*> owner,
|
not_null<History*> history,
|
||||||
FullReplyTo replyTo);
|
FullReplyTo replyTo);
|
||||||
|
[[nodiscard]] MTPInputMedia WebPageForMTP(
|
||||||
|
const Data::WebPageDraft &draft,
|
||||||
|
bool required = false);
|
||||||
|
|
||||||
class Histories final {
|
class Histories final {
|
||||||
public:
|
public:
|
||||||
|
@ -108,7 +112,7 @@ public:
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
FullReplyTo replyTo,
|
FullReplyTo replyTo,
|
||||||
uint64 randomId,
|
uint64 randomId,
|
||||||
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message,
|
Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
|
||||||
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
|
Fn<void(const MTPUpdates&, const MTP::Response&)> done,
|
||||||
Fn<void(const MTP::Error&, const MTP::Response&)> fail);
|
Fn<void(const MTP::Error&, const MTP::Response&)> fail);
|
||||||
|
|
||||||
|
@ -116,14 +120,17 @@ public:
|
||||||
};
|
};
|
||||||
template <typename RequestType, typename ...Args>
|
template <typename RequestType, typename ...Args>
|
||||||
static auto PrepareMessage(const Args &...args)
|
static auto PrepareMessage(const Args &...args)
|
||||||
-> Fn<Histories::PreparedMessage(not_null<Session*>, FullReplyTo)> {
|
-> Fn<Histories::PreparedMessage(not_null<History*>, FullReplyTo)> {
|
||||||
return [=](not_null<Session*> owner, FullReplyTo replyTo)
|
return [=](not_null<History*> history, FullReplyTo replyTo)
|
||||||
-> RequestType {
|
-> RequestType {
|
||||||
return { ReplaceReplyIds(owner, args, replyTo)... };
|
return { ReplaceReplyIds(history, args, replyTo)... };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
|
void checkTopicCreated(FullMsgId rootId, MsgId realRoot);
|
||||||
|
[[nodiscard]] FullMsgId convertTopicReplyToId(
|
||||||
|
not_null<History*> history,
|
||||||
|
FullMsgId replyToId) const;
|
||||||
[[nodiscard]] MsgId convertTopicReplyToId(
|
[[nodiscard]] MsgId convertTopicReplyToId(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId replyToId) const;
|
MsgId replyToId) const;
|
||||||
|
@ -152,8 +159,8 @@ private:
|
||||||
};
|
};
|
||||||
struct DelayedByTopicMessage {
|
struct DelayedByTopicMessage {
|
||||||
uint64 randomId = 0;
|
uint64 randomId = 0;
|
||||||
MsgId replyTo = 0;
|
FullMsgId replyTo;
|
||||||
Fn<PreparedMessage(not_null<Session*>, FullReplyTo)> message;
|
Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message;
|
||||||
Fn<void(const MTPUpdates&, const MTP::Response&)> done;
|
Fn<void(const MTPUpdates&, const MTP::Response&)> done;
|
||||||
Fn<void(const MTP::Error&, const MTP::Response&)> fail;
|
Fn<void(const MTP::Error&, const MTP::Response&)> fail;
|
||||||
int requestId = 0;
|
int requestId = 0;
|
||||||
|
@ -169,11 +176,11 @@ private:
|
||||||
|
|
||||||
template <typename Arg>
|
template <typename Arg>
|
||||||
static auto ReplaceReplyIds(
|
static auto ReplaceReplyIds(
|
||||||
not_null<Session*> owner,
|
not_null<History*> history,
|
||||||
Arg arg,
|
Arg arg,
|
||||||
FullReplyTo replyTo) {
|
FullReplyTo replyTo) {
|
||||||
if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
|
if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
|
||||||
return ReplyToForMTP(owner, replyTo);
|
return ReplyToForMTP(history, replyTo);
|
||||||
} else {
|
} else {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/media/history_view_contact.h"
|
#include "history/view/media/history_view_contact.h"
|
||||||
#include "history/view/media/history_view_location.h"
|
#include "history/view/media/history_view_location.h"
|
||||||
#include "history/view/media/history_view_game.h"
|
#include "history/view/media/history_view_game.h"
|
||||||
|
#include "history/view/media/history_view_giveaway.h"
|
||||||
#include "history/view/media/history_view_invoice.h"
|
#include "history/view/media/history_view_invoice.h"
|
||||||
#include "history/view/media/history_view_call.h"
|
#include "history/view/media/history_view_call.h"
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
|
@ -361,6 +362,28 @@ Call ComputeCallData(const MTPDmessageActionPhoneCall &call) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Giveaway ComputeGiveawayData(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const MTPDmessageMediaGiveaway &data) {
|
||||||
|
auto result = Giveaway{
|
||||||
|
.untilDate = data.vuntil_date().v,
|
||||||
|
.quantity = data.vquantity().v,
|
||||||
|
.months = data.vmonths().v,
|
||||||
|
};
|
||||||
|
result.channels.reserve(data.vchannels().v.size());
|
||||||
|
const auto owner = &item->history()->owner();
|
||||||
|
for (const auto &id : data.vchannels().v) {
|
||||||
|
result.channels.push_back(owner->channel(ChannelId(id)));
|
||||||
|
}
|
||||||
|
if (const auto countries = data.vcountries_iso2()) {
|
||||||
|
result.countries.reserve(countries->v.size());
|
||||||
|
for (const auto &country : countries->v) {
|
||||||
|
result.countries.push_back(qs(country));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
|
Media::Media(not_null<HistoryItem*> parent) : _parent(parent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +403,10 @@ WebPageData *Media::webpage() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaWebPageFlags Media::webpageFlags() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const SharedContact *Media::sharedContact() const {
|
const SharedContact *Media::sharedContact() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -420,6 +447,10 @@ bool Media::storyMention() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Giveaway *Media::giveaway() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool Media::uploading() const {
|
bool Media::uploading() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1406,9 +1437,11 @@ QString MediaCall::Text(
|
||||||
|
|
||||||
MediaWebPage::MediaWebPage(
|
MediaWebPage::MediaWebPage(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
not_null<WebPageData*> page)
|
not_null<WebPageData*> page,
|
||||||
|
MediaWebPageFlags flags)
|
||||||
: Media(parent)
|
: Media(parent)
|
||||||
, _page(page) {
|
, _page(page)
|
||||||
|
, _flags(flags) {
|
||||||
parent->history()->owner().registerWebPageItem(_page, parent);
|
parent->history()->owner().registerWebPageItem(_page, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1417,7 +1450,7 @@ MediaWebPage::~MediaWebPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) {
|
std::unique_ptr<Media> MediaWebPage::clone(not_null<HistoryItem*> parent) {
|
||||||
return std::make_unique<MediaWebPage>(parent, _page);
|
return std::make_unique<MediaWebPage>(parent, _page, _flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentData *MediaWebPage::document() const {
|
DocumentData *MediaWebPage::document() const {
|
||||||
|
@ -1432,6 +1465,10 @@ WebPageData *MediaWebPage::webpage() const {
|
||||||
return _page;
|
return _page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaWebPageFlags MediaWebPage::webpageFlags() const {
|
||||||
|
return _flags;
|
||||||
|
}
|
||||||
|
|
||||||
bool MediaWebPage::hasReplyPreview() const {
|
bool MediaWebPage::hasReplyPreview() const {
|
||||||
if (const auto document = MediaWebPage::document()) {
|
if (const auto document = MediaWebPage::document()) {
|
||||||
return document->hasThumbnail()
|
return document->hasThumbnail()
|
||||||
|
@ -1462,10 +1499,13 @@ bool MediaWebPage::replyPreviewLoaded() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const {
|
||||||
return { .text = options.translated
|
auto text = options.translated
|
||||||
? parent()->translatedText()
|
? parent()->translatedText()
|
||||||
: parent()->originalText()
|
: parent()->originalText();
|
||||||
};
|
if (text.empty()) {
|
||||||
|
text = Ui::Text::Colorized(_page->url);
|
||||||
|
}
|
||||||
|
return { .text = text };
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities MediaWebPage::notificationText() const {
|
TextWithEntities MediaWebPage::notificationText() const {
|
||||||
|
@ -1496,7 +1536,7 @@ std::unique_ptr<HistoryView::Media> MediaWebPage::createView(
|
||||||
not_null<HistoryView::Element*> message,
|
not_null<HistoryView::Element*> message,
|
||||||
not_null<HistoryItem*> realParent,
|
not_null<HistoryItem*> realParent,
|
||||||
HistoryView::Element *replacing) {
|
HistoryView::Element *replacing) {
|
||||||
return std::make_unique<HistoryView::WebPage>(message, _page);
|
return std::make_unique<HistoryView::WebPage>(message, _page, _flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaGame::MediaGame(
|
MediaGame::MediaGame(
|
||||||
|
@ -1887,21 +1927,28 @@ MediaGiftBox::MediaGiftBox(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
not_null<PeerData*> from,
|
not_null<PeerData*> from,
|
||||||
int months)
|
int months)
|
||||||
|
: MediaGiftBox(parent, from, GiftCode{ .months = months }) {
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaGiftBox::MediaGiftBox(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> from,
|
||||||
|
GiftCode data)
|
||||||
: Media(parent)
|
: Media(parent)
|
||||||
, _from(from)
|
, _from(from)
|
||||||
, _months(months) {
|
, _data(std::move(data)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Media> MediaGiftBox::clone(not_null<HistoryItem*> parent) {
|
std::unique_ptr<Media> MediaGiftBox::clone(not_null<HistoryItem*> parent) {
|
||||||
return std::make_unique<MediaGiftBox>(parent, _from, _months);
|
return std::make_unique<MediaGiftBox>(parent, _from, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<PeerData*> MediaGiftBox::from() const {
|
not_null<PeerData*> MediaGiftBox::from() const {
|
||||||
return _from;
|
return _from;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MediaGiftBox::months() const {
|
const GiftCode &MediaGiftBox::data() const {
|
||||||
return _months;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities MediaGiftBox::notificationText() const {
|
TextWithEntities MediaGiftBox::notificationText() const {
|
||||||
|
@ -1933,14 +1980,6 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
||||||
std::make_unique<HistoryView::PremiumGift>(message, this));
|
std::make_unique<HistoryView::PremiumGift>(message, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaGiftBox::activated() const {
|
|
||||||
return _activated;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaGiftBox::setActivated(bool activated) {
|
|
||||||
_activated = activated;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaWallPaper::MediaWallPaper(
|
MediaWallPaper::MediaWallPaper(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
const WallPaper &paper)
|
const WallPaper &paper)
|
||||||
|
@ -2144,4 +2183,50 @@ std::unique_ptr<HistoryView::Media> MediaStory::createView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaGiveaway::MediaGiveaway(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const Giveaway &data)
|
||||||
|
: Media(parent)
|
||||||
|
, _giveaway(data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Media> MediaGiveaway::clone(not_null<HistoryItem*> parent) {
|
||||||
|
return std::make_unique<MediaGiveaway>(parent, _giveaway);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Giveaway *MediaGiveaway::giveaway() const {
|
||||||
|
return &_giveaway;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithEntities MediaGiveaway::notificationText() const {
|
||||||
|
return {
|
||||||
|
.text = tr::lng_prizes_title(tr::now, lt_count, _giveaway.quantity),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MediaGiveaway::pinnedTextSubstring() const {
|
||||||
|
return QString::fromUtf8("\xC2\xAB")
|
||||||
|
+ notificationText().text
|
||||||
|
+ QString::fromUtf8("\xC2\xBB");
|
||||||
|
}
|
||||||
|
|
||||||
|
TextForMimeData MediaGiveaway::clipboardText() const {
|
||||||
|
return TextForMimeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MediaGiveaway::updateInlineResultMedia(const MTPMessageMedia &media) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MediaGiveaway::updateSentMedia(const MTPMessageMedia &media) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<HistoryView::Media> MediaGiveaway::createView(
|
||||||
|
not_null<HistoryView::Element*> message,
|
||||||
|
not_null<HistoryItem*> realParent,
|
||||||
|
HistoryView::Element *replacing) {
|
||||||
|
return std::make_unique<HistoryView::Giveaway>(message, &_giveaway);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -90,6 +90,23 @@ struct Invoice {
|
||||||
bool isTest = false;
|
bool isTest = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Giveaway {
|
||||||
|
std::vector<not_null<ChannelData*>> channels;
|
||||||
|
std::vector<QString> countries;
|
||||||
|
TimeId untilDate = 0;
|
||||||
|
int quantity = 0;
|
||||||
|
int months = 0;
|
||||||
|
bool all = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GiftCode {
|
||||||
|
QString slug;
|
||||||
|
ChannelData *channel = nullptr;
|
||||||
|
int months = 0;
|
||||||
|
bool viaGiveaway = false;
|
||||||
|
bool unclaimed = false;
|
||||||
|
};
|
||||||
|
|
||||||
class Media {
|
class Media {
|
||||||
public:
|
public:
|
||||||
Media(not_null<HistoryItem*> parent);
|
Media(not_null<HistoryItem*> parent);
|
||||||
|
@ -106,6 +123,7 @@ public:
|
||||||
virtual DocumentData *document() const;
|
virtual DocumentData *document() const;
|
||||||
virtual PhotoData *photo() const;
|
virtual PhotoData *photo() const;
|
||||||
virtual WebPageData *webpage() const;
|
virtual WebPageData *webpage() const;
|
||||||
|
virtual MediaWebPageFlags webpageFlags() const;
|
||||||
virtual const SharedContact *sharedContact() const;
|
virtual const SharedContact *sharedContact() const;
|
||||||
virtual const Call *call() const;
|
virtual const Call *call() const;
|
||||||
virtual GameData *game() const;
|
virtual GameData *game() const;
|
||||||
|
@ -116,6 +134,7 @@ public:
|
||||||
virtual FullStoryId storyId() const;
|
virtual FullStoryId storyId() const;
|
||||||
virtual bool storyExpired(bool revalidate = false);
|
virtual bool storyExpired(bool revalidate = false);
|
||||||
virtual bool storyMention() const;
|
virtual bool storyMention() const;
|
||||||
|
virtual const Giveaway *giveaway() const;
|
||||||
|
|
||||||
virtual bool uploading() const;
|
virtual bool uploading() const;
|
||||||
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const;
|
||||||
|
@ -355,7 +374,8 @@ class MediaWebPage final : public Media {
|
||||||
public:
|
public:
|
||||||
MediaWebPage(
|
MediaWebPage(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
not_null<WebPageData*> page);
|
not_null<WebPageData*> page,
|
||||||
|
MediaWebPageFlags flags);
|
||||||
~MediaWebPage();
|
~MediaWebPage();
|
||||||
|
|
||||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||||
|
@ -363,6 +383,7 @@ public:
|
||||||
DocumentData *document() const override;
|
DocumentData *document() const override;
|
||||||
PhotoData *photo() const override;
|
PhotoData *photo() const override;
|
||||||
WebPageData *webpage() const override;
|
WebPageData *webpage() const override;
|
||||||
|
MediaWebPageFlags webpageFlags() const override;
|
||||||
|
|
||||||
bool hasReplyPreview() const override;
|
bool hasReplyPreview() const override;
|
||||||
Image *replyPreview() const override;
|
Image *replyPreview() const override;
|
||||||
|
@ -381,7 +402,8 @@ public:
|
||||||
HistoryView::Element *replacing = nullptr) override;
|
HistoryView::Element *replacing = nullptr) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
not_null<WebPageData*> _page;
|
const not_null<WebPageData*> _page;
|
||||||
|
const MediaWebPageFlags _flags;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -517,14 +539,15 @@ public:
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
not_null<PeerData*> from,
|
not_null<PeerData*> from,
|
||||||
int months);
|
int months);
|
||||||
|
MediaGiftBox(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> from,
|
||||||
|
GiftCode data);
|
||||||
|
|
||||||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||||
|
|
||||||
[[nodiscard]] not_null<PeerData*> from() const;
|
[[nodiscard]] not_null<PeerData*> from() const;
|
||||||
[[nodiscard]] int months() const;
|
[[nodiscard]] const GiftCode &data() const;
|
||||||
|
|
||||||
[[nodiscard]] bool activated() const;
|
|
||||||
void setActivated(bool activated);
|
|
||||||
|
|
||||||
TextWithEntities notificationText() const override;
|
TextWithEntities notificationText() const override;
|
||||||
QString pinnedTextSubstring() const override;
|
QString pinnedTextSubstring() const override;
|
||||||
|
@ -539,8 +562,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
not_null<PeerData*> _from;
|
not_null<PeerData*> _from;
|
||||||
int _months = 0;
|
GiftCode _data;
|
||||||
bool _activated = false;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -605,6 +627,32 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MediaGiveaway final : public Media {
|
||||||
|
public:
|
||||||
|
MediaGiveaway(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const Giveaway &data);
|
||||||
|
|
||||||
|
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||||
|
|
||||||
|
const Giveaway *giveaway() const override;
|
||||||
|
|
||||||
|
TextWithEntities notificationText() const override;
|
||||||
|
QString pinnedTextSubstring() const override;
|
||||||
|
TextForMimeData clipboardText() const override;
|
||||||
|
|
||||||
|
bool updateInlineResultMedia(const MTPMessageMedia &media) override;
|
||||||
|
bool updateSentMedia(const MTPMessageMedia &media) override;
|
||||||
|
std::unique_ptr<HistoryView::Media> createView(
|
||||||
|
not_null<HistoryView::Element*> message,
|
||||||
|
not_null<HistoryItem*> realParent,
|
||||||
|
HistoryView::Element *replacing = nullptr) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Giveaway _giveaway;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] TextForMimeData WithCaptionClipboardText(
|
[[nodiscard]] TextForMimeData WithCaptionClipboardText(
|
||||||
const QString &attachType,
|
const QString &attachType,
|
||||||
TextForMimeData &&caption);
|
TextForMimeData &&caption);
|
||||||
|
@ -615,4 +663,8 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
|
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
|
||||||
|
|
||||||
|
[[nodiscard]] Giveaway ComputeGiveawayData(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const MTPDmessageMediaGiveaway &data);
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/qt/qt_compare.h"
|
||||||
#include "data/data_peer_id.h"
|
#include "data/data_peer_id.h"
|
||||||
|
#include "ui/text/text_entity.h"
|
||||||
|
|
||||||
struct MsgId {
|
struct MsgId {
|
||||||
constexpr MsgId() noexcept = default;
|
constexpr MsgId() noexcept = default;
|
||||||
|
@ -67,21 +69,6 @@ struct FullStoryId {
|
||||||
friend inline bool operator==(FullStoryId, FullStoryId) = default;
|
friend inline bool operator==(FullStoryId, FullStoryId) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FullReplyTo {
|
|
||||||
MsgId msgId = 0;
|
|
||||||
MsgId topicRootId = 0;
|
|
||||||
FullStoryId storyId;
|
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const {
|
|
||||||
return msgId || (storyId && peerIsUser(storyId.peer));
|
|
||||||
}
|
|
||||||
explicit operator bool() const {
|
|
||||||
return valid();
|
|
||||||
}
|
|
||||||
friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
|
|
||||||
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
|
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
|
||||||
constexpr auto ClientMsgIds = (1LL << 31);
|
constexpr auto ClientMsgIds = (1LL << 31);
|
||||||
constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds);
|
constexpr auto EndClientMsgId = MsgId(StartClientMsgId.bare + ClientMsgIds);
|
||||||
|
@ -169,6 +156,22 @@ struct FullMsgId {
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(FullMsgId);
|
Q_DECLARE_METATYPE(FullMsgId);
|
||||||
|
|
||||||
|
struct FullReplyTo {
|
||||||
|
FullMsgId messageId;
|
||||||
|
TextWithEntities quote;
|
||||||
|
FullStoryId storyId;
|
||||||
|
MsgId topicRootId = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const {
|
||||||
|
return messageId || (storyId && peerIsUser(storyId.peer));
|
||||||
|
}
|
||||||
|
explicit operator bool() const {
|
||||||
|
return valid();
|
||||||
|
}
|
||||||
|
friend inline auto operator<=>(FullReplyTo, FullReplyTo) = default;
|
||||||
|
friend inline bool operator==(FullReplyTo, FullReplyTo) = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct GlobalMsgId {
|
struct GlobalMsgId {
|
||||||
FullMsgId itemId;
|
FullMsgId itemId;
|
||||||
uint64 sessionUniqueId = 0;
|
uint64 sessionUniqueId = 0;
|
||||||
|
|
|
@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/main_window.h" // Window::LogoNoMargin.
|
#include "window/main_window.h" // Window::LogoNoMargin.
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
|
#include "ui/chat/chat_style.h"
|
||||||
#include "ui/empty_userpic.h"
|
#include "ui/empty_userpic.h"
|
||||||
#include "ui/text/text_options.h"
|
#include "ui/text/text_options.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
|
@ -59,8 +60,8 @@ using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
int PeerColorIndex(PeerId peerId) {
|
uint8 DecideColorIndex(PeerId peerId) {
|
||||||
return Ui::EmptyUserpic::ColorIndex(peerId.value & PeerId::kChatTypeMask);
|
return Ui::DecideColorIndex(peerId.value & PeerId::kChatTypeMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerId FakePeerIdForJustName(const QString &name) {
|
PeerId FakePeerIdForJustName(const QString &name) {
|
||||||
|
@ -125,6 +126,40 @@ AllowedReactions Parse(const MTPChatReactions &value) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PeerData *PeerFromInputMTP(
|
||||||
|
not_null<Session*> owner,
|
||||||
|
const MTPInputPeer &input) {
|
||||||
|
return input.match([&](const MTPDinputPeerUser &data) {
|
||||||
|
const auto user = owner->user(data.vuser_id().v);
|
||||||
|
user->setAccessHash(data.vaccess_hash().v);
|
||||||
|
return (PeerData*)user;
|
||||||
|
}, [&](const MTPDinputPeerChat &data) {
|
||||||
|
return (PeerData*)owner->chat(data.vchat_id().v);
|
||||||
|
}, [&](const MTPDinputPeerChannel &data) {
|
||||||
|
const auto channel = owner->channel(data.vchannel_id().v);
|
||||||
|
channel->setAccessHash(data.vaccess_hash().v);
|
||||||
|
return (PeerData*)channel;
|
||||||
|
}, [&](const MTPDinputPeerSelf &data) {
|
||||||
|
return (PeerData*)owner->session().user();
|
||||||
|
}, [&](const auto &data) {
|
||||||
|
return (PeerData*)nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
UserData *UserFromInputMTP(
|
||||||
|
not_null<Session*> owner,
|
||||||
|
const MTPInputUser &input) {
|
||||||
|
return input.match([&](const MTPDinputUser &data) {
|
||||||
|
const auto user = owner->user(data.vuser_id().v);
|
||||||
|
user->setAccessHash(data.vaccess_hash().v);
|
||||||
|
return user.get();
|
||||||
|
}, [&](const MTPDinputUserSelf &data) {
|
||||||
|
return owner->session().user().get();
|
||||||
|
}, [](const auto &data) {
|
||||||
|
return (UserData*)nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)
|
PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)
|
||||||
|
@ -157,7 +192,8 @@ void PeerClickHandler::onClick(ClickContext context) const {
|
||||||
|
|
||||||
PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
|
PeerData::PeerData(not_null<Data::Session*> owner, PeerId id)
|
||||||
: id(id)
|
: id(id)
|
||||||
, _owner(owner) {
|
, _owner(owner)
|
||||||
|
, _colorIndex(Data::DecideColorIndex(id)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Data::Session &PeerData::owner() const {
|
Data::Session &PeerData::owner() const {
|
||||||
|
@ -230,7 +266,7 @@ not_null<Ui::EmptyUserpic*> PeerData::ensureEmptyUserpic() const {
|
||||||
if (!_userpicEmpty) {
|
if (!_userpicEmpty) {
|
||||||
const auto user = asUser();
|
const auto user = asUser();
|
||||||
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
|
_userpicEmpty = std::make_unique<Ui::EmptyUserpic>(
|
||||||
Ui::EmptyUserpic::UserpicColor(Data::PeerColorIndex(id)),
|
Ui::EmptyUserpic::UserpicColor(colorIndex()),
|
||||||
((user && user->isInaccessible())
|
((user && user->isInaccessible())
|
||||||
? Ui::EmptyUserpic::InaccessibleName()
|
? Ui::EmptyUserpic::InaccessibleName()
|
||||||
: name()));
|
: name()));
|
||||||
|
@ -251,7 +287,7 @@ void PeerData::setUserpic(
|
||||||
const ImageLocation &location,
|
const ImageLocation &location,
|
||||||
bool hasVideo) {
|
bool hasVideo) {
|
||||||
_userpicPhotoId = photoId;
|
_userpicPhotoId = photoId;
|
||||||
_userpicHasVideo = hasVideo;
|
_userpicHasVideo = hasVideo ? 1 : 0;
|
||||||
_userpic.set(&session(), ImageWithLocation{ .location = location });
|
_userpic.set(&session(), ImageWithLocation{ .location = location });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,6 +425,22 @@ QImage PeerData::generateUserpicImage(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageLocation PeerData::userpicLocation() const {
|
||||||
|
return _userpic.location();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerData::userpicPhotoUnknown() const {
|
||||||
|
return (_userpicPhotoId == kUnknownPhotoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotoId PeerData::userpicPhotoId() const {
|
||||||
|
return userpicPhotoUnknown() ? 0 : _userpicPhotoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerData::userpicHasVideo() const {
|
||||||
|
return _userpicHasVideo != 0;
|
||||||
|
}
|
||||||
|
|
||||||
Data::FileOrigin PeerData::userpicOrigin() const {
|
Data::FileOrigin PeerData::userpicOrigin() const {
|
||||||
return Data::FileOriginPeerPhoto(id);
|
return Data::FileOriginPeerPhoto(id);
|
||||||
}
|
}
|
||||||
|
@ -428,7 +480,7 @@ void PeerData::setUserpicChecked(
|
||||||
bool hasVideo) {
|
bool hasVideo) {
|
||||||
if (_userpicPhotoId != photoId
|
if (_userpicPhotoId != photoId
|
||||||
|| _userpic.location() != location
|
|| _userpic.location() != location
|
||||||
|| _userpicHasVideo != hasVideo) {
|
|| _userpicHasVideo != (hasVideo ? 1 : 0)) {
|
||||||
const auto known = !userpicPhotoUnknown();
|
const auto known = !userpicPhotoUnknown();
|
||||||
setUserpic(photoId, location, hasVideo);
|
setUserpic(photoId, location, hasVideo);
|
||||||
session().changes().peerUpdated(this, UpdateFlag::Photo);
|
session().changes().peerUpdated(this, UpdateFlag::Photo);
|
||||||
|
@ -602,6 +654,20 @@ void PeerData::setSettings(const MTPPeerSettings &data) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerData::changeColorIndex(
|
||||||
|
const tl::conditional<MTPint> &cloudColorIndex) {
|
||||||
|
return cloudColorIndex
|
||||||
|
? changeColorIndex(cloudColorIndex->v)
|
||||||
|
: clearColorIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerData::changeBackgroundEmojiId(
|
||||||
|
const tl::conditional<MTPlong> &cloudBackgroundEmoji) {
|
||||||
|
return changeBackgroundEmojiId(cloudBackgroundEmoji
|
||||||
|
? cloudBackgroundEmoji->v
|
||||||
|
: DocumentId());
|
||||||
|
}
|
||||||
|
|
||||||
void PeerData::fillNames() {
|
void PeerData::fillNames() {
|
||||||
_nameWords.clear();
|
_nameWords.clear();
|
||||||
_nameFirstLetters.clear();
|
_nameFirstLetters.clear();
|
||||||
|
@ -818,6 +884,36 @@ QString PeerData::userName() const {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerData::changeColorIndex(uint8 index) {
|
||||||
|
index %= Ui::kColorIndexCount;
|
||||||
|
if (_colorIndexCloud && _colorIndex == index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_colorIndexCloud = 1;
|
||||||
|
_colorIndex = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerData::clearColorIndex() {
|
||||||
|
if (!_colorIndexCloud) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_colorIndexCloud = 0;
|
||||||
|
_colorIndex = Data::DecideColorIndex(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentId PeerData::backgroundEmojiId() const {
|
||||||
|
return _backgroundEmojiId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PeerData::changeBackgroundEmojiId(DocumentId id) {
|
||||||
|
if (_backgroundEmojiId == id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_backgroundEmojiId = id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool PeerData::isSelf() const {
|
bool PeerData::isSelf() const {
|
||||||
if (const auto user = asUser()) {
|
if (const auto user = asUser()) {
|
||||||
return (user->flags() & UserDataFlag::Self);
|
return (user->flags() & UserDataFlag::Self);
|
||||||
|
|
|
@ -39,7 +39,7 @@ class GroupCall;
|
||||||
struct ReactionId;
|
struct ReactionId;
|
||||||
class WallPaper;
|
class WallPaper;
|
||||||
|
|
||||||
[[nodiscard]] int PeerColorIndex(PeerId peerId);
|
[[nodiscard]] uint8 DecideColorIndex(PeerId peerId);
|
||||||
|
|
||||||
// Must be used only for PeerColor-s.
|
// Must be used only for PeerColor-s.
|
||||||
[[nodiscard]] PeerId FakePeerIdForJustName(const QString &name);
|
[[nodiscard]] PeerId FakePeerIdForJustName(const QString &name);
|
||||||
|
@ -116,6 +116,12 @@ bool operator<(const AllowedReactions &a, const AllowedReactions &b);
|
||||||
bool operator==(const AllowedReactions &a, const AllowedReactions &b);
|
bool operator==(const AllowedReactions &a, const AllowedReactions &b);
|
||||||
|
|
||||||
[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
|
[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
|
||||||
|
[[nodiscard]] PeerData *PeerFromInputMTP(
|
||||||
|
not_null<Session*> owner,
|
||||||
|
const MTPInputPeer &input);
|
||||||
|
[[nodiscard]] UserData *UserFromInputMTP(
|
||||||
|
not_null<Session*> owner,
|
||||||
|
const MTPInputUser &input);
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
@ -164,6 +170,14 @@ public:
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
[[nodiscard]] Main::Account &account() const;
|
[[nodiscard]] Main::Account &account() const;
|
||||||
|
|
||||||
|
[[nodiscard]] uint8 colorIndex() const {
|
||||||
|
return _colorIndex;
|
||||||
|
}
|
||||||
|
bool changeColorIndex(uint8 index);
|
||||||
|
bool clearColorIndex();
|
||||||
|
[[nodiscard]] DocumentId backgroundEmojiId() const;
|
||||||
|
bool changeBackgroundEmojiId(DocumentId id);
|
||||||
|
|
||||||
[[nodiscard]] bool isUser() const {
|
[[nodiscard]] bool isUser() const {
|
||||||
return peerIsUser(id);
|
return peerIsUser(id);
|
||||||
}
|
}
|
||||||
|
@ -285,20 +299,12 @@ public:
|
||||||
Ui::PeerUserpicView &view,
|
Ui::PeerUserpicView &view,
|
||||||
int size,
|
int size,
|
||||||
std::optional<int> radius = {}) const;
|
std::optional<int> radius = {}) const;
|
||||||
[[nodiscard]] ImageLocation userpicLocation() const {
|
[[nodiscard]] ImageLocation userpicLocation() const;
|
||||||
return _userpic.location();
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
static constexpr auto kUnknownPhotoId = PhotoId(0xFFFFFFFFFFFFFFFFULL);
|
||||||
[[nodiscard]] bool userpicPhotoUnknown() const {
|
[[nodiscard]] bool userpicPhotoUnknown() const;
|
||||||
return (_userpicPhotoId == kUnknownPhotoId);
|
[[nodiscard]] PhotoId userpicPhotoId() const;
|
||||||
}
|
[[nodiscard]] bool userpicHasVideo() const;
|
||||||
[[nodiscard]] PhotoId userpicPhotoId() const {
|
|
||||||
return userpicPhotoUnknown() ? 0 : _userpicPhotoId;
|
|
||||||
}
|
|
||||||
[[nodiscard]] bool userpicHasVideo() const {
|
|
||||||
return _userpicHasVideo;
|
|
||||||
}
|
|
||||||
[[nodiscard]] Data::FileOrigin userpicOrigin() const;
|
[[nodiscard]] Data::FileOrigin userpicOrigin() const;
|
||||||
[[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const;
|
[[nodiscard]] Data::FileOrigin userpicPhotoOrigin() const;
|
||||||
|
|
||||||
|
@ -361,6 +367,9 @@ public:
|
||||||
void saveTranslationDisabled(bool disabled);
|
void saveTranslationDisabled(bool disabled);
|
||||||
|
|
||||||
void setSettings(const MTPPeerSettings &data);
|
void setSettings(const MTPPeerSettings &data);
|
||||||
|
bool changeColorIndex(const tl::conditional<MTPint> &cloudColorIndex);
|
||||||
|
bool changeBackgroundEmojiId(
|
||||||
|
const tl::conditional<MTPlong> &cloudBackgroundEmoji);
|
||||||
|
|
||||||
enum class BlockStatus : char {
|
enum class BlockStatus : char {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -453,6 +462,7 @@ private:
|
||||||
base::flat_set<QString> _nameWords; // for filtering
|
base::flat_set<QString> _nameWords; // for filtering
|
||||||
base::flat_set<QChar> _nameFirstLetters;
|
base::flat_set<QChar> _nameFirstLetters;
|
||||||
|
|
||||||
|
uint64 _backgroundEmojiId = 0;
|
||||||
crl::time _lastFullUpdate = 0;
|
crl::time _lastFullUpdate = 0;
|
||||||
|
|
||||||
QString _name;
|
QString _name;
|
||||||
|
@ -460,14 +470,16 @@ private:
|
||||||
|
|
||||||
TimeId _ttlPeriod = 0;
|
TimeId _ttlPeriod = 0;
|
||||||
|
|
||||||
|
QString _requestChatTitle;
|
||||||
|
TimeId _requestChatDate = 0;
|
||||||
|
|
||||||
Settings _settings = PeerSettings(PeerSetting::Unknown);
|
Settings _settings = PeerSettings(PeerSetting::Unknown);
|
||||||
BlockStatus _blockStatus = BlockStatus::Unknown;
|
BlockStatus _blockStatus = BlockStatus::Unknown;
|
||||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||||
TranslationFlag _translationFlag = TranslationFlag::Unknown;
|
TranslationFlag _translationFlag = TranslationFlag::Unknown;
|
||||||
bool _userpicHasVideo = false;
|
uint8 _colorIndex : 6 = 0;
|
||||||
|
uint8 _colorIndexCloud : 1 = 0;
|
||||||
QString _requestChatTitle;
|
uint8 _userpicHasVideo : 1 = 0;
|
||||||
TimeId _requestChatDate = 0;
|
|
||||||
|
|
||||||
QString _about;
|
QString _about;
|
||||||
QString _themeEmoticon;
|
QString _themeEmoticon;
|
||||||
|
|
|
@ -40,14 +40,14 @@ void ReplyPreview::prepare(
|
||||||
if (h <= 0) h = 1;
|
if (h <= 0) h = 1;
|
||||||
auto thumbSize = (w > h)
|
auto thumbSize = (w > h)
|
||||||
? QSize(
|
? QSize(
|
||||||
w * st::msgReplyBarSize.height() / h,
|
w * st::historyReplyPreview / h,
|
||||||
st::msgReplyBarSize.height())
|
st::historyReplyPreview)
|
||||||
: QSize(
|
: QSize(
|
||||||
st::msgReplyBarSize.height(),
|
st::historyReplyPreview,
|
||||||
h * st::msgReplyBarSize.height() / w);
|
h * st::historyReplyPreview / w);
|
||||||
thumbSize *= style::DevicePixelRatio();
|
thumbSize *= style::DevicePixelRatio();
|
||||||
options |= Option::TransparentBackground;
|
options |= Option::TransparentBackground;
|
||||||
auto outerSize = st::msgReplyBarSize.height();
|
auto outerSize = st::historyReplyPreview;
|
||||||
auto original = spoiler
|
auto original = spoiler
|
||||||
? image->original().scaled(
|
? image->original().scaled(
|
||||||
{ 40, 40 },
|
{ 40, 40 },
|
||||||
|
|
|
@ -716,6 +716,19 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
||||||
if (canShareThisContact != result->canShareThisContactFast()) {
|
if (canShareThisContact != result->canShareThisContactFast()) {
|
||||||
flags |= UpdateFlag::CanShareContact;
|
flags |= UpdateFlag::CanShareContact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto decorationsUpdated = false;
|
||||||
|
if (result->changeColorIndex(data.vcolor())) {
|
||||||
|
flags |= UpdateFlag::Color;
|
||||||
|
decorationsUpdated = true;
|
||||||
|
}
|
||||||
|
if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) {
|
||||||
|
flags |= UpdateFlag::BackgroundEmoji;
|
||||||
|
decorationsUpdated = true;
|
||||||
|
}
|
||||||
|
if (decorationsUpdated && result->isMinimalLoaded()) {
|
||||||
|
_peerDecorationsUpdated.fire_copy(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (minimal) {
|
if (minimal) {
|
||||||
|
@ -990,6 +1003,18 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||||
if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) {
|
if (wasCallNotEmpty != Data::ChannelHasActiveCall(channel)) {
|
||||||
flags |= UpdateFlag::GroupCall;
|
flags |= UpdateFlag::GroupCall;
|
||||||
}
|
}
|
||||||
|
auto decorationsUpdated = false;
|
||||||
|
if (result->changeColorIndex(data.vcolor())) {
|
||||||
|
flags |= UpdateFlag::Color;
|
||||||
|
decorationsUpdated = true;
|
||||||
|
}
|
||||||
|
if (result->changeBackgroundEmojiId(data.vbackground_emoji_id())) {
|
||||||
|
flags |= UpdateFlag::BackgroundEmoji;
|
||||||
|
decorationsUpdated = true;
|
||||||
|
}
|
||||||
|
if (decorationsUpdated && result->isMinimalLoaded()) {
|
||||||
|
_peerDecorationsUpdated.fire_copy(result);
|
||||||
|
}
|
||||||
}, [&](const MTPDchannelForbidden &data) {
|
}, [&](const MTPDchannelForbidden &data) {
|
||||||
const auto channel = result->asChannel();
|
const auto channel = result->asChannel();
|
||||||
|
|
||||||
|
@ -3319,8 +3344,10 @@ not_null<WebPageData*> Session::processWebpage(const MTPWebPage &data) {
|
||||||
return processWebpage(data.c_webPage());
|
return processWebpage(data.c_webPage());
|
||||||
case mtpc_webPageEmpty: {
|
case mtpc_webPageEmpty: {
|
||||||
const auto result = webpage(data.c_webPageEmpty().vid().v);
|
const auto result = webpage(data.c_webPageEmpty().vid().v);
|
||||||
|
result->type = WebPageType::None;
|
||||||
if (result->pendingTill > 0) {
|
if (result->pendingTill > 0) {
|
||||||
result->pendingTill = -1; // failed
|
result->pendingTill = 0;
|
||||||
|
result->failed = 1;
|
||||||
notifyWebPageUpdateDelayed(result);
|
notifyWebPageUpdateDelayed(result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -3341,12 +3368,13 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPage &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
|
not_null<WebPageData*> Session::processWebpage(
|
||||||
|
const MTPDwebPagePending &data) {
|
||||||
constexpr auto kDefaultPendingTimeout = 60;
|
constexpr auto kDefaultPendingTimeout = 60;
|
||||||
const auto result = webpage(data.vid().v);
|
const auto result = webpage(data.vid().v);
|
||||||
webpageApplyFields(
|
webpageApplyFields(
|
||||||
result,
|
result,
|
||||||
WebPageType::Article,
|
WebPageType::None,
|
||||||
QString(),
|
QString(),
|
||||||
QString(),
|
QString(),
|
||||||
QString(),
|
QString(),
|
||||||
|
@ -3358,6 +3386,7 @@ not_null<WebPageData*> Session::processWebpage(const MTPDwebPagePending &data) {
|
||||||
WebPageCollage(),
|
WebPageCollage(),
|
||||||
0,
|
0,
|
||||||
QString(),
|
QString(),
|
||||||
|
false,
|
||||||
data.vdate().v
|
data.vdate().v
|
||||||
? data.vdate().v
|
? data.vdate().v
|
||||||
: (base::unixtime::now() + kDefaultPendingTimeout));
|
: (base::unixtime::now() + kDefaultPendingTimeout));
|
||||||
|
@ -3381,6 +3410,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
WebPageCollage(),
|
WebPageCollage(),
|
||||||
0,
|
0,
|
||||||
QString(),
|
QString(),
|
||||||
|
false,
|
||||||
TimeId(0));
|
TimeId(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3397,6 +3427,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
|
bool hasLargeMedia,
|
||||||
TimeId pendingTill) {
|
TimeId pendingTill) {
|
||||||
const auto result = webpage(id);
|
const auto result = webpage(id);
|
||||||
webpageApplyFields(
|
webpageApplyFields(
|
||||||
|
@ -3413,6 +3444,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
std::move(collage),
|
std::move(collage),
|
||||||
duration,
|
duration,
|
||||||
author,
|
author,
|
||||||
|
hasLargeMedia,
|
||||||
pendingTill);
|
pendingTill);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -3512,6 +3544,7 @@ void Session::webpageApplyFields(
|
||||||
WebPageCollage(this, data),
|
WebPageCollage(this, data),
|
||||||
data.vduration().value_or_empty(),
|
data.vduration().value_or_empty(),
|
||||||
qs(data.vauthor().value_or_empty()),
|
qs(data.vauthor().value_or_empty()),
|
||||||
|
data.is_has_large_media(),
|
||||||
pendingTill);
|
pendingTill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3529,6 +3562,7 @@ void Session::webpageApplyFields(
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
|
bool hasLargeMedia,
|
||||||
TimeId pendingTill) {
|
TimeId pendingTill) {
|
||||||
const auto requestPending = (!page->pendingTill && pendingTill > 0);
|
const auto requestPending = (!page->pendingTill && pendingTill > 0);
|
||||||
const auto changed = page->applyChanges(
|
const auto changed = page->applyChanges(
|
||||||
|
@ -3544,6 +3578,7 @@ void Session::webpageApplyFields(
|
||||||
std::move(collage),
|
std::move(collage),
|
||||||
duration,
|
duration,
|
||||||
author,
|
author,
|
||||||
|
hasLargeMedia,
|
||||||
pendingTill);
|
pendingTill);
|
||||||
if (requestPending) {
|
if (requestPending) {
|
||||||
_session->api().requestWebPageDelayed(page);
|
_session->api().requestWebPageDelayed(page);
|
||||||
|
@ -4377,7 +4412,8 @@ auto Session::dialogsRowReplacements() const
|
||||||
|
|
||||||
void Session::serviceNotification(
|
void Session::serviceNotification(
|
||||||
const TextWithEntities &message,
|
const TextWithEntities &message,
|
||||||
const MTPMessageMedia &media) {
|
const MTPMessageMedia &media,
|
||||||
|
bool invertMedia) {
|
||||||
const auto date = base::unixtime::now();
|
const auto date = base::unixtime::now();
|
||||||
if (!peerLoaded(PeerData::kServiceNotificationsId)) {
|
if (!peerLoaded(PeerData::kServiceNotificationsId)) {
|
||||||
processUser(MTP_user(
|
processUser(MTP_user(
|
||||||
|
@ -4400,25 +4436,32 @@ void Session::serviceNotification(
|
||||||
MTPstring(), // lang_code
|
MTPstring(), // lang_code
|
||||||
MTPEmojiStatus(),
|
MTPEmojiStatus(),
|
||||||
MTPVector<MTPUsername>(),
|
MTPVector<MTPUsername>(),
|
||||||
MTPint())); // stories_max_id
|
MTPint(), // stories_max_id
|
||||||
|
MTP_int(0), // color
|
||||||
|
MTPlong())); // background_emoji_id
|
||||||
}
|
}
|
||||||
const auto history = this->history(PeerData::kServiceNotificationsId);
|
const auto history = this->history(PeerData::kServiceNotificationsId);
|
||||||
|
const auto insert = [=] {
|
||||||
|
insertCheckedServiceNotification(message, media, date, invertMedia);
|
||||||
|
};
|
||||||
if (!history->folderKnown()) {
|
if (!history->folderKnown()) {
|
||||||
histories().requestDialogEntry(history, [=] {
|
histories().requestDialogEntry(history, insert);
|
||||||
insertCheckedServiceNotification(message, media, date);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
insertCheckedServiceNotification(message, media, date);
|
insert();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::insertCheckedServiceNotification(
|
void Session::insertCheckedServiceNotification(
|
||||||
const TextWithEntities &message,
|
const TextWithEntities &message,
|
||||||
const MTPMessageMedia &media,
|
const MTPMessageMedia &media,
|
||||||
TimeId date) {
|
TimeId date,
|
||||||
|
bool invertMedia) {
|
||||||
const auto flags = MTPDmessage::Flag::f_entities
|
const auto flags = MTPDmessage::Flag::f_entities
|
||||||
| MTPDmessage::Flag::f_from_id
|
| MTPDmessage::Flag::f_from_id
|
||||||
| MTPDmessage::Flag::f_media;
|
| MTPDmessage::Flag::f_media
|
||||||
|
| (invertMedia
|
||||||
|
? MTPDmessage::Flag::f_invert_media
|
||||||
|
: MTPDmessage::Flag());
|
||||||
const auto localFlags = MessageFlag::ClientSideUnread
|
const auto localFlags = MessageFlag::ClientSideUnread
|
||||||
| MessageFlag::Local;
|
| MessageFlag::Local;
|
||||||
auto sending = TextWithEntities(), left = message;
|
auto sending = TextWithEntities(), left = message;
|
||||||
|
@ -4562,6 +4605,10 @@ auto Session::webViewResultSent() const -> rpl::producer<WebViewResultSent> {
|
||||||
return _webViewResultSent.events();
|
return _webViewResultSent.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<not_null<PeerData*>> Session::peerDecorationsUpdated() const {
|
||||||
|
return _peerDecorationsUpdated.events();
|
||||||
|
}
|
||||||
|
|
||||||
void Session::clearLocalStorage() {
|
void Session::clearLocalStorage() {
|
||||||
_cache->close();
|
_cache->close();
|
||||||
_cache->clear();
|
_cache->clear();
|
||||||
|
|
|
@ -20,7 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
class Image;
|
class Image;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
struct WebPageCollage;
|
struct WebPageCollage;
|
||||||
enum class WebPageType;
|
enum class WebPageType : uint8;
|
||||||
enum class NewMessageType;
|
enum class NewMessageType;
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
@ -562,6 +562,7 @@ public:
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
|
bool hasLargeMedia,
|
||||||
TimeId pendingTill);
|
TimeId pendingTill);
|
||||||
|
|
||||||
[[nodiscard]] not_null<GameData*> game(GameId id);
|
[[nodiscard]] not_null<GameData*> game(GameId id);
|
||||||
|
@ -704,7 +705,8 @@ public:
|
||||||
|
|
||||||
void serviceNotification(
|
void serviceNotification(
|
||||||
const TextWithEntities &message,
|
const TextWithEntities &message,
|
||||||
const MTPMessageMedia &media = MTP_messageMediaEmpty());
|
const MTPMessageMedia &media = MTP_messageMediaEmpty(),
|
||||||
|
bool invertMedia = false);
|
||||||
|
|
||||||
void setMimeForwardIds(MessageIdsList &&list);
|
void setMimeForwardIds(MessageIdsList &&list);
|
||||||
MessageIdsList takeMimeForwardIds();
|
MessageIdsList takeMimeForwardIds();
|
||||||
|
@ -725,6 +727,9 @@ public:
|
||||||
void webViewResultSent(WebViewResultSent &&sent);
|
void webViewResultSent(WebViewResultSent &&sent);
|
||||||
[[nodiscard]] rpl::producer<WebViewResultSent> webViewResultSent() const;
|
[[nodiscard]] rpl::producer<WebViewResultSent> webViewResultSent() const;
|
||||||
|
|
||||||
|
[[nodiscard]] auto peerDecorationsUpdated() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>>;
|
||||||
|
|
||||||
void clearLocalStorage();
|
void clearLocalStorage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -824,6 +829,7 @@ private:
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
|
bool hasLargeMedia,
|
||||||
TimeId pendingTill);
|
TimeId pendingTill);
|
||||||
|
|
||||||
void gameApplyFields(
|
void gameApplyFields(
|
||||||
|
@ -846,7 +852,8 @@ private:
|
||||||
void insertCheckedServiceNotification(
|
void insertCheckedServiceNotification(
|
||||||
const TextWithEntities &message,
|
const TextWithEntities &message,
|
||||||
const MTPMessageMedia &media,
|
const MTPMessageMedia &media,
|
||||||
TimeId date);
|
TimeId date,
|
||||||
|
bool invertMedia);
|
||||||
|
|
||||||
void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash);
|
void setWallpapers(const QVector<MTPWallPaper> &data, uint64 hash);
|
||||||
void highlightProcessDone(uint64 processId);
|
void highlightProcessDone(uint64 processId);
|
||||||
|
@ -1009,6 +1016,8 @@ private:
|
||||||
|
|
||||||
rpl::event_stream<WebViewResultSent> _webViewResultSent;
|
rpl::event_stream<WebViewResultSent> _webViewResultSent;
|
||||||
|
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _peerDecorationsUpdated;
|
||||||
|
|
||||||
Groups _groups;
|
Groups _groups;
|
||||||
const std::unique_ptr<ChatFilters> _chatsFilters;
|
const std::unique_ptr<ChatFilters> _chatsFilters;
|
||||||
std::unique_ptr<ScheduledMessages> _scheduledMessages;
|
std::unique_ptr<ScheduledMessages> _scheduledMessages;
|
||||||
|
|
|
@ -246,65 +246,78 @@ enum class MessageFlag : uint64 {
|
||||||
MentionsMe = (1ULL << 15),
|
MentionsMe = (1ULL << 15),
|
||||||
IsOrWasScheduled = (1ULL << 16),
|
IsOrWasScheduled = (1ULL << 16),
|
||||||
NoForwards = (1ULL << 17),
|
NoForwards = (1ULL << 17),
|
||||||
|
InvertMedia = (1ULL << 18),
|
||||||
|
|
||||||
// Needs to return back to inline mode.
|
// Needs to return back to inline mode.
|
||||||
HasSwitchInlineButton = (1ULL << 18),
|
HasSwitchInlineButton = (1ULL << 19),
|
||||||
|
|
||||||
// For "shared links" indexing.
|
// For "shared links" indexing.
|
||||||
HasTextLinks = (1ULL << 19),
|
HasTextLinks = (1ULL << 20),
|
||||||
|
|
||||||
// Group / channel create or migrate service message.
|
// Group / channel create or migrate service message.
|
||||||
IsGroupEssential = (1ULL << 20),
|
IsGroupEssential = (1ULL << 21),
|
||||||
|
|
||||||
// Edited media is generated on the client
|
// Edited media is generated on the client
|
||||||
// and should not update media from server.
|
// and should not update media from server.
|
||||||
IsLocalUpdateMedia = (1ULL << 21),
|
IsLocalUpdateMedia = (1ULL << 22),
|
||||||
|
|
||||||
// Sent from inline bot, need to re-set media when sent.
|
// Sent from inline bot, need to re-set media when sent.
|
||||||
FromInlineBot = (1ULL << 22),
|
FromInlineBot = (1ULL << 23),
|
||||||
|
|
||||||
// Generated on the client side and should be unread.
|
// Generated on the client side and should be unread.
|
||||||
ClientSideUnread = (1ULL << 23),
|
ClientSideUnread = (1ULL << 24),
|
||||||
|
|
||||||
// In a supergroup.
|
// In a supergroup.
|
||||||
HasAdminBadge = (1ULL << 24),
|
HasAdminBadge = (1ULL << 25),
|
||||||
|
|
||||||
// Outgoing message that is being sent.
|
// Outgoing message that is being sent.
|
||||||
BeingSent = (1ULL << 25),
|
BeingSent = (1ULL << 26),
|
||||||
|
|
||||||
// Outgoing message and failed to be sent.
|
// Outgoing message and failed to be sent.
|
||||||
SendingFailed = (1ULL << 26),
|
SendingFailed = (1ULL << 27),
|
||||||
|
|
||||||
// No media and only a several emoji or an only custom emoji text.
|
// No media and only a several emoji or an only custom emoji text.
|
||||||
SpecialOnlyEmoji = (1ULL << 27),
|
SpecialOnlyEmoji = (1ULL << 28),
|
||||||
|
|
||||||
// Message existing in the message history.
|
// Message existing in the message history.
|
||||||
HistoryEntry = (1ULL << 28),
|
HistoryEntry = (1ULL << 29),
|
||||||
|
|
||||||
// Local message, not existing on the server.
|
// Local message, not existing on the server.
|
||||||
Local = (1ULL << 29),
|
Local = (1ULL << 30),
|
||||||
|
|
||||||
// Fake message for some UI element.
|
// Fake message for some UI element.
|
||||||
FakeHistoryItem = (1ULL << 30),
|
FakeHistoryItem = (1ULL << 31),
|
||||||
|
|
||||||
// Contact sign-up message, notification should be skipped for Silent.
|
// Contact sign-up message, notification should be skipped for Silent.
|
||||||
IsContactSignUp = (1ULL << 31),
|
IsContactSignUp = (1ULL << 32),
|
||||||
|
|
||||||
// Optimization for item text custom emoji repainting.
|
// Optimization for item text custom emoji repainting.
|
||||||
CustomEmojiRepainting = (1ULL << 32),
|
CustomEmojiRepainting = (1ULL << 33),
|
||||||
|
|
||||||
// Profile photo suggestion, views have special media type.
|
// Profile photo suggestion, views have special media type.
|
||||||
IsUserpicSuggestion = (1ULL << 33),
|
IsUserpicSuggestion = (1ULL << 34),
|
||||||
|
|
||||||
OnlyEmojiAndSpaces = (1ULL << 34),
|
OnlyEmojiAndSpaces = (1ULL << 35),
|
||||||
OnlyEmojiAndSpacesSet = (1ULL << 35),
|
OnlyEmojiAndSpacesSet = (1ULL << 36),
|
||||||
|
|
||||||
// Fake message with bot cover and information.
|
// Fake message with bot cover and information.
|
||||||
FakeBotAbout = (1ULL << 36),
|
FakeBotAbout = (1ULL << 37),
|
||||||
|
|
||||||
StoryItem = (1ULL << 37),
|
StoryItem = (1ULL << 38),
|
||||||
|
|
||||||
InHighlightProcess = (1ULL << 38),
|
InHighlightProcess = (1ULL << 39),
|
||||||
|
|
||||||
|
// If not set then we need to refresh _displayFrom value.
|
||||||
|
DisplayFromChecked = (1ULL << 40),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||||
using MessageFlags = base::flags<MessageFlag>;
|
using MessageFlags = base::flags<MessageFlag>;
|
||||||
|
|
||||||
|
enum class MediaWebPageFlag : uint8 {
|
||||||
|
ForceLargeMedia = (1 << 0),
|
||||||
|
ForceSmallMedia = (1 << 1),
|
||||||
|
Manual = (1 << 2),
|
||||||
|
Safe = (1 << 3),
|
||||||
|
};
|
||||||
|
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
|
||||||
|
using MediaWebPageFlags = base::flags<MediaWebPageFlag>;
|
||||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
|
|
||||||
|
@ -224,9 +225,10 @@ bool WebPageData::applyChanges(
|
||||||
WebPageCollage &&newCollage,
|
WebPageCollage &&newCollage,
|
||||||
int newDuration,
|
int newDuration,
|
||||||
const QString &newAuthor,
|
const QString &newAuthor,
|
||||||
|
bool newHasLargeMedia,
|
||||||
int newPendingTill) {
|
int newPendingTill) {
|
||||||
if (newPendingTill != 0
|
if (newPendingTill != 0
|
||||||
&& (!url.isEmpty() || pendingTill < 0)
|
&& (!url.isEmpty() || failed)
|
||||||
&& (!pendingTill
|
&& (!pendingTill
|
||||||
|| pendingTill == newPendingTill
|
|| pendingTill == newPendingTill
|
||||||
|| newPendingTill < -1)) {
|
|| newPendingTill < -1)) {
|
||||||
|
@ -252,6 +254,15 @@ bool WebPageData::applyChanges(
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
}();
|
}();
|
||||||
|
const auto hasSiteName = !resultSiteName.isEmpty() ? 1 : 0;
|
||||||
|
const auto hasTitle = !resultTitle.isEmpty() ? 1 : 0;
|
||||||
|
const auto hasDescription = !newDescription.text.isEmpty() ? 1 : 0;
|
||||||
|
if (newDocument
|
||||||
|
|| !newCollage.items.empty()
|
||||||
|
|| !newPhoto
|
||||||
|
|| (hasSiteName + hasTitle + hasDescription < 2)) {
|
||||||
|
newHasLargeMedia = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (type == newType
|
if (type == newType
|
||||||
&& url == resultUrl
|
&& url == resultUrl
|
||||||
|
@ -265,6 +276,7 @@ bool WebPageData::applyChanges(
|
||||||
&& collage.items == newCollage.items
|
&& collage.items == newCollage.items
|
||||||
&& duration == newDuration
|
&& duration == newDuration
|
||||||
&& author == resultAuthor
|
&& author == resultAuthor
|
||||||
|
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||||
&& pendingTill == newPendingTill) {
|
&& pendingTill == newPendingTill) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -272,6 +284,7 @@ bool WebPageData::applyChanges(
|
||||||
_owner->session().api().clearWebPageRequest(this);
|
_owner->session().api().clearWebPageRequest(this);
|
||||||
}
|
}
|
||||||
type = newType;
|
type = newType;
|
||||||
|
hasLargeMedia = newHasLargeMedia ? 1 : 0;
|
||||||
url = resultUrl;
|
url = resultUrl;
|
||||||
displayUrl = resultDisplayUrl;
|
displayUrl = resultDisplayUrl;
|
||||||
siteName = resultSiteName;
|
siteName = resultSiteName;
|
||||||
|
@ -343,3 +356,38 @@ void WebPageData::ApplyChanges(
|
||||||
}
|
}
|
||||||
session->data().sendWebPageGamePollNotifications();
|
session->data().sendWebPageGamePollNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString WebPageData::displayedSiteName() const {
|
||||||
|
return (document && document->isWallPaper())
|
||||||
|
? tr::lng_media_chat_background(tr::now)
|
||||||
|
: (document && document->isTheme())
|
||||||
|
? tr::lng_media_color_theme(tr::now)
|
||||||
|
: siteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebPageData::computeDefaultSmallMedia() const {
|
||||||
|
if (!collage.items.empty()) {
|
||||||
|
return false;
|
||||||
|
} else if (siteName.isEmpty()
|
||||||
|
&& title.isEmpty()
|
||||||
|
&& description.empty()
|
||||||
|
&& author.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
} else if (!document
|
||||||
|
&& photo
|
||||||
|
&& type != WebPageType::Photo
|
||||||
|
&& type != WebPageType::Document
|
||||||
|
&& type != WebPageType::Story
|
||||||
|
&& type != WebPageType::Video) {
|
||||||
|
if (type == WebPageType::Profile) {
|
||||||
|
return true;
|
||||||
|
} else if (siteName == u"Twitter"_q
|
||||||
|
|| siteName == u"Facebook"_q
|
||||||
|
|| type == WebPageType::ArticleWithIV) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/flags.h"
|
||||||
#include "data/data_photo.h"
|
#include "data/data_photo.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
|
|
||||||
|
@ -16,7 +17,9 @@ namespace Data {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
enum class WebPageType {
|
enum class WebPageType : uint8 {
|
||||||
|
None,
|
||||||
|
|
||||||
Message,
|
Message,
|
||||||
|
|
||||||
Group,
|
Group,
|
||||||
|
@ -44,8 +47,7 @@ enum class WebPageType {
|
||||||
VoiceChat,
|
VoiceChat,
|
||||||
Livestream,
|
Livestream,
|
||||||
};
|
};
|
||||||
|
[[nodiscard]] WebPageType ParseWebPageType(const MTPDwebPage &type);
|
||||||
WebPageType ParseWebPageType(const MTPDwebPage &type);
|
|
||||||
|
|
||||||
struct WebPageCollage {
|
struct WebPageCollage {
|
||||||
using Item = std::variant<PhotoData*, DocumentData*>;
|
using Item = std::variant<PhotoData*, DocumentData*>;
|
||||||
|
@ -78,6 +80,7 @@ struct WebPageData {
|
||||||
WebPageCollage &&newCollage,
|
WebPageCollage &&newCollage,
|
||||||
int newDuration,
|
int newDuration,
|
||||||
const QString &newAuthor,
|
const QString &newAuthor,
|
||||||
|
bool newHasLargeMedia,
|
||||||
int newPendingTill);
|
int newPendingTill);
|
||||||
|
|
||||||
static void ApplyChanges(
|
static void ApplyChanges(
|
||||||
|
@ -85,21 +88,26 @@ struct WebPageData {
|
||||||
ChannelData *channel,
|
ChannelData *channel,
|
||||||
const MTPmessages_Messages &result);
|
const MTPmessages_Messages &result);
|
||||||
|
|
||||||
WebPageId id = 0;
|
[[nodiscard]] QString displayedSiteName() const;
|
||||||
WebPageType type = WebPageType::Article;
|
[[nodiscard]] bool computeDefaultSmallMedia() const;
|
||||||
|
|
||||||
|
const WebPageId id = 0;
|
||||||
|
WebPageType type = WebPageType::None;
|
||||||
QString url;
|
QString url;
|
||||||
QString displayUrl;
|
QString displayUrl;
|
||||||
QString siteName;
|
QString siteName;
|
||||||
QString title;
|
QString title;
|
||||||
TextWithEntities description;
|
TextWithEntities description;
|
||||||
FullStoryId storyId;
|
FullStoryId storyId;
|
||||||
int duration = 0;
|
|
||||||
QString author;
|
QString author;
|
||||||
PhotoData *photo = nullptr;
|
PhotoData *photo = nullptr;
|
||||||
DocumentData *document = nullptr;
|
DocumentData *document = nullptr;
|
||||||
WebPageCollage collage;
|
WebPageCollage collage;
|
||||||
int pendingTill = 0;
|
int duration = 0;
|
||||||
int version = 0;
|
TimeId pendingTill = 0;
|
||||||
|
uint32 version : 30 = 0;
|
||||||
|
uint32 hasLargeMedia : 1 = 0;
|
||||||
|
uint32 failed : 1 = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void replaceDocumentGoodThumbnail();
|
void replaceDocumentGoodThumbnail();
|
||||||
|
|
|
@ -53,7 +53,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
|
||||||
| (data.is_masks() ? Flag::Masks : Flag())
|
| (data.is_masks() ? Flag::Masks : Flag())
|
||||||
| (data.is_emojis() ? Flag::Emoji : Flag())
|
| (data.is_emojis() ? Flag::Emoji : Flag())
|
||||||
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
| (data.vinstalled_date() ? Flag::Installed : Flag())
|
||||||
| (data.is_videos() ? Flag::Webm : Flag());
|
| (data.is_videos() ? Flag::Webm : Flag())
|
||||||
|
| (data.is_text_color() ? Flag::TextColor : Flag());
|
||||||
}
|
}
|
||||||
|
|
||||||
StickersSet::StickersSet(
|
StickersSet::StickersSet(
|
||||||
|
@ -108,6 +109,10 @@ StickersType StickersSet::type() const {
|
||||||
: StickersType::Stickers;
|
: StickersType::Stickers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StickersSet::textColor() const {
|
||||||
|
return flags & StickersSetFlag::TextColor;
|
||||||
|
}
|
||||||
|
|
||||||
void StickersSet::setThumbnail(const ImageWithLocation &data) {
|
void StickersSet::setThumbnail(const ImageWithLocation &data) {
|
||||||
Data::UpdateCloudFile(
|
Data::UpdateCloudFile(
|
||||||
_thumbnail,
|
_thumbnail,
|
||||||
|
|
|
@ -57,6 +57,7 @@ enum class StickersSetFlag {
|
||||||
Special = (1 << 7),
|
Special = (1 << 7),
|
||||||
Webm = (1 << 8),
|
Webm = (1 << 8),
|
||||||
Emoji = (1 << 9),
|
Emoji = (1 << 9),
|
||||||
|
TextColor = (1 << 10),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
|
||||||
using StickersSetFlags = base::flags<StickersSetFlag>;
|
using StickersSetFlags = base::flags<StickersSetFlag>;
|
||||||
|
@ -84,6 +85,7 @@ public:
|
||||||
[[nodiscard]] MTPInputStickerSet mtpInput() const;
|
[[nodiscard]] MTPInputStickerSet mtpInput() const;
|
||||||
[[nodiscard]] StickerSetIdentifier identifier() const;
|
[[nodiscard]] StickerSetIdentifier identifier() const;
|
||||||
[[nodiscard]] StickersType type() const;
|
[[nodiscard]] StickersType type() const;
|
||||||
|
[[nodiscard]] bool textColor() const;
|
||||||
|
|
||||||
void setThumbnail(const ImageWithLocation &data);
|
void setThumbnail(const ImageWithLocation &data);
|
||||||
|
|
||||||
|
|
|
@ -108,10 +108,9 @@ struct EntryState {
|
||||||
Key key;
|
Key key;
|
||||||
Section section = Section::History;
|
Section section = Section::History;
|
||||||
FilterId filterId = 0;
|
FilterId filterId = 0;
|
||||||
MsgId rootId = 0;
|
FullReplyTo currentReplyTo;
|
||||||
MsgId currentReplyToId = 0;
|
|
||||||
|
|
||||||
friend inline constexpr auto operator<=>(EntryState, EntryState) noexcept
|
friend inline auto operator<=>(EntryState, EntryState) noexcept
|
||||||
= default;
|
= default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -54,17 +54,16 @@ QString PrepareStoryFileName(
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int PeerColorIndex(BareId bareId) {
|
uint8 PeerColorIndex(BareId bareId) {
|
||||||
const auto index = bareId % 7;
|
const uint8 map[] = { 0, 7, 4, 1, 6, 3, 5 };
|
||||||
const int map[] = { 0, 7, 4, 1, 6, 3, 5 };
|
return map[bareId % base::array_size(map)];
|
||||||
return map[index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BareId PeerToBareId(PeerId peerId) {
|
BareId PeerToBareId(PeerId peerId) {
|
||||||
return (peerId.value & PeerId::kChatTypeMask);
|
return (peerId.value & PeerId::kChatTypeMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PeerColorIndex(PeerId peerId) {
|
uint8 PeerColorIndex(PeerId peerId) {
|
||||||
return PeerColorIndex(PeerToBareId(peerId));
|
return PeerColorIndex(PeerToBareId(peerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +77,7 @@ BareId StringBarePeerId(const Utf8String &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ApplicationColorIndex(int applicationId) {
|
uint8 ApplicationColorIndex(int applicationId) {
|
||||||
static const auto official = std::map<int, int> {
|
static const auto official = std::map<int, int> {
|
||||||
{ 1, 0 }, // iOS
|
{ 1, 0 }, // iOS
|
||||||
{ 7, 0 }, // iOS X
|
{ 7, 0 }, // iOS X
|
||||||
|
@ -576,6 +575,18 @@ Poll ParsePoll(const MTPDmessageMediaPoll &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Giveaway ParseGiveaway(const MTPDmessageMediaGiveaway &data) {
|
||||||
|
auto result = Giveaway{
|
||||||
|
.untilDate = data.vuntil_date().v,
|
||||||
|
.quantity = data.vquantity().v,
|
||||||
|
.months = data.vmonths().v,
|
||||||
|
};
|
||||||
|
for (const auto &id : data.vchannels().v) {
|
||||||
|
result.channels.push_back(ChannelId(id));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
UserpicsSlice ParseUserpicsSlice(
|
UserpicsSlice ParseUserpicsSlice(
|
||||||
const MTPVector<MTPPhoto> &data,
|
const MTPVector<MTPPhoto> &data,
|
||||||
int baseIndex) {
|
int baseIndex) {
|
||||||
|
@ -755,6 +766,8 @@ ContactInfo ParseContactInfo(const MTPUser &data) {
|
||||||
auto result = ContactInfo();
|
auto result = ContactInfo();
|
||||||
data.match([&](const MTPDuser &data) {
|
data.match([&](const MTPDuser &data) {
|
||||||
result.userId = data.vid().v;
|
result.userId = data.vid().v;
|
||||||
|
result.colorIndex = data.vcolor().value_or(
|
||||||
|
PeerColorIndex(result.userId));
|
||||||
if (const auto firstName = data.vfirst_name()) {
|
if (const auto firstName = data.vfirst_name()) {
|
||||||
result.firstName = ParseString(*firstName);
|
result.firstName = ParseString(*firstName);
|
||||||
}
|
}
|
||||||
|
@ -766,15 +779,13 @@ ContactInfo ParseContactInfo(const MTPUser &data) {
|
||||||
}
|
}
|
||||||
}, [&](const MTPDuserEmpty &data) {
|
}, [&](const MTPDuserEmpty &data) {
|
||||||
result.userId = data.vid().v;
|
result.userId = data.vid().v;
|
||||||
|
result.colorIndex = PeerColorIndex(result.userId);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ContactColorIndex(const ContactInfo &data) {
|
uint8 ContactColorIndex(const ContactInfo &data) {
|
||||||
if (data.userId != 0) {
|
return data.colorIndex;
|
||||||
return PeerColorIndex(data.userId.bare);
|
|
||||||
}
|
|
||||||
return PeerColorIndex(StringBarePeerId(data.phoneNumber));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerId User::id() const {
|
PeerId User::id() const {
|
||||||
|
@ -786,6 +797,8 @@ User ParseUser(const MTPUser &data) {
|
||||||
result.info = ParseContactInfo(data);
|
result.info = ParseContactInfo(data);
|
||||||
data.match([&](const MTPDuser &data) {
|
data.match([&](const MTPDuser &data) {
|
||||||
result.bareId = data.vid().v;
|
result.bareId = data.vid().v;
|
||||||
|
result.colorIndex = data.vcolor().value_or(
|
||||||
|
PeerColorIndex(result.bareId));
|
||||||
if (const auto username = data.vusername()) {
|
if (const auto username = data.vusername()) {
|
||||||
result.username = ParseString(*username);
|
result.username = ParseString(*username);
|
||||||
}
|
}
|
||||||
|
@ -840,6 +853,8 @@ Chat ParseChat(const MTPChat &data) {
|
||||||
result.input = MTP_inputPeerChat(MTP_long(result.bareId));
|
result.input = MTP_inputPeerChat(MTP_long(result.bareId));
|
||||||
}, [&](const MTPDchannel &data) {
|
}, [&](const MTPDchannel &data) {
|
||||||
result.bareId = data.vid().v;
|
result.bareId = data.vid().v;
|
||||||
|
result.colorIndex = data.vcolor().value_or(
|
||||||
|
PeerColorIndex(result.bareId));
|
||||||
result.isBroadcast = data.is_broadcast();
|
result.isBroadcast = data.is_broadcast();
|
||||||
result.isSupergroup = data.is_megagroup();
|
result.isSupergroup = data.is_megagroup();
|
||||||
result.title = ParseString(data.vtitle());
|
result.title = ParseString(data.vtitle());
|
||||||
|
@ -923,6 +938,15 @@ MTPInputPeer Peer::input() const {
|
||||||
Unexpected("Variant in Peer::id.");
|
Unexpected("Variant in Peer::id.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8 Peer::colorIndex() const {
|
||||||
|
if (const auto user = this->user()) {
|
||||||
|
return user->colorIndex;
|
||||||
|
} else if (const auto chat = this->chat()) {
|
||||||
|
return chat->colorIndex;
|
||||||
|
}
|
||||||
|
Unexpected("Variant in Peer::colorIndex.");
|
||||||
|
}
|
||||||
|
|
||||||
std::map<PeerId, Peer> ParsePeersLists(
|
std::map<PeerId, Peer> ParsePeersLists(
|
||||||
const MTPVector<MTPUser> &users,
|
const MTPVector<MTPUser> &users,
|
||||||
const MTPVector<MTPChat> &chats) {
|
const MTPVector<MTPChat> &chats) {
|
||||||
|
@ -1057,7 +1081,9 @@ Media ParseMedia(
|
||||||
}, [](const MTPDmessageMediaDice &data) {
|
}, [](const MTPDmessageMediaDice &data) {
|
||||||
// #TODO dice
|
// #TODO dice
|
||||||
}, [](const MTPDmessageMediaStory &data) {
|
}, [](const MTPDmessageMediaStory &data) {
|
||||||
// #TODO stories export
|
// #TODO export stories
|
||||||
|
}, [&](const MTPDmessageMediaGiveaway &data) {
|
||||||
|
result.content = ParseGiveaway(data);
|
||||||
}, [](const MTPDmessageMediaEmpty &data) {});
|
}, [](const MTPDmessageMediaEmpty &data) {});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1298,6 +1324,19 @@ ServiceAction ParseServiceAction(
|
||||||
content.peerId = ParsePeerId(data.vpeer());
|
content.peerId = ParsePeerId(data.vpeer());
|
||||||
content.buttonId = data.vbutton_id().v;
|
content.buttonId = data.vbutton_id().v;
|
||||||
result.content = content;
|
result.content = content;
|
||||||
|
}, [&](const MTPDmessageActionGiftCode &data) {
|
||||||
|
auto content = ActionGiftCode();
|
||||||
|
content.boostPeerId = data.vboost_peer()
|
||||||
|
? peerFromMTP(*data.vboost_peer())
|
||||||
|
: PeerId();
|
||||||
|
content.viaGiveaway = data.is_via_giveaway();
|
||||||
|
content.unclaimed = data.is_unclaimed();
|
||||||
|
content.months = data.vmonths().v;
|
||||||
|
content.code = data.vslug().v;
|
||||||
|
result.content = content;
|
||||||
|
}, [&](const MTPDmessageActionGiveawayLaunch &) {
|
||||||
|
auto content = ActionGiveawayLaunch();
|
||||||
|
result.content = content;
|
||||||
}, [](const MTPDmessageActionEmpty &data) {});
|
}, [](const MTPDmessageActionEmpty &data) {});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1358,15 +1397,19 @@ Message ParseMessage(
|
||||||
}
|
}
|
||||||
if (const auto replyTo = data.vreply_to()) {
|
if (const auto replyTo = data.vreply_to()) {
|
||||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||||
result.replyToMsgId = data.vreply_to_msg_id().v;
|
if (const auto replyToMsg = data.vreply_to_msg_id()) {
|
||||||
result.replyToPeerId = data.vreply_to_peer_id()
|
result.replyToMsgId = replyToMsg->v;
|
||||||
? ParsePeerId(*data.vreply_to_peer_id())
|
result.replyToPeerId = data.vreply_to_peer_id()
|
||||||
: 0;
|
? ParsePeerId(*data.vreply_to_peer_id())
|
||||||
if (result.replyToPeerId == result.peerId) {
|
: 0;
|
||||||
result.replyToPeerId = 0;
|
if (result.replyToPeerId == result.peerId) {
|
||||||
|
result.replyToPeerId = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// #TODO export replies
|
||||||
}
|
}
|
||||||
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
||||||
// #TODO stories export
|
// #TODO export stories
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1410,12 +1453,16 @@ Message ParseMessage(
|
||||||
}
|
}
|
||||||
if (const auto replyTo = data.vreply_to()) {
|
if (const auto replyTo = data.vreply_to()) {
|
||||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||||
result.replyToMsgId = data.vreply_to_msg_id().v;
|
if (const auto replyToMsg = data.vreply_to_msg_id()) {
|
||||||
result.replyToPeerId = data.vreply_to_peer_id()
|
result.replyToMsgId = replyToMsg->v;
|
||||||
? ParsePeerId(*data.vreply_to_peer_id())
|
result.replyToPeerId = data.vreply_to_peer_id()
|
||||||
: PeerId(0);
|
? ParsePeerId(*data.vreply_to_peer_id())
|
||||||
|
: PeerId(0);
|
||||||
|
} else {
|
||||||
|
// #TODO export replies
|
||||||
|
}
|
||||||
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
||||||
// #TODO stories export
|
// #TODO export stories
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (const auto viaBotId = data.vvia_bot_id()) {
|
if (const auto viaBotId = data.vvia_bot_id()) {
|
||||||
|
@ -1507,6 +1554,8 @@ ContactsList ParseContactsList(const MTPVector<MTPSavedContact> &data) {
|
||||||
info.lastName = ParseString(data.vlast_name());
|
info.lastName = ParseString(data.vlast_name());
|
||||||
info.phoneNumber = ParseString(data.vphone());
|
info.phoneNumber = ParseString(data.vphone());
|
||||||
info.date = data.vdate().v;
|
info.date = data.vdate().v;
|
||||||
|
info.colorIndex = PeerColorIndex(
|
||||||
|
StringBarePeerId(info.phoneNumber));
|
||||||
return info;
|
return info;
|
||||||
});
|
});
|
||||||
result.list.push_back(std::move(info));
|
result.list.push_back(std::move(info));
|
||||||
|
@ -1724,6 +1773,7 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
|
||||||
info.lastName = peer.user()
|
info.lastName = peer.user()
|
||||||
? peer.user()->info.lastName
|
? peer.user()->info.lastName
|
||||||
: Utf8String();
|
: Utf8String();
|
||||||
|
info.colorIndex = peer.colorIndex();
|
||||||
info.input = peer.input();
|
info.input = peer.input();
|
||||||
info.migratedToChannelId = peer.chat()
|
info.migratedToChannelId = peer.chat()
|
||||||
? peer.chat()->migratedToChannelId
|
? peer.chat()->migratedToChannelId
|
||||||
|
|
|
@ -24,10 +24,10 @@ namespace Data {
|
||||||
|
|
||||||
using Utf8String = QByteArray;
|
using Utf8String = QByteArray;
|
||||||
|
|
||||||
int PeerColorIndex(BareId bareId);
|
uint8 PeerColorIndex(BareId bareId);
|
||||||
BareId PeerToBareId(PeerId peerId);
|
BareId PeerToBareId(PeerId peerId);
|
||||||
int PeerColorIndex(PeerId peerId);
|
uint8 PeerColorIndex(PeerId peerId);
|
||||||
int ApplicationColorIndex(int applicationId);
|
uint8 ApplicationColorIndex(int applicationId);
|
||||||
int DomainApplicationId(const Utf8String &data);
|
int DomainApplicationId(const Utf8String &data);
|
||||||
|
|
||||||
Utf8String ParseString(const MTPstring &data);
|
Utf8String ParseString(const MTPstring &data);
|
||||||
|
@ -108,12 +108,13 @@ struct ContactInfo {
|
||||||
Utf8String lastName;
|
Utf8String lastName;
|
||||||
Utf8String phoneNumber;
|
Utf8String phoneNumber;
|
||||||
TimeId date = 0;
|
TimeId date = 0;
|
||||||
|
uint8 colorIndex = 0;
|
||||||
|
|
||||||
Utf8String name() const;
|
Utf8String name() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
ContactInfo ParseContactInfo(const MTPUser &data);
|
ContactInfo ParseContactInfo(const MTPUser &data);
|
||||||
int ContactColorIndex(const ContactInfo &data);
|
uint8 ContactColorIndex(const ContactInfo &data);
|
||||||
|
|
||||||
struct Photo {
|
struct Photo {
|
||||||
uint64 id = 0;
|
uint64 id = 0;
|
||||||
|
@ -196,6 +197,13 @@ struct Poll {
|
||||||
bool closed = false;
|
bool closed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Giveaway {
|
||||||
|
std::vector<ChannelId> channels;
|
||||||
|
TimeId untilDate = 0;
|
||||||
|
int quantity = 0;
|
||||||
|
int months = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct UserpicsSlice {
|
struct UserpicsSlice {
|
||||||
std::vector<Photo> list;
|
std::vector<Photo> list;
|
||||||
};
|
};
|
||||||
|
@ -210,6 +218,7 @@ struct User {
|
||||||
BareId bareId = 0;
|
BareId bareId = 0;
|
||||||
ContactInfo info;
|
ContactInfo info;
|
||||||
Utf8String username;
|
Utf8String username;
|
||||||
|
uint8 colorIndex = 0;
|
||||||
bool isBot = false;
|
bool isBot = false;
|
||||||
bool isSelf = false;
|
bool isSelf = false;
|
||||||
bool isReplies = false;
|
bool isReplies = false;
|
||||||
|
@ -229,6 +238,7 @@ struct Chat {
|
||||||
ChannelId migratedToChannelId = 0;
|
ChannelId migratedToChannelId = 0;
|
||||||
Utf8String title;
|
Utf8String title;
|
||||||
Utf8String username;
|
Utf8String username;
|
||||||
|
uint8 colorIndex = 0;
|
||||||
bool isBroadcast = false;
|
bool isBroadcast = false;
|
||||||
bool isSupergroup = false;
|
bool isSupergroup = false;
|
||||||
|
|
||||||
|
@ -242,6 +252,7 @@ struct Peer {
|
||||||
PeerId id() const;
|
PeerId id() const;
|
||||||
Utf8String name() const;
|
Utf8String name() const;
|
||||||
MTPInputPeer input() const;
|
MTPInputPeer input() const;
|
||||||
|
uint8 colorIndex() const;
|
||||||
|
|
||||||
const User *user() const;
|
const User *user() const;
|
||||||
const Chat *chat() const;
|
const Chat *chat() const;
|
||||||
|
@ -325,6 +336,7 @@ struct Media {
|
||||||
Game,
|
Game,
|
||||||
Invoice,
|
Invoice,
|
||||||
Poll,
|
Poll,
|
||||||
|
Giveaway,
|
||||||
UnsupportedMedia> content;
|
UnsupportedMedia> content;
|
||||||
TimeId ttl = 0;
|
TimeId ttl = 0;
|
||||||
|
|
||||||
|
@ -527,11 +539,22 @@ struct ActionSetChatWallPaper {
|
||||||
struct ActionSetSameChatWallPaper {
|
struct ActionSetSameChatWallPaper {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ActionGiftCode {
|
||||||
|
QByteArray code;
|
||||||
|
PeerId boostPeerId = 0;
|
||||||
|
int months = 0;
|
||||||
|
bool viaGiveaway = false;
|
||||||
|
bool unclaimed = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct ActionRequestedPeer {
|
struct ActionRequestedPeer {
|
||||||
PeerId peerId = 0;
|
PeerId peerId = 0;
|
||||||
int buttonId = 0;
|
int buttonId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ActionGiveawayLaunch {
|
||||||
|
};
|
||||||
|
|
||||||
struct ServiceAction {
|
struct ServiceAction {
|
||||||
std::variant<
|
std::variant<
|
||||||
v::null_t,
|
v::null_t,
|
||||||
|
@ -570,7 +593,9 @@ struct ServiceAction {
|
||||||
ActionSuggestProfilePhoto,
|
ActionSuggestProfilePhoto,
|
||||||
ActionRequestedPeer,
|
ActionRequestedPeer,
|
||||||
ActionSetChatWallPaper,
|
ActionSetChatWallPaper,
|
||||||
ActionSetSameChatWallPaper> content;
|
ActionSetSameChatWallPaper,
|
||||||
|
ActionGiftCode,
|
||||||
|
ActionGiveawayLaunch> content;
|
||||||
};
|
};
|
||||||
|
|
||||||
ServiceAction ParseServiceAction(
|
ServiceAction ParseServiceAction(
|
||||||
|
@ -726,6 +751,7 @@ struct DialogInfo {
|
||||||
int32 topMessageId = 0;
|
int32 topMessageId = 0;
|
||||||
TimeId topMessageDate = 0;
|
TimeId topMessageDate = 0;
|
||||||
PeerId peerId = 0;
|
PeerId peerId = 0;
|
||||||
|
uint8 colorIndex = 0;
|
||||||
|
|
||||||
MTPInputPeer migratedFromInput = MTP_inputPeerEmpty();
|
MTPInputPeer migratedFromInput = MTP_inputPeerEmpty();
|
||||||
ChannelId migratedToChannelId = 0;
|
ChannelId migratedToChannelId = 0;
|
||||||
|
|
|
@ -25,7 +25,7 @@ constexpr auto kPersonalUserpicSize = 90;
|
||||||
constexpr auto kEntryUserpicSize = 48;
|
constexpr auto kEntryUserpicSize = 48;
|
||||||
constexpr auto kServiceMessagePhotoSize = 60;
|
constexpr auto kServiceMessagePhotoSize = 60;
|
||||||
constexpr auto kHistoryUserpicSize = 42;
|
constexpr auto kHistoryUserpicSize = 42;
|
||||||
constexpr auto kSavedMessagesColorIndex = 3;
|
constexpr auto kSavedMessagesColorIndex = uint8(3);
|
||||||
constexpr auto kJoinWithinSeconds = 900;
|
constexpr auto kJoinWithinSeconds = 900;
|
||||||
constexpr auto kPhotoMaxWidth = 520;
|
constexpr auto kPhotoMaxWidth = 520;
|
||||||
constexpr auto kPhotoMaxHeight = 520;
|
constexpr auto kPhotoMaxHeight = 520;
|
||||||
|
@ -351,7 +351,7 @@ QByteArray FormatTimeText(TimeId date) {
|
||||||
namespace details {
|
namespace details {
|
||||||
|
|
||||||
struct UserpicData {
|
struct UserpicData {
|
||||||
int colorIndex = 0;
|
uint8 colorIndex = 0;
|
||||||
int pixelSize = 0;
|
int pixelSize = 0;
|
||||||
QString imageLink;
|
QString imageLink;
|
||||||
QString largeLink;
|
QString largeLink;
|
||||||
|
@ -611,6 +611,9 @@ private:
|
||||||
const Data::Photo &data,
|
const Data::Photo &data,
|
||||||
const QString &basePath);
|
const QString &basePath);
|
||||||
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
|
[[nodiscard]] QByteArray pushPoll(const Data::Poll &data);
|
||||||
|
[[nodiscard]] QByteArray pushGiveaway(
|
||||||
|
const PeersMap &peers,
|
||||||
|
const Data::Giveaway &data);
|
||||||
|
|
||||||
File _file;
|
File _file;
|
||||||
QByteArray _composedStart;
|
QByteArray _composedStart;
|
||||||
|
@ -988,7 +991,7 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
|
||||||
result.append(popTag());
|
result.append(popTag());
|
||||||
if (photo) {
|
if (photo) {
|
||||||
auto userpic = UserpicData();
|
auto userpic = UserpicData();
|
||||||
userpic.colorIndex = Data::PeerColorIndex(dialog.peerId);
|
userpic.colorIndex = dialog.colorIndex;
|
||||||
userpic.firstName = dialog.name;
|
userpic.firstName = dialog.name;
|
||||||
userpic.lastName = dialog.lastName;
|
userpic.lastName = dialog.lastName;
|
||||||
userpic.pixelSize = kServiceMessagePhotoSize;
|
userpic.pixelSize = kServiceMessagePhotoSize;
|
||||||
|
@ -1276,6 +1279,24 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||||
+ " set "
|
+ " set "
|
||||||
+ wrapReplyToLink("the same background")
|
+ wrapReplyToLink("the same background")
|
||||||
+ " for this chat";
|
+ " for this chat";
|
||||||
|
}, [&](const ActionGiftCode &data) {
|
||||||
|
return data.unclaimed
|
||||||
|
? ("This is an unclaimed Telegram Premium for "
|
||||||
|
+ NumberToString(data.months)
|
||||||
|
+ (data.months > 1 ? " months" : "month")
|
||||||
|
+ " prize in a giveaway organized by a channel.")
|
||||||
|
: data.viaGiveaway
|
||||||
|
? ("You won a Telegram Premium for "
|
||||||
|
+ NumberToString(data.months)
|
||||||
|
+ (data.months > 1 ? " months" : "month")
|
||||||
|
+ " prize in a giveaway organized by a channel.")
|
||||||
|
: ("You've received a Telegram Premium for "
|
||||||
|
+ NumberToString(data.months)
|
||||||
|
+ (data.months > 1 ? " months" : "month")
|
||||||
|
+ " gift from a channel.");
|
||||||
|
}, [&](const ActionGiveawayLaunch &data) {
|
||||||
|
return serviceFrom + " just started a giveaway "
|
||||||
|
"of Telegram Premium subscriptions to its followers.";
|
||||||
}, [](v::null_t) { return QByteArray(); });
|
}, [](v::null_t) { return QByteArray(); });
|
||||||
|
|
||||||
if (!serviceText.isEmpty()) {
|
if (!serviceText.isEmpty()) {
|
||||||
|
@ -1459,8 +1480,9 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||||
if (!data.classes.isEmpty()) {
|
if (!data.classes.isEmpty()) {
|
||||||
return pushGenericMedia(data);
|
return pushGenericMedia(data);
|
||||||
}
|
}
|
||||||
|
using namespace Data;
|
||||||
const auto &content = message.media.content;
|
const auto &content = message.media.content;
|
||||||
if (const auto document = std::get_if<Data::Document>(&content)) {
|
if (const auto document = std::get_if<Document>(&content)) {
|
||||||
Assert(!message.media.ttl);
|
Assert(!message.media.ttl);
|
||||||
if (document->isSticker) {
|
if (document->isSticker) {
|
||||||
return pushStickerMedia(*document, basePath);
|
return pushStickerMedia(*document, basePath);
|
||||||
|
@ -1470,11 +1492,13 @@ QByteArray HtmlWriter::Wrap::pushMedia(
|
||||||
return pushVideoFileMedia(*document, basePath);
|
return pushVideoFileMedia(*document, basePath);
|
||||||
}
|
}
|
||||||
Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia.");
|
Unexpected("Non generic document in HtmlWriter::Wrap::pushMedia.");
|
||||||
} else if (const auto photo = std::get_if<Data::Photo>(&content)) {
|
} else if (const auto photo = std::get_if<Photo>(&content)) {
|
||||||
Assert(!message.media.ttl);
|
Assert(!message.media.ttl);
|
||||||
return pushPhotoMedia(*photo, basePath);
|
return pushPhotoMedia(*photo, basePath);
|
||||||
} else if (const auto poll = std::get_if<Data::Poll>(&content)) {
|
} else if (const auto poll = std::get_if<Poll>(&content)) {
|
||||||
return pushPoll(*poll);
|
return pushPoll(*poll);
|
||||||
|
} else if (const auto giveaway = std::get_if<Giveaway>(&content)) {
|
||||||
|
return pushGiveaway(peers, *giveaway);
|
||||||
}
|
}
|
||||||
Assert(v::is_null(content));
|
Assert(v::is_null(content));
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
|
@ -1796,6 +1820,52 @@ QByteArray HtmlWriter::Wrap::pushPoll(const Data::Poll &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray HtmlWriter::Wrap::pushGiveaway(
|
||||||
|
const PeersMap &peers,
|
||||||
|
const Data::Giveaway &data) {
|
||||||
|
auto result = pushDiv("media_wrap clearfix");
|
||||||
|
result.append(pushDiv("media_giveaway"));
|
||||||
|
|
||||||
|
result.append(pushDiv("section_title bold"));
|
||||||
|
result.append(SerializeString("Giveaway Prizes"));
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(pushDiv("section_body"));
|
||||||
|
result.append("<b>"
|
||||||
|
+ Data::NumberToString(data.quantity)
|
||||||
|
+ "</b> "
|
||||||
|
+ SerializeString((data.quantity > 1)
|
||||||
|
? "Telegram Premium Subscriptions"
|
||||||
|
: "Telegram Premium Subscription")
|
||||||
|
+ " for <b>" + Data::NumberToString(data.months) + "</b> "
|
||||||
|
+ (data.months > 1 ? "months." : "month."));
|
||||||
|
result.append(popTag());
|
||||||
|
|
||||||
|
result.append(pushDiv("section_title bold"));
|
||||||
|
result.append(SerializeString("Participants"));
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(pushDiv("section_body"));
|
||||||
|
auto channels = QByteArrayList();
|
||||||
|
for (const auto &channel : data.channels) {
|
||||||
|
channels.append("<b>" + peers.wrapPeerName(channel) + "</b>");
|
||||||
|
}
|
||||||
|
result.append(SerializeString((channels.size() > 1)
|
||||||
|
? "All subscribers of those channels: "
|
||||||
|
: "All subscribers of the channel: ")
|
||||||
|
+ channels.join(", "));
|
||||||
|
result.append(popTag());
|
||||||
|
|
||||||
|
result.append(pushDiv("section_title bold"));
|
||||||
|
result.append(SerializeString("Winners Selection Date"));
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(pushDiv("section_body"));
|
||||||
|
result.append(Data::FormatDateTime(data.untilDate));
|
||||||
|
result.append(popTag());
|
||||||
|
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(popTag());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
MediaData HtmlWriter::Wrap::prepareMediaData(
|
MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||||
const Data::Message &message,
|
const Data::Message &message,
|
||||||
const QString &basePath,
|
const QString &basePath,
|
||||||
|
@ -1954,6 +2024,7 @@ MediaData HtmlWriter::Wrap::prepareMediaData(
|
||||||
result.description = data.description;
|
result.description = data.description;
|
||||||
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
|
result.status = Data::FormatMoneyAmount(data.amount, data.currency);
|
||||||
}, [](const Poll &data) {
|
}, [](const Poll &data) {
|
||||||
|
}, [](const Giveaway &data) {
|
||||||
}, [](const UnsupportedMedia &data) {
|
}, [](const UnsupportedMedia &data) {
|
||||||
Unexpected("Unsupported message.");
|
Unexpected("Unsupported message.");
|
||||||
}, [](v::null_t) {});
|
}, [](v::null_t) {});
|
||||||
|
@ -2104,7 +2175,7 @@ Result HtmlWriter::start(
|
||||||
Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) {
|
Result HtmlWriter::writePersonal(const Data::PersonalInfo &data) {
|
||||||
Expects(_summary != nullptr);
|
Expects(_summary != nullptr);
|
||||||
|
|
||||||
_selfColorIndex = Data::PeerColorIndex(data.user.info.userId);
|
_selfColorIndex = data.user.info.colorIndex;
|
||||||
if (_settings.types & Settings::Type::Userpics) {
|
if (_settings.types & Settings::Type::Userpics) {
|
||||||
_delayedPersonalInfo = std::make_unique<Data::PersonalInfo>(data);
|
_delayedPersonalInfo = std::make_unique<Data::PersonalInfo>(data);
|
||||||
return Result::Success();
|
return Result::Success();
|
||||||
|
|
|
@ -150,7 +150,7 @@ private:
|
||||||
bool _summaryNeedDivider = false;
|
bool _summaryNeedDivider = false;
|
||||||
bool _haveSections = false;
|
bool _haveSections = false;
|
||||||
|
|
||||||
int _selfColorIndex = 0;
|
uint8 _selfColorIndex = 0;
|
||||||
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
|
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
|
||||||
|
|
||||||
int _userpicsCount = 0;
|
int _userpicsCount = 0;
|
||||||
|
|
|
@ -287,11 +287,12 @@ QByteArray SerializeMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto push = [&](const QByteArray &key, const auto &value) {
|
const auto push = [&](const QByteArray &key, const auto &value) {
|
||||||
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(value)>>) {
|
using V = std::decay_t<decltype(value)>;
|
||||||
|
if constexpr (std::is_same_v<V, bool>) {
|
||||||
|
pushBare(key, value ? "true" : "false");
|
||||||
|
} else if constexpr (std::is_arithmetic_v<V>) {
|
||||||
pushBare(key, Data::NumberToString(value));
|
pushBare(key, Data::NumberToString(value));
|
||||||
} else if constexpr (std::is_same_v<
|
} else if constexpr (std::is_same_v<V, PeerId>) {
|
||||||
std::decay_t<decltype(value)>,
|
|
||||||
PeerId>) {
|
|
||||||
if (const auto chat = peerToChat(value)) {
|
if (const auto chat = peerToChat(value)) {
|
||||||
pushBare(
|
pushBare(
|
||||||
key,
|
key,
|
||||||
|
@ -592,6 +593,17 @@ QByteArray SerializeMessage(
|
||||||
pushAction("requested_peer");
|
pushAction("requested_peer");
|
||||||
push("button_id", data.buttonId);
|
push("button_id", data.buttonId);
|
||||||
push("peer_id", data.peerId.value);
|
push("peer_id", data.peerId.value);
|
||||||
|
}, [&](const ActionGiftCode &data) {
|
||||||
|
pushAction("gift_code_prize");
|
||||||
|
push("gift_code", data.code);
|
||||||
|
if (data.boostPeerId) {
|
||||||
|
push("boost_peer_id", data.boostPeerId);
|
||||||
|
}
|
||||||
|
push("months", data.months);
|
||||||
|
push("unclaimed", data.unclaimed);
|
||||||
|
push("via_giveaway", data.viaGiveaway);
|
||||||
|
}, [&](const ActionGiveawayLaunch &data) {
|
||||||
|
pushAction("giveaway_launch");
|
||||||
}, [&](const ActionSetChatWallPaper &data) {
|
}, [&](const ActionSetChatWallPaper &data) {
|
||||||
pushActor();
|
pushActor();
|
||||||
pushAction("set_chat_wallpaper");
|
pushAction("set_chat_wallpaper");
|
||||||
|
@ -738,6 +750,22 @@ QByteArray SerializeMessage(
|
||||||
{ "total_voters", NumberToString(data.totalVotes) },
|
{ "total_voters", NumberToString(data.totalVotes) },
|
||||||
{ "answers", serialized }
|
{ "answers", serialized }
|
||||||
}));
|
}));
|
||||||
|
}, [&](const Giveaway &data) {
|
||||||
|
context.nesting.push_back(Context::kObject);
|
||||||
|
const auto channels = ranges::views::all(
|
||||||
|
data.channels
|
||||||
|
) | ranges::views::transform([&](ChannelId id) {
|
||||||
|
return NumberToString(id.bare);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
const auto serialized = SerializeArray(context, channels);
|
||||||
|
context.nesting.pop_back();
|
||||||
|
|
||||||
|
push("giveaway_information", SerializeObject(context, {
|
||||||
|
{ "quantity", NumberToString(data.quantity) },
|
||||||
|
{ "months", NumberToString(data.months) },
|
||||||
|
{ "until_date", SerializeDate(data.untilDate) },
|
||||||
|
{ "channels", serialized },
|
||||||
|
}));
|
||||||
}, [](const UnsupportedMedia &data) {
|
}, [](const UnsupportedMedia &data) {
|
||||||
Unexpected("Unsupported message.");
|
Unexpected("Unsupported message.");
|
||||||
}, [](v::null_t) {});
|
}, [](v::null_t) {});
|
||||||
|
|
|
@ -659,7 +659,7 @@ not_null<Ui::PathShiftGradient*> InnerWidget::elementPathShiftGradient() {
|
||||||
return _pathGradient.get();
|
return _pathGradient.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::elementReplyTo(const FullMsgId &to) {
|
void InnerWidget::elementReplyTo(const FullReplyTo &to) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||||
|
|
|
@ -127,7 +127,7 @@ public:
|
||||||
void elementHandleViaClick(not_null<UserData*> bot) override;
|
void elementHandleViaClick(not_null<UserData*> bot) override;
|
||||||
bool elementIsChatWide() override;
|
bool elementIsChatWide() override;
|
||||||
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
||||||
void elementReplyTo(const FullMsgId &to) override;
|
void elementReplyTo(const FullReplyTo &to) override;
|
||||||
void elementStartInteraction(
|
void elementStartInteraction(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
void elementStartPremium(
|
void elementStartPremium(
|
||||||
|
|
|
@ -767,6 +767,8 @@ void GenerateItems(
|
||||||
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
|
using LogDeleteTopic = MTPDchannelAdminLogEventActionDeleteTopic;
|
||||||
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
|
using LogPinTopic = MTPDchannelAdminLogEventActionPinTopic;
|
||||||
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
|
using LogToggleAntiSpam = MTPDchannelAdminLogEventActionToggleAntiSpam;
|
||||||
|
using LogChangeColor = MTPDchannelAdminLogEventActionChangeColor;
|
||||||
|
using LogChangeBackgroundEmoji = MTPDchannelAdminLogEventActionChangeBackgroundEmoji;
|
||||||
|
|
||||||
const auto session = &history->session();
|
const auto session = &history->session();
|
||||||
const auto id = event.vid().v;
|
const auto id = event.vid().v;
|
||||||
|
@ -1815,6 +1817,54 @@ void GenerateItems(
|
||||||
addSimpleServiceMessage(text);
|
addSimpleServiceMessage(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto createChangeColor = [&](const LogChangeColor &data) {
|
||||||
|
const auto text = tr::lng_admin_log_change_color(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText,
|
||||||
|
lt_previous,
|
||||||
|
{ '#' + QString::number(data.vprev_value().v + 1) },
|
||||||
|
lt_color,
|
||||||
|
{ '#' + QString::number(data.vnew_value().v + 1) },
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
addSimpleServiceMessage(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto createChangeBackgroundEmoji = [&](const LogChangeBackgroundEmoji &data) {
|
||||||
|
const auto was = data.vprev_value().v;
|
||||||
|
const auto now = data.vnew_value().v;
|
||||||
|
const auto text = !was
|
||||||
|
? tr::lng_admin_log_set_background_emoji(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText,
|
||||||
|
lt_emoji,
|
||||||
|
Ui::Text::SingleCustomEmoji(
|
||||||
|
Data::SerializeCustomEmojiId(now)),
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: !now
|
||||||
|
? tr::lng_admin_log_removed_background_emoji(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText,
|
||||||
|
lt_emoji,
|
||||||
|
Ui::Text::SingleCustomEmoji(
|
||||||
|
Data::SerializeCustomEmojiId(was)),
|
||||||
|
Ui::Text::WithEntities)
|
||||||
|
: tr::lng_admin_log_change_background_emoji(
|
||||||
|
tr::now,
|
||||||
|
lt_from,
|
||||||
|
fromLinkText,
|
||||||
|
lt_previous,
|
||||||
|
Ui::Text::SingleCustomEmoji(
|
||||||
|
Data::SerializeCustomEmojiId(was)),
|
||||||
|
lt_emoji,
|
||||||
|
Ui::Text::SingleCustomEmoji(
|
||||||
|
Data::SerializeCustomEmojiId(now)),
|
||||||
|
Ui::Text::WithEntities);
|
||||||
|
addSimpleServiceMessage(text);
|
||||||
|
};
|
||||||
|
|
||||||
action.match(
|
action.match(
|
||||||
createChangeTitle,
|
createChangeTitle,
|
||||||
createChangeAbout,
|
createChangeAbout,
|
||||||
|
@ -1858,7 +1908,9 @@ void GenerateItems(
|
||||||
createEditTopic,
|
createEditTopic,
|
||||||
createDeleteTopic,
|
createDeleteTopic,
|
||||||
createPinTopic,
|
createPinTopic,
|
||||||
createToggleAntiSpam);
|
createToggleAntiSpam,
|
||||||
|
createChangeColor,
|
||||||
|
createChangeBackgroundEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AdminLog
|
} // namespace AdminLog
|
||||||
|
|
|
@ -181,8 +181,7 @@ void History::takeLocalDraft(not_null<History*> from) {
|
||||||
&& !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
|
&& !_drafts.contains(Data::DraftKey::Local(topicRootId))) {
|
||||||
// Edit and reply to drafts can't migrate.
|
// Edit and reply to drafts can't migrate.
|
||||||
// Cloud drafts do not migrate automatically.
|
// Cloud drafts do not migrate automatically.
|
||||||
draft->msgId = 0;
|
draft->reply = FullReplyTo();
|
||||||
|
|
||||||
setLocalDraft(std::move(draft));
|
setLocalDraft(std::move(draft));
|
||||||
}
|
}
|
||||||
from->clearLocalDraft(topicRootId);
|
from->clearLocalDraft(topicRootId);
|
||||||
|
@ -198,6 +197,7 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draft->reply.topicRootId = topicRootId;
|
||||||
auto existing = localDraft(topicRootId);
|
auto existing = localDraft(topicRootId);
|
||||||
if (Data::DraftIsNull(existing)
|
if (Data::DraftIsNull(existing)
|
||||||
|| !existing->date
|
|| !existing->date
|
||||||
|
@ -205,17 +205,15 @@ void History::createLocalDraftFromCloud(MsgId topicRootId) {
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
setLocalDraft(std::make_unique<Data::Draft>(
|
setLocalDraft(std::make_unique<Data::Draft>(
|
||||||
draft->textWithTags,
|
draft->textWithTags,
|
||||||
draft->msgId,
|
draft->reply,
|
||||||
topicRootId,
|
|
||||||
draft->cursor,
|
draft->cursor,
|
||||||
draft->previewState));
|
draft->webpage));
|
||||||
existing = localDraft(topicRootId);
|
existing = localDraft(topicRootId);
|
||||||
} else if (existing != draft) {
|
} else if (existing != draft) {
|
||||||
existing->textWithTags = draft->textWithTags;
|
existing->textWithTags = draft->textWithTags;
|
||||||
existing->msgId = draft->msgId;
|
existing->reply = draft->reply;
|
||||||
existing->topicRootId = draft->topicRootId;
|
|
||||||
existing->cursor = draft->cursor;
|
existing->cursor = draft->cursor;
|
||||||
existing->previewState = draft->previewState;
|
existing->webpage = draft->webpage;
|
||||||
}
|
}
|
||||||
existing->date = draft->date;
|
existing->date = draft->date;
|
||||||
}
|
}
|
||||||
|
@ -281,28 +279,29 @@ Data::Draft *History::createCloudDraft(
|
||||||
if (Data::DraftIsNull(fromDraft)) {
|
if (Data::DraftIsNull(fromDraft)) {
|
||||||
setCloudDraft(std::make_unique<Data::Draft>(
|
setCloudDraft(std::make_unique<Data::Draft>(
|
||||||
TextWithTags(),
|
TextWithTags(),
|
||||||
0,
|
FullReplyTo{ .topicRootId = topicRootId },
|
||||||
topicRootId,
|
|
||||||
MessageCursor(),
|
MessageCursor(),
|
||||||
Data::PreviewState::Allowed));
|
Data::WebPageDraft()));
|
||||||
cloudDraft(topicRootId)->date = TimeId(0);
|
cloudDraft(topicRootId)->date = TimeId(0);
|
||||||
} else {
|
} else {
|
||||||
auto existing = cloudDraft(topicRootId);
|
auto existing = cloudDraft(topicRootId);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
|
auto reply = fromDraft->reply;
|
||||||
|
reply.topicRootId = topicRootId;
|
||||||
setCloudDraft(std::make_unique<Data::Draft>(
|
setCloudDraft(std::make_unique<Data::Draft>(
|
||||||
fromDraft->textWithTags,
|
fromDraft->textWithTags,
|
||||||
fromDraft->msgId,
|
reply,
|
||||||
topicRootId,
|
|
||||||
fromDraft->cursor,
|
fromDraft->cursor,
|
||||||
fromDraft->previewState));
|
fromDraft->webpage));
|
||||||
existing = cloudDraft(topicRootId);
|
existing = cloudDraft(topicRootId);
|
||||||
} else if (existing != fromDraft) {
|
} else if (existing != fromDraft) {
|
||||||
existing->textWithTags = fromDraft->textWithTags;
|
existing->textWithTags = fromDraft->textWithTags;
|
||||||
existing->msgId = fromDraft->msgId;
|
existing->reply = fromDraft->reply;
|
||||||
existing->cursor = fromDraft->cursor;
|
existing->cursor = fromDraft->cursor;
|
||||||
existing->previewState = fromDraft->previewState;
|
existing->webpage = fromDraft->webpage;
|
||||||
}
|
}
|
||||||
existing->date = base::unixtime::now();
|
existing->date = base::unixtime::now();
|
||||||
|
existing->reply.topicRootId = topicRootId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto thread = threadFor(topicRootId)) {
|
if (const auto thread = threadFor(topicRootId)) {
|
||||||
|
@ -1185,8 +1184,8 @@ void History::applyServiceChanges(
|
||||||
}, [&](const MTPDmessageActionPinMessage &data) {
|
}, [&](const MTPDmessageActionPinMessage &data) {
|
||||||
if (replyTo) {
|
if (replyTo) {
|
||||||
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
replyTo->match([&](const MTPDmessageReplyHeader &data) {
|
||||||
const auto id = data.vreply_to_msg_id().v;
|
const auto id = data.vreply_to_msg_id().value_or_empty();
|
||||||
if (item) {
|
if (id && item) {
|
||||||
session().storage().add(Storage::SharedMediaAddSlice(
|
session().storage().add(Storage::SharedMediaAddSlice(
|
||||||
peer->id,
|
peer->id,
|
||||||
MsgId(0),
|
MsgId(0),
|
||||||
|
|