Merge tag 'v4.15.0' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/dialogs/dialogs_row.cpp
#	Telegram/SourceFiles/dialogs/dialogs_widget.cpp
#	Telegram/SourceFiles/history/view/history_view_message.cpp
#	Telegram/SourceFiles/info/profile/info_profile_actions.cpp
#	Telegram/lib_ui
#	snap/snapcraft.yaml
This commit is contained in:
ZavaruKitsu 2024-02-18 21:37:55 +03:00
commit 43b0df3f94
200 changed files with 3605 additions and 1270 deletions

View file

@ -85,7 +85,7 @@ jobs:
docker run --rm \ docker run --rm \
-v $PWD:/usr/src/tdesktop \ -v $PWD:/usr/src/tdesktop \
-e DEBUG=1 \ -e CONFIG=Debug \
tdesktop:centos_env \ tdesktop:centos_env \
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \ /usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
-D CMAKE_C_FLAGS_DEBUG="" \ -D CMAKE_C_FLAGS_DEBUG="" \

View file

@ -40,7 +40,7 @@ jobs:
macos: macos:
name: MacOS name: MacOS
runs-on: macos-12 runs-on: macos-13
strategy: strategy:
matrix: matrix:

View file

@ -744,8 +744,6 @@ PRIVATE
history/view/controls/history_view_ttl_button.h history/view/controls/history_view_ttl_button.h
history/view/controls/history_view_voice_record_bar.cpp history/view/controls/history_view_voice_record_bar.cpp
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.h
history/view/controls/history_view_webpage_processor.cpp history/view/controls/history_view_webpage_processor.cpp
history/view/controls/history_view_webpage_processor.h history/view/controls/history_view_webpage_processor.h
history/view/media/history_view_call.cpp history/view/media/history_view_call.cpp
@ -1487,6 +1485,8 @@ PRIVATE
ui/widgets/level_meter.h ui/widgets/level_meter.h
ui/countryinput.cpp ui/countryinput.cpp
ui/countryinput.h ui/countryinput.h
ui/dynamic_thumbnails.cpp
ui/dynamic_thumbnails.h
ui/filter_icons.cpp ui/filter_icons.cpp
ui/filter_icons.h ui/filter_icons.h
ui/filter_icon_panel.cpp ui/filter_icon_panel.cpp

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -867,6 +867,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_sure_delete" = "Are you sure you want to delete this background?"; "lng_background_sure_delete" = "Are you sure you want to delete this background?";
"lng_background_other_info" = "{user} will be able to apply this wallpaper"; "lng_background_other_info" = "{user} will be able to apply this wallpaper";
"lng_background_other_channel" = "All subscribers will see this wallpaper"; "lng_background_other_channel" = "All subscribers will see this wallpaper";
"lng_background_other_group" = "All members will see this wallpaper";
"lng_background_apply1" = "Apply the wallpaper in this chat."; "lng_background_apply1" = "Apply the wallpaper in this chat.";
"lng_background_apply2" = "Enjoy the view."; "lng_background_apply2" = "Enjoy the view.";
"lng_background_apply_button" = "Apply For This Chat"; "lng_background_apply_button" = "Apply For This Chat";
@ -876,6 +877,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_apply_me" = "Apply for me"; "lng_background_apply_me" = "Apply for me";
"lng_background_apply_both" = "Apply for me and {user}"; "lng_background_apply_both" = "Apply for me and {user}";
"lng_background_apply_channel" = "Apply For Channel"; "lng_background_apply_channel" = "Apply For Channel";
"lng_background_apply_group" = "Apply For Group";
"lng_download_path_ask" = "Ask download path for each file"; "lng_download_path_ask" = "Ask download path for each file";
"lng_download_path" = "Download path"; "lng_download_path" = "Download path";
@ -1736,11 +1738,14 @@ 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_group" = "{from} just started a giveaway of Telegram Premium subscriptions to its members.";
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers."; "lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#one" = "{count} winner of the giveaway was randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results#other" = "{count} winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes."; "lng_action_giveaway_results_some" = "Some winners of the giveaway were randomly selected by Telegram and received private messages with giftcodes.";
"lng_action_giveaway_results_none" = "No winners of the giveaway could be selected."; "lng_action_giveaway_results_none" = "No winners of the giveaway could be selected.";
"lng_action_boost_apply#one" = "{from} boosted the group";
"lng_action_boost_apply#other" = "{from} boosted the group {count} times";
"lng_similar_channels_title" = "Similar channels"; "lng_similar_channels_title" = "Similar channels";
"lng_similar_channels_view_all" = "View all"; "lng_similar_channels_view_all" = "View all";
@ -1983,6 +1988,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_stickers" = "Group stickers"; "lng_group_stickers" = "Group stickers";
"lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat."; "lng_group_stickers_description" = "You can choose a sticker set which will be available for every member while in the group chat.";
"lng_group_stickers_add" = "Choose sticker set"; "lng_group_stickers_add" = "Choose sticker set";
"lng_group_emoji" = "Group emoji pack";
"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group.";
"lng_premium" = "Premium"; "lng_premium" = "Premium";
"lng_premium_free" = "Free"; "lng_premium_free" = "Free";
@ -2033,6 +2040,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message."; "lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message.";
"lng_premium_summary_subtitle_tags_for_messages" = "Tags for Messages"; "lng_premium_summary_subtitle_tags_for_messages" = "Tags for Messages";
"lng_premium_summary_about_tags_for_messages" = "Organize your Saved Messages with tags for quicker access."; "lng_premium_summary_about_tags_for_messages" = "Organize your Saved Messages with tags for quicker access.";
"lng_premium_summary_subtitle_last_seen" = "Last Seen Times";
"lng_premium_summary_about_last_seen" = "View the last seen and read times of others even if you hide yours.";
"lng_premium_summary_subtitle_message_privacy" = "Message Privacy";
"lng_premium_summary_about_message_privacy" = "Restrict people you don't know from sending you messages.";
"lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers"; "lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers";
"lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; "lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
"lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji"; "lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji";
@ -2144,28 +2155,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gifts_terms_policy" = "Privacy Policy"; "lng_premium_gifts_terms_policy" = "Privacy Policy";
"lng_boost_channel_button" = "Boost Channel"; "lng_boost_channel_button" = "Boost Channel";
"lng_boost_group_button" = "Boost Group";
"lng_boost_again_button" = "Boost Again"; "lng_boost_again_button" = "Boost Again";
"lng_boost_group_about" = "Boost your group to unlock additional\nappearance settings.";
"lng_boost_level#one" = "Level {count}"; "lng_boost_level#one" = "Level {count}";
"lng_boost_level#other" = "Level {count}"; "lng_boost_level#other" = "Level {count}";
"lng_boost_level_unlocks#one" = "Level {count} Unlocks:";
"lng_boost_level_unlocks#other" = "Level {count} Unlocks:";
"lng_boost_channel_title_first" = "Enable stories for channel"; "lng_boost_channel_title_first" = "Enable stories for channel";
"lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!"; "lng_boost_channel_title_first_group" = "Enable stories for group";
"lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!"; "lng_boost_channel_needs_unlock#one" = "{channel} needs **{count}** more boost to unlock new features.";
"lng_boost_channel_needs_unlock#other" = "{channel} needs **{count}** more boosts to unlock new features.";
//"lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!";
//"lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!";
"lng_boost_channel_title_more" = "Help upgrade channel"; "lng_boost_channel_title_more" = "Help upgrade channel";
"lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}."; "lng_boost_channel_title_more_group" = "Help upgrade group";
"lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}."; //"lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}.";
//"lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}.";
"lng_boost_channel_title_max" = "Maximum level reached"; "lng_boost_channel_title_max" = "Maximum level reached";
"lng_boost_channel_you_title" = "You boosted {channel}!"; "lng_boost_channel_you_title" = "You boosted {channel}!";
"lng_boost_channel_you_first#one" = "This channel needs **{count}** more boost\nto enable stories."; //"lng_boost_channel_you_first#one" = "This channel needs **{count}** more boost\nto enable stories.";
"lng_boost_channel_you_first#other" = "This channel needs **{count}** more boosts\nto enable stories."; //"lng_boost_channel_you_first#other" = "This channel needs **{count}** more boosts\nto enable stories.";
"lng_boost_channel_you_more#one" = "This channel needs **{count}** more boost\nto be able to {post}."; //"lng_boost_channel_you_more#one" = "This channel needs **{count}** more boost\nto be able to {post}.";
"lng_boost_channel_you_more#other" = "This channel needs **{count}** more boosts\nto be able to {post}."; //"lng_boost_channel_you_more#other" = "This channel needs **{count}** more boosts\nto be able to {post}.";
"lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories."; "lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories.";
"lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}."; "lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}.";
"lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}."; "lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}.";
//"lng_boost_channel_you_first_group#one" = "This group needs **{count}** more boost\nto enable stories.";
//"lng_boost_channel_you_first_group#other" = "This group needs **{count}** more boosts\nto enable stories.";
//"lng_boost_channel_you_more_group#one" = "This group needs **{count}** more boost\nto be able to {post}.";
//"lng_boost_channel_you_more_group#other" = "This group needs **{count}** more boosts\nto be able to {post}.";
"lng_boost_channel_reached_first_group" = "This group reached **Level 1** and can now post stories.";
"lng_boost_channel_reached_more_group#one" = "This group reached **Level {count}** and can now {post}.";
"lng_boost_channel_reached_more_group#other" = "This group reached **Level {count}** and can now {post}.";
"lng_boost_channel_post_stories#one" = "post **{count} story** per day"; "lng_boost_channel_post_stories#one" = "post **{count} story** per day";
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day"; "lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
"lng_boost_channel_features" = "Your boosts will help {channel} to unlock new features.";
"lng_boost_group_lift_restrictions" = "Boost the group to remove messaging restrictions.";
"lng_boost_group_lift_restrictions_many#one" = "Boost the group **{count} times** to remove messaging restrictions.";
"lng_boost_group_lift_restrictions_many#other" = "Boost the group **{count} times** to remove messaging restrictions.";
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; "lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels."; "lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
"lng_boost_error_gifted_text_group" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost groups.";
"lng_boost_need_more" = "More boosts needed"; "lng_boost_need_more" = "More boosts needed";
"lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; "lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
"lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts."; "lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
@ -2173,10 +2204,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts."; "lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts.";
"lng_boost_error_already_title" = "Already Boosted!"; "lng_boost_error_already_title" = "Already Boosted!";
"lng_boost_error_already_text" = "You are already boosting this channel."; "lng_boost_error_already_text" = "You are already boosting this channel.";
"lng_boost_error_already_text_group" = "You are already boosting this group.";
"lng_boost_error_premium_title" = "Premium needed!"; "lng_boost_error_premium_title" = "Premium needed!";
"lng_boost_error_premium_text_group" = "Only **Telegram Premium** subscribers can boost groups. Do you want to subscribe to **Telegram Premium**?";
"lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?"; "lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?";
"lng_boost_error_premium_yes" = "Yes"; "lng_boost_error_premium_yes" = "Yes";
"lng_boost_error_flood_title" = "Can't boost too often!"; "lng_boost_error_flood_title" = "Can't boost too often!";
"lng_boost_error_flood_text_group" = "You can change the group you boost only once a day. Next time you can boost is in {left}.";
"lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}."; "lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}.";
"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";
@ -2193,6 +2227,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}."; "lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}.";
"lng_boost_reassign_channels#one" = "{count} channel"; "lng_boost_reassign_channels#one" = "{count} channel";
"lng_boost_reassign_channels#other" = "{count} channels"; "lng_boost_reassign_channels#other" = "{count} channels";
"lng_boost_reassign_groups#one" = "{count} group";
"lng_boost_reassign_groups#other" = "{count} groups";
"lng_boost_reassign_mixed#one" = "{count} group or channel";
"lng_boost_reassign_mixed#other" = "{count} groups and channels";
"lng_boost_channel_title_color" = "Enable colors"; "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#one" = "Your channel needs to reach **Level {count}** to change channel color.";
@ -2201,23 +2239,51 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_title_wallpaper" = "Enable wallpapers"; "lng_boost_channel_title_wallpaper" = "Enable wallpapers";
"lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; "lng_boost_channel_needs_level_wallpaper#one" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper."; "lng_boost_channel_needs_level_wallpaper#other" = "Your channel needs to reach **Level {count}** to change channel wallpaper.";
"lng_boost_group_needs_level_wallpaper#one" = "Your group needs to reach **Level {count}** to change group wallpaper.";
"lng_boost_group_needs_level_wallpaper#other" = "Your group needs to reach **Level {count}** to change group wallpaper.";
"lng_boost_channel_title_status" = "Enable emoji status"; "lng_boost_channel_title_status" = "Enable emoji status";
"lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status."; "lng_boost_channel_needs_level_status#one" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status."; "lng_boost_channel_needs_level_status#other" = "Your channel needs to reach **Level {count}** to set emoji status.";
"lng_boost_group_needs_level_status#one" = "Your group needs to reach **Level {count}** to set emoji status.";
"lng_boost_group_needs_level_status#other" = "Your group needs to reach **Level {count}** to set emoji status.";
"lng_boost_channel_title_reactions" = "Custom reactions"; "lng_boost_channel_title_reactions" = "Custom reactions";
"lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction."; "lng_boost_channel_needs_level_reactions#one" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as a reaction.";
"lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions."; "lng_boost_channel_needs_level_reactions#other" = "Your channel needs to reach **Level {count}** to add **{same_count}** custom emoji as reactions.";
"lng_boost_group_title_emoji" = "Enable emoji pack";
"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack.";
"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack.";
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; "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_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or"; "lng_boost_channel_or" = "or";
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}"; "lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
"lng_boost_channel_gifting_link" = "Get boosts >"; "lng_boost_channel_gifting_link" = "Get boosts >";
"lng_feature_stories#one" = "**{count}** Story Per Day";
"lng_feature_stories#other" = "**{count}** Stories Per Day";
"lng_feature_reactions#one" = "**{count}** Custom Reaction";
"lng_feature_reactions#other" = "**{count}** Custom Reactions";
"lng_feature_name_color_channel#one" = "**{count}** Channel Name Color";
"lng_feature_name_color_channel#other" = "**{count}** Channel Name Colors";
"lng_feature_link_style_channel#one" = "**{count}** Style for Links and Quotes";
"lng_feature_link_style_channel#other" = "**{count}** Styles for Links and Quotes";
"lng_feature_link_emoji" = "Custom Logo for Links and Quotes";
"lng_feature_emoji_status" = "**1000+** Emoji Statuses";
"lng_feature_backgrounds_channel#one" = "**{count}** Channel Background";
"lng_feature_backgrounds_channel#other" = "**{count}** Channel Backgrounds";
"lng_feature_custom_background_channel" = "Custom Channel Background";
"lng_feature_backgrounds_group#one" = "**{count}** Group Background";
"lng_feature_backgrounds_group#other" = "**{count}** Group Backgrounds";
"lng_feature_custom_background_group" = "Custom Group Background";
"lng_feature_custom_emoji_pack" = "Custom Emoji Pack";
"lng_feature_transcribe" = "Voice-to-Text Conversion";
"lng_giveaway_new_title" = "Boosts via Gifts"; "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_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
"lng_giveaway_new_about_group" = "Get more boosts for your group by gifting Premium to your subscribers.";
"lng_giveaway_create_option" = "Create Giveaway"; "lng_giveaway_create_option" = "Create Giveaway";
"lng_giveaway_create_subtitle" = "winners are chosen randomly"; "lng_giveaway_create_subtitle" = "winners are chosen randomly";
"lng_giveaway_award_option" = "Award Specific Users"; "lng_giveaway_award_option" = "Award Specific Users";
@ -2231,30 +2297,35 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_channels_title" = "Channels included in the giveaway"; "lng_giveaway_channels_title" = "Channels included in the giveaway";
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost"; "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_this#other" = "this channel will receive {count} boosts";
"lng_giveaway_channels_this_group#one" = "this group will receive {count} boost";
"lng_giveaway_channels_this_group#other" = "this group will receive {count} boosts";
"lng_giveaway_channels_add" = "Add Channel"; "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_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_title" = "Users eligible for the giveaway";
"lng_giveaway_users_all" = "All subscribers"; "lng_giveaway_users_all" = "All subscribers";
"lng_giveaway_users_all_group" = "All members";
"lng_giveaway_users_from_all_countries" = "from all countries"; "lng_giveaway_users_from_all_countries" = "from all countries";
"lng_giveaway_users_from_one_country" = "from {country}"; "lng_giveaway_users_from_one_country" = "from {country}";
"lng_giveaway_users_from_countries#one" = "from {count} country"; "lng_giveaway_users_from_countries#one" = "from {count} country";
"lng_giveaway_users_from_countries#other" = "from {count} countries"; "lng_giveaway_users_from_countries#other" = "from {count} countries";
"lng_giveaway_users_new" = "Only new subscribers"; "lng_giveaway_users_new" = "Only new subscribers";
"lng_giveaway_users_new_group" = "Only new members";
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries."; "lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries.";
"lng_giveaway_users_about_group" = "Choose if you want to limit the giveaway only to those who joined the group after the giveaway started or to members from specific countries.";
"lng_giveaway_start" = "Start Giveaway"; "lng_giveaway_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium"; "lng_giveaway_award" = "Gift Premium";
"lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone."; "lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone.";
"lng_giveaway_date_title" = "Date when giveaway ends"; "lng_giveaway_date_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time"; "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#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_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_date_about_group#one" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium.";
"lng_giveaway_date_about_group#other" = "Choose when {count} members of your group will be randomly selected to receive Telegram Premium.";
"lng_giveaway_duration_title#one" = "Duration of Premium subscription"; "lng_giveaway_duration_title#one" = "Duration of Premium subscription";
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions"; "lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
"lng_giveaway_duration_price" = "{price} x {amount}"; "lng_giveaway_duration_price" = "{price} x {amount}";
"lng_giveaway_date_select" = "Select Date and Time"; "lng_giveaway_date_select" = "Select Date and Time";
"lng_giveaway_date_confirm" = "Confirm"; "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_save" = "Save Recipients";
"lng_giveaway_recipients_deselect" = "Deselect All"; "lng_giveaway_recipients_deselect" = "Deselect All";
"lng_giveaway_maximum_countries_error#one" = "You can select maximum {count} country."; "lng_giveaway_maximum_countries_error#one" = "You can select maximum {count} country.";
@ -2277,8 +2348,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_created_title" = "Giveaway created"; "lng_giveaway_created_title" = "Giveaway created";
"lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; "lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel.";
"lng_giveaway_created_body_group" = "Check your groups' {link} to see how this giveaway boosted your group.";
"lng_giveaway_awarded_title" = "Premium subscriptions gifted"; "lng_giveaway_awarded_title" = "Premium subscriptions gifted";
"lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel."; "lng_giveaway_awarded_body" = "Check your channels' {link} to see how gifts boosted your channel.";
"lng_giveaway_awarded_body_group" = "Check your groups' {link} to see how gifts boosted your group.";
"lng_giveaway_created_link" = "Statistics"; "lng_giveaway_created_link" = "Statistics";
"lng_prize_title" = "Congratulations!"; "lng_prize_title" = "Congratulations!";
@ -2301,8 +2374,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_participants" = "Participants"; "lng_prizes_participants" = "Participants";
"lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#one" = "All subscribers of the channel:";
"lng_prizes_participants_all#other" = "All subscribers of the channels:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:";
"lng_prizes_participants_all_group#one" = "All members of the group:";
"lng_prizes_participants_all_group#other" = "All members of the groups:";
"lng_prizes_participants_all_mixed#one" = "All members of the group:";
"lng_prizes_participants_all_mixed#other" = "All members of the groups and channels:";
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:"; "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_participants_new#other" = "All users who joined the channels below after this date:";
"lng_prizes_participants_new_group#one" = "All users who joined the group below after this date:";
"lng_prizes_participants_new_group#other" = "All users who joined the groups below after this date:";
"lng_prizes_participants_new_mixed#one" = "All users who joined the group below after this date:";
"lng_prizes_participants_new_mixed#other" = "All users who joined the groups and channels below after this date:";
"lng_prizes_countries" = "from {countries}"; "lng_prizes_countries" = "from {countries}";
"lng_prizes_countries_and_one" = "{countries}, {country}"; "lng_prizes_countries_and_one" = "{countries}, {country}";
"lng_prizes_countries_and_last" = "{countries} and {country}"; "lng_prizes_countries_and_last" = "{countries} and {country}";
@ -2313,32 +2394,43 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; "lng_prizes_how_text" = "This giveaway is sponsored by {admins}.";
"lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; "lng_prizes_end_text" = "This giveaway was sponsored by {admins}.";
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; "lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers."; "lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers";
"lng_prizes_admins_group#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its members";
"lng_prizes_admins_group#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its members";
"lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize."; "lng_prizes_additional_added#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the channel are responsible for delivering this prize.";
"lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes."; "lng_prizes_additional_added#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the channel are responsible for delivering these prizes.";
"lng_prizes_additional_added_group#one" = "{channel} also included **{count} {prize}** in the prize. Admins of the group are responsible for delivering this prize.";
"lng_prizes_additional_added_group#other" = "{channel} also included **{count} {prize}** in the prizes. Admins of the group are responsible for delivering these prizes.";
"lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; "lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}.";
"lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; "lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; "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#one" = "{count} random subscriber of {channel}";
"lng_prizes_winners_all_of_one#other" = "{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_one_group#one" = "{count} random member of {channel}";
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels"; "lng_prizes_winners_all_of_one_group#other" = "{count} random members of {channel}";
"lng_prizes_winners_all_of_many#one" = "{count} random subscriber of {channel} and other listed groups and channels";
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed groups and channels";
"lng_prizes_winners_all_of_many_group#one" = "{count} random member of {channel} and other listed groups and channels";
"lng_prizes_winners_all_of_many_group#other" = "{count} random members of {channel} and other listed groups and channels";
"lng_prizes_winners_new_of_one#one" = "{count} random user that joined {channel} after {start_date}"; "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_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#one" = "{count} random user that joined {channel} and other listed groups and 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_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed groups and 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_one" = "To take part in this giveaway please join {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_participate_many" = "To take part in this giveaway please join {channel} and other listed groups and 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_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel}).";
"lng_prizes_how_no_admin_group" = "You are not eligible to participate in this giveaway, because you are an admin of participating group ({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_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_joined_group" = "You are not eligible to participate in this giveaway, because you joined this group 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_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_one" = "You are participating in this giveaway, because you have joined {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_how_yes_joined_many" = "You are participating in this giveaway, because you have joined {channel} (and other listed groups and channels).";
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; "lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
"lng_prizes_view_prize" = "View my prize"; "lng_prizes_view_prize" = "View my prize";
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; "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_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_cancelled_group" = "The channel cancelled the prizes by reversing the payment for them.";
"lng_prizes_badge" = "x{amount}"; "lng_prizes_badge" = "x{amount}";
"lng_prizes_results_title" = "Winners Selected!"; "lng_prizes_results_title" = "Winners Selected!";
@ -2441,6 +2533,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stickers_remove_pack_confirm" = "Remove"; "lng_stickers_remove_pack_confirm" = "Remove";
"lng_stickers_archive_pack" = "Archive Stickers"; "lng_stickers_archive_pack" = "Archive Stickers";
"lng_stickers_has_been_archived" = "Sticker pack has been archived."; "lng_stickers_has_been_archived" = "Sticker pack has been archived.";
"lng_emoji_group_set" = "Group emoji set";
"lng_emoji_remove_group_set" = "Remove group emoji set?";
"lng_emoji_group_from_your" = "Choose from your emoji";
"lng_emoji_group_from_featured" = "Choose from trending emoji";
"lng_masks_archive_pack" = "Archive Masks"; "lng_masks_archive_pack" = "Archive Masks";
"lng_masks_has_been_archived" = "Mask pack has been archived."; "lng_masks_has_been_archived" = "Mask pack has been archived.";
"lng_masks_installed" = "Mask pack has been installed."; "lng_masks_installed" = "Mask pack has been installed.";
@ -2520,6 +2616,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_broadcast_silent_ph" = "Silent broadcast..."; "lng_broadcast_silent_ph" = "Silent broadcast...";
"lng_send_anonymous_ph" = "Send anonymously..."; "lng_send_anonymous_ph" = "Send anonymously...";
"lng_story_reply_ph" = "Reply privately..."; "lng_story_reply_ph" = "Reply privately...";
"lng_story_comment_ph" = "Comment story...";
"lng_send_text_no" = "Text not allowed."; "lng_send_text_no" = "Text not allowed.";
"lng_send_text_no_about" = "The admins of this group only allow sending {types}."; "lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
"lng_send_text_type_and_last" = "{types} and {last}"; "lng_send_text_type_and_last" = "{types} and {last}";
@ -2752,6 +2849,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_copy_email" = "Copy Email Address"; "lng_context_copy_email" = "Copy Email Address";
"lng_context_copy_hashtag" = "Copy Hashtag"; "lng_context_copy_hashtag" = "Copy Hashtag";
"lng_context_copy_mention" = "Copy Username"; "lng_context_copy_mention" = "Copy Username";
"lng_context_copy_filename" = "Copy Filename";
"lng_context_save_image" = "Save Image As..."; "lng_context_save_image" = "Save Image As...";
"lng_context_copy_image" = "Copy Image"; "lng_context_copy_image" = "Copy Image";
"lng_context_cancel_download" = "Cancel Download"; "lng_context_cancel_download" = "Cancel Download";
@ -2768,6 +2866,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_save_gif" = "Save GIF"; "lng_context_save_gif" = "Save GIF";
"lng_context_delete_gif" = "Delete GIF"; "lng_context_delete_gif" = "Delete GIF";
"lng_context_open_channel" = "Open Channel"; "lng_context_open_channel" = "Open Channel";
"lng_context_open_group" = "Open Group";
"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";
@ -2950,8 +3049,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_channel_level_min" = "Level 1+"; "lng_edit_channel_level_min" = "Level 1+";
"lng_edit_channel_wallpaper" = "Channel wallpaper"; "lng_edit_channel_wallpaper" = "Channel wallpaper";
"lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel."; "lng_edit_channel_wallpaper_about" = "Set a wallpaper that will be visible for everyone reading your channel.";
"lng_edit_channel_wallpaper_group" = "Group wallpaper";
"lng_edit_channel_wallpaper_about_group" = "Set a wallpaper that will be visible for everyone participating in your group.";
"lng_edit_channel_status" = "Channel emoji status"; "lng_edit_channel_status" = "Channel emoji status";
"lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name."; "lng_edit_channel_status_about" = "Choose a status that will be shown next to the channel's name.";
"lng_edit_channel_status_group" = "Group emoji status";
"lng_edit_channel_status_about_group" = "Choose a status that will be shown next to the group's name.";
"lng_edit_self_title" = "Edit your name"; "lng_edit_self_title" = "Edit your name";
"lng_confirm_contact_data" = "New Contact"; "lng_confirm_contact_data" = "New Contact";
"lng_add_contact" = "Create"; "lng_add_contact" = "Create";
@ -3515,6 +3618,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_slowmode_interval_seconds#other" = "every {count} seconds"; "lng_rights_slowmode_interval_seconds#other" = "every {count} seconds";
"lng_rights_slowmode_interval_minutes#one" = "every {count} minute"; "lng_rights_slowmode_interval_minutes#one" = "every {count} minute";
"lng_rights_slowmode_interval_minutes#other" = "every {count} minutes"; "lng_rights_slowmode_interval_minutes#other" = "every {count} minutes";
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
"lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}."; "lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}.";
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time."; "lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
@ -3610,6 +3716,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here."; "lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here.";
"lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here."; "lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here.";
"lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here."; "lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here.";
"lng_restricted_boost_group" = "Boost this group to send messages";
"lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}."; "lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}.";
"lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}."; "lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}.";
@ -3747,6 +3854,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}"; "lng_admin_log_changed_stickers_group" = "{from} changed the group's {sticker_set}";
"lng_admin_log_changed_stickers_set" = "sticker set"; "lng_admin_log_changed_stickers_set" = "sticker set";
"lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set"; "lng_admin_log_removed_stickers_group" = "{from} removed the group's sticker set";
"lng_admin_log_changed_emoji_group" = "{from} changed the group's {sticker_set}";
"lng_admin_log_changed_emoji_set" = "emoji set";
"lng_admin_log_removed_emoji_group" = "{from} removed the group's emoji set";
"lng_admin_log_changed_linked_chat" = "{from} changed the discussion group to «{chat}»"; "lng_admin_log_changed_linked_chat" = "{from} changed the discussion group to «{chat}»";
"lng_admin_log_removed_linked_chat" = "{from} removed the discussion group"; "lng_admin_log_removed_linked_chat" = "{from} removed the discussion group";
"lng_admin_log_changed_linked_channel" = "{from} changed the linked channel to «{chat}»"; "lng_admin_log_changed_linked_channel" = "{from} changed the linked channel to «{chat}»";
@ -4582,10 +4692,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_level" = "Level"; "lng_boosts_level" = "Level";
"lng_boosts_existing" = "Existing boosts"; "lng_boosts_existing" = "Existing boosts";
"lng_boosts_premium_audience" = "Premium subscribers"; "lng_boosts_premium_audience" = "Premium subscribers";
"lng_boosts_premium_members" = "Premium members";
"lng_boosts_next_level" = "Boosts to level up"; "lng_boosts_next_level" = "Boosts to level up";
"lng_boosts_list_title#one" = "{count} Boost"; "lng_boosts_list_title#one" = "{count} Boost";
"lng_boosts_list_title#other" = "{count} Boosts"; "lng_boosts_list_title#other" = "{count} Boosts";
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users."; "lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
"lng_boosts_list_subtext_group" = "Your group is currently boosted by these users.";
"lng_boosts_show_more_boosts#one" = "Show {count} More Boosts"; "lng_boosts_show_more_boosts#one" = "Show {count} More Boosts";
"lng_boosts_show_more_boosts#other" = "Show {count} More Boosts"; "lng_boosts_show_more_boosts#other" = "Show {count} More Boosts";
"lng_boosts_show_more_gifts#one" = "Show {count} More Boosts"; "lng_boosts_show_more_gifts#one" = "Show {count} More Boosts";
@ -4593,8 +4705,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_list_status" = "boost expires on {date}"; "lng_boosts_list_status" = "boost expires on {date}";
"lng_boosts_link_title" = "Link for boosting"; "lng_boosts_link_title" = "Link for boosting";
"lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts."; "lng_boosts_link_subtext" = "Share this link with your subscribers to get more boosts.";
"lng_boosts_link_subtext_group" = "Share this link with the members of your group to get more boosts.";
"lng_boosts_get_boosts" = "Get Boosts via Gifts"; "lng_boosts_get_boosts" = "Get Boosts via Gifts";
"lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers."; "lng_boosts_get_boosts_subtext" = "Get more boosts for your channel by gifting Telegram Premium to your subscribers.";
"lng_boosts_get_boosts_subtext_group" = "Get more boosts for your group by gifting Telegram Premium to the members.";
"lng_boosts_list_unclaimed" = "Unclaimed"; "lng_boosts_list_unclaimed" = "Unclaimed";
"lng_boosts_list_pending" = "To be distributed"; "lng_boosts_list_pending" = "To be distributed";
"lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends."; "lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends.";

View file

@ -13,5 +13,6 @@
<file alias="stats.tgs">../../animations/stats.tgs</file> <file alias="stats.tgs">../../animations/stats.tgs</file>
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file> <file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file> <file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
<file alias="palette.tgs">../../animations/palette.tgs</file>
</qresource> </qresource>
</RCC> </RCC>

View file

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

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,14,13,0 FILEVERSION 4,15,0,0
PRODUCTVERSION 4,14,13,0 PRODUCTVERSION 4,15,0,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.14.13.0" VALUE "FileVersion", "4.15.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.14.13.0" VALUE "ProductVersion", "4.15.0.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,14,13,0 FILEVERSION 4,15,0,0
PRODUCTVERSION 4,14,13,0 PRODUCTVERSION 4,15,0,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.14.13.0" VALUE "FileVersion", "4.15.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.14.13.0" VALUE "ProductVersion", "4.15.0.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -55,19 +55,42 @@ rpl::producer<std::vector<uint8>> PeerColors::suggestedValue() const {
auto PeerColors::indicesValue() const auto PeerColors::indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed> { -> rpl::producer<Ui::ColorIndicesCompressed> {
return rpl::single(_colorIndicesCurrent return rpl::single(
? *_colorIndicesCurrent indicesCurrent()
: Ui::ColorIndicesCompressed()
) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] { ) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] {
return *_colorIndicesCurrent; return indicesCurrent();
})); }));
} }
int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const { Ui::ColorIndicesCompressed PeerColors::indicesCurrent() const {
return _colorIndicesCurrent
? *_colorIndicesCurrent
: Ui::ColorIndicesCompressed();
}
const base::flat_map<uint8, int> &PeerColors::requiredLevelsGroup() const {
return _requiredLevelsGroup;
}
const base::flat_map<uint8, int> &PeerColors::requiredLevelsChannel() const {
return _requiredLevelsChannel;
}
int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) { if (Data::DecideColorIndex(channel) == index) {
return 0; return 0;
} else if (const auto i = _requiredLevels.find(index) } else if (const auto i = _requiredLevelsGroup.find(index)
; i != end(_requiredLevels)) { ; i != end(_requiredLevelsGroup)) {
return i->second;
}
return 1;
}
int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevelsChannel.find(index)
; i != end(_requiredLevelsChannel)) {
return i->second; return i->second;
} }
return 1; return 1;
@ -100,7 +123,8 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
}; };
const auto &list = data.vcolors().v; const auto &list = data.vcolors().v;
_requiredLevels.clear(); _requiredLevelsGroup.clear();
_requiredLevelsChannel.clear();
suggested.reserve(list.size()); suggested.reserve(list.size());
for (const auto &color : list) { for (const auto &color : list) {
const auto &data = color.data(); const auto &data = color.data();
@ -110,8 +134,11 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
continue; continue;
} }
const auto colorIndex = uint8(colorIndexBare); const auto colorIndex = uint8(colorIndexBare);
if (const auto min = data.vgroup_min_level()) {
_requiredLevelsGroup[colorIndex] = min->v;
}
if (const auto min = data.vchannel_min_level()) { if (const auto min = data.vchannel_min_level()) {
_requiredLevels[colorIndex] = min->v; _requiredLevelsChannel[colorIndex] = min->v;
} }
if (!data.is_hidden()) { if (!data.is_hidden()) {
suggested.push_back(colorIndex); suggested.push_back(colorIndex);

View file

@ -25,10 +25,19 @@ public:
[[nodiscard]] std::vector<uint8> suggested() const; [[nodiscard]] std::vector<uint8> suggested() const;
[[nodiscard]] rpl::producer<std::vector<uint8>> suggestedValue() const; [[nodiscard]] rpl::producer<std::vector<uint8>> suggestedValue() const;
[[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const;
[[nodiscard]] auto indicesValue() const [[nodiscard]] auto indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed>; -> rpl::producer<Ui::ColorIndicesCompressed>;
[[nodiscard]] int requiredLevelFor( [[nodiscard]] auto requiredLevelsGroup() const
-> const base::flat_map<uint8, int> &;
[[nodiscard]] auto requiredLevelsChannel() const
-> const base::flat_map<uint8, int> &;
[[nodiscard]] int requiredGroupLevelFor(
PeerId channel,
uint8 index) const;
[[nodiscard]] int requiredChannelLevelFor(
PeerId channel, PeerId channel,
uint8 index) const; uint8 index) const;
@ -42,7 +51,8 @@ private:
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
base::Timer _timer; base::Timer _timer;
rpl::variable<std::vector<uint8>> _suggested; rpl::variable<std::vector<uint8>> _suggested;
base::flat_map<uint8, int> _requiredLevels; base::flat_map<uint8, int> _requiredLevelsGroup;
base::flat_map<uint8, int> _requiredLevelsChannel;
rpl::event_stream<> _colorIndicesChanged; rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent; std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;

View file

@ -604,7 +604,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
const auto channel = _peer->asChannel(); const auto channel = _peer->asChannel();
if (!channel || channel->isMegagroup()) { if (!channel) {
return lifetime; return lifetime;
} }
@ -628,6 +628,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
const auto slots = data.vmy_boost_slots(); const auto slots = data.vmy_boost_slots();
_boostStatus.overview = Data::BoostsOverview{ _boostStatus.overview = Data::BoostsOverview{
.group = channel->isMegagroup(),
.mine = slots ? int(slots->v.size()) : 0, .mine = slots ? int(slots->v.size()) : 0,
.level = std::max(data.vlevel().v, 0), .level = std::max(data.vlevel().v, 0),
.boostCount = std::max( .boostCount = std::max(

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_transcribes.h" #include "api/api_transcribes.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -25,6 +26,14 @@ Transcribes::Transcribes(not_null<ApiWrap*> api)
, _api(&api->instance()) { , _api(&api->instance()) {
} }
bool Transcribes::freeFor(not_null<HistoryItem*> item) const {
if (const auto channel = item->history()->peer->asMegagroup()) {
const auto owner = &channel->owner();
return channel->levelHint() >= owner->groupFreeTranscribeLevel();
}
return false;
}
bool Transcribes::trialsSupport() { bool Transcribes::trialsSupport() {
if (!_trialsSupport) { if (!_trialsSupport) {
const auto count = _session->account().appConfig().get<int>( const auto count = _session->account().appConfig().get<int>(

View file

@ -36,6 +36,8 @@ public:
void apply(const MTPDupdateTranscribedAudio &update); void apply(const MTPDupdateTranscribedAudio &update);
[[nodiscard]] bool freeFor(not_null<HistoryItem*> item) const;
[[nodiscard]] bool trialsSupport(); [[nodiscard]] bool trialsSupport();
[[nodiscard]] TimeId trialsRefreshAt(); [[nodiscard]] TimeId trialsRefreshAt();
[[nodiscard]] int trialsCount(); [[nodiscard]] int trialsCount();

View file

@ -1122,6 +1122,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
(d.is_out() (d.is_out()
? peerToMTP(_session->userPeerId()) ? peerToMTP(_session->userPeerId())
: MTP_peerUser(d.vuser_id())), : MTP_peerUser(d.vuser_id())),
MTPint(), // from_boosts_applied
MTP_peerUser(d.vuser_id()), MTP_peerUser(d.vuser_id()),
MTPPeer(), // saved_peer_id MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
@ -1154,6 +1155,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
MTP_flags(flags), MTP_flags(flags),
d.vid(), d.vid(),
MTP_peerUser(d.vfrom_id()), MTP_peerUser(d.vfrom_id()),
MTPint(), // from_boosts_applied
MTP_peerChat(d.vchat_id()), MTP_peerChat(d.vchat_id()),
MTPPeer(), // saved_peer_id MTPPeer(), // saved_peer_id
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(), d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),

View file

@ -2638,6 +2638,22 @@ void ApiWrap::setGroupStickerSet(
_session->data().stickers().notifyUpdated(Data::StickersType::Stickers); _session->data().stickers().notifyUpdated(Data::StickersType::Stickers);
} }
void ApiWrap::setGroupEmojiSet(
not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set) {
Expects(megagroup->mgInfo != nullptr);
megagroup->mgInfo->emojiSet = set;
request(MTPchannels_SetEmojiStickers(
megagroup->inputChannel,
Data::InputStickerSet(set)
)).send();
_session->changes().peerUpdated(
megagroup,
Data::PeerUpdate::Flag::EmojiSet);
_session->data().stickers().notifyUpdated(Data::StickersType::Emoji);
}
std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji( std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
const QString &key) { const QString &key) {
const auto it = _stickersByEmoji.find(key); const auto it = _stickersByEmoji.find(key);

View file

@ -246,6 +246,9 @@ public:
void setGroupStickerSet( void setGroupStickerSet(
not_null<ChannelData*> megagroup, not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set); const StickerSetIdentifier &set);
void setGroupEmojiSet(
not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set);
[[nodiscard]] std::vector<not_null<DocumentData*>> *stickersByEmoji( [[nodiscard]] std::vector<not_null<DocumentData*>> *stickersByEmoji(
const QString &key); const QString &key);

View file

@ -467,6 +467,10 @@ bool BackgroundPreviewBox::forChannel() const {
return _forPeer && _forPeer->isChannel(); return _forPeer && _forPeer->isChannel();
} }
bool BackgroundPreviewBox::forGroup() const {
return forChannel() && _forPeer->isMegagroup();
}
void BackgroundPreviewBox::generateBackground() { void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) { if (_paper.backgroundColors().empty()) {
return; return;
@ -492,7 +496,9 @@ void BackgroundPreviewBox::resetTitle() {
void BackgroundPreviewBox::rebuildButtons(bool dark) { void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons(); clearButtons();
addButton(forChannel() addButton(forGroup()
? tr::lng_background_apply_group()
: forChannel()
? tr::lng_background_apply_channel() ? tr::lng_background_apply_channel()
: _forPeer : _forPeer
? tr::lng_background_apply_button() ? tr::lng_background_apply_button()
@ -708,7 +714,7 @@ void BackgroundPreviewBox::checkLevelForChannel() {
return std::optional<Ui::AskBoostReason>(); return std::optional<Ui::AskBoostReason>();
} }
return std::make_optional(Ui::AskBoostReason{ return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostWallpaper{ required } Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()}
}); });
}, [=] { _forPeerLevelCheck = false; }); }, [=] { _forPeerLevelCheck = false; });
} }
@ -1083,7 +1089,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
_service = GenerateServiceItem( _service = GenerateServiceItem(
delegate(), delegate(),
_serviceHistory, _serviceHistory,
(forChannel() (forGroup()
? tr::lng_background_other_group(tr::now)
: forChannel()
? tr::lng_background_other_channel(tr::now) ? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId) : (_forPeer && !_fromMessageId)
? tr::lng_background_other_info( ? tr::lng_background_other_info(

View file

@ -95,6 +95,7 @@ private:
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark); [[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
[[nodiscard]] bool forChannel() const; [[nodiscard]] bool forChannel() const;
[[nodiscard]] bool forGroup() const;
void checkLevelForChannel(); void checkLevelForChannel();
void recreate(bool dark); void recreate(bool dark);

View file

@ -791,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
slowmodeLabel: LabelSimple(defaultLabelSimple) { slowmodeLabel: LabelSimple(defaultLabelSimple) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
} }
boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
customBadgeField: InputField(defaultInputField) { customBadgeField: InputField(defaultInputField) {
textMargins: margins(2px, 7px, 2px, 0px); textMargins: margins(2px, 7px, 2px, 0px);

View file

@ -470,8 +470,8 @@ void EditCaptionBox::rebuildPreview() {
void EditCaptionBox::setupField() { void EditCaptionBox::setupField() {
const auto peer = _historyItem->history()->peer; const auto peer = _historyItem->history()->peer;
const auto allow = [=](const auto&) { const auto allow = [=](not_null<DocumentData*> emoji) {
return Data::AllowEmojiWithoutPremium(peer); return Data::AllowEmojiWithoutPremium(peer, emoji);
}; };
InitMessageFieldHandlers( InitMessageFieldHandlers(
_controller, _controller,

View file

@ -1363,20 +1363,26 @@ void GiveawayInfoBox(
? start->quantity ? start->quantity
: (results->winnersCount + results->unclaimedCount); : (results->winnersCount + results->unclaimedCount);
const auto months = start ? start->months : results->months; const auto months = start ? start->months : results->months;
const auto group = results
? results->channel->isMegagroup()
: (!start->channels.empty()
&& start->channels.front()->isMegagroup());
text.append((finished text.append((finished
? tr::lng_prizes_end_text ? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)( : tr::lng_prizes_how_text)(
tr::now, tr::now,
lt_admins, lt_admins,
tr::lng_prizes_admins( (group
tr::now, ? tr::lng_prizes_admins_group
lt_count, : tr::lng_prizes_admins)(
quantity, tr::now,
lt_channel, lt_count,
Ui::Text::Bold(first), quantity,
lt_duration, lt_channel,
TextWithEntities{ GiftDuration(months) }, Ui::Text::Bold(first),
Ui::Text::RichLangValue), lt_duration,
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue)); Ui::Text::RichLangValue));
const auto many = start const auto many = start
? (start->channels.size() > 1) ? (start->channels.size() > 1)
@ -1387,8 +1393,12 @@ void GiveawayInfoBox(
const auto all = start ? start->all : results->all; const auto all = start ? start->all : results->all;
auto winners = all auto winners = all
? (many ? (many
? tr::lng_prizes_winners_all_of_many ? (group
: tr::lng_prizes_winners_all_of_one)( ? tr::lng_prizes_winners_all_of_many_group
: tr::lng_prizes_winners_all_of_many)
: (group
? tr::lng_prizes_winners_all_of_one_group
: tr::lng_prizes_winners_all_of_one))(
tr::now, tr::now,
lt_count, lt_count,
count, count,
@ -1411,15 +1421,17 @@ void GiveawayInfoBox(
? results->additionalPrize ? results->additionalPrize
: start->additionalPrize; : start->additionalPrize;
if (!additionalPrize.isEmpty()) { if (!additionalPrize.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_additional_added( text.append("\n\n").append((group
tr::now, ? tr::lng_prizes_additional_added_group
lt_count, : tr::lng_prizes_additional_added)(
count, tr::now,
lt_channel, lt_count,
Ui::Text::Bold(first), count,
lt_prize, lt_channel,
TextWithEntities{ additionalPrize }, Ui::Text::Bold(first),
Ui::Text::RichLangValue)); lt_prize,
TextWithEntities{ additionalPrize },
Ui::Text::RichLangValue));
} }
const auto untilDate = start const auto untilDate = start
? start->untilDate ? start->untilDate
@ -1448,18 +1460,25 @@ void GiveawayInfoBox(
if (info.adminChannelId) { if (info.adminChannelId) {
const auto channel = controller->session().data().channel( const auto channel = controller->session().data().channel(
info.adminChannelId); info.adminChannelId);
text.append("\n\n").append(tr::lng_prizes_how_no_admin( text.append("\n\n").append((channel->isMegagroup()
tr::now, ? tr::lng_prizes_how_no_admin_group
lt_channel, : tr::lng_prizes_how_no_admin)(
Ui::Text::Bold(channel->name()), tr::now,
Ui::Text::RichLangValue)); lt_channel,
Ui::Text::Bold(channel->name()),
Ui::Text::RichLangValue));
} else if (info.tooEarlyDate) { } else if (info.tooEarlyDate) {
text.append("\n\n").append(tr::lng_prizes_how_no_joined( const auto channel = controller->session().data().channel(
tr::now, info.adminChannelId);
lt_date, text.append("\n\n").append((channel->isMegagroup()
Ui::Text::Bold( ? tr::lng_prizes_how_no_joined_group
langDateTime(base::unixtime::parse(info.tooEarlyDate))), : tr::lng_prizes_how_no_joined)(
Ui::Text::RichLangValue)); tr::now,
lt_date,
Ui::Text::Bold(
langDateTime(
base::unixtime::parse(info.tooEarlyDate))),
Ui::Text::RichLangValue));
} else if (!info.disallowedCountry.isEmpty()) { } else if (!info.disallowedCountry.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_how_no_country( text.append("\n\n").append(tr::lng_prizes_how_no_country(
tr::now, tr::now,
@ -1499,7 +1518,9 @@ void GiveawayInfoBox(
box.get(), box.get(),
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box.get(), box.get(),
tr::lng_prizes_cancelled(), (group
? tr::lng_prizes_cancelled_group()
: tr::lng_prizes_cancelled()),
st::giveawayRefundedLabel), st::giveawayRefundedLabel),
st::giveawayRefundedPadding), st::giveawayRefundedPadding),
{ padding.left(), 0, padding.right(), padding.bottom() }); { padding.left(), 0, padding.right(), padding.bottom() });

View file

@ -13,11 +13,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h" #include "boxes/peers/replace_boost_box.h"
#include "boxes/background_box.h" #include "boxes/background_box.h"
#include "boxes/stickers_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h" #include "data/data_document_media.h"
#include "data/data_emoji_statuses.h" #include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
@ -28,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_emoji_status_panel.h" #include "info/profile/info_profile_emoji_status_panel.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "lottie/lottie_single_player.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -478,6 +484,7 @@ void Set(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id), MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(values.colorIndex), MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId))); MTP_long(values.backgroundEmojiId)));
} else if (peer->isMegagroup()) {
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag; using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor( send(MTPchannels_UpdateColor(
@ -527,9 +534,13 @@ void Apply(
} else { } else {
CheckBoostLevel(show, peer, [=](int level) { CheckBoostLevel(show, peer, [=](int level) {
const auto peerColors = &peer->session().api().peerColors(); const auto peerColors = &peer->session().api().peerColors();
const auto colorRequired = peerColors->requiredLevelFor( const auto colorRequired = peer->isMegagroup()
peer->id, ? peerColors->requiredGroupLevelFor(
values.colorIndex); peer->id,
values.colorIndex)
: peerColors->requiredChannelLevelFor(
peer->id,
values.colorIndex);
const auto iconRequired = values.backgroundEmojiId const auto iconRequired = values.backgroundEmojiId
? session->account().appConfig().get<int>( ? session->account().appConfig().get<int>(
"channel_bg_icon_level_min", "channel_bg_icon_level_min",
@ -553,7 +564,10 @@ void Apply(
} }
const auto reason = [&]() -> Ui::AskBoostReason { const auto reason = [&]() -> Ui::AskBoostReason {
if (level < statusRequired) { if (level < statusRequired) {
return { Ui::AskBoostEmojiStatus{ statusRequired } }; return { Ui::AskBoostEmojiStatus{
statusRequired,
peer->isMegagroup()
} };
} else if (level < iconRequired) { } else if (level < iconRequired) {
return { Ui::AskBoostChannelColor{ iconRequired } }; return { Ui::AskBoostChannelColor{ iconRequired } };
} }
@ -670,6 +684,44 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return (top - skip) + ((count % columns) ? (isize + skip) : 0); return (top - skip) + ((count % columns) ? (isize + skip) : 0);
} }
[[nodiscard]] auto ButtonStyleWithAddedPadding(
not_null<Ui::RpWidget*> parent,
const style::SettingsButton &basicSt,
QMargins added) {
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding += added;
return st;
}
struct ButtonWithEmoji {
not_null<const style::SettingsButton*> st;
int emojiWidth = 0;
int noneWidth = 0;
int added = 0;
};
[[nodiscard]] ButtonWithEmoji ButtonStyleWithRightEmoji(
not_null<Ui::RpWidget*> parent) {
const auto ratio = style::DevicePixelRatio();
const auto emojiWidth = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = st::normalFont->width(
tr::lng_settings_color_emoji_off(tr::now));
const auto added = st::normalFont->spacew;
const auto rightAdded = std::max(noneWidth, emojiWidth);
return {
.st = ButtonStyleWithAddedPadding(
parent,
st::peerAppearanceButton,
QMargins(0, 0, added + rightAdded, 0)),
.emojiWidth = emojiWidth,
.noneWidth = noneWidth,
.added = added,
};
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton( [[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
@ -677,22 +729,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
rpl::producer<uint8> colorIndexValue, rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> emojiIdValue, rpl::producer<DocumentId> emojiIdValue,
Fn<void(DocumentId)> emojiIdChosen) { Fn<void(DocumentId)> emojiIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon; const auto button = ButtonStyleWithRightEmoji(parent);
const auto ratio = style::DevicePixelRatio(); auto result = Settings::CreateButtonWithIcon(
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
parent, parent,
tr::lng_settings_color_emoji(), tr::lng_settings_color_emoji(),
*st); *button.st,
{ &st::menuBlueIconColorNames });
const auto raw = result.data(); const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw); const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
@ -719,6 +761,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
}, right->lifetime()); }, right->lifetime());
const auto session = &show->session(); const auto session = &show->session();
const auto added = st::normalFont->spacew;
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) { std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
state->emojiId = emojiId; state->emojiId = emojiId;
state->emoji = emojiId state->emoji = emojiId
@ -727,7 +770,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
[=] { right->update(); }) [=] { right->update(); })
: nullptr; : nullptr;
right->resize( right->resize(
(emojiId ? emojiWidth : noneWidth) + added, (emojiId ? button.emojiWidth : button.noneWidth) + button.added,
right->height()); right->height());
right->update(); right->update();
}, right->lifetime()); }, right->lifetime());
@ -738,7 +781,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
) | rpl::start_with_next([=](QSize outer, int width) { ) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height()); right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right(); const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width()); right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime()); }, right->lifetime());
right->paintRequest( right->paintRequest(
@ -752,7 +795,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto colors = style->coloredValues(false, state->index); const auto colors = style->coloredValues(false, state->index);
state->emoji->paint(p, { state->emoji->paint(p, {
.textColor = colors.name, .textColor = colors.name,
.position = QPoint(added, (height - emojiSize) / 2), .position = QPoint(added, (height - button.emojiWidth) / 2),
.internal = { .internal = {
.forceFirstFrame = true, .forceFirstFrame = true,
}, },
@ -791,23 +834,16 @@ int ColorSelector::resizeGetHeight(int newWidth) {
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentId> statusIdValue, rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen) { Fn<void(DocumentId,TimeId)> statusIdChosen,
const auto &basicSt = st::settingsButtonNoIcon; bool group) {
const auto ratio = style::DevicePixelRatio(); const auto button = ButtonStyleWithRightEmoji(parent);
const auto added = st::normalFont->spacew; auto result = Settings::CreateButtonWithIcon(
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
parent, parent,
tr::lng_edit_channel_status(), (group
*st); ? tr::lng_edit_channel_status_group()
: tr::lng_edit_channel_status()),
*button.st,
{ &st::menuBlueIconEmojiStatus });
const auto raw = result.data(); const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw); const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
@ -834,7 +870,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
[=] { right->update(); }) [=] { right->update(); })
: nullptr; : nullptr;
right->resize( right->resize(
(id ? emojiWidth : noneWidth) + added, (id ? button.emojiWidth : button.noneWidth) + button.added,
right->height()); right->height());
right->update(); right->update();
}, right->lifetime()); }, right->lifetime());
@ -845,7 +881,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
) | rpl::start_with_next([=](QSize outer, int width) { ) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height()); right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right(); const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width()); right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime()); }, right->lifetime());
right->paintRequest( right->paintRequest(
@ -861,14 +897,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
st::stickerPanPremium1, st::stickerPanPremium1,
st::stickerPanPremium2, st::stickerPanPremium2,
0.5), 0.5),
.position = QPoint(added, (height - emojiSize) / 2), .position = QPoint(
button.added,
(height - button.emojiWidth) / 2),
}); });
} else { } else {
const auto &font = st::normalFont; const auto &font = st::normalFont;
p.setFont(font); p.setFont(font);
p.setPen(st::windowActiveTextFg); p.setPen(st::windowActiveTextFg);
p.drawText( p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent), QPoint(
button.added,
(height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now)); tr::lng_settings_color_emoji_off(tr::now));
} }
}, right->lifetime()); }, right->lifetime());
@ -889,6 +929,116 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result; return result;
} }
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiPackButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> channel) {
Expects(channel->mgInfo != nullptr);
const auto button = ButtonStyleWithRightEmoji(parent);
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_group_emoji(),
*button.st,
{ &st::menuBlueIconEmojiPack });
const auto raw = result.data();
struct State {
DocumentData *icon = nullptr;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
QImage cache;
};
const auto state = parent->lifetime().make_state<State>();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
right->resize(
button.emojiWidth + button.added,
right->height());
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 - button.added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::filter([=] {
return state->icon != nullptr;
}) | rpl::start_with_next([=] {
auto p = QPainter(right);
const auto x = button.added;
const auto y = (right->height() - button.emojiWidth) / 2;
const auto active = right->window()->isActiveWindow();
if (const auto emoji = state->icon) {
if (!state->custom
&& emoji->sticker()
&& emoji->sticker()->setType == Data::StickersType::Emoji) {
auto &manager = emoji->owner().customEmojiManager();
state->custom = manager.create(
emoji->id,
[=] { right->update(); },
{});
}
if (state->custom) {
state->custom->paint(p, Ui::Text::CustomEmoji::Context{
.textColor = st::windowFg->c,
.now = crl::now(),
.position = { x, y },
.paused = !active,
});
}
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto isEmoji = true;
show->showBox(Box<StickersBox>(show, channel, isEmoji));
});
channel->session().changes().peerFlagsValue(
channel,
Data::PeerUpdate::Flag::EmojiSet
) | rpl::map([=]() -> rpl::producer<DocumentData*> {
const auto id = channel->mgInfo->emojiSet.id;
if (!id) {
return rpl::single<DocumentData*>(nullptr);
}
const auto sets = &channel->owner().stickers().sets();
auto wrapLoaded = [=](Data::StickersSets::const_iterator it) {
return it->second->lookupThumbnailDocument();
};
const auto it = sets->find(id);
if (it != sets->cend()
&& !(it->second->flags & Data::StickersSetFlag::NotLoaded)) {
return rpl::single(wrapLoaded(it));
}
return rpl::single<DocumentData*>(
nullptr
) | rpl::then(channel->owner().stickers().updated(
Data::StickersType::Emoji
) | rpl::filter([=] {
const auto it = sets->find(id);
return (it != sets->cend())
&& !(it->second->flags & Data::StickersSetFlag::NotLoaded);
}) | rpl::map([=] {
return wrapLoaded(sets->find(id));
}));
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](DocumentData *icon) {
if (state->icon != icon) {
state->icon = icon;
state->custom = nullptr;
right->update();
}
}, right->lifetime());
return result;
}
} // namespace } // namespace
void EditPeerColorBox( void EditPeerColorBox(
@ -897,7 +1047,12 @@ void EditPeerColorBox(
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style, std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme) { std::shared_ptr<Ui::ChatTheme> theme) {
box->setTitle(tr::lng_settings_color_title()); const auto group = peer->isMegagroup();
const auto container = box->verticalLayout();
box->setTitle(peer->isSelf()
? tr::lng_settings_color_title()
: tr::lng_edit_channel_color());
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
struct State { struct State {
@ -914,53 +1069,94 @@ void EditPeerColorBox(
state->emojiId = peer->backgroundEmojiId(); state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId(); state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>( if (group) {
box, const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(
style, box.get());
theme, const auto verticalLayout = box->verticalLayout()->add(
peer, object_ptr<Ui::VerticalLayout>(box.get()));
state->index.value(),
state->emojiId.value()
), {});
auto indices = peer->session().api().peerColors().suggestedValue(); auto icon = CreateLottieIcon(
const auto margin = st::settingsColorRadioMargin; verticalLayout,
const auto skip = st::settingsColorRadioSkip; {
box->addRow( .name = u"palette"_q,
object_ptr<ColorSelector>( .sizeOverride = {
st::settingsCloudPasswordIconSize,
st::settingsCloudPasswordIconSize,
},
},
st::peerAppearanceIconPadding);
box->setShowFinishedCallback([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
});
verticalLayout->add(std::move(icon.widget));
verticalLayout->add(
object_ptr<Ui::FlatLabel>(
verticalLayout,
tr::lng_boost_group_about(),
st::peerAppearanceCoverLabel),
st::peerAppearanceCoverLabelMargin);
verticalLayout->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
divider->setGeometry(r);
}, divider->lifetime());
} else {
box->addRow(object_ptr<PreviewWrap>(
box, box,
style, style,
std::move(indices), theme,
state->index.current(), peer,
[=](uint8 index) { state->index = index; }), state->index.value(),
{ margin, skip, margin, skip }); state->emojiId.value()
), {});
const auto container = box->verticalLayout(); auto indices = peer->session().api().peerColors().suggestedValue();
Ui::AddDividerText(container, peer->isSelf() const auto margin = st::settingsColorRadioMargin;
? tr::lng_settings_color_about() const auto skip = st::settingsColorRadioSkip;
: tr::lng_settings_color_about_channel()); box->addRow(
object_ptr<ColorSelector>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
Ui::AddSkip(container, st::settingsColorSampleSkip); Ui::AddDividerText(
container,
(peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel()),
st::peerAppearanceDividerTextMargin);
container->add(CreateEmojiIconButton( Ui::AddSkip(container, st::settingsColorSampleSkip);
container,
show,
style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
Ui::AddSkip(container, st::settingsColorSampleSkip); container->add(CreateEmojiIconButton(
Ui::AddDividerText(container, peer->isSelf() container,
? tr::lng_settings_color_emoji_about() show,
: tr::lng_settings_color_emoji_about_channel()); style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
(peer->isSelf()
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel()),
st::peerAppearanceDividerTextMargin);
}
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
Ui::AddSkip(container, st::settingsColorSampleSkip); Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(object_ptr<Ui::SettingsButton>( Settings::AddButtonWithIcon(
container, container,
tr::lng_edit_channel_wallpaper(), (group
st::settingsButtonNoIcon) ? tr::lng_edit_channel_wallpaper_group()
: tr::lng_edit_channel_wallpaper()),
st::peerAppearanceButton,
{ &st::menuBlueIconWallpaper }
)->setClickedCallback([=] { )->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo; const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto strong = show->resolveWindow(usage)) { if (const auto strong = show->resolveWindow(usage)) {
@ -971,7 +1167,25 @@ void EditPeerColorBox(
Ui::AddSkip(container, st::settingsColorSampleSkip); Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText( Ui::AddDividerText(
container, container,
tr::lng_edit_channel_wallpaper_about()); (group
? tr::lng_edit_channel_wallpaper_about_group()
: tr::lng_edit_channel_wallpaper_about()),
st::peerAppearanceDividerTextMargin);
if (group) {
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiPackButton(
container,
show,
channel));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
tr::lng_group_emoji_description(),
st::peerAppearanceDividerTextMargin);
}
// Preload exceptions list. // Preload exceptions list.
const auto peerPhoto = &channel->session().api().peerPhoto(); const auto peerPhoto = &channel->session().api().peerPhoto();
@ -992,10 +1206,16 @@ void EditPeerColorBox(
state->statusId = id; state->statusId = id;
state->statusUntil = until; state->statusUntil = until;
state->statusChanged = true; state->statusChanged = true;
})); },
group));
Ui::AddSkip(container, st::settingsColorSampleSkip); Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, tr::lng_edit_channel_status_about()); Ui::AddDividerText(
container,
(group
? tr::lng_edit_channel_status_about_group()
: tr::lng_edit_channel_status_about()),
st::peerAppearanceDividerTextMargin);
} }
box->addButton(tr::lng_settings_apply(), [=] { box->addButton(tr::lng_settings_apply(), [=] {
@ -1020,19 +1240,11 @@ void EditPeerColorBox(
}); });
} }
void AddPeerColorButton( void SetupPeerColorSample(
not_null<Ui::VerticalLayout*> container, not_null<Button*> button,
std::shared_ptr<ChatHelpers::Show> show, not_null<PeerData*> peer,
not_null<PeerData*> peer) { rpl::producer<QString> label,
auto label = peer->isSelf() std::shared_ptr<Ui::ChatStyle> style) {
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
auto colorIndexValue = peer->session().changes().peerFlagsValue( auto colorIndexValue = peer->session().changes().peerFlagsValue(
peer, peer,
Data::PeerUpdate::Flag::Color Data::PeerUpdate::Flag::Color
@ -1041,12 +1253,6 @@ void AddPeerColorButton(
}); });
const auto name = peer->shortName(); 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>( const auto sample = Ui::CreateChild<ColorSample>(
button.get(), button.get(),
style, style,
@ -1098,6 +1304,30 @@ void AddPeerColorButton(
}, sample->lifetime()); }, sample->lifetime());
sample->setAttribute(Qt::WA_TransparentForMouseEvents); sample->setAttribute(Qt::WA_TransparentForMouseEvents);
}
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
auto label = peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
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());
if (!peer->isMegagroup()) {
SetupPeerColorSample(button, peer, rpl::duplicate(label), style);
}
button->setClickedCallback([=] { button->setClickedCallback([=] {
show->show(Box(EditPeerColorBox, show, peer, style, theme)); show->show(Box(EditPeerColorBox, show, peer, style, theme));

View file

@ -198,6 +198,37 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId); api->registerModifyRequest(key, requestId);
} }
void SaveBoostsUnrestrict(
not_null<ChannelData*> channel,
int boostsUnrestrict,
Fn<void()> done) {
const auto api = &channel->session().api();
const auto key = Api::RequestKey("boosts_unrestrict", channel->id);
const auto requestId = api->request(
MTPchannels_SetBoostsToUnblockRestrictions(
channel->inputChannel,
MTP_int(boostsUnrestrict))
).done([=](const MTPUpdates &result) {
api->clearModifyRequest(key);
api->applyUpdates(result);
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).fail([=](const MTP::Error &error) {
api->clearModifyRequest(key);
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
return;
}
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).send();
api->registerModifyRequest(key, requestId);
}
void ShowEditPermissions( void ShowEditPermissions(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
@ -215,6 +246,10 @@ void ShowEditPermissions(
close); close);
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close); SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
SaveBoostsUnrestrict(
channel,
result.boostsUnrestrict,
close);
} }
}; };
auto done = [=](EditPeerPermissionsBoxResult result) { auto done = [=](EditPeerPermissionsBoxResult result) {
@ -225,7 +260,8 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe(); const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat(); const auto chat = saveFor->asChat();
if (!result.slowmodeSeconds || !chat) { if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
save(saveFor, result); save(saveFor, result);
return; return;
} }
@ -595,8 +631,9 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
tr::lng_group_stickers_add(), tr::lng_group_stickers_add(),
rpl::single(QString()), //Empty count. rpl::single(QString()), //Empty count.
[=, controller = _navigation->parentController()] { [=, controller = _navigation->parentController()] {
const auto isEmoji = false;
controller->show( controller->show(
Box<StickersBox>(controller->uiShow(), channel)); Box<StickersBox>(controller->uiShow(), channel, isEmoji));
}, },
{ &st::menuIconStickers }); { &st::menuIconStickers });
@ -1057,9 +1094,7 @@ void Controller::fillManageSection() {
&& (channel->hasAdminRights() || channel->amCreator()); && (channel->hasAdminRights() || channel->amCreator());
const auto canEditStickers = isChannel && channel->canEditStickers(); const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete(); const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel const auto canEditColorIndex = isChannel && channel->canEditEmoji();
&& !channel->isMegagroup()
&& channel->canEditInformation();
const auto canViewOrEditLinkedChat = isChannel const auto canViewOrEditLinkedChat = isChannel
&& (channel->linkedChat() && (channel->linkedChat()
|| (channel->isBroadcast() && channel->canEditInformation())); || (channel->isBroadcast() && channel->canEditInformation()));

View file

@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_permissions_box.h" #include "boxes/peers/edit_peer_permissions_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_session.h"
#include "ui/effects/toggle_arrow.h" #include "ui/effects/toggle_arrow.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
@ -34,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include "styles/style_window.h" #include "styles/style_window.h"
@ -42,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kSlowmodeValues = 7; constexpr auto kSlowmodeValues = 7;
constexpr auto kBoostsUnrestrictValues = 5;
constexpr auto kSuggestGigagroupThreshold = 199000; constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
@ -99,7 +104,7 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
using Flag = ChatAdminRight; using Flag = ChatAdminRight;
if (options.isGroup) { if (options.isGroup) {
auto result = std::vector<AdminRightLabel>{ auto first = std::vector<AdminRightLabel>{
{ Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) }, { Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
{ Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) }, { Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) },
{ Flag::BanUsers, tr::lng_rights_group_ban(tr::now) }, { Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
@ -108,19 +113,30 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
: tr::lng_rights_group_invite(tr::now) }, : tr::lng_rights_group_invite(tr::now) },
{ Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) }, { Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) },
{ Flag::PinMessages, tr::lng_rights_group_pin(tr::now) }, { Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
};
auto stories = std::vector<AdminRightLabel>{
{ Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
{ Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
{ Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
};
auto second = std::vector<AdminRightLabel>{
{ Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) }, { Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) },
{ Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) }, { Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) }, { Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
}; };
if (!options.isForum) { if (!options.isForum) {
result.erase( first.erase(
ranges::remove( ranges::remove(
result, first,
Flag::ManageTopics | Flag(), Flag::ManageTopics | Flag(),
&AdminRightLabel::flags), &AdminRightLabel::flags),
end(result)); end(first));
} }
return { { std::nullopt, std::move(result) } }; return {
{ std::nullopt, std::move(first) },
{ tr::lng_rights_channel_manage_stories(), std::move(stories) },
{ std::nullopt, std::move(second) },
};
} }
auto first = std::vector<AdminRightLabel>{ auto first = std::vector<AdminRightLabel>{
{ Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) }, { Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) },
@ -163,6 +179,10 @@ int SlowmodeDelayByIndex(int index) {
Unexpected("Index in SlowmodeDelayByIndex."); Unexpected("Index in SlowmodeDelayByIndex.");
} }
[[nodiscard]] int BoostsUnrestrictByIndex(int index) {
return index + 1;
}
template <typename CheckboxesMap, typename DependenciesMap> template <typename CheckboxesMap, typename DependenciesMap>
void ApplyDependencies( void ApplyDependencies(
const CheckboxesMap &checkboxes, const CheckboxesMap &checkboxes,
@ -770,14 +790,14 @@ void AddSlowmodeLabels(
} }
} }
Fn<int()> AddSlowmodeSlider( rpl::producer<int> AddSlowmodeSlider(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
using namespace rpl::mappers; using namespace rpl::mappers;
if (const auto chat = peer->asChat()) { if (const auto chat = peer->asChat()) {
if (!chat->amCreator()) { if (!chat->amCreator()) {
return [] { return 0; }; return rpl::single(0);
} }
} }
const auto channel = peer->asChannel(); const auto channel = peer->asChannel();
@ -785,10 +805,6 @@ Fn<int()> AddSlowmodeSlider(
const auto secondsCount = lifetime.make_state<rpl::variable<int>>( const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
channel ? channel->slowmodeSeconds() : 0); channel ? channel->slowmodeSeconds() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
container->add( container->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
container, container,
@ -858,7 +874,157 @@ Fn<int()> AddSlowmodeSlider(
st::proxyAboutPadding), st::proxyAboutPadding),
style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip)); style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
return [=] { return secondsCount->current(); }; return secondsCount->value();
}
void AddBoostsUnrestrictLabels(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session) {
const auto labels = container->add(
object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
st::slowmodeLabelsMargin);
const auto manager = &session->data().customEmojiManager();
const auto one = Ui::Text::SingleCustomEmoji(
manager->registerInternalEmoji(
st::boostMessageIcon,
st::boostMessageIconPadding));
const auto many = Ui::Text::SingleCustomEmoji(
manager->registerInternalEmoji(
st::boostsMessageIcon,
st::boostsMessageIconPadding));
const auto context = Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1,
};
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
labels,
st::boostsUnrestrictLabel);
label->setMarkedText(
TextWithEntities(i ? many : one).append(QString::number(i + 1)),
context);
rpl::combine(
labels->widthValue(),
label->widthValue()
) | rpl::start_with_next([=](int outer, int inner) {
const auto skip = st::localStorageLimitMargin;
const auto size = st::localStorageLimitSlider.seekSize;
const auto available = outer
- skip.left()
- skip.right()
- size.width();
const auto shift = (i == 0)
? -(size.width() / 2)
: (i + 1 == kBoostsUnrestrictValues)
? (size.width() - (size.width() / 2) - inner)
: (-inner / 2);
const auto left = skip.left()
+ (size.width() / 2)
+ (i * available) / (kBoostsUnrestrictValues - 1)
+ shift;
label->moveToLeft(left, 0, outer);
}, label->lifetime());
}
}
rpl::producer<int> AddBoostsUnrestrictSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer) {
using namespace rpl::mappers;
if (const auto chat = peer->asChat()) {
if (!chat->amCreator()) {
return rpl::single(0);
}
}
const auto channel = peer->asChannel();
auto &lifetime = container->lifetime();
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
channel ? channel->boostsUnrestrict() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
auto enabled = boostsUnrestrict->value(
) | rpl::map(_1 > 0);
container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_rights_boosts_no_restrict(),
st::defaultSettingsButton
))->toggleOn(rpl::duplicate(enabled))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
if (toggled && !boostsUnrestrict->current()) {
*boostsUnrestrict = 1;
} else if (!toggled && boostsUnrestrict->current()) {
*boostsUnrestrict = 0;
}
}, container->lifetime());
const auto outer = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
outer->toggleOn(rpl::duplicate(enabled), anim::type::normal);
outer->finishAnimating();
const auto inner = outer->entity();
AddBoostsUnrestrictLabels(inner, &peer->session());
const auto slider = inner->add(
object_ptr<Ui::MediaSlider>(inner, st::localStorageLimitSlider),
st::localStorageLimitMargin);
slider->resize(st::localStorageLimitSlider.seekSize);
slider->setPseudoDiscrete(
kBoostsUnrestrictValues,
BoostsUnrestrictByIndex,
boostsUnrestrict->current(),
[=](int boosts) {
(*boostsUnrestrict) = boosts;
});
inner->add(
object_ptr<Ui::DividerLabel>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
rpl::conditional(
boostsUnrestrict->value() | rpl::map(_1 > 0),
tr::lng_rights_boosts_about_on(),
tr::lng_rights_boosts_about()),
st::boxDividerLabel),
st::proxyAboutPadding),
style::margins(0, st::infoProfileSkip, 0, 0));
return boostsUnrestrict->value();
}
rpl::producer<int> AddBoostsUnrestrictWrapped(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
rpl::producer<bool> shown) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
wrap->finishAnimating();
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
const auto divider = container->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container,
object_ptr<Ui::BoxContentDivider>(container),
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
divider->toggleOn(rpl::combine(
std::move(shown),
rpl::duplicate(result),
!rpl::mappers::_1 || !rpl::mappers::_2));
divider->finishAnimating();
return result;
} }
void AddSuggestGigagroup( void AddSuggestGigagroup(
@ -985,7 +1151,40 @@ void ShowEditPeerPermissionsBox(
inner->add(std::move(checkboxes)); inner->add(std::move(checkboxes));
const auto getSlowmodeSeconds = AddSlowmodeSlider(inner, peer); struct State {
rpl::variable<int> slowmodeSeconds;
rpl::variable<int> boostsUnrestrict;
rpl::variable<bool> hasSendRestrictions;
};
static constexpr auto kSendRestrictions = Flag::EmbedLinks
| Flag::SendGames
| Flag::SendGifs
| Flag::SendInline
| Flag::SendPolls
| Flag::SendStickers
| Flag::SendPhotos
| Flag::SendVideos
| Flag::SendVideoMessages
| Flag::SendMusic
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther;
const auto state = inner->lifetime().make_state<State>();
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
inner,
peer,
state->hasSendRestrictions.value());
state->slowmodeSeconds = AddSlowmodeSlider(inner, peer);
state->hasSendRestrictions = rpl::combine(
rpl::single(
restrictions
) | rpl::then(std::move(changes)),
state->slowmodeSeconds.value()
) | rpl::map([](ChatRestrictions restrictions, int slowmodeSeconds) {
return ((restrictions & kSendRestrictions) != 0) || slowmodeSeconds;
});
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
if (channel->amCreator() if (channel->amCreator()
@ -1001,7 +1200,18 @@ void ShowEditPeerPermissionsBox(
AddBannedButtons(inner, navigation, peer); AddBannedButtons(inner, navigation, peer);
box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] { box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] {
done({ rights(), getSlowmodeSeconds() }); const auto restrictions = rights();
const auto slowmodeSeconds = state->slowmodeSeconds.current();
const auto hasRestrictions = (slowmodeSeconds > 0)
|| ((restrictions & kSendRestrictions) != 0);
const auto boostsUnrestrict = hasRestrictions
? state->boostsUnrestrict.current()
: 0;
done({
restrictions,
slowmodeSeconds,
boostsUnrestrict,
});
}); });
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });

View file

@ -37,6 +37,7 @@ class SessionNavigation;
struct EditPeerPermissionsBoxResult final { struct EditPeerPermissionsBoxResult final {
ChatRestrictions rights; ChatRestrictions rights;
int slowmodeSeconds = 0; int slowmodeSeconds = 0;
int boostsUnrestrict = 0;
}; };
void ShowEditPeerPermissionsBox( void ShowEditPeerPermissionsBox(

View file

@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/peers/replace_boost_box.h" #include "boxes/peers/replace_boost_box.h"
#include "api/api_peer_colors.h"
#include "apiwrap.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_cloud_themes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_account.h" #include "main/main_account.h"
@ -313,21 +316,23 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
} }
} }
object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) { object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds, bool group) {
const auto days = seconds / 86400; const auto days = seconds / 86400;
const auto hours = seconds / 3600; const auto hours = seconds / 3600;
const auto minutes = seconds / 60; const auto minutes = seconds / 60;
return Ui::MakeInformBox({ return Ui::MakeInformBox({
.text = tr::lng_boost_error_flood_text( .text = (group
lt_left, ? tr::lng_boost_error_flood_text_group
rpl::single(Ui::Text::Bold((days > 1) : tr::lng_boost_error_flood_text)(
? tr::lng_days(tr::now, lt_count, days) lt_left,
: (hours > 1) rpl::single(Ui::Text::Bold((days > 1)
? tr::lng_hours(tr::now, lt_count, hours) ? tr::lng_days(tr::now, lt_count, days)
: (minutes > 1) : (hours > 1)
? tr::lng_minutes(tr::now, lt_count, minutes) ? tr::lng_hours(tr::now, lt_count, hours)
: tr::lng_seconds(tr::now, lt_count, seconds))), : (minutes > 1)
Ui::Text::RichLangValue), ? tr::lng_minutes(tr::now, lt_count, minutes)
: tr::lng_seconds(tr::now, lt_count, seconds))),
Ui::Text::RichLangValue),
.title = tr::lng_boost_error_flood_title(), .title = tr::lng_boost_error_flood_title(),
}); });
} }
@ -335,14 +340,15 @@ object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
object_ptr<Ui::BoxContent> ReassignBoostSingleBox( object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
not_null<ChannelData*> to, not_null<ChannelData*> to,
TakenBoostSlot from, TakenBoostSlot from,
Fn<void(std::vector<int> slots, int sources)> reassign, Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel) { Fn<void()> cancel) {
const auto reassigned = std::make_shared<bool>(); const auto reassigned = std::make_shared<bool>();
const auto slot = from.id; const auto slot = from.id;
const auto peer = to->owner().peer(from.peerId); const auto peer = to->owner().peer(from.peerId);
const auto group = peer->isMegagroup();
const auto confirmed = [=](Fn<void()> close) { const auto confirmed = [=](Fn<void()> close) {
*reassigned = true; *reassigned = true;
reassign({ slot }, 1); reassign({ slot }, group ? 1 : 0, group ? 0 : 1);
close(); close();
}; };
@ -417,35 +423,94 @@ Ui::BoostCounters ParseBoostCounters(
}; };
} }
Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
const auto group = channel->isMegagroup();
const auto appConfig = &channel->session().account().appConfig();
const auto get = [&](const QString &key, int fallback, bool ok = true) {
return ok ? appConfig->get<int>(key, fallback) : 0;
};
auto nameColorsByLevel = base::flat_map<int, int>();
auto linkStylesByLevel = base::flat_map<int, int>();
const auto peerColors = &channel->session().api().peerColors();
const auto &list = group
? peerColors->requiredLevelsGroup()
: peerColors->requiredLevelsChannel();
const auto indices = peerColors->indicesCurrent();
for (const auto &[index, level] : list) {
if (!Ui::ColorPatternIndex(indices, index, false)) {
++nameColorsByLevel[level];
}
++linkStylesByLevel[level];
}
const auto &themes = channel->owner().cloudThemes().chatThemes();
if (themes.empty()) {
channel->owner().cloudThemes().refreshChatThemes();
}
return Ui::BoostFeatures{
.nameColorsByLevel = std::move(nameColorsByLevel),
.linkStylesByLevel = std::move(linkStylesByLevel),
.linkLogoLevel = get(u"channel_bg_icon_level_min"_q, 4, !group),
.transcribeLevel = get(u"group_transcribe_level_min"_q, 6, group),
.emojiPackLevel = get(u"group_emoji_stickers_level_min"_q, 4, group),
.emojiStatusLevel = get(group
? u"group_emoji_status_level_min"_q
: u"channel_emoji_status_level_min"_q, 8),
.wallpaperLevel = get(group
? u"group_wallpaper_level_min"_q
: u"channel_wallpaper_level_min"_q, 9),
.wallpapersCount = themes.empty() ? 8 : int(themes.size()),
.customWallpaperLevel = get(group
? u"channel_custom_wallpaper_level_min"_q
: u"group_custom_wallpaper_level_min"_q, 10),
};
}
int BoostsForGift(not_null<Main::Session*> session) { int BoostsForGift(not_null<Main::Session*> session) {
const auto key = u"boosts_per_sent_gift"_q; const auto key = u"boosts_per_sent_gift"_q;
return session->account().appConfig().get<int>(key, 0); return session->account().appConfig().get<int>(key, 0);
} }
[[nodiscard]] int SourcesCount( struct Sources {
int groups = 0;
int channels = 0;
};
[[nodiscard]] Sources SourcesCount(
not_null<ChannelData*> to,
const std::vector<TakenBoostSlot> &from, const std::vector<TakenBoostSlot> &from,
const std::vector<int> &slots) { const std::vector<int> &slots) {
auto checked = base::flat_set<PeerId>(); auto groups = base::flat_set<PeerId>();
checked.reserve(slots.size()); groups.reserve(slots.size());
auto channels = base::flat_set<PeerId>();
channels.reserve(slots.size());
const auto owner = &to->owner();
for (const auto slot : slots) { for (const auto slot : slots) {
const auto i = ranges::find(from, slot, &TakenBoostSlot::id); const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
Assert(i != end(from)); Assert(i != end(from));
checked.emplace(i->peerId); const auto id = i->peerId;
if (!groups.contains(id) && !channels.contains(id)) {
(owner->peer(id)->isMegagroup() ? groups : channels).insert(id);
}
} }
return checked.size(); return {
.groups = int(groups.size()),
.channels = int(channels.size()),
};
} }
object_ptr<Ui::BoxContent> ReassignBoostsBox( object_ptr<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to, not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from, std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign, Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel) { Fn<void()> cancel) {
Expects(!from.empty()); Expects(!from.empty());
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
if (from.size() == 1 && from.front().cooldown > now) { if (from.size() == 1 && from.front().cooldown > now) {
cancel(); cancel();
return ReassignBoostFloodBox(from.front().cooldown - now); return ReassignBoostFloodBox(
from.front().cooldown - now,
to->owner().peer(from.front().peerId)->isMegagroup());
} else if (from.size() == 1 && from.front().peerId) { } else if (from.size() == 1 && from.front().peerId) {
return ReassignBoostSingleBox(to, from.front(), reassign, cancel); return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
} }
@ -457,10 +522,10 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
) | rpl::start_with_next([=](std::vector<int> slots) { ) | rpl::start_with_next([=](std::vector<int> slots) {
box->clearButtons(); box->clearButtons();
if (!slots.empty()) { if (!slots.empty()) {
const auto sources = SourcesCount(from, slots); const auto sources = SourcesCount(to, from, slots);
box->addButton(tr::lng_boost_reassign_button(), [=] { box->addButton(tr::lng_boost_reassign_button(), [=] {
*reassigned = true; *reassigned = true;
reassign(slots, sources); reassign(slots, sources.groups, sources.channels);
}); });
} }
box->addButton(tr::lng_cancel(), [=] { box->addButton(tr::lng_cancel(), [=] {

View file

@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h" #include "base/object_ptr.h"
class ChannelData;
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Ui { namespace Ui {
struct BoostCounters; struct BoostCounters;
struct BoostFeatures;
class BoxContent; class BoxContent;
class RpWidget; class RpWidget;
} // namespace Ui } // namespace Ui
@ -39,12 +42,15 @@ struct ForChannelBoostSlots {
[[nodiscard]] Ui::BoostCounters ParseBoostCounters( [[nodiscard]] Ui::BoostCounters ParseBoostCounters(
const MTPpremium_BoostsStatus &status); const MTPpremium_BoostsStatus &status);
[[nodiscard]] Ui::BoostFeatures LookupBoostFeatures(
not_null<ChannelData*> channel);
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session); [[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
object_ptr<Ui::BoxContent> ReassignBoostsBox( object_ptr<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to, not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from, std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign, Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel); Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics( [[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(

View file

@ -112,6 +112,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_infinite_reactions(); return tr::lng_premium_summary_subtitle_infinite_reactions();
case PremiumPreview::TagsForMessages: case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_subtitle_tags_for_messages(); return tr::lng_premium_summary_subtitle_tags_for_messages();
case PremiumPreview::LastSeen:
return tr::lng_premium_summary_subtitle_last_seen();
case PremiumPreview::MessagePrivacy:
return tr::lng_premium_summary_subtitle_message_privacy();
case PremiumPreview::Stickers: case PremiumPreview::Stickers:
return tr::lng_premium_summary_subtitle_premium_stickers(); return tr::lng_premium_summary_subtitle_premium_stickers();
case PremiumPreview::AnimatedEmoji: case PremiumPreview::AnimatedEmoji:
@ -150,6 +154,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_infinite_reactions(); return tr::lng_premium_summary_about_infinite_reactions();
case PremiumPreview::TagsForMessages: case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_about_tags_for_messages(); return tr::lng_premium_summary_about_tags_for_messages();
case PremiumPreview::LastSeen:
return tr::lng_premium_summary_about_last_seen();
case PremiumPreview::MessagePrivacy:
return tr::lng_premium_summary_about_message_privacy();
case PremiumPreview::Stickers: case PremiumPreview::Stickers:
return tr::lng_premium_summary_about_premium_stickers(); return tr::lng_premium_summary_about_premium_stickers();
case PremiumPreview::AnimatedEmoji: case PremiumPreview::AnimatedEmoji:
@ -480,6 +488,8 @@ struct VideoPreviewDocument {
case PremiumPreview::AnimatedUserpics: return "animated_userpics"; case PremiumPreview::AnimatedUserpics: return "animated_userpics";
case PremiumPreview::RealTimeTranslation: return "translations"; case PremiumPreview::RealTimeTranslation: return "translations";
case PremiumPreview::Wallpapers: return "wallpapers"; case PremiumPreview::Wallpapers: return "wallpapers";
case PremiumPreview::LastSeen: return "last_seen";
case PremiumPreview::MessagePrivacy: return "message_privacy";
} }
return ""; return "";
}(); }();

View file

@ -62,6 +62,8 @@ enum class PremiumPreview {
RealTimeTranslation, RealTimeTranslation,
Wallpapers, Wallpapers,
TagsForMessages, TagsForMessages,
LastSeen,
MessagePrivacy,
kCount, kCount,
}; };

View file

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/send_files_box.h" #include "boxes/send_files_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "storage/localimageloader.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "storage/storage_media_prepare.h" #include "storage/storage_media_prepare.h"
#include "mainwidget.h" #include "mainwidget.h"
@ -22,8 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "editor/photo_editor_layer_widget.h" #include "editor/photo_editor_layer_widget.h"
#include "history/history_drag_area.h" #include "history/history_drag_area.h"
#include "history/view/controls/history_view_characters_limit.h"
#include "history/view/history_view_schedule_box.h" #include "history/view/history_view_schedule_box.h"
#include "core/file_utilities.h"
#include "core/mime_type.h" #include "core/mime_type.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/call_delayed.h" #include "base/call_delayed.h"
@ -31,13 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "ui/effects/scroll_content_shadow.h" #include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/chat/attach/attach_prepare.h"
#include "ui/chat/attach/attach_send_files_way.h"
#include "ui/chat/attach/attach_album_preview.h" #include "ui/chat/attach/attach_album_preview.h"
#include "ui/chat/attach/attach_single_file_preview.h" #include "ui/chat/attach/attach_single_file_preview.h"
#include "ui/chat/attach/attach_single_media_preview.h" #include "ui/chat/attach/attach_single_media_preview.h"
@ -48,17 +42,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h" #include "lottie/lottie_single_player.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
#include "data/data_premium_limits.h" #include "data/data_premium_limits.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "api/api_common.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h"
#include <QtCore/QMimeData> #include <QtCore/QMimeData>
@ -339,16 +332,16 @@ SendFilesBox::SendFilesBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Ui::PreparedList &&list, Ui::PreparedList &&list,
const TextWithTags &caption, const TextWithTags &caption,
SendFilesLimits limits, not_null<PeerData*> toPeer,
SendFilesCheck check,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Type sendMenuType) SendMenu::Type sendMenuType)
: SendFilesBox(nullptr, { : SendFilesBox(nullptr, {
.show = controller->uiShow(), .show = controller->uiShow(),
.list = std::move(list), .list = std::move(list),
.caption = caption, .caption = caption,
.limits = limits, .captionToPeer = toPeer,
.check = check, .limits = DefaultLimitsForPeer(toPeer),
.check = DefaultCheckForPeer(controller, toPeer),
.sendType = sendType, .sendType = sendType,
.sendMenuType = sendMenuType, .sendMenuType = sendMenuType,
}) { }) {
@ -364,6 +357,7 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
, _list(std::move(descriptor.list)) , _list(std::move(descriptor.list))
, _limits(descriptor.limits) , _limits(descriptor.limits)
, _sendMenuType(descriptor.sendMenuType) , _sendMenuType(descriptor.sendMenuType)
, _captionToPeer(descriptor.captionToPeer)
, _check(std::move(descriptor.check)) , _check(std::move(descriptor.check))
, _confirmedCallback(std::move(descriptor.confirmed)) , _confirmedCallback(std::move(descriptor.confirmed))
, _cancelledCallback(std::move(descriptor.cancelled)) , _cancelledCallback(std::move(descriptor.cancelled))
@ -1034,8 +1028,10 @@ void SendFilesBox::updateSendWayControls() {
} }
void SendFilesBox::setupCaption() { void SendFilesBox::setupCaption() {
const auto allow = [=](const auto &) { const auto allow = [=](not_null<DocumentData*> emoji) {
return (_limits & SendFilesAllow::EmojiWithoutPremium); return _captionToPeer
? Data::AllowEmojiWithoutPremium(_captionToPeer, emoji)
: (_limits & SendFilesAllow::EmojiWithoutPremium);
}; };
const auto show = _show; const auto show = _show;
InitMessageFieldHandlers( InitMessageFieldHandlers(
@ -1096,6 +1092,39 @@ void SendFilesBox::setupCaption() {
updateCaptionPlaceholder(); updateCaptionPlaceholder();
setupEmojiPanel(); setupEmojiPanel();
rpl::single(rpl::empty_value()) | rpl::then(
_caption->changes()
) | rpl::start_with_next([=] {
checkCharsLimitation();
}, _caption->lifetime());
}
void SendFilesBox::checkCharsLimitation() {
const auto limits = Data::PremiumLimits(&_show->session());
const auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
const auto remove = caption.text.size() - limits.captionLengthCurrent();
if ((remove > 0) && _emojiToggle) {
if (!_charsLimitation) {
_charsLimitation = base::make_unique_q<CharactersLimitLabel>(
this,
_emojiToggle.data(),
style::al_top);
_charsLimitation->show();
Data::AmPremiumValue(
&_show->session()
) | rpl::start_with_next([=] {
checkCharsLimitation();
}, _charsLimitation->lifetime());
}
_charsLimitation->setLeft(remove);
} else {
if (_charsLimitation) {
_charsLimitation = nullptr;
}
}
} }
void SendFilesBox::setupEmojiPanel() { void SendFilesBox::setupEmojiPanel() {
@ -1114,7 +1143,6 @@ void SendFilesBox::setupEmojiPanel() {
.level = Window::GifPauseReason::Layer, .level = Window::GifPauseReason::Layer,
.mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly, .mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly,
.features = { .features = {
.megagroupSet = false,
.stickersSettings = false, .stickersSettings = false,
.openStickerSets = false, .openStickerSets = false,
}, },
@ -1125,6 +1153,7 @@ void SendFilesBox::setupEmojiPanel() {
st::emojiPanMinHeight / 2, st::emojiPanMinHeight / 2,
st::emojiPanMinHeight); st::emojiPanMinHeight);
_emojiPanel->hide(); _emojiPanel->hide();
_emojiPanel->selector()->setCurrentPeer(_captionToPeer);
_emojiPanel->selector()->setAllowEmojiWithoutPremium( _emojiPanel->selector()->setAllowEmojiWithoutPremium(
_limits & SendFilesAllow::EmojiWithoutPremium); _limits & SendFilesAllow::EmojiWithoutPremium);
_emojiPanel->selector()->emojiChosen( _emojiPanel->selector()->emojiChosen(
@ -1137,7 +1166,11 @@ void SendFilesBox::setupEmojiPanel() {
if (info if (info
&& info->setType == Data::StickersType::Emoji && info->setType == Data::StickersType::Emoji
&& !_show->session().premium() && !_show->session().premium()
&& !(_limits & SendFilesAllow::EmojiWithoutPremium)) { && !(_captionToPeer
? Data::AllowEmojiWithoutPremium(
_captionToPeer,
data.document)
: (_limits & SendFilesAllow::EmojiWithoutPremium))) {
ShowPremiumPreviewBox(_show, PremiumPreview::AnimatedEmoji); ShowPremiumPreviewBox(_show, PremiumPreview::AnimatedEmoji);
} else { } else {
Data::InsertCustomEmoji(_caption.data(), data.document); Data::InsertCustomEmoji(_caption.data(), data.document);

View file

@ -50,6 +50,10 @@ namespace SendMenu {
enum class Type; enum class Type;
} // namespace SendMenu } // namespace SendMenu
namespace HistoryView::Controls {
class CharactersLimitLabel;
} // namespace HistoryView::Controls
enum class SendFilesAllow { enum class SendFilesAllow {
OnlyOne = (1 << 0), OnlyOne = (1 << 0),
Photos = (1 << 1), Photos = (1 << 1),
@ -88,6 +92,7 @@ struct SendFilesBoxDescriptor {
std::shared_ptr<ChatHelpers::Show> show; std::shared_ptr<ChatHelpers::Show> show;
Ui::PreparedList list; Ui::PreparedList list;
TextWithTags caption; TextWithTags caption;
PeerData *captionToPeer = nullptr;
SendFilesLimits limits = {}; SendFilesLimits limits = {};
SendFilesCheck check; SendFilesCheck check;
Api::SendType sendType = {}; Api::SendType sendType = {};
@ -108,8 +113,7 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
Ui::PreparedList &&list, Ui::PreparedList &&list,
const TextWithTags &caption, const TextWithTags &caption,
SendFilesLimits limits, not_null<PeerData*> toPeer,
SendFilesCheck check,
Api::SendType sendType, Api::SendType sendType,
SendMenu::Type sendMenuType); SendMenu::Type sendMenuType);
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor); SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
@ -221,6 +225,8 @@ private:
void enqueueNextPrepare(); void enqueueNextPrepare();
void addPreparedAsyncFile(Ui::PreparedFile &&file); void addPreparedAsyncFile(Ui::PreparedFile &&file);
void checkCharsLimitation();
const std::shared_ptr<ChatHelpers::Show> _show; const std::shared_ptr<ChatHelpers::Show> _show;
const style::ComposeControls &_st; const style::ComposeControls &_st;
const Api::SendType _sendType = Api::SendType(); const Api::SendType _sendType = Api::SendType();
@ -233,7 +239,7 @@ private:
SendFilesLimits _limits = {}; SendFilesLimits _limits = {};
SendMenu::Type _sendMenuType = {}; SendMenu::Type _sendMenuType = {};
PeerData *_captionToPeer = nullptr;
SendFilesCheck _check; SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback; SendFilesConfirmed _confirmedCallback;
Fn<void()> _cancelledCallback; Fn<void()> _cancelledCallback;
@ -244,6 +250,8 @@ private:
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr }; object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel; base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
base::unique_qptr<QObject> _emojiFilter; base::unique_qptr<QObject> _emojiFilter;
using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel;
base::unique_qptr<CharactersLimitLabel> _charsLimitation;
object_ptr<Ui::Checkbox> _groupFiles = { nullptr }; object_ptr<Ui::Checkbox> _groupFiles = { nullptr };
object_ptr<Ui::Checkbox> _sendImagesAsPhotos = { nullptr }; object_ptr<Ui::Checkbox> _sendImagesAsPhotos = { nullptr };

View file

@ -17,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "ui/boxes/boost_box.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
@ -38,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/unread_badge_paint.h" #include "ui/unread_badge_paint.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
@ -99,7 +103,8 @@ public:
Inner( Inner(
QWidget *parent, QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup); not_null<ChannelData*> megagroup,
bool isEmoji);
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
@ -108,7 +113,7 @@ public:
} }
void setInnerFocus(); void setInnerFocus();
void saveGroupSet(); void saveGroupSet(Fn<void()> done);
void rebuild(bool masks); void rebuild(bool masks);
void updateSize(int newWidth = 0); void updateSize(int newWidth = 0);
@ -221,6 +226,7 @@ private:
StickersSetsOrder collectSets(Check check) const; StickersSetsOrder collectSets(Check check) const;
void updateSelected(); void updateSelected();
void checkGroupLevel(Fn<void()> done);
void checkLoadMore(); void checkLoadMore();
void updateScrollbarWidth(); void updateScrollbarWidth();
@ -323,6 +329,8 @@ private:
int _scrollbar = 0; int _scrollbar = 0;
ChannelData *_megagroupSet = nullptr; ChannelData *_megagroupSet = nullptr;
bool _megagroupSetEmoji = false;
bool _checkingGroupLevel = false;
StickerSetIdentifier _megagroupSetInput; StickerSetIdentifier _megagroupSetInput;
std::unique_ptr<Row> _megagroupSelectedSet; std::unique_ptr<Row> _megagroupSelectedSet;
object_ptr<AddressField> _megagroupSetField = { nullptr }; object_ptr<AddressField> _megagroupSetField = { nullptr };
@ -429,15 +437,16 @@ StickersBox::StickersBox(
StickersBox::StickersBox( StickersBox::StickersBox(
QWidget*, QWidget*,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup) not_null<ChannelData*> megagroup,
bool isEmoji)
: _st(st::stickersRowItem) : _st(st::stickersRowItem)
, _show(std::move(show)) , _show(std::move(show))
, _session(&_show->session()) , _session(&_show->session())
, _api(&_session->mtp()) , _api(&_session->mtp())
, _section(Section::Installed) , _section(Section::Installed)
, _isMasks(false) , _isMasks(false)
, _isEmoji(false) , _isEmoji(isEmoji)
, _installed(0, this, _show, megagroup) , _installed(0, this, _show, megagroup, isEmoji)
, _megagroupSet(megagroup) { , _megagroupSet(megagroup) {
_installed.widget()->scrollsToY( _installed.widget()->scrollsToY(
) | rpl::start_with_next([=](int y) { ) | rpl::start_with_next([=](int y) {
@ -581,7 +590,9 @@ void StickersBox::prepare() {
session().local().readArchivedStickers(); session().local().readArchivedStickers();
} }
} else { } else {
setTitle(tr::lng_stickers_group_set()); setTitle(_isEmoji
? tr::lng_emoji_group_set()
: tr::lng_stickers_group_set());
} }
} else if (_section == Section::Archived) { } else if (_section == Section::Archived) {
requestArchivedSets(); requestArchivedSets();
@ -659,9 +670,11 @@ void StickersBox::prepare() {
} }
if (_megagroupSet) { if (_megagroupSet) {
addButton( addButton(tr::lng_settings_save(), [=] {
tr::lng_settings_save(), _installed.widget()->saveGroupSet(crl::guard(this, [=] {
[=] { _installed.widget()->saveGroupSet(); closeBox(); }); closeBox();
}));
});
addButton(tr::lng_cancel(), [=] { closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); });
} else { } else {
const auto close = _section == Section::Attached; const auto close = _section == Section::Attached;
@ -1220,7 +1233,8 @@ StickersBox::Inner::Inner(
StickersBox::Inner::Inner( StickersBox::Inner::Inner(
QWidget *parent, QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup) not_null<ChannelData*> megagroup,
bool isEmoji)
: RpWidget(parent) : RpWidget(parent)
, _st(st::stickersRowItem) , _st(st::stickersRowItem)
, _show(std::move(show)) , _show(std::move(show))
@ -1248,19 +1262,30 @@ StickersBox::Inner::Inner(
}) })
, _itemsTop(st::lineWidth) , _itemsTop(st::lineWidth)
, _megagroupSet(megagroup) , _megagroupSet(megagroup)
, _megagroupSetInput(_megagroupSet->mgInfo->stickerSet) , _megagroupSetEmoji(isEmoji)
, _megagroupSetInput(isEmoji
? _megagroupSet->mgInfo->emojiSet
: _megagroupSet->mgInfo->stickerSet)
, _megagroupSetField( , _megagroupSetField(
this, this,
st::groupStickersField, st::groupStickersField,
rpl::single(u"stickerset"_q), rpl::single(isEmoji ? u"emojipack"_q : u"stickerset"_q),
QString(), QString(),
_session->createInternalLink(QString())) _session->createInternalLink(QString()))
, _megagroupDivider(this) , _megagroupDivider(this)
, _megagroupSubTitle(this, tr::lng_stickers_group_from_your(tr::now), st::boxTitle) { , _megagroupSubTitle(
this,
(isEmoji
? tr::lng_emoji_group_from_your
: tr::lng_stickers_group_from_your)(tr::now),
st::boxTitle) {
_megagroupSetField->setLinkPlaceholder( _megagroupSetField->setLinkPlaceholder(
_session->createInternalLink(u"addstickers/"_q)); _session->createInternalLink(
isEmoji ? u"addemoji/"_q : u"addstickers/"_q));
_megagroupSetField->setPlaceholderHidden(false); _megagroupSetField->setPlaceholderHidden(false);
_megagroupSetAddressChangedTimer.setCallback([this] { handleMegagroupSetAddressChange(); }); _megagroupSetAddressChangedTimer.setCallback([this] {
handleMegagroupSetAddressChange();
});
connect( connect(
_megagroupSetField, _megagroupSetField,
&Ui::MaskedInputField::changed, &Ui::MaskedInputField::changed,
@ -1689,7 +1714,9 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
} }
void StickersBox::Inner::mousePressEvent(QMouseEvent *e) { void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
if (_dragging >= 0) mouseReleaseEvent(e); if (_dragging >= 0) {
mouseReleaseEvent(e);
}
_mouse = e->globalPos(); _mouse = e->globalPos();
updateSelected(); updateSelected();
@ -1979,18 +2006,64 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
setActionDown(-1); setActionDown(-1);
} }
void StickersBox::Inner::saveGroupSet() { void StickersBox::Inner::saveGroupSet(Fn<void()> done) {
Expects(_megagroupSet != nullptr); Expects(_megagroupSet != nullptr);
auto oldId = _megagroupSet->mgInfo->stickerSet.id; auto oldId = _megagroupSetEmoji
? _megagroupSet->mgInfo->emojiSet.id
: _megagroupSet->mgInfo->stickerSet.id;
auto newId = _megagroupSetInput.id; auto newId = _megagroupSetInput.id;
if (newId != oldId) { if (newId == oldId) {
done();
} else if (_megagroupSetEmoji) {
checkGroupLevel(done);
} else {
session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput); session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput);
session().data().stickers().notifyStickerSetInstalled( session().data().stickers().notifyStickerSetInstalled(
Data::Stickers::MegagroupSetId); Data::Stickers::MegagroupSetId);
} }
} }
void StickersBox::Inner::checkGroupLevel(Fn<void()> done) {
Expects(_megagroupSet != nullptr);
Expects(_megagroupSetEmoji);
const auto peer = _megagroupSet;
const auto save = [=] {
session().api().setGroupEmojiSet(peer, _megagroupSetInput);
session().data().stickers().notifyEmojiSetInstalled(
Data::Stickers::MegagroupSetId);
done();
};
if (!_megagroupSetInput) {
save();
return;
} else if (_checkingGroupLevel) {
return;
}
_checkingGroupLevel = true;
const auto weak = Ui::MakeWeak(this);
CheckBoostLevel(_show, peer, [=](int level) {
if (!weak) {
return std::optional<Ui::AskBoostReason>();
}
_checkingGroupLevel = false;
const auto appConfig = &peer->session().account().appConfig();
const auto required = appConfig->get<int>(
"group_emoji_stickers_level_min",
4);
if (level >= required) {
save();
return std::optional<Ui::AskBoostReason>();
}
return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostEmojiPack{ required }
});
}, [=] { _checkingGroupLevel = false; });
}
void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) { void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) {
const auto index = getRowIndex(setId); const auto index = getRowIndex(setId);
if (index >= 0) { if (index >= 0) {
@ -2233,9 +2306,13 @@ void StickersBox::Inner::rebuild(bool masks) {
clear(); clear();
const auto &order = ([&]() -> const StickersSetsOrder & { const auto &order = ([&]() -> const StickersSetsOrder & {
if (_section == Section::Installed) { if (_section == Section::Installed) {
auto &result = session().data().stickers().setsOrder(); auto &result = _megagroupSetEmoji
? session().data().stickers().emojiSetsOrder()
: session().data().stickers().setsOrder();
if (_megagroupSet && result.empty()) { if (_megagroupSet && result.empty()) {
return session().data().stickers().featuredSetsOrder(); return _megagroupSetEmoji
? session().data().stickers().featuredEmojiSetsOrder()
: session().data().stickers().featuredSetsOrder();
} }
return result; return result;
} else if (_section == Section::Masks) { } else if (_section == Section::Masks) {
@ -2252,9 +2329,15 @@ void StickersBox::Inner::rebuild(bool masks) {
const auto &sets = session().data().stickers().sets(); const auto &sets = session().data().stickers().sets();
if (_megagroupSet) { if (_megagroupSet) {
auto usingFeatured = session().data().stickers().setsOrder().empty(); auto usingFeatured = _megagroupSetEmoji
? session().data().stickers().emojiSetsOrder().empty()
: session().data().stickers().setsOrder().empty();
_megagroupSubTitle->setText(usingFeatured _megagroupSubTitle->setText(usingFeatured
? tr::lng_stickers_group_from_featured(tr::now) ? (_megagroupSetEmoji
? tr::lng_stickers_group_from_featured(tr::now)
: tr::lng_emoji_group_from_featured(tr::now))
: _megagroupSetEmoji
? tr::lng_emoji_group_from_your(tr::now)
: tr::lng_stickers_group_from_your(tr::now)); : tr::lng_stickers_group_from_your(tr::now));
updateControlsGeometry(); updateControlsGeometry();
} else if (_isInstalledTab) { } else if (_isInstalledTab) {

View file

@ -66,7 +66,8 @@ public:
StickersBox( StickersBox(
QWidget*, QWidget*,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup); not_null<ChannelData*> megagroup,
bool isEmoji);
StickersBox( StickersBox(
QWidget*, QWidget*,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,

View file

@ -968,6 +968,7 @@ historyComposeField: InputField(defaultInputField) {
duration: 100; duration: 100;
} }
historyComposeFieldMaxHeight: 224px; historyComposeFieldMaxHeight: 224px;
historyComposeFieldFadeHeight: 6px;
// historyMinHeight: 56px; // historyMinHeight: 56px;
historyAttach: IconButton(defaultIconButton) { historyAttach: IconButton(defaultIconButton) {
@ -1070,6 +1071,13 @@ historyMessagesTTLLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
} }
historyCharsLimitationLabel: FlatLabel(defaultFlatLabel) {
// The same as a width of the historySendSize.
minWidth: 44px;
align: align(center);
textFg: attentionButtonFg;
}
historyRecordVoiceFg: historyComposeIconFg; historyRecordVoiceFg: historyComposeIconFg;
historyRecordVoiceFgOver: historyComposeIconFgOver; historyRecordVoiceFgOver: historyComposeIconFgOver;
historyRecordVoiceFgInactive: attentionButtonFg; historyRecordVoiceFgInactive: attentionButtonFg;

View file

@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "layout/layout_position.h" #include "layout/layout_position.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
@ -41,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "emoji_suggestions_data.h" #include "emoji_suggestions_data.h"
#include "emoji_suggestions_helper.h" #include "emoji_suggestions_helper.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "core/application.h" #include "core/application.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
@ -467,6 +470,7 @@ EmojiListWidget::EmojiListWidget(
, _show(std::move(descriptor.show)) , _show(std::move(descriptor.show))
, _features(descriptor.features) , _features(descriptor.features)
, _mode(descriptor.mode) , _mode(descriptor.mode)
, _api(&session().mtp())
, _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1) , _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1)
, _premiumIcon(_mode == Mode::EmojiStatus , _premiumIcon(_mode == Mode::EmojiStatus
? std::make_unique<GradientPremiumStar>() ? std::make_unique<GradientPremiumStar>()
@ -523,6 +527,15 @@ EmojiListWidget::EmojiListWidget(
pickerHidden(); pickerHidden();
}, lifetime()); }, lifetime());
session().changes().peerUpdates(
Data::PeerUpdate::Flag::EmojiSet
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == _megagroupSet);
}) | rpl::start_with_next([=] {
refreshCustom();
resizeToWidth(width());
}, lifetime());
session().data().stickers().updated( session().data().stickers().updated(
Data::StickersType::Emoji Data::StickersType::Emoji
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -1693,6 +1706,13 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
} }
void EmojiListWidget::displaySet(uint64 setId) { void EmojiListWidget::displaySet(uint64 setId) {
if (setId == Data::Stickers::MegagroupSetId) {
if (_megagroupSet->mgInfo->emojiSet.id) {
setId = _megagroupSet->mgInfo->emojiSet.id;
} else {
return;
}
}
const auto &sets = session().data().stickers().sets(); const auto &sets = session().data().stickers().sets();
auto it = sets.find(setId); auto it = sets.find(setId);
if (it != sets.cend()) { if (it != sets.cend()) {
@ -1700,9 +1720,37 @@ void EmojiListWidget::displaySet(uint64 setId) {
} }
} }
void EmojiListWidget::removeMegagroupSet(bool locally) {
if (locally) {
session().settings().setGroupEmojiSectionHidden(_megagroupSet->id);
session().saveSettings();
refreshCustom();
return;
}
checkHideWithBox(Ui::MakeConfirmBox({
.text = tr::lng_emoji_remove_group_set(),
.confirmed = crl::guard(this, [this, group = _megagroupSet](
Fn<void()> &&close) {
Expects(group->mgInfo != nullptr);
if (group->mgInfo->emojiSet) {
session().api().setGroupEmojiSet(group, {});
}
close();
}),
.cancelled = [](Fn<void()> &&close) { close(); },
.labelStyle = &st().boxLabel,
}));
}
void EmojiListWidget::removeSet(uint64 setId) { void EmojiListWidget::removeSet(uint64 setId) {
const auto &labelSt = st().boxLabel; const auto &labelSt = st().boxLabel;
if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { if (setId == Data::Stickers::MegagroupSetId) {
const auto i = ranges::find(_custom, setId, &CustomSet::id);
Assert(i != end(_custom));
const auto removeLocally = !_megagroupSet->canEditEmoji();
removeMegagroupSet(removeLocally);
} else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
checkHideWithBox(std::move(box)); checkHideWithBox(std::move(box));
} }
} }
@ -1799,6 +1847,13 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
return false; return false;
} }
const auto &set = _custom[index - _staticCount]; const auto &set = _custom[index - _staticCount];
if (set.id == Data::Stickers::MegagroupSetId) {
Assert(_megagroupSet != nullptr);
if (index + 1 != _staticCount + _custom.size()) {
return true;
}
return !set.list.empty() && _megagroupSet->canEditEmoji();
}
return set.canRemove && !set.premiumRequired; return set.canRemove && !set.premiumRequired;
} }
@ -1825,7 +1880,9 @@ bool EmojiListWidget::hasAddButton(int index) const {
return false; return false;
} }
const auto &set = _custom[index - _staticCount]; const auto &set = _custom[index - _staticCount];
return !set.canRemove && !set.premiumRequired; return !set.canRemove
&& !set.premiumRequired
&& set.id != Data::Stickers::MegagroupSetId;
} }
QRect EmojiListWidget::addButtonRect(int index) const { QRect EmojiListWidget::addButtonRect(int index) const {
@ -1849,10 +1906,13 @@ QRect EmojiListWidget::unlockButtonRect(int index) const {
} }
bool EmojiListWidget::hasButton(int index) const { bool EmojiListWidget::hasButton(int index) const {
if (hasColorButton(index) if (hasColorButton(index)) {
|| (index >= _staticCount
&& index < _staticCount + _custom.size())) {
return true; return true;
} else if (index >= _staticCount
&& index < _staticCount + _custom.size()) {
const auto &custom = _custom[index - _staticCount];
return (custom.id != Data::Stickers::MegagroupSetId)
|| custom.canRemove;
} }
return false; return false;
} }
@ -1982,6 +2042,16 @@ void EmojiListWidget::setAllowWithoutPremium(bool allow) {
resizeToWidth(width()); resizeToWidth(width());
} }
void EmojiListWidget::showMegagroupSet(ChannelData *megagroup) {
Expects(!megagroup || megagroup->isMegagroup());
if (_megagroupSet != megagroup) {
_megagroupSet = megagroup;
refreshCustom();
resizeToWidth(width());
}
}
QString EmojiListWidget::tooltipText() const { QString EmojiListWidget::tooltipText() const {
if (_mode != Mode::Full) { if (_mode != Mode::Full) {
return {}; return {};
@ -2052,7 +2122,25 @@ void EmojiListWidget::refreshCustom() {
const auto owner = &session->data(); const auto owner = &session->data();
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); const auto megagroup = _megagroupSet
&& (setId == Data::Stickers::MegagroupSetId);
const auto lookupId = megagroup
? _megagroupSet->mgInfo->emojiSet.id
: setId;
if (!lookupId) {
return;
} else if (!megagroup
&& !_custom.empty()
&& _custom.front().id == Data::Stickers::MegagroupSetId
&& _megagroupSet->mgInfo->emojiSet.id == setId) {
// Skip the set that is already added as a megagroup set.
return;
} else if (megagroup
&& ranges::contains(_custom, lookupId, &CustomSet::id)) {
// Skip the set that is already added as a custom set.
return;
}
auto it = sets.find(lookupId);
if (it == sets.cend() if (it == sets.cend()
|| it->second->stickers.isEmpty() || it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji && !it->second->textColor()) || (_mode == Mode::BackgroundEmoji && !it->second->textColor())
@ -2060,12 +2148,13 @@ void EmojiListWidget::refreshCustom() {
&& !it->second->channelStatus())) { && !it->second->channelStatus())) {
return; return;
} }
const auto canRemove = !!(it->second->flags const auto canRemove = megagroup
& Data::StickersSetFlag::Installed); ? (_megagroupSet->canEditEmoji() || installed)
: !!(it->second->flags & Data::StickersSetFlag::Installed);
const auto sortAsInstalled = canRemove const auto sortAsInstalled = canRemove
&& (!(it->second->flags & Data::StickersSetFlag::Featured) && (!(it->second->flags & Data::StickersSetFlag::Featured)
|| !_localSetsManager->isInstalledLocally(setId)); || !_localSetsManager->isInstalledLocally(lookupId));
if (sortAsInstalled != installed) { if (!megagroup && sortAsInstalled != installed) {
return; return;
} }
auto premium = false; auto premium = false;
@ -2078,7 +2167,7 @@ void EmojiListWidget::refreshCustom() {
return false; return false;
} }
for (auto k = 0; k != count; ++k) { for (auto k = 0; k != count; ++k) {
if (!premium && list[k]->isPremiumEmoji()) { if (!premium && !megagroup && list[k]->isPremiumEmoji()) {
premium = true; premium = true;
} }
if (i->list[k].document != list[k]) { if (i->list[k].document != list[k]) {
@ -2112,11 +2201,11 @@ void EmojiListWidget::refreshCustom() {
continue; continue;
} else if (const auto sticker = document->sticker()) { } else if (const auto sticker = document->sticker()) {
set.push_back({ set.push_back({
.custom = resolveCustomEmoji(document, setId), .custom = resolveCustomEmoji(document, lookupId),
.document = document, .document = document,
.emoji = Ui::Emoji::Find(sticker->alt), .emoji = Ui::Emoji::Find(sticker->alt),
}); });
if (!premium && document->isPremiumEmoji()) { if (!premium && !megagroup && document->isPremiumEmoji()) {
premium = true; premium = true;
} }
} }
@ -2134,12 +2223,14 @@ void EmojiListWidget::refreshCustom() {
.premiumRequired = premium && premiumMayBeBought, .premiumRequired = premium && premiumMayBeBought,
}); });
}; };
refreshMegagroupStickers(push, GroupStickersPlace::Visible);
for (const auto setId : owner->stickers().emojiSetsOrder()) { for (const auto setId : owner->stickers().emojiSetsOrder()) {
push(setId, true); push(setId, true);
} }
for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) { for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) {
push(setId, false); push(setId, false);
} }
refreshMegagroupStickers(push, GroupStickersPlace::Hidden);
_footer->refreshIcons( _footer->refreshIcons(
fillIcons(), fillIcons(),
@ -2232,6 +2323,60 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
).first->second.emoji.get(); ).first->second.emoji.get();
} }
void EmojiListWidget::refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place) {
if (!_features.megagroupSet
|| !_megagroupSet
|| !_megagroupSet->mgInfo->emojiSet) {
return;
}
auto canEdit = _megagroupSet->canEditEmoji();
auto isShownHere = [place](bool hidden) {
return (hidden == (place == GroupStickersPlace::Hidden));
};
auto hidden = session().settings().isGroupEmojiSectionHidden(_megagroupSet->id);
auto removeHiddenForGroup = [this, &hidden] {
if (hidden) {
session().settings().removeGroupEmojiSectionHidden(_megagroupSet->id);
session().saveSettings();
hidden = false;
}
};
if (canEdit && hidden) {
removeHiddenForGroup();
}
const auto &set = _megagroupSet->mgInfo->emojiSet;
if (!set.id || !isShownHere(hidden)) {
return;
}
push(Data::Stickers::MegagroupSetId, !hidden);
if (!_custom.empty()
&& _custom.back().id == Data::Stickers::MegagroupSetId) {
return;
} else if (_megagroupSetIdRequested == set.id) {
return;
}
_megagroupSetIdRequested = set.id;
_api.request(MTPmessages_GetStickerSet(
Data::InputStickerSet(set),
MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &data) {
if (const auto set = session().data().stickers().feedSetFull(data)) {
refreshCustom();
if (set->id == _megagroupSetIdRequested) {
_megagroupSetIdRequested = 0;
} else {
LOG(("API Error: Got different set."));
}
}
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).send();
}
std::vector<StickerIcon> EmojiListWidget::fillIcons() { std::vector<StickerIcon> EmojiListWidget::fillIcons() {
auto result = std::vector<StickerIcon>(); auto result = std::vector<StickerIcon>();
result.reserve(2 + _custom.size()); result.reserve(2 + _custom.size());
@ -2248,6 +2393,11 @@ std::vector<StickerIcon> EmojiListWidget::fillIcons() {
} }
const auto esize = StickersListFooter::IconFrameSize(); const auto esize = StickersListFooter::IconFrameSize();
for (const auto &custom : _custom) { for (const auto &custom : _custom) {
if (custom.id == Data::Stickers::MegagroupSetId) {
result.emplace_back(Data::Stickers::MegagroupSetId);
result.back().megagroup = _megagroupSet;
continue;
}
const auto set = custom.set; const auto set = custom.set;
result.emplace_back(set, custom.thumbnailDocument, esize, esize); result.emplace_back(set, custom.thumbnailDocument, esize, esize);
} }

View file

@ -117,6 +117,7 @@ public:
void showSet(uint64 setId); void showSet(uint64 setId);
[[nodiscard]] uint64 currentSet(int yOffset) const; [[nodiscard]] uint64 currentSet(int yOffset) const;
void setAllowWithoutPremium(bool allow); void setAllowWithoutPremium(bool allow);
void showMegagroupSet(ChannelData *megagroup);
// Ui::AbstractTooltipShower interface. // Ui::AbstractTooltipShower interface.
QString tooltipText() const override; QString tooltipText() const override;
@ -257,6 +258,13 @@ private:
void colorChosen(EmojiChosen data); void colorChosen(EmojiChosen data);
bool checkPickerHide(); bool checkPickerHide();
void refreshCustom(); void refreshCustom();
enum class GroupStickersPlace {
Visible,
Hidden,
};
void refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place);
void unloadNotSeenCustom(int visibleTop, int visibleBottom); void unloadNotSeenCustom(int visibleTop, int visibleBottom);
void unloadAllCustom(); void unloadAllCustom();
void unloadCustomIn(const SectionInfo &info); void unloadCustomIn(const SectionInfo &info);
@ -340,6 +348,7 @@ private:
void displaySet(uint64 setId); void displaySet(uint64 setId);
void removeSet(uint64 setId); void removeSet(uint64 setId);
void removeMegagroupSet(bool locally);
void initButton(RightButton &button, const QString &text, bool gradient); void initButton(RightButton &button, const QString &text, bool gradient);
[[nodiscard]] std::unique_ptr<Ui::RippleAnimation> createButtonRipple( [[nodiscard]] std::unique_ptr<Ui::RippleAnimation> createButtonRipple(
@ -370,10 +379,13 @@ private:
const ComposeFeatures _features; const ComposeFeatures _features;
Mode _mode = Mode::Full; Mode _mode = Mode::Full;
std::unique_ptr<Ui::TabbedSearch> _search; std::unique_ptr<Ui::TabbedSearch> _search;
MTP::Sender _api;
const int _staticCount = 0; const int _staticCount = 0;
StickersListFooter *_footer = nullptr; StickersListFooter *_footer = nullptr;
std::unique_ptr<GradientPremiumStar> _premiumIcon; std::unique_ptr<GradientPremiumStar> _premiumIcon;
std::unique_ptr<LocalStickersManager> _localSetsManager; std::unique_ptr<LocalStickersManager> _localSetsManager;
ChannelData *_megagroupSet = nullptr;
uint64 _megagroupSetIdRequested = 0;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>( Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
DocumentId, DocumentId,
Fn<void()>)> _customRecentFactory; Fn<void()>)> _customRecentFactory;

View file

@ -10,11 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_widget.h" #include "history/history_widget.h"
#include "history/history.h" // History::session #include "history/history.h" // History::session
#include "history/history_item.h" // HistoryItem::originalText #include "history/history_item.h" // HistoryItem::originalText
#include "history/history_item_helpers.h" // DropCustomEmoji #include "history/history_item_helpers.h" // DropDisallowedCustomEmoji
#include "base/qthelp_regex.h" #include "base/qthelp_regex.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
@ -277,11 +278,9 @@ TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
auto original = item->history()->session().supportMode() auto original = item->history()->session().supportMode()
? StripSupportHashtag(item->originalText()) ? StripSupportHashtag(item->originalText())
: item->originalText(); : item->originalText();
const auto dropCustomEmoji = !item->history()->session().premium() original = DropDisallowedCustomEmoji(
&& !item->history()->peer->isSelf(); item->history()->peer,
if (dropCustomEmoji) { std::move(original));
original = DropCustomEmoji(std::move(original));
}
return TextWithTags{ return TextWithTags{
original.text, original.text,
TextUtilities::ConvertEntitiesToTextTags(original.entities) TextUtilities::ConvertEntitiesToTextTags(original.entities)
@ -442,6 +441,73 @@ bool HasSendText(not_null<const Ui::InputField*> field) {
return false; return false;
} }
void InitMessageFieldFade(
not_null<Ui::InputField*> field,
const style::color &bg) {
class Fade final : public Ui::RpWidget {
public:
using Ui::RpWidget::RpWidget;
void setFade(QPixmap &&fade) {
_fade = std::move(fade);
}
int resizeGetHeight(int newWidth) override {
return st::historyComposeFieldFadeHeight;
}
private:
void paintEvent(QPaintEvent *event) override {
auto p = QPainter(this);
p.drawTiledPixmap(rect(), _fade);
}
QPixmap _fade;
};
const auto topFade = Ui::CreateChild<Fade>(field.get());
const auto bottomFade = Ui::CreateChild<Fade>(field.get());
const auto generateFade = [=] {
const auto size = QSize(1, st::historyComposeFieldFadeHeight);
auto fade = QPixmap(size * style::DevicePixelRatio());
fade.setDevicePixelRatio(style::DevicePixelRatio());
fade.fill(Qt::transparent);
{
auto p = QPainter(&fade);
auto gradient = QLinearGradient(0, 1, 0, size.height());
gradient.setStops({ { 0., bg->c }, { .9, Qt::transparent } });
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawRect(Rect(size));
}
bottomFade->setFade(fade.transformed(QTransform().scale(1, -1)));
topFade->setFade(std::move(fade));
};
generateFade();
style::PaletteChanged(
) | rpl::start_with_next([=] {
generateFade();
}, topFade->lifetime());
field->sizeValue(
) | rpl::start_with_next_done([=](const QSize &size) {
topFade->resizeToWidth(size.width());
bottomFade->resizeToWidth(size.width());
bottomFade->move(
0,
size.height() - st::historyComposeFieldFadeHeight);
}, [t = Ui::MakeWeak(topFade), b = Ui::MakeWeak(bottomFade)] {
Ui::DestroyChild(t.data());
Ui::DestroyChild(b.data());
}, topFade->lifetime());
topFade->show();
bottomFade->show();
}
InlineBotQuery ParseInlineBotQuery( InlineBotQuery ParseInlineBotQuery(
not_null<Main::Session*> session, not_null<Main::Session*> session,
not_null<const Ui::InputField*> field) { not_null<const Ui::InputField*> field) {

View file

@ -79,6 +79,10 @@ void InitSpellchecker(
bool HasSendText(not_null<const Ui::InputField*> field); bool HasSendText(not_null<const Ui::InputField*> field);
void InitMessageFieldFade(
not_null<Ui::InputField*> field,
const style::color &bg);
struct InlineBotQuery { struct InlineBotQuery {
QString query; QString query;
QString username; QString username;

View file

@ -1790,7 +1790,8 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
removeSet(sets[button->section].id); removeSet(sets[button->section].id);
} }
} else if (std::get_if<OverGroupAdd>(&pressed)) { } else if (std::get_if<OverGroupAdd>(&pressed)) {
_show->showBox(Box<StickersBox>(_show, _megagroupSet)); const auto isEmoji = false;
_show->showBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
} }
} }
} }
@ -2642,7 +2643,8 @@ void StickersListWidget::setupSearch() {
void StickersListWidget::displaySet(uint64 setId) { void StickersListWidget::displaySet(uint64 setId) {
if (setId == Data::Stickers::MegagroupSetId) { if (setId == Data::Stickers::MegagroupSetId) {
if (_megagroupSet->canEditStickers()) { if (_megagroupSet->canEditStickers()) {
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet)); const auto isEmoji = false;
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
return; return;
} else if (_megagroupSet->mgInfo->stickerSet.id) { } else if (_megagroupSet->mgInfo->stickerSet.id) {
setId = _megagroupSet->mgInfo->stickerSet.id; setId = _megagroupSet->mgInfo->stickerSet.id;

View file

@ -966,6 +966,9 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
} }
_currentPeer = peer; _currentPeer = peer;
checkRestrictedPeer(); checkRestrictedPeer();
if (hasEmojiTab()) {
emoji()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
}
if (hasStickersTab()) { if (hasStickersTab()) {
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr); stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
} }

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QSessionManager> #include <QtGui/QSessionManager>
#include <QtGui/QScreen> #include <QtGui/QScreen>
#include <QtGui/qpa/qplatformscreen.h> #include <QtGui/qpa/qplatformscreen.h>
#include <ksandbox.h>
namespace Core { namespace Core {
namespace { namespace {
@ -517,8 +518,10 @@ void Sandbox::refreshGlobalProxy() {
|| proxy.type == MTP::ProxyData::Type::Http) { || proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy( QNetworkProxy::setApplicationProxy(
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy))); MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
} else if (!Core::IsAppLaunched() } else if ((!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem()) { || Core::App().settings().proxy().isSystem())
// this works stable only in sandboxed environment where it works through portal
&& (!Platform::IsLinux() || KSandbox::isInside() || cDebugMode())) {
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
} else { } else {
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4014013; constexpr auto AppVersion = 4015000;
constexpr auto AppVersionStr = "4.14.13"; constexpr auto AppVersionStr = "4.15";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data { namespace Data {
struct BoostsOverview final { struct BoostsOverview final {
bool group = false;
int mine = 0; int mine = 0;
int level = 0; int level = 0;
int boostCount = 0; int boostCount = 0;

View file

@ -101,13 +101,14 @@ struct PeerUpdate {
// For channels // For channels
ChannelAmIn = (1ULL << 36), ChannelAmIn = (1ULL << 36),
StickersSet = (1ULL << 37), StickersSet = (1ULL << 37),
ChannelLinkedChat = (1ULL << 38), EmojiSet = (1ULL << 38),
ChannelLocation = (1ULL << 39), ChannelLinkedChat = (1ULL << 39),
Slowmode = (1ULL << 40), ChannelLocation = (1ULL << 40),
GroupCall = (1ULL << 41), Slowmode = (1ULL << 41),
GroupCall = (1ULL << 42),
// For iteration // For iteration
LastUsedBit = (1ULL << 41), LastUsedBit = (1ULL << 42),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -556,25 +556,16 @@ bool ChannelData::canDeleteMessages() const {
} }
bool ChannelData::canPostStories() const { bool ChannelData::canPostStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator() return amCreator()
|| (adminRights() & AdminRight::PostStories); || (adminRights() & AdminRight::PostStories);
} }
bool ChannelData::canEditStories() const { bool ChannelData::canEditStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator() return amCreator()
|| (adminRights() & AdminRight::EditStories); || (adminRights() & AdminRight::EditStories);
} }
bool ChannelData::canDeleteStories() const { bool ChannelData::canDeleteStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator() return amCreator()
|| (adminRights() & AdminRight::DeleteStories); || (adminRights() & AdminRight::DeleteStories);
} }
@ -648,6 +639,10 @@ bool ChannelData::canEditStickers() const {
return (flags() & Flag::CanSetStickers); return (flags() & Flag::CanSetStickers);
} }
bool ChannelData::canEditEmoji() const {
return amCreator() || (adminRights() & ChatAdminRight::ChangeInfo);
}
bool ChannelData::canDelete() const { bool ChannelData::canDelete() const {
constexpr auto kDeleteChannelMembersLimit = 1000; constexpr auto kDeleteChannelMembersLimit = 1000;
return amCreator() return amCreator()
@ -772,34 +767,93 @@ void ChannelData::setMigrateFromChat(ChatData *chat) {
} }
int ChannelData::slowmodeSeconds() const { int ChannelData::slowmodeSeconds() const {
return _slowmodeSeconds; if (const auto info = mgInfo.get()) {
return info->slowmodeSeconds;
}
return 0;
} }
void ChannelData::setSlowmodeSeconds(int seconds) { void ChannelData::setSlowmodeSeconds(int seconds) {
if (_slowmodeSeconds == seconds) { if (!mgInfo || slowmodeSeconds() == seconds) {
return; return;
} }
_slowmodeSeconds = seconds; mgInfo->slowmodeSeconds = seconds;
session().changes().peerUpdated(this, UpdateFlag::Slowmode); session().changes().peerUpdated(this, UpdateFlag::Slowmode);
} }
TimeId ChannelData::slowmodeLastMessage() const { TimeId ChannelData::slowmodeLastMessage() const {
return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage; return (hasAdminRights()
|| amCreator()
|| unrestrictedByBoosts()
|| !mgInfo)
? 0
: mgInfo->slowmodeLastMessage;
} }
void ChannelData::growSlowmodeLastMessage(TimeId when) { void ChannelData::growSlowmodeLastMessage(TimeId when) {
const auto info = mgInfo.get();
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
accumulate_min(when, now); accumulate_min(when, now);
if (_slowmodeLastMessage > now) { if (!info) {
_slowmodeLastMessage = when; return;
} else if (_slowmodeLastMessage >= when) { } else if (info->slowmodeLastMessage > now) {
info->slowmodeLastMessage = when;
} else if (info->slowmodeLastMessage >= when) {
return; return;
} else { } else {
_slowmodeLastMessage = when; info->slowmodeLastMessage = when;
} }
session().changes().peerUpdated(this, UpdateFlag::Slowmode); session().changes().peerUpdated(this, UpdateFlag::Slowmode);
} }
int ChannelData::boostsApplied() const {
if (const auto info = mgInfo.get()) {
return info->boostsApplied;
}
return 0;
}
int ChannelData::boostsUnrestrict() const {
if (const auto info = mgInfo.get()) {
return info->boostsUnrestrict;
}
return 0;
}
bool ChannelData::unrestrictedByBoosts() const {
if (const auto info = mgInfo.get()) {
return (info->boostsUnrestrict > 0)
&& (info->boostsApplied >= info->boostsUnrestrict);
}
return 0;
}
rpl::producer<bool> ChannelData::unrestrictedByBoostsValue() const {
return mgInfo
? mgInfo->unrestrictedByBoostsChanges.events_starting_with(
unrestrictedByBoosts())
: (rpl::single(false) | rpl::type_erased());
}
void ChannelData::setBoostsUnrestrict(int applied, int unrestrict) {
if (const auto info = mgInfo.get()) {
if (info->boostsApplied == applied
&& info->boostsUnrestrict == unrestrict) {
return;
}
const auto wasUnrestricted = unrestrictedByBoosts();
info->boostsApplied = applied;
info->boostsUnrestrict = unrestrict;
const auto nowUnrestricted = unrestrictedByBoosts();
if (wasUnrestricted != nowUnrestricted) {
info->unrestrictedByBoostsChanges.fire_copy(nowUnrestricted);
session().changes().peerUpdated(
this,
UpdateFlag::Rights | UpdateFlag::Slowmode);
}
}
}
void ChannelData::setInvitePeek(const QString &hash, TimeId expires) { void ChannelData::setInvitePeek(const QString &hash, TimeId expires) {
if (!_invitePeek) { if (!_invitePeek) {
_invitePeek = std::make_unique<InvitePeek>(); _invitePeek = std::make_unique<InvitePeek>();
@ -1104,20 +1158,37 @@ void ApplyChannelUpdate(
channel->owner().botCommandsChanged(channel); channel->owner().botCommandsChanged(channel);
} }
const auto stickerSet = update.vstickerset(); const auto stickerSet = update.vstickerset();
const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr; const auto sset = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
const auto newSetId = (set ? set->vid().v : 0); const auto newStickerSetId = (sset ? sset->vid().v : 0);
const auto oldSetId = channel->mgInfo->stickerSet.id; const auto oldStickerSetId = channel->mgInfo->stickerSet.id;
const auto stickersChanged = (canEditStickers != channel->canEditStickers()) const auto stickersChanged = (canEditStickers != channel->canEditStickers())
|| (oldSetId != newSetId); || (oldStickerSetId != newStickerSetId);
if (oldSetId != newSetId) { if (oldStickerSetId != newStickerSetId) {
channel->mgInfo->stickerSet = StickerSetIdentifier{ channel->mgInfo->stickerSet = StickerSetIdentifier{
.id = set ? set->vid().v : 0, .id = sset ? sset->vid().v : 0,
.accessHash = set ? set->vaccess_hash().v : 0, .accessHash = sset ? sset->vaccess_hash().v : 0,
}; };
} }
if (stickersChanged) { if (stickersChanged) {
session->changes().peerUpdated(channel, UpdateFlag::StickersSet); session->changes().peerUpdated(channel, UpdateFlag::StickersSet);
} }
const auto emojiSet = update.vemojiset();
const auto eset = emojiSet ? &emojiSet->c_stickerSet() : nullptr;
const auto newEmojiSetId = (eset ? eset->vid().v : 0);
const auto oldEmojiSetId = channel->mgInfo->emojiSet.id;
const auto emojiChanged = (oldEmojiSetId != newEmojiSetId);
if (oldEmojiSetId != newEmojiSetId) {
channel->mgInfo->emojiSet = StickerSetIdentifier{
.id = eset ? eset->vid().v : 0,
.accessHash = eset ? eset->vaccess_hash().v : 0,
};
}
if (emojiChanged) {
session->changes().peerUpdated(channel, UpdateFlag::EmojiSet);
}
channel->setBoostsUnrestrict(
update.vboosts_applied().value_or_empty(),
update.vboosts_unrestrict().value_or_empty());
} }
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty())); channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
channel->setTranslationDisabled(update.is_translations_disabled()); channel->setTranslationDisabled(update.is_translations_disabled());

View file

@ -114,6 +114,7 @@ public:
base::flat_map<not_null<UserData*>, Restricted> lastRestricted; base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
base::flat_set<not_null<PeerData*>> markupSenders; base::flat_set<not_null<PeerData*>> markupSenders;
base::flat_set<not_null<UserData*>> bots; base::flat_set<not_null<UserData*>> bots;
rpl::event_stream<bool> unrestrictedByBoostsChanges;
// For admin badges, full admins list with ranks. // For admin badges, full admins list with ranks.
base::flat_map<UserId, QString> admins; base::flat_map<UserId, QString> admins;
@ -124,6 +125,7 @@ public:
bool joinedMessageFound = false; bool joinedMessageFound = false;
bool adminsLoaded = false; bool adminsLoaded = false;
StickerSetIdentifier stickerSet; StickerSetIdentifier stickerSet;
StickerSetIdentifier emojiSet;
enum LastParticipantsStatus { enum LastParticipantsStatus {
LastParticipantsUpToDate = 0x00, LastParticipantsUpToDate = 0x00,
@ -132,6 +134,11 @@ public:
}; };
mutable int lastParticipantsStatus = LastParticipantsUpToDate; mutable int lastParticipantsStatus = LastParticipantsUpToDate;
int lastParticipantsCount = 0; int lastParticipantsCount = 0;
int boostsApplied = 0;
int boostsUnrestrict = 0;
int slowmodeSeconds = 0;
TimeId slowmodeLastMessage = 0;
private: private:
ChatData *_migratedFrom = nullptr; ChatData *_migratedFrom = nullptr;
@ -354,6 +361,7 @@ public:
[[nodiscard]] bool canViewBanned() const; [[nodiscard]] bool canViewBanned() const;
[[nodiscard]] bool canEditSignatures() const; [[nodiscard]] bool canEditSignatures() const;
[[nodiscard]] bool canEditStickers() const; [[nodiscard]] bool canEditStickers() const;
[[nodiscard]] bool canEditEmoji() const;
[[nodiscard]] bool canDelete() const; [[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const; [[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant( [[nodiscard]] bool canRestrictParticipant(
@ -432,6 +440,12 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const; [[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when); void growSlowmodeLastMessage(TimeId when);
[[nodiscard]] int boostsApplied() const;
[[nodiscard]] int boostsUnrestrict() const;
[[nodiscard]] bool unrestrictedByBoosts() const;
[[nodiscard]] rpl::producer<bool> unrestrictedByBoostsValue() const;
void setBoostsUnrestrict(int applied, int unrestrict);
void setInvitePeek(const QString &hash, TimeId expires); void setInvitePeek(const QString &hash, TimeId expires);
void clearInvitePeek(); void clearInvitePeek();
[[nodiscard]] TimeId invitePeekExpires() const; [[nodiscard]] TimeId invitePeekExpires() const;
@ -519,9 +533,6 @@ private:
std::unique_ptr<Data::GroupCall> _call; std::unique_ptr<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0; PeerId _callDefaultJoinAs = 0;
int _slowmodeSeconds = 0;
TimeId _slowmodeLastMessage = 0;
}; };
namespace Data { namespace Data {

View file

@ -44,11 +44,9 @@ MTPInputReplyTo ReplyToForMTP(
const auto owner = &history->owner(); 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()) { return MTP_inputReplyToStory(
return MTP_inputReplyToStory( peer->input,
user->inputUser, MTP_int(replyTo.storyId.story));
MTP_int(replyTo.storyId.story));
}
} }
} else if (replyTo.messageId || replyTo.topicRootId) { } else if (replyTo.messageId || replyTo.topicRootId) {
const auto to = LookupReplyTo(history, replyTo.messageId); const auto to = LookupReplyTo(history, replyTo.messageId);

View file

@ -302,20 +302,27 @@ bool UpdateExtendedMedia(
}); });
} }
} // namespace
TextForMimeData WithCaptionClipboardText( TextForMimeData WithCaptionClipboardText(
const QString &attachType, const QString &attachType,
TextForMimeData &&caption) { TextForMimeData &&caption) {
auto result = TextForMimeData(); auto result = TextForMimeData();
result.reserve(5 + attachType.size() + caption.expanded.size()); if (attachType.isEmpty()) {
result.append(u"[ "_q).append(attachType).append(u" ]"_q); result.reserve(1 + caption.expanded.size());
if (!caption.empty()) { if (!caption.empty()) {
result.append('\n').append(std::move(caption)); result.append(std::move(caption));
}
} else {
result.reserve(5 + attachType.size() + caption.expanded.size());
result.append(u"[ "_q).append(attachType).append(u" ]"_q);
if (!caption.empty()) {
result.append('\n').append(std::move(caption));
}
} }
return result; return result;
} }
} // namespace
Invoice ComputeInvoiceData( Invoice ComputeInvoiceData(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data) { const MTPDmessageMediaInvoice &data) {
@ -767,9 +774,7 @@ QString MediaPhoto::pinnedTextSubstring() const {
} }
TextForMimeData MediaPhoto::clipboardText() const { TextForMimeData MediaPhoto::clipboardText() const {
return WithCaptionClipboardText( return TextForMimeData();
tr::lng_in_dlg_photo(tr::now),
parent()->clipboardText());
} }
bool MediaPhoto::allowsEditCaption() const { bool MediaPhoto::allowsEditCaption() const {
@ -1072,42 +1077,9 @@ QString MediaFile::pinnedTextSubstring() const {
} }
TextForMimeData MediaFile::clipboardText() const { TextForMimeData MediaFile::clipboardText() const {
const auto attachType = [&] {
const auto name = Ui::Text::FormatSongNameFor(_document).string();
const auto addName = !name.isEmpty()
? u" : "_q + name
: QString();
if (const auto sticker = _document->sticker()) {
if (!_emoji.isEmpty()) {
return tr::lng_in_dlg_sticker_emoji(
tr::now,
lt_emoji,
_emoji);
}
return tr::lng_in_dlg_sticker(tr::now);
} else if (_document->isAnimation()) {
if (_document->isVideoMessage()) {
const auto media = parent()->media();
return (media && media->ttlSeconds())
? tr::lng_in_dlg_video_message_ttl(tr::now)
: tr::lng_in_dlg_video_message(tr::now);
}
return u"GIF"_q;
} else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now);
} else if (_document->isVoiceMessage()) {
const auto media = parent()->media();
return ((media && media->ttlSeconds())
? tr::lng_in_dlg_voice_message_ttl
: tr::lng_in_dlg_audio)(tr::now) + addName;;
} else if (_document->isSong()) {
return tr::lng_in_dlg_audio_file(tr::now) + addName;
}
return tr::lng_in_dlg_file(tr::now) + addName;
}();
auto caption = parent()->clipboardText(); auto caption = parent()->clipboardText();
if (_document->isVoiceMessage()) { if (_document->isVoiceMessage() || _document->isVideoMessage()) {
const auto &entry = _document->session().api().transcribes().entry( const auto &entry = _document->session().api().transcribes().entry(
parent()); parent());
if (!entry.requestId if (!entry.requestId
@ -1115,17 +1087,18 @@ TextForMimeData MediaFile::clipboardText() const {
&& !entry.toolong && !entry.toolong
&& !entry.failed && !entry.failed
&& (entry.pending || !entry.result.isEmpty())) { && (entry.pending || !entry.result.isEmpty())) {
const auto text = "{{\n" const auto hasCaption = !caption.rich.text.isEmpty();
const auto text = (hasCaption ? "{{\n" : "")
+ entry.result + entry.result
+ (entry.result.isEmpty() ? "" : " ") + (entry.result.isEmpty() ? "" : " ")
+ (entry.pending ? "[...]" : "") + (entry.pending ? "[...]" : "")
+ "\n}}" + (hasCaption ? "\n}}\n" : "");
+ (caption.rich.text.isEmpty() ? "" : "\n"); caption = TextForMimeData{ text, { text } }.append(
caption = TextForMimeData{ text, { text } }.append(std::move(caption)); std::move(caption));
} }
} }
return WithCaptionClipboardText(attachType, std::move(caption)); return caption;
} }
bool MediaFile::allowsEditCaption() const { bool MediaFile::allowsEditCaption() const {

View file

@ -709,10 +709,6 @@ private:
}; };
[[nodiscard]] TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption);
[[nodiscard]] Invoice ComputeInvoiceData( [[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data); const MTPDmessageMediaInvoice &data);

View file

@ -164,7 +164,7 @@ struct FullReplyTo {
int quoteOffset = 0; int quoteOffset = 0;
[[nodiscard]] bool valid() const { [[nodiscard]] bool valid() const {
return messageId || (storyId && peerIsUser(storyId.peer)); return messageId || (storyId && storyId.peer);
} }
explicit operator bool() const { explicit operator bool() const {
return valid(); return valid();

View file

@ -1122,7 +1122,8 @@ Data::RestrictionCheckResult PeerData::amRestricted(
: ChatRestrictions(0)); : ChatRestrictions(0));
return (channel->amCreator() || allowByAdminRights(right, channel)) return (channel->amCreator() || allowByAdminRights(right, channel))
? Result::Allowed() ? Result::Allowed()
: (defaultRestrictions & right) : ((defaultRestrictions & right)
&& !channel->unrestrictedByBoosts())
? Result::WithEveryone() ? Result::WithEveryone()
: (channel->restrictions() & right) : (channel->restrictions() & right)
? Result::Explicit() ? Result::Explicit()

View file

@ -271,11 +271,13 @@ inline auto DefaultRestrictionValue(
AdminRightValue( AdminRightValue(
channel, channel,
ChatAdminRight::PostMessages), ChatAdminRight::PostMessages),
channel->unrestrictedByBoostsValue(),
RestrictionsValue(channel, rights), RestrictionsValue(channel, rights),
DefaultRestrictionsValue(channel, rights), DefaultRestrictionsValue(channel, rights),
[=]( [=](
ChannelDataFlags flags, ChannelDataFlags flags,
bool postMessagesRight, bool postMessagesRight,
bool unrestrictedByBoosts,
ChatRestrictions sendRestriction, ChatRestrictions sendRestriction,
ChatRestrictions defaultSendRestriction) { ChatRestrictions defaultSendRestriction) {
const auto notAmInFlags = Flag::Left | Flag::Forbidden; const auto notAmInFlags = Flag::Left | Flag::Forbidden;
@ -285,7 +287,7 @@ inline auto DefaultRestrictionValue(
|| ((flags & Flag::HasLink) || ((flags & Flag::HasLink)
&& !(flags & Flag::JoinToWrite)); && !(flags & Flag::JoinToWrite));
const auto restricted = sendRestriction const auto restricted = sendRestriction
| defaultSendRestriction; | (defaultSendRestriction && !unrestrictedByBoosts);
return allowed return allowed
&& !forumRestriction && !forumRestriction
&& (postMessagesRight && (postMessagesRight

View file

@ -67,6 +67,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled), MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
data.vid(), data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
MTPint(), // from_boosts_applied
data.vpeer_id(), data.vpeer_id(),
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(), data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
MTP_flags(flags), MTP_flags(flags),
update.vid(), update.vid(),
peerToMTP(local->from()->id), peerToMTP(local->from()->id),
MTPint(), // from_boosts_applied
peerToMTP(history->peer->id), peerToMTP(history->peer->id),
MTPPeer(), // saved_peer_id MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(), MTPMessageFwdHeader(),

View file

@ -245,6 +245,12 @@ Session::Session(not_null<Main::Session*> session)
, _bigFileCache(Core::App().databases().get( , _bigFileCache(Core::App().databases().get(
_session->local().cacheBigFilePath(), _session->local().cacheBigFilePath(),
_session->local().cacheBigFileSettings())) _session->local().cacheBigFileSettings()))
, _groupFreeTranscribeLevel(session->account().appConfig().value(
) | rpl::map([=] {
return session->account().appConfig().get<int>(
u"group_transcribe_level_min"_q,
6);
}))
, _chatsList( , _chatsList(
session, session,
FilterId(), FilterId(),
@ -2189,8 +2195,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
// We always use premium limit in the MainList limit producer, // We always use premium limit in the MainList limit producer,
// because it slices the list to that limit. We don't want to slice // because it slices the list to that limit. We don't want to slice
// premium-ly added chats from the pinned list because of sync issues. // premium-ly added chats from the pinned list because of sync issues.
return rpl::single(rpl::empty_value()) | rpl::then( return _session->account().appConfig().value(
_session->account().appConfig().refreshed()
) | rpl::map([=] { ) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session); const auto limits = Data::PremiumLimits(_session);
return folder return folder
@ -2205,8 +2210,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
// We always use premium limit in the MainList limit producer, // We always use premium limit in the MainList limit producer,
// because it slices the list to that limit. We don't want to slice // because it slices the list to that limit. We don't want to slice
// premium-ly added chats from the pinned list because of sync issues. // premium-ly added chats from the pinned list because of sync issues.
return rpl::single(rpl::empty_value()) | rpl::then( return _session->account().appConfig().value(
_session->account().appConfig().refreshed()
) | rpl::map([=] { ) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session); const auto limits = Data::PremiumLimits(_session);
return limits.dialogFiltersChatsPremium(); return limits.dialogFiltersChatsPremium();
@ -2215,8 +2219,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
rpl::producer<int> Session::maxPinnedChatsLimitValue( rpl::producer<int> Session::maxPinnedChatsLimitValue(
not_null<Data::Forum*> forum) const { not_null<Data::Forum*> forum) const {
return rpl::single(rpl::empty_value()) | rpl::then( return _session->account().appConfig().value(
_session->account().appConfig().refreshed()
) | rpl::map([=] { ) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session); const auto limits = Data::PremiumLimits(_session);
return limits.topicsPinnedCurrent(); return limits.topicsPinnedCurrent();
@ -2229,14 +2232,17 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
// We always use premium limit in the MainList limit producer, // We always use premium limit in the MainList limit producer,
// because it slices the list to that limit. We don't want to slice // because it slices the list to that limit. We don't want to slice
// premium-ly added chats from the pinned list because of sync issues. // premium-ly added chats from the pinned list because of sync issues.
return rpl::single(rpl::empty_value()) | rpl::then( return _session->account().appConfig().value(
_session->account().appConfig().refreshed()
) | rpl::map([=] { ) | rpl::map([=] {
const auto limits = Data::PremiumLimits(_session); const auto limits = Data::PremiumLimits(_session);
return limits.savedSublistsPinnedPremium(); return limits.savedSublistsPinnedPremium();
}); });
} }
int Session::groupFreeTranscribeLevel() const {
return _groupFreeTranscribeLevel.current();
}
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder( const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
Data::Folder *folder) const { Data::Folder *folder) const {
return chatsList(folder)->pinned()->order(); return chatsList(folder)->pinned()->order();
@ -4518,6 +4524,7 @@ void Session::insertCheckedServiceNotification(
MTP_flags(flags), MTP_flags(flags),
MTP_int(0), // Not used (would've been trimmed to 32 bits). MTP_int(0), // Not used (would've been trimmed to 32 bits).
peerToMTP(PeerData::kServiceNotificationsId), peerToMTP(PeerData::kServiceNotificationsId),
MTPint(), // from_boosts_applied
peerToMTP(PeerData::kServiceNotificationsId), peerToMTP(PeerData::kServiceNotificationsId),
MTPPeer(), // saved_peer_id MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(), MTPMessageFwdHeader(),

View file

@ -367,6 +367,7 @@ public:
not_null<Forum*> forum) const; not_null<Forum*> forum) const;
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue( [[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
not_null<SavedMessages*> saved) const; not_null<SavedMessages*> saved) const;
[[nodiscard]] int groupFreeTranscribeLevel() const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
Folder *folder) const; Folder *folder) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
@ -887,6 +888,7 @@ private:
QPointer<Ui::BoxContent> _exportSuggestion; QPointer<Ui::BoxContent> _exportSuggestion;
rpl::variable<bool> _contactsLoaded = false; rpl::variable<bool> _contactsLoaded = false;
rpl::variable<int> _groupFreeTranscribeLevel;
rpl::event_stream<Folder*> _chatsListLoadedEvents; rpl::event_stream<Folder*> _chatsListLoadedEvents;
rpl::event_stream<Folder*> _chatsListChanged; rpl::event_stream<Folder*> _chatsListChanged;
rpl::event_stream<not_null<UserData*>> _userIsBotChanges; rpl::event_stream<not_null<UserData*>> _userIsBotChanges;

View file

@ -164,6 +164,15 @@ using UpdateFlag = StoryUpdate::Flag;
return false; return false;
} }
[[nodiscard]] PeerData *FromPeer(
not_null<Session*> owner,
const MTPDstoryItem &data) {
if (const auto from = data.vfrom_id()) {
return owner->peer(peerFromMTP(*from));
}
return nullptr;
}
} // namespace } // namespace
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
@ -278,6 +287,7 @@ Story::Story(
, _repostSourcePeer(RepostSourcePeer(&peer->owner(), data)) , _repostSourcePeer(RepostSourcePeer(&peer->owner(), data))
, _repostSourceName(RepostSourceName(data)) , _repostSourceName(RepostSourceName(data))
, _repostSourceId(RepostSourceId(data)) , _repostSourceId(RepostSourceId(data))
, _fromPeer(FromPeer(&peer->owner(), data))
, _date(data.vdate().v) , _date(data.vdate().v)
, _expires(data.vexpire_date().v) , _expires(data.vexpire_date().v)
, _repostModified(RepostModified(data)) { , _repostModified(RepostModified(data)) {
@ -890,6 +900,10 @@ StoryId Story::repostSourceId() const {
return _repostSourceId; return _repostSourceId;
} }
PeerData *Story::fromPeer() const {
return _fromPeer;
}
StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done) StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done)
: _story(story) : _story(story)
, _done(std::move(done)) { , _done(std::move(done)) {

View file

@ -208,6 +208,8 @@ public:
[[nodiscard]] QString repostSourceName() const; [[nodiscard]] QString repostSourceName() const;
[[nodiscard]] StoryId repostSourceId() const; [[nodiscard]] StoryId repostSourceId() const;
[[nodiscard]] PeerData *fromPeer() const;
private: private:
struct ViewsCounts { struct ViewsCounts {
int views = 0; int views = 0;
@ -234,6 +236,7 @@ private:
PeerData * const _repostSourcePeer = nullptr; PeerData * const _repostSourcePeer = nullptr;
const QString _repostSourceName; const QString _repostSourceName;
const StoryId _repostSourceId = 0; const StoryId _repostSourceId = 0;
PeerData * const _fromPeer = nullptr;
Data::ReactionId _sentReactionId; Data::ReactionId _sentReactionId;
StoryMedia _media; StoryMedia _media;
TextWithEntities _caption; TextWithEntities _caption;

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_channel.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
@ -18,14 +19,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "lottie/lottie_common.h" #include "lottie/lottie_common.h"
#include "lottie/lottie_frame_generator.h" #include "lottie/lottie_frame_generator.h"
#include "ffmpeg/ffmpeg_frame_generator.h" #include "ffmpeg/ffmpeg_frame_generator.h"
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "storage/file_download.h" // kMaxFileInMemory #include "storage/file_download.h" // kMaxFileInMemory
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/text/custom_emoji_instance.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -93,6 +98,10 @@ private:
return u"internal:"_q; return u"internal:"_q;
} }
[[nodiscard]] QString UserpicEmojiPrefix() {
return u"userpic:"_q;
}
[[nodiscard]] QString InternalPadding(QMargins value) { [[nodiscard]] QString InternalPadding(QMargins value) {
return value.isNull() ? QString() : QString(",%1,%2,%3,%4" return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
).arg(value.left() ).arg(value.left()
@ -527,6 +536,10 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
int sizeOverride) { int sizeOverride) {
if (data.startsWith(InternalPrefix())) { if (data.startsWith(InternalPrefix())) {
return internal(data); return internal(data);
} else if (data.startsWith(UserpicEmojiPrefix())) {
const auto ratio = style::DevicePixelRatio();
const auto size = EmojiSizeFromTag(tag) / ratio;
return userpic(data, std::move(update), size);
} }
const auto parsed = ParseCustomEmojiData(data); const auto parsed = ParseCustomEmojiData(data);
return parsed return parsed
@ -574,6 +587,26 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::internal(
info.textColor); info.textColor);
} }
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::userpic(
QStringView data,
Fn<void()> update,
int size) {
const auto v = data.mid(UserpicEmojiPrefix().size()).split(',');
if (v.size() != 5 && v.size() != 1) {
return nullptr;
}
const auto id = PeerId(v[0].toULongLong());
const auto padding = (v.size() == 5)
? QMargins(v[1].toInt(), v[2].toInt(), v[3].toInt(), v[4].toInt())
: QMargins();
return std::make_unique<Ui::CustomEmoji::DynamicImageEmoji>(
data.toString(),
Ui::MakeUserpicThumbnail(_owner->peer(id)),
std::move(update),
padding,
size);
}
void CustomEmojiManager::resolve( void CustomEmojiManager::resolve(
QStringView data, QStringView data,
not_null<Listener*> listener) { not_null<Listener*> listener) {
@ -954,6 +987,14 @@ QString CustomEmojiManager::registerInternalEmoji(
return result + InternalPadding(padding); return result + InternalPadding(padding);
} }
[[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData(
not_null<PeerData*> peer,
QMargins padding) {
return UserpicEmojiPrefix()
+ QString::number(peer->id.value)
+ InternalPadding(padding);
}
int FrameSizeFromTag(SizeTag tag) { int FrameSizeFromTag(SizeTag tag) {
const auto emoji = EmojiSizeFromTag(tag); const auto emoji = EmojiSizeFromTag(tag);
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
@ -980,8 +1021,21 @@ TextWithEntities SingleCustomEmoji(not_null<DocumentData*> document) {
return SingleCustomEmoji(document->id); return SingleCustomEmoji(document->id);
} }
bool AllowEmojiWithoutPremium(not_null<PeerData*> peer) { bool AllowEmojiWithoutPremium(
return peer->isSelf(); not_null<PeerData*> peer,
DocumentData *exactEmoji) {
if (peer->isSelf()) {
return true;
} else if (!exactEmoji) {
return false;
} else if (const auto sticker = exactEmoji->sticker()) {
if (const auto channel = peer->asMegagroup()) {
if (channel->mgInfo->emojiSet.id == sticker->set.id) {
return (sticker->set.id != 0);
}
}
}
return false;
} }
void InsertCustomEmoji( void InsertCustomEmoji(

View file

@ -92,6 +92,10 @@ public:
QMargins padding = {}, QMargins padding = {},
bool textColor = true); bool textColor = true);
[[nodiscard]] QString peerUserpicEmojiData(
not_null<PeerData*> peer,
QMargins padding = {});
[[nodiscard]] uint64 coloredSetId() const; [[nodiscard]] uint64 coloredSetId() const;
private: private:
@ -146,6 +150,10 @@ private:
LoaderFactory factory); LoaderFactory factory);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> internal( [[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> internal(
QStringView data); QStringView data);
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> userpic(
QStringView data,
Fn<void()> update,
int size);
[[nodiscard]] static int SizeIndex(SizeTag tag); [[nodiscard]] static int SizeIndex(SizeTag tag);
const not_null<Session*> _owner; const not_null<Session*> _owner;
@ -201,7 +209,9 @@ private:
[[nodiscard]] TextWithEntities SingleCustomEmoji( [[nodiscard]] TextWithEntities SingleCustomEmoji(
not_null<DocumentData*> document); not_null<DocumentData*> document);
[[nodiscard]] bool AllowEmojiWithoutPremium(not_null<PeerData*> peer); [[nodiscard]] bool AllowEmojiWithoutPremium(
not_null<PeerData*> peer,
DocumentData *exactEmoji = nullptr);
void InsertCustomEmoji( void InsertCustomEmoji(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,

View file

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_three_state_icon.h" #include "dialogs/dialogs_three_state_icon.h"
#include "dialogs/ui/dialogs_layout.h" #include "dialogs/ui/dialogs_layout.h"
#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
#include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_video_userpic.h"
#include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_indexed_list.h"
#include "dialogs/dialogs_widget.h" #include "dialogs/dialogs_widget.h"

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