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
2
.github/workflows/linux.yml
vendored
|
@ -85,7 +85,7 @@ jobs:
|
|||
|
||||
docker run --rm \
|
||||
-v $PWD:/usr/src/tdesktop \
|
||||
-e DEBUG=1 \
|
||||
-e CONFIG=Debug \
|
||||
tdesktop:centos_env \
|
||||
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
|
||||
-D CMAKE_C_FLAGS_DEBUG="" \
|
||||
|
|
2
.github/workflows/mac.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
|
||||
macos:
|
||||
name: MacOS
|
||||
runs-on: macos-12
|
||||
runs-on: macos-13
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
|
@ -744,8 +744,6 @@ PRIVATE
|
|||
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.h
|
||||
history/view/controls/history_view_voice_record_button.cpp
|
||||
history/view/controls/history_view_voice_record_button.h
|
||||
history/view/controls/history_view_webpage_processor.cpp
|
||||
history/view/controls/history_view_webpage_processor.h
|
||||
history/view/media/history_view_call.cpp
|
||||
|
@ -1487,6 +1485,8 @@ PRIVATE
|
|||
ui/widgets/level_meter.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
ui/dynamic_thumbnails.h
|
||||
ui/filter_icons.cpp
|
||||
ui/filter_icons.h
|
||||
ui/filter_icon_panel.cpp
|
||||
|
|
BIN
Telegram/Resources/animations/palette.tgs
Normal file
After Width: | Height: | Size: 808 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 732 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 816 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 538 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 790 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 821 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 667 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/settings/premium/lastseen.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
Telegram/Resources/icons/settings/premium/lastseen@2x.png
Normal file
After Width: | Height: | Size: 1,015 B |
BIN
Telegram/Resources/icons/settings/premium/lastseen@3x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/settings/premium/privacy.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
Telegram/Resources/icons/settings/premium/privacy@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/settings/premium/privacy@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/stories/boosts_mini.png
Normal file
After Width: | Height: | Size: 374 B |
BIN
Telegram/Resources/icons/stories/boosts_mini@2x.png
Normal file
After Width: | Height: | Size: 690 B |
BIN
Telegram/Resources/icons/stories/boosts_mini@3x.png
Normal file
After Width: | Height: | Size: 1 KiB |
|
@ -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_other_info" = "{user} will be able to apply 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_apply2" = "Enjoy the view.";
|
||||
"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_both" = "Apply for me and {user}";
|
||||
"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" = "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_me_unavailable" = "The story where you mentioned {user} is no longer available.";
|
||||
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
|
||||
"lng_action_giveaway_started_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_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_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_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_view_all" = "View all";
|
||||
|
@ -1983,6 +1988,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"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_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_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_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_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_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
|
||||
"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_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_group_button" = "Boost Group";
|
||||
"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#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_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_first_group" = "Enable stories for group";
|
||||
"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_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_more_group" = "Help upgrade group";
|
||||
//"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_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#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#other" = "This channel needs **{count}** more boosts\nto be able to {post}.";
|
||||
//"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_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_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#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#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_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_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.";
|
||||
|
@ -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_error_already_title" = "Already Boosted!";
|
||||
"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_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_yes" = "Yes";
|
||||
"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_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
|
||||
"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_channels#one" = "{count} channel";
|
||||
"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_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_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_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_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_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_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_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_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
"lng_boost_channel_gifting" = "Boost your channel by gifting your subscribers Telegram Premium. {link}";
|
||||
"lng_boost_channel_gifting_link" = "Get boosts >";
|
||||
|
||||
"lng_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_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_subtitle" = "winners are chosen randomly";
|
||||
"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_this#one" = "this channel will receive {count} boost";
|
||||
"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_about" = "Choose the channels the users need to join to take part in the giveaway.";
|
||||
"lng_giveaway_users_title" = "Users eligible for the giveaway";
|
||||
"lng_giveaway_users_all" = "All subscribers";
|
||||
"lng_giveaway_users_all_group" = "All members";
|
||||
"lng_giveaway_users_from_all_countries" = "from all countries";
|
||||
"lng_giveaway_users_from_one_country" = "from {country}";
|
||||
"lng_giveaway_users_from_countries#one" = "from {count} country";
|
||||
"lng_giveaway_users_from_countries#other" = "from {count} countries";
|
||||
"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_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_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_date_title" = "Date when giveaway ends";
|
||||
"lng_giveaway_date" = "Date and Time";
|
||||
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
|
||||
"lng_giveaway_date_about#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_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#other" = "Duration of Premium subscriptions";
|
||||
"lng_giveaway_duration_price" = "{price} x {amount}";
|
||||
"lng_giveaway_date_select" = "Select Date and Time";
|
||||
"lng_giveaway_date_confirm" = "Confirm";
|
||||
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
|
||||
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
|
||||
"lng_giveaway_recipients_save" = "Save Recipients";
|
||||
"lng_giveaway_recipients_deselect" = "Deselect All";
|
||||
"lng_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_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_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_prize_title" = "Congratulations!";
|
||||
|
@ -2301,8 +2374,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_prizes_participants" = "Participants";
|
||||
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
|
||||
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
|
||||
"lng_prizes_participants_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#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_and_one" = "{countries}, {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_end_text" = "This giveaway was sponsored by {admins}.";
|
||||
"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers";
|
||||
"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers.";
|
||||
"lng_prizes_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#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_end_when_finish" = "On {date}, Telegram automatically selected {winners}.";
|
||||
"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link.";
|
||||
"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links.";
|
||||
"lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}";
|
||||
"lng_prizes_winners_all_of_one#one" = "{count} random subscriber of {channel}";
|
||||
"lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}";
|
||||
"lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels";
|
||||
"lng_prizes_winners_all_of_many#other" = "{count} random subscribers of {channel} and other listed channels";
|
||||
"lng_prizes_winners_all_of_one_group#one" = "{count} random member of {channel}";
|
||||
"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#other" = "{count} random users that joined {channel} after {start_date}";
|
||||
"lng_prizes_winners_new_of_many#one" = "{count} random user that joined {channel} and other listed channels after {start_date}";
|
||||
"lng_prizes_winners_new_of_many#other" = "{count} random users that joined {channel} and other listed channels after {start_date}";
|
||||
"lng_prizes_how_participate_one" = "To take part in this giveaway please join channel {channel} before {date}.";
|
||||
"lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}.";
|
||||
"lng_prizes_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 groups and channels after {start_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} 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_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_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_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}.";
|
||||
"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels).";
|
||||
"lng_prizes_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} (and other listed groups and channels).";
|
||||
"lng_prizes_you_won" = "You won a prize in this giveaway {cup}";
|
||||
"lng_prizes_view_prize" = "View my prize";
|
||||
"lng_prizes_you_didnt" = "You didn't win a prize in this giveaway.";
|
||||
"lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||
"lng_prizes_cancelled_group" = "The channel cancelled the prizes by reversing the payment for them.";
|
||||
"lng_prizes_badge" = "x{amount}";
|
||||
|
||||
"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_archive_pack" = "Archive Stickers";
|
||||
"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_has_been_archived" = "Mask pack has been archived.";
|
||||
"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_send_anonymous_ph" = "Send anonymously...";
|
||||
"lng_story_reply_ph" = "Reply privately...";
|
||||
"lng_story_comment_ph" = "Comment story...";
|
||||
"lng_send_text_no" = "Text not allowed.";
|
||||
"lng_send_text_no_about" = "The admins of this group only allow sending {types}.";
|
||||
"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_hashtag" = "Copy Hashtag";
|
||||
"lng_context_copy_mention" = "Copy Username";
|
||||
"lng_context_copy_filename" = "Copy Filename";
|
||||
"lng_context_save_image" = "Save Image As...";
|
||||
"lng_context_copy_image" = "Copy Image";
|
||||
"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_delete_gif" = "Delete GIF";
|
||||
"lng_context_open_channel" = "Open Channel";
|
||||
"lng_context_open_group" = "Open Group";
|
||||
"lng_context_attached_stickers" = "Attached Stickers";
|
||||
"lng_context_to_msg" = "Go To Message";
|
||||
"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_wallpaper" = "Channel wallpaper";
|
||||
"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_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_confirm_contact_data" = "New Contact";
|
||||
"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_minutes#one" = "every {count} minute";
|
||||
"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_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_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_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_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_set" = "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_removed_linked_chat" = "{from} removed the discussion group";
|
||||
"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_existing" = "Existing boosts";
|
||||
"lng_boosts_premium_audience" = "Premium subscribers";
|
||||
"lng_boosts_premium_members" = "Premium members";
|
||||
"lng_boosts_next_level" = "Boosts to level up";
|
||||
"lng_boosts_list_title#one" = "{count} Boost";
|
||||
"lng_boosts_list_title#other" = "{count} Boosts";
|
||||
"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#other" = "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_link_title" = "Link for boosting";
|
||||
"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_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_pending" = "To be distributed";
|
||||
"lng_boosts_list_pending_about" = "The recipient will be selected when the giveaway ends.";
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
<file alias="stats.tgs">../../animations/stats.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="palette.tgs">../../animations/palette.tgs</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.14.13.0" />
|
||||
Version="4.15.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,14,13,0
|
||||
PRODUCTVERSION 4,14,13,0
|
||||
FILEVERSION 4,15,0,0
|
||||
PRODUCTVERSION 4,15,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "4.14.13.0"
|
||||
VALUE "FileVersion", "4.15.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.14.13.0"
|
||||
VALUE "ProductVersion", "4.15.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,14,13,0
|
||||
PRODUCTVERSION 4,14,13,0
|
||||
FILEVERSION 4,15,0,0
|
||||
PRODUCTVERSION 4,15,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "4.14.13.0"
|
||||
VALUE "FileVersion", "4.15.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "4.14.13.0"
|
||||
VALUE "ProductVersion", "4.15.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -55,19 +55,42 @@ rpl::producer<std::vector<uint8>> PeerColors::suggestedValue() const {
|
|||
|
||||
auto PeerColors::indicesValue() const
|
||||
-> rpl::producer<Ui::ColorIndicesCompressed> {
|
||||
return rpl::single(_colorIndicesCurrent
|
||||
? *_colorIndicesCurrent
|
||||
: Ui::ColorIndicesCompressed()
|
||||
return rpl::single(
|
||||
indicesCurrent()
|
||||
) | 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) {
|
||||
return 0;
|
||||
} else if (const auto i = _requiredLevels.find(index)
|
||||
; i != end(_requiredLevels)) {
|
||||
} else if (const auto i = _requiredLevelsGroup.find(index)
|
||||
; 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 1;
|
||||
|
@ -100,7 +123,8 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
|||
};
|
||||
|
||||
const auto &list = data.vcolors().v;
|
||||
_requiredLevels.clear();
|
||||
_requiredLevelsGroup.clear();
|
||||
_requiredLevelsChannel.clear();
|
||||
suggested.reserve(list.size());
|
||||
for (const auto &color : list) {
|
||||
const auto &data = color.data();
|
||||
|
@ -110,8 +134,11 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
|
|||
continue;
|
||||
}
|
||||
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()) {
|
||||
_requiredLevels[colorIndex] = min->v;
|
||||
_requiredLevelsChannel[colorIndex] = min->v;
|
||||
}
|
||||
if (!data.is_hidden()) {
|
||||
suggested.push_back(colorIndex);
|
||||
|
|
|
@ -25,10 +25,19 @@ public:
|
|||
|
||||
[[nodiscard]] std::vector<uint8> suggested() const;
|
||||
[[nodiscard]] rpl::producer<std::vector<uint8>> suggestedValue() const;
|
||||
[[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const;
|
||||
[[nodiscard]] auto indicesValue() const
|
||||
-> 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,
|
||||
uint8 index) const;
|
||||
|
||||
|
@ -42,7 +51,8 @@ private:
|
|||
mtpRequestId _requestId = 0;
|
||||
base::Timer _timer;
|
||||
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;
|
||||
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
|
||||
|
||||
|
|
|
@ -604,7 +604,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
|||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
const auto channel = _peer->asChannel();
|
||||
if (!channel || channel->isMegagroup()) {
|
||||
if (!channel) {
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
|
@ -628,6 +628,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
|
|||
|
||||
const auto slots = data.vmy_boost_slots();
|
||||
_boostStatus.overview = Data::BoostsOverview{
|
||||
.group = channel->isMegagroup(),
|
||||
.mine = slots ? int(slots->v.size()) : 0,
|
||||
.level = std::max(data.vlevel().v, 0),
|
||||
.boostCount = std::max(
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_transcribes.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
|
@ -25,6 +26,14 @@ Transcribes::Transcribes(not_null<ApiWrap*> api)
|
|||
, _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() {
|
||||
if (!_trialsSupport) {
|
||||
const auto count = _session->account().appConfig().get<int>(
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
|
||||
void apply(const MTPDupdateTranscribedAudio &update);
|
||||
|
||||
[[nodiscard]] bool freeFor(not_null<HistoryItem*> item) const;
|
||||
|
||||
[[nodiscard]] bool trialsSupport();
|
||||
[[nodiscard]] TimeId trialsRefreshAt();
|
||||
[[nodiscard]] int trialsCount();
|
||||
|
|
|
@ -1122,6 +1122,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
|||
(d.is_out()
|
||||
? peerToMTP(_session->userPeerId())
|
||||
: MTP_peerUser(d.vuser_id())),
|
||||
MTPint(), // from_boosts_applied
|
||||
MTP_peerUser(d.vuser_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
|
@ -1154,6 +1155,7 @@ void Updates::applyUpdatesNoPtsCheck(const MTPUpdates &updates) {
|
|||
MTP_flags(flags),
|
||||
d.vid(),
|
||||
MTP_peerUser(d.vfrom_id()),
|
||||
MTPint(), // from_boosts_applied
|
||||
MTP_peerChat(d.vchat_id()),
|
||||
MTPPeer(), // saved_peer_id
|
||||
d.vfwd_from() ? *d.vfwd_from() : MTPMessageFwdHeader(),
|
||||
|
|
|
@ -2638,6 +2638,22 @@ void ApiWrap::setGroupStickerSet(
|
|||
_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(
|
||||
const QString &key) {
|
||||
const auto it = _stickersByEmoji.find(key);
|
||||
|
|
|
@ -246,6 +246,9 @@ public:
|
|||
void setGroupStickerSet(
|
||||
not_null<ChannelData*> megagroup,
|
||||
const StickerSetIdentifier &set);
|
||||
void setGroupEmojiSet(
|
||||
not_null<ChannelData*> megagroup,
|
||||
const StickerSetIdentifier &set);
|
||||
[[nodiscard]] std::vector<not_null<DocumentData*>> *stickersByEmoji(
|
||||
const QString &key);
|
||||
|
||||
|
|
|
@ -467,6 +467,10 @@ bool BackgroundPreviewBox::forChannel() const {
|
|||
return _forPeer && _forPeer->isChannel();
|
||||
}
|
||||
|
||||
bool BackgroundPreviewBox::forGroup() const {
|
||||
return forChannel() && _forPeer->isMegagroup();
|
||||
}
|
||||
|
||||
void BackgroundPreviewBox::generateBackground() {
|
||||
if (_paper.backgroundColors().empty()) {
|
||||
return;
|
||||
|
@ -492,7 +496,9 @@ void BackgroundPreviewBox::resetTitle() {
|
|||
|
||||
void BackgroundPreviewBox::rebuildButtons(bool dark) {
|
||||
clearButtons();
|
||||
addButton(forChannel()
|
||||
addButton(forGroup()
|
||||
? tr::lng_background_apply_group()
|
||||
: forChannel()
|
||||
? tr::lng_background_apply_channel()
|
||||
: _forPeer
|
||||
? tr::lng_background_apply_button()
|
||||
|
@ -708,7 +714,7 @@ void BackgroundPreviewBox::checkLevelForChannel() {
|
|||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
return std::make_optional(Ui::AskBoostReason{
|
||||
Ui::AskBoostWallpaper{ required }
|
||||
Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()}
|
||||
});
|
||||
}, [=] { _forPeerLevelCheck = false; });
|
||||
}
|
||||
|
@ -1083,7 +1089,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
|
|||
_service = GenerateServiceItem(
|
||||
delegate(),
|
||||
_serviceHistory,
|
||||
(forChannel()
|
||||
(forGroup()
|
||||
? tr::lng_background_other_group(tr::now)
|
||||
: forChannel()
|
||||
? tr::lng_background_other_channel(tr::now)
|
||||
: (_forPeer && !_fromMessageId)
|
||||
? tr::lng_background_other_info(
|
||||
|
|
|
@ -95,6 +95,7 @@ private:
|
|||
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
|
||||
|
||||
[[nodiscard]] bool forChannel() const;
|
||||
[[nodiscard]] bool forGroup() const;
|
||||
void checkLevelForChannel();
|
||||
|
||||
void recreate(bool dark);
|
||||
|
|
|
@ -791,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
|
|||
slowmodeLabel: LabelSimple(defaultLabelSimple) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) {
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
customBadgeField: InputField(defaultInputField) {
|
||||
textMargins: margins(2px, 7px, 2px, 0px);
|
||||
|
|
|
@ -470,8 +470,8 @@ void EditCaptionBox::rebuildPreview() {
|
|||
|
||||
void EditCaptionBox::setupField() {
|
||||
const auto peer = _historyItem->history()->peer;
|
||||
const auto allow = [=](const auto&) {
|
||||
return Data::AllowEmojiWithoutPremium(peer);
|
||||
const auto allow = [=](not_null<DocumentData*> emoji) {
|
||||
return Data::AllowEmojiWithoutPremium(peer, emoji);
|
||||
};
|
||||
InitMessageFieldHandlers(
|
||||
_controller,
|
||||
|
|
|
@ -1363,20 +1363,26 @@ void GiveawayInfoBox(
|
|||
? start->quantity
|
||||
: (results->winnersCount + results->unclaimedCount);
|
||||
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
|
||||
? tr::lng_prizes_end_text
|
||||
: tr::lng_prizes_how_text)(
|
||||
tr::now,
|
||||
lt_admins,
|
||||
tr::lng_prizes_admins(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
(group
|
||||
? tr::lng_prizes_admins_group
|
||||
: tr::lng_prizes_admins)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
quantity,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_duration,
|
||||
TextWithEntities{ GiftDuration(months) },
|
||||
Ui::Text::RichLangValue),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto many = start
|
||||
? (start->channels.size() > 1)
|
||||
|
@ -1387,8 +1393,12 @@ void GiveawayInfoBox(
|
|||
const auto all = start ? start->all : results->all;
|
||||
auto winners = all
|
||||
? (many
|
||||
? tr::lng_prizes_winners_all_of_many
|
||||
: tr::lng_prizes_winners_all_of_one)(
|
||||
? (group
|
||||
? 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,
|
||||
lt_count,
|
||||
count,
|
||||
|
@ -1411,15 +1421,17 @@ void GiveawayInfoBox(
|
|||
? results->additionalPrize
|
||||
: start->additionalPrize;
|
||||
if (!additionalPrize.isEmpty()) {
|
||||
text.append("\n\n").append(tr::lng_prizes_additional_added(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_prize,
|
||||
TextWithEntities{ additionalPrize },
|
||||
Ui::Text::RichLangValue));
|
||||
text.append("\n\n").append((group
|
||||
? tr::lng_prizes_additional_added_group
|
||||
: tr::lng_prizes_additional_added)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
count,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(first),
|
||||
lt_prize,
|
||||
TextWithEntities{ additionalPrize },
|
||||
Ui::Text::RichLangValue));
|
||||
}
|
||||
const auto untilDate = start
|
||||
? start->untilDate
|
||||
|
@ -1448,18 +1460,25 @@ void GiveawayInfoBox(
|
|||
if (info.adminChannelId) {
|
||||
const auto channel = controller->session().data().channel(
|
||||
info.adminChannelId);
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(channel->name()),
|
||||
Ui::Text::RichLangValue));
|
||||
text.append("\n\n").append((channel->isMegagroup()
|
||||
? tr::lng_prizes_how_no_admin_group
|
||||
: tr::lng_prizes_how_no_admin)(
|
||||
tr::now,
|
||||
lt_channel,
|
||||
Ui::Text::Bold(channel->name()),
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (info.tooEarlyDate) {
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
|
||||
Ui::Text::RichLangValue));
|
||||
const auto channel = controller->session().data().channel(
|
||||
info.adminChannelId);
|
||||
text.append("\n\n").append((channel->isMegagroup()
|
||||
? tr::lng_prizes_how_no_joined_group
|
||||
: tr::lng_prizes_how_no_joined)(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::Text::Bold(
|
||||
langDateTime(
|
||||
base::unixtime::parse(info.tooEarlyDate))),
|
||||
Ui::Text::RichLangValue));
|
||||
} else if (!info.disallowedCountry.isEmpty()) {
|
||||
text.append("\n\n").append(tr::lng_prizes_how_no_country(
|
||||
tr::now,
|
||||
|
@ -1499,7 +1518,9 @@ void GiveawayInfoBox(
|
|||
box.get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box.get(),
|
||||
tr::lng_prizes_cancelled(),
|
||||
(group
|
||||
? tr::lng_prizes_cancelled_group()
|
||||
: tr::lng_prizes_cancelled()),
|
||||
st::giveawayRefundedLabel),
|
||||
st::giveawayRefundedPadding),
|
||||
{ padding.left(), 0, padding.right(), padding.bottom() });
|
||||
|
|
|
@ -13,11 +13,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/unixtime.h"
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
#include "boxes/background_box.h"
|
||||
#include "boxes/stickers_box.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_channel.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.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/info_memento.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_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -478,6 +484,7 @@ void Set(
|
|||
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
|
||||
MTP_int(values.colorIndex),
|
||||
MTP_long(values.backgroundEmojiId)));
|
||||
} else if (peer->isMegagroup()) {
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
using Flag = MTPchannels_UpdateColor::Flag;
|
||||
send(MTPchannels_UpdateColor(
|
||||
|
@ -527,9 +534,13 @@ void Apply(
|
|||
} else {
|
||||
CheckBoostLevel(show, peer, [=](int level) {
|
||||
const auto peerColors = &peer->session().api().peerColors();
|
||||
const auto colorRequired = peerColors->requiredLevelFor(
|
||||
peer->id,
|
||||
values.colorIndex);
|
||||
const auto colorRequired = peer->isMegagroup()
|
||||
? peerColors->requiredGroupLevelFor(
|
||||
peer->id,
|
||||
values.colorIndex)
|
||||
: peerColors->requiredChannelLevelFor(
|
||||
peer->id,
|
||||
values.colorIndex);
|
||||
const auto iconRequired = values.backgroundEmojiId
|
||||
? session->account().appConfig().get<int>(
|
||||
"channel_bg_icon_level_min",
|
||||
|
@ -553,7 +564,10 @@ void Apply(
|
|||
}
|
||||
const auto reason = [&]() -> Ui::AskBoostReason {
|
||||
if (level < statusRequired) {
|
||||
return { Ui::AskBoostEmojiStatus{ statusRequired } };
|
||||
return { Ui::AskBoostEmojiStatus{
|
||||
statusRequired,
|
||||
peer->isMegagroup()
|
||||
} };
|
||||
} else if (level < iconRequired) {
|
||||
return { Ui::AskBoostChannelColor{ iconRequired } };
|
||||
}
|
||||
|
@ -670,6 +684,44 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
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(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
@ -677,22 +729,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
rpl::producer<uint8> colorIndexValue,
|
||||
rpl::producer<DocumentId> emojiIdValue,
|
||||
Fn<void(DocumentId)> emojiIdChosen) {
|
||||
const auto &basicSt = st::settingsButtonNoIcon;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto added = st::normalFont->spacew;
|
||||
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
|
||||
const auto noneWidth = added
|
||||
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
|
||||
const auto emojiWidth = added + emojiSize;
|
||||
const auto rightPadding = std::max(noneWidth, emojiWidth)
|
||||
+ basicSt.padding.right();
|
||||
const auto st = parent->lifetime().make_state<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
const auto button = ButtonStyleWithRightEmoji(parent);
|
||||
auto result = Settings::CreateButtonWithIcon(
|
||||
parent,
|
||||
tr::lng_settings_color_emoji(),
|
||||
*st);
|
||||
*button.st,
|
||||
{ &st::menuBlueIconColorNames });
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
|
@ -719,6 +761,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
}, right->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
const auto added = st::normalFont->spacew;
|
||||
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
|
||||
state->emojiId = emojiId;
|
||||
state->emoji = emojiId
|
||||
|
@ -727,7 +770,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
(emojiId ? emojiWidth : noneWidth) + added,
|
||||
(emojiId ? button.emojiWidth : button.noneWidth) + button.added,
|
||||
right->height());
|
||||
right->update();
|
||||
}, right->lifetime());
|
||||
|
@ -738,7 +781,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
) | rpl::start_with_next([=](QSize outer, int width) {
|
||||
right->resize(width, outer.height());
|
||||
const auto skip = st::settingsButton.padding.right();
|
||||
right->moveToRight(skip - added, 0, outer.width());
|
||||
right->moveToRight(skip - button.added, 0, outer.width());
|
||||
}, right->lifetime());
|
||||
|
||||
right->paintRequest(
|
||||
|
@ -752,7 +795,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
const auto colors = style->coloredValues(false, state->index);
|
||||
state->emoji->paint(p, {
|
||||
.textColor = colors.name,
|
||||
.position = QPoint(added, (height - emojiSize) / 2),
|
||||
.position = QPoint(added, (height - button.emojiWidth) / 2),
|
||||
.internal = {
|
||||
.forceFirstFrame = true,
|
||||
},
|
||||
|
@ -791,23 +834,16 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen) {
|
||||
const auto &basicSt = st::settingsButtonNoIcon;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto added = st::normalFont->spacew;
|
||||
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
|
||||
const auto noneWidth = added
|
||||
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
|
||||
const auto emojiWidth = added + emojiSize;
|
||||
const auto rightPadding = std::max(noneWidth, emojiWidth)
|
||||
+ basicSt.padding.right();
|
||||
const auto st = parent->lifetime().make_state<style::SettingsButton>(
|
||||
basicSt);
|
||||
st->padding.setRight(rightPadding);
|
||||
auto result = object_ptr<Ui::SettingsButton>(
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen,
|
||||
bool group) {
|
||||
const auto button = ButtonStyleWithRightEmoji(parent);
|
||||
auto result = Settings::CreateButtonWithIcon(
|
||||
parent,
|
||||
tr::lng_edit_channel_status(),
|
||||
*st);
|
||||
(group
|
||||
? tr::lng_edit_channel_status_group()
|
||||
: tr::lng_edit_channel_status()),
|
||||
*button.st,
|
||||
{ &st::menuBlueIconEmojiStatus });
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
|
@ -834,7 +870,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
(id ? emojiWidth : noneWidth) + added,
|
||||
(id ? button.emojiWidth : button.noneWidth) + button.added,
|
||||
right->height());
|
||||
right->update();
|
||||
}, right->lifetime());
|
||||
|
@ -845,7 +881,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
) | rpl::start_with_next([=](QSize outer, int width) {
|
||||
right->resize(width, outer.height());
|
||||
const auto skip = st::settingsButton.padding.right();
|
||||
right->moveToRight(skip - added, 0, outer.width());
|
||||
right->moveToRight(skip - button.added, 0, outer.width());
|
||||
}, right->lifetime());
|
||||
|
||||
right->paintRequest(
|
||||
|
@ -861,14 +897,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
st::stickerPanPremium1,
|
||||
st::stickerPanPremium2,
|
||||
0.5),
|
||||
.position = QPoint(added, (height - emojiSize) / 2),
|
||||
.position = QPoint(
|
||||
button.added,
|
||||
(height - button.emojiWidth) / 2),
|
||||
});
|
||||
} else {
|
||||
const auto &font = st::normalFont;
|
||||
p.setFont(font);
|
||||
p.setPen(st::windowActiveTextFg);
|
||||
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));
|
||||
}
|
||||
}, right->lifetime());
|
||||
|
@ -889,6 +929,116 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
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
|
||||
|
||||
void EditPeerColorBox(
|
||||
|
@ -897,7 +1047,12 @@ void EditPeerColorBox(
|
|||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Ui::ChatStyle> style,
|
||||
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);
|
||||
|
||||
struct State {
|
||||
|
@ -914,53 +1069,94 @@ void EditPeerColorBox(
|
|||
state->emojiId = peer->backgroundEmojiId();
|
||||
state->statusId = peer->emojiStatusId();
|
||||
|
||||
box->addRow(object_ptr<PreviewWrap>(
|
||||
box,
|
||||
style,
|
||||
theme,
|
||||
peer,
|
||||
state->index.value(),
|
||||
state->emojiId.value()
|
||||
), {});
|
||||
if (group) {
|
||||
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(
|
||||
box.get());
|
||||
const auto verticalLayout = box->verticalLayout()->add(
|
||||
object_ptr<Ui::VerticalLayout>(box.get()));
|
||||
|
||||
auto indices = peer->session().api().peerColors().suggestedValue();
|
||||
const auto margin = st::settingsColorRadioMargin;
|
||||
const auto skip = st::settingsColorRadioSkip;
|
||||
box->addRow(
|
||||
object_ptr<ColorSelector>(
|
||||
auto icon = CreateLottieIcon(
|
||||
verticalLayout,
|
||||
{
|
||||
.name = u"palette"_q,
|
||||
.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,
|
||||
style,
|
||||
std::move(indices),
|
||||
state->index.current(),
|
||||
[=](uint8 index) { state->index = index; }),
|
||||
{ margin, skip, margin, skip });
|
||||
theme,
|
||||
peer,
|
||||
state->index.value(),
|
||||
state->emojiId.value()
|
||||
), {});
|
||||
|
||||
const auto container = box->verticalLayout();
|
||||
Ui::AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_settings_color_about()
|
||||
: tr::lng_settings_color_about_channel());
|
||||
auto indices = peer->session().api().peerColors().suggestedValue();
|
||||
const auto margin = st::settingsColorRadioMargin;
|
||||
const auto skip = st::settingsColorRadioSkip;
|
||||
box->addRow(
|
||||
object_ptr<ColorSelector>(
|
||||
box,
|
||||
style,
|
||||
std::move(indices),
|
||||
state->index.current(),
|
||||
[=](uint8 index) { state->index = index; }),
|
||||
{ margin, skip, margin, skip });
|
||||
|
||||
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(
|
||||
container,
|
||||
show,
|
||||
style,
|
||||
state->index.value(),
|
||||
state->emojiId.value(),
|
||||
[=](DocumentId id) { state->emojiId = id; }));
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
Ui::AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_settings_color_emoji_about()
|
||||
: tr::lng_settings_color_emoji_about_channel());
|
||||
container->add(CreateEmojiIconButton(
|
||||
container,
|
||||
show,
|
||||
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()) {
|
||||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
container->add(object_ptr<Ui::SettingsButton>(
|
||||
Settings::AddButtonWithIcon(
|
||||
container,
|
||||
tr::lng_edit_channel_wallpaper(),
|
||||
st::settingsButtonNoIcon)
|
||||
(group
|
||||
? tr::lng_edit_channel_wallpaper_group()
|
||||
: tr::lng_edit_channel_wallpaper()),
|
||||
st::peerAppearanceButton,
|
||||
{ &st::menuBlueIconWallpaper }
|
||||
)->setClickedCallback([=] {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto strong = show->resolveWindow(usage)) {
|
||||
|
@ -971,7 +1167,25 @@ void EditPeerColorBox(
|
|||
Ui::AddSkip(container, st::settingsColorSampleSkip);
|
||||
Ui::AddDividerText(
|
||||
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.
|
||||
const auto peerPhoto = &channel->session().api().peerPhoto();
|
||||
|
@ -992,10 +1206,16 @@ void EditPeerColorBox(
|
|||
state->statusId = id;
|
||||
state->statusUntil = until;
|
||||
state->statusChanged = true;
|
||||
}));
|
||||
},
|
||||
group));
|
||||
|
||||
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(), [=] {
|
||||
|
@ -1020,19 +1240,11 @@ void EditPeerColorBox(
|
|||
});
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
void SetupPeerColorSample(
|
||||
not_null<Button*> button,
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> label,
|
||||
std::shared_ptr<Ui::ChatStyle> style) {
|
||||
auto colorIndexValue = peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
Data::PeerUpdate::Flag::Color
|
||||
|
@ -1041,12 +1253,6 @@ void AddPeerColorButton(
|
|||
});
|
||||
const auto name = peer->shortName();
|
||||
|
||||
const auto style = std::make_shared<Ui::ChatStyle>(
|
||||
peer->session().colorIndicesValue());
|
||||
const auto theme = std::shared_ptr<Ui::ChatTheme>(
|
||||
Window::Theme::DefaultChatThemeOn(button->lifetime()));
|
||||
style->apply(theme.get());
|
||||
|
||||
const auto sample = Ui::CreateChild<ColorSample>(
|
||||
button.get(),
|
||||
style,
|
||||
|
@ -1098,6 +1304,30 @@ void AddPeerColorButton(
|
|||
}, sample->lifetime());
|
||||
|
||||
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([=] {
|
||||
show->show(Box(EditPeerColorBox, show, peer, style, theme));
|
||||
|
|
|
@ -198,6 +198,37 @@ void SaveSlowmodeSeconds(
|
|||
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(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
|
@ -215,6 +246,10 @@ void ShowEditPermissions(
|
|||
close);
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
|
||||
SaveBoostsUnrestrict(
|
||||
channel,
|
||||
result.boostsUnrestrict,
|
||||
close);
|
||||
}
|
||||
};
|
||||
auto done = [=](EditPeerPermissionsBoxResult result) {
|
||||
|
@ -225,7 +260,8 @@ void ShowEditPermissions(
|
|||
|
||||
const auto saveFor = peer->migrateToOrMe();
|
||||
const auto chat = saveFor->asChat();
|
||||
if (!result.slowmodeSeconds || !chat) {
|
||||
if (!chat
|
||||
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|
||||
save(saveFor, result);
|
||||
return;
|
||||
}
|
||||
|
@ -595,8 +631,9 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
|
|||
tr::lng_group_stickers_add(),
|
||||
rpl::single(QString()), //Empty count.
|
||||
[=, controller = _navigation->parentController()] {
|
||||
const auto isEmoji = false;
|
||||
controller->show(
|
||||
Box<StickersBox>(controller->uiShow(), channel));
|
||||
Box<StickersBox>(controller->uiShow(), channel, isEmoji));
|
||||
},
|
||||
{ &st::menuIconStickers });
|
||||
|
||||
|
@ -1057,9 +1094,7 @@ void Controller::fillManageSection() {
|
|||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto canEditStickers = isChannel && channel->canEditStickers();
|
||||
const auto canDeleteChannel = isChannel && channel->canDelete();
|
||||
const auto canEditColorIndex = isChannel
|
||||
&& !channel->isMegagroup()
|
||||
&& channel->canEditInformation();
|
||||
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
|
||||
const auto canViewOrEditLinkedChat = isChannel
|
||||
&& (channel->linkedChat()
|
||||
|| (channel->isBroadcast() && channel->canEditInformation()));
|
||||
|
|
|
@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/edit_peer_permissions_box.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_chat.h"
|
||||
#include "data/data_session.h"
|
||||
#include "ui/effects/toggle_arrow.h"
|
||||
#include "ui/wrap/slide_wrap.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 "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_window.h"
|
||||
|
@ -42,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace {
|
||||
|
||||
constexpr auto kSlowmodeValues = 7;
|
||||
constexpr auto kBoostsUnrestrictValues = 5;
|
||||
constexpr auto kSuggestGigagroupThreshold = 199000;
|
||||
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
||||
|
||||
|
@ -99,7 +104,7 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
|||
using Flag = ChatAdminRight;
|
||||
|
||||
if (options.isGroup) {
|
||||
auto result = std::vector<AdminRightLabel>{
|
||||
auto first = std::vector<AdminRightLabel>{
|
||||
{ Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
|
||||
{ Flag::DeleteMessages, tr::lng_rights_group_delete(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) },
|
||||
{ Flag::ManageTopics, tr::lng_rights_group_topics(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::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
|
||||
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
|
||||
};
|
||||
if (!options.isForum) {
|
||||
result.erase(
|
||||
first.erase(
|
||||
ranges::remove(
|
||||
result,
|
||||
first,
|
||||
Flag::ManageTopics | Flag(),
|
||||
&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>{
|
||||
{ Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) },
|
||||
|
@ -163,6 +179,10 @@ int SlowmodeDelayByIndex(int index) {
|
|||
Unexpected("Index in SlowmodeDelayByIndex.");
|
||||
}
|
||||
|
||||
[[nodiscard]] int BoostsUnrestrictByIndex(int index) {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
template <typename CheckboxesMap, typename DependenciesMap>
|
||||
void ApplyDependencies(
|
||||
const CheckboxesMap &checkboxes,
|
||||
|
@ -770,14 +790,14 @@ void AddSlowmodeLabels(
|
|||
}
|
||||
}
|
||||
|
||||
Fn<int()> AddSlowmodeSlider(
|
||||
rpl::producer<int> AddSlowmodeSlider(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<PeerData*> peer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
if (const auto chat = peer->asChat()) {
|
||||
if (!chat->amCreator()) {
|
||||
return [] { return 0; };
|
||||
return rpl::single(0);
|
||||
}
|
||||
}
|
||||
const auto channel = peer->asChannel();
|
||||
|
@ -785,10 +805,6 @@ Fn<int()> AddSlowmodeSlider(
|
|||
const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
|
||||
channel ? channel->slowmodeSeconds() : 0);
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::BoxContentDivider>(container),
|
||||
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
|
||||
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
|
@ -858,7 +874,157 @@ Fn<int()> AddSlowmodeSlider(
|
|||
st::proxyAboutPadding),
|
||||
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(
|
||||
|
@ -985,7 +1151,40 @@ void ShowEditPeerPermissionsBox(
|
|||
|
||||
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 (channel->amCreator()
|
||||
|
@ -1001,7 +1200,18 @@ void ShowEditPeerPermissionsBox(
|
|||
AddBannedButtons(inner, navigation, peer);
|
||||
|
||||
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(); });
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class SessionNavigation;
|
|||
struct EditPeerPermissionsBoxResult final {
|
||||
ChatRestrictions rights;
|
||||
int slowmodeSeconds = 0;
|
||||
int boostsUnrestrict = 0;
|
||||
};
|
||||
|
||||
void ShowEditPeerPermissionsBox(
|
||||
|
|
|
@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "boxes/peers/replace_boost_box.h"
|
||||
|
||||
#include "api/api_peer_colors.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.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 hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
return Ui::MakeInformBox({
|
||||
.text = tr::lng_boost_error_flood_text(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_seconds(tr::now, lt_count, seconds))),
|
||||
Ui::Text::RichLangValue),
|
||||
.text = (group
|
||||
? tr::lng_boost_error_flood_text_group
|
||||
: tr::lng_boost_error_flood_text)(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? 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(),
|
||||
});
|
||||
}
|
||||
|
@ -335,14 +340,15 @@ object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
|
|||
object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
|
||||
not_null<ChannelData*> to,
|
||||
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) {
|
||||
const auto reassigned = std::make_shared<bool>();
|
||||
const auto slot = from.id;
|
||||
const auto peer = to->owner().peer(from.peerId);
|
||||
const auto group = peer->isMegagroup();
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*reassigned = true;
|
||||
reassign({ slot }, 1);
|
||||
reassign({ slot }, group ? 1 : 0, group ? 0 : 1);
|
||||
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) {
|
||||
const auto key = u"boosts_per_sent_gift"_q;
|
||||
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<int> &slots) {
|
||||
auto checked = base::flat_set<PeerId>();
|
||||
checked.reserve(slots.size());
|
||||
auto groups = base::flat_set<PeerId>();
|
||||
groups.reserve(slots.size());
|
||||
auto channels = base::flat_set<PeerId>();
|
||||
channels.reserve(slots.size());
|
||||
const auto owner = &to->owner();
|
||||
for (const auto slot : slots) {
|
||||
const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
|
||||
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(
|
||||
not_null<ChannelData*> to,
|
||||
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) {
|
||||
Expects(!from.empty());
|
||||
|
||||
const auto now = base::unixtime::now();
|
||||
if (from.size() == 1 && from.front().cooldown > now) {
|
||||
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) {
|
||||
return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
|
||||
}
|
||||
|
@ -457,10 +522,10 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
|||
) | rpl::start_with_next([=](std::vector<int> slots) {
|
||||
box->clearButtons();
|
||||
if (!slots.empty()) {
|
||||
const auto sources = SourcesCount(from, slots);
|
||||
const auto sources = SourcesCount(to, from, slots);
|
||||
box->addButton(tr::lng_boost_reassign_button(), [=] {
|
||||
*reassigned = true;
|
||||
reassign(slots, sources);
|
||||
reassign(slots, sources.groups, sources.channels);
|
||||
});
|
||||
}
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
|
|
|
@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
class ChannelData;
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
struct BoostCounters;
|
||||
struct BoostFeatures;
|
||||
class BoxContent;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
@ -39,12 +42,15 @@ struct ForChannelBoostSlots {
|
|||
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status);
|
||||
|
||||
[[nodiscard]] Ui::BoostFeatures LookupBoostFeatures(
|
||||
not_null<ChannelData*> channel);
|
||||
|
||||
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
|
||||
|
||||
object_ptr<Ui::BoxContent> ReassignBoostsBox(
|
||||
not_null<ChannelData*> to,
|
||||
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);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
|
||||
|
|
|
@ -112,6 +112,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
|||
return tr::lng_premium_summary_subtitle_infinite_reactions();
|
||||
case PremiumPreview::TagsForMessages:
|
||||
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:
|
||||
return tr::lng_premium_summary_subtitle_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
|
@ -150,6 +154,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
|||
return tr::lng_premium_summary_about_infinite_reactions();
|
||||
case PremiumPreview::TagsForMessages:
|
||||
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:
|
||||
return tr::lng_premium_summary_about_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
|
@ -480,6 +488,8 @@ struct VideoPreviewDocument {
|
|||
case PremiumPreview::AnimatedUserpics: return "animated_userpics";
|
||||
case PremiumPreview::RealTimeTranslation: return "translations";
|
||||
case PremiumPreview::Wallpapers: return "wallpapers";
|
||||
case PremiumPreview::LastSeen: return "last_seen";
|
||||
case PremiumPreview::MessagePrivacy: return "message_privacy";
|
||||
}
|
||||
return "";
|
||||
}();
|
||||
|
|
|
@ -62,6 +62,8 @@ enum class PremiumPreview {
|
|||
RealTimeTranslation,
|
||||
Wallpapers,
|
||||
TagsForMessages,
|
||||
LastSeen,
|
||||
MessagePrivacy,
|
||||
|
||||
kCount,
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/send_files_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "mainwidget.h"
|
||||
|
@ -22,8 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "editor/photo_editor_layer_widget.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 "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "base/event_filter.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 "ui/effects/scroll_content_shadow.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/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_single_file_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 "data/data_document.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "api/api_common.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
#include <QtCore/QMimeData>
|
||||
|
||||
|
@ -339,16 +332,16 @@ SendFilesBox::SendFilesBox(
|
|||
not_null<Window::SessionController*> controller,
|
||||
Ui::PreparedList &&list,
|
||||
const TextWithTags &caption,
|
||||
SendFilesLimits limits,
|
||||
SendFilesCheck check,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType)
|
||||
: SendFilesBox(nullptr, {
|
||||
.show = controller->uiShow(),
|
||||
.list = std::move(list),
|
||||
.caption = caption,
|
||||
.limits = limits,
|
||||
.check = check,
|
||||
.captionToPeer = toPeer,
|
||||
.limits = DefaultLimitsForPeer(toPeer),
|
||||
.check = DefaultCheckForPeer(controller, toPeer),
|
||||
.sendType = sendType,
|
||||
.sendMenuType = sendMenuType,
|
||||
}) {
|
||||
|
@ -364,6 +357,7 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
|
|||
, _list(std::move(descriptor.list))
|
||||
, _limits(descriptor.limits)
|
||||
, _sendMenuType(descriptor.sendMenuType)
|
||||
, _captionToPeer(descriptor.captionToPeer)
|
||||
, _check(std::move(descriptor.check))
|
||||
, _confirmedCallback(std::move(descriptor.confirmed))
|
||||
, _cancelledCallback(std::move(descriptor.cancelled))
|
||||
|
@ -1034,8 +1028,10 @@ void SendFilesBox::updateSendWayControls() {
|
|||
}
|
||||
|
||||
void SendFilesBox::setupCaption() {
|
||||
const auto allow = [=](const auto &) {
|
||||
return (_limits & SendFilesAllow::EmojiWithoutPremium);
|
||||
const auto allow = [=](not_null<DocumentData*> emoji) {
|
||||
return _captionToPeer
|
||||
? Data::AllowEmojiWithoutPremium(_captionToPeer, emoji)
|
||||
: (_limits & SendFilesAllow::EmojiWithoutPremium);
|
||||
};
|
||||
const auto show = _show;
|
||||
InitMessageFieldHandlers(
|
||||
|
@ -1096,6 +1092,39 @@ void SendFilesBox::setupCaption() {
|
|||
|
||||
updateCaptionPlaceholder();
|
||||
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() {
|
||||
|
@ -1114,7 +1143,6 @@ void SendFilesBox::setupEmojiPanel() {
|
|||
.level = Window::GifPauseReason::Layer,
|
||||
.mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly,
|
||||
.features = {
|
||||
.megagroupSet = false,
|
||||
.stickersSettings = false,
|
||||
.openStickerSets = false,
|
||||
},
|
||||
|
@ -1125,6 +1153,7 @@ void SendFilesBox::setupEmojiPanel() {
|
|||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
_emojiPanel->hide();
|
||||
_emojiPanel->selector()->setCurrentPeer(_captionToPeer);
|
||||
_emojiPanel->selector()->setAllowEmojiWithoutPremium(
|
||||
_limits & SendFilesAllow::EmojiWithoutPremium);
|
||||
_emojiPanel->selector()->emojiChosen(
|
||||
|
@ -1137,7 +1166,11 @@ void SendFilesBox::setupEmojiPanel() {
|
|||
if (info
|
||||
&& info->setType == Data::StickersType::Emoji
|
||||
&& !_show->session().premium()
|
||||
&& !(_limits & SendFilesAllow::EmojiWithoutPremium)) {
|
||||
&& !(_captionToPeer
|
||||
? Data::AllowEmojiWithoutPremium(
|
||||
_captionToPeer,
|
||||
data.document)
|
||||
: (_limits & SendFilesAllow::EmojiWithoutPremium))) {
|
||||
ShowPremiumPreviewBox(_show, PremiumPreview::AnimatedEmoji);
|
||||
} else {
|
||||
Data::InsertCustomEmoji(_caption.data(), data.document);
|
||||
|
|
|
@ -50,6 +50,10 @@ namespace SendMenu {
|
|||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace HistoryView::Controls {
|
||||
class CharactersLimitLabel;
|
||||
} // namespace HistoryView::Controls
|
||||
|
||||
enum class SendFilesAllow {
|
||||
OnlyOne = (1 << 0),
|
||||
Photos = (1 << 1),
|
||||
|
@ -88,6 +92,7 @@ struct SendFilesBoxDescriptor {
|
|||
std::shared_ptr<ChatHelpers::Show> show;
|
||||
Ui::PreparedList list;
|
||||
TextWithTags caption;
|
||||
PeerData *captionToPeer = nullptr;
|
||||
SendFilesLimits limits = {};
|
||||
SendFilesCheck check;
|
||||
Api::SendType sendType = {};
|
||||
|
@ -108,8 +113,7 @@ public:
|
|||
not_null<Window::SessionController*> controller,
|
||||
Ui::PreparedList &&list,
|
||||
const TextWithTags &caption,
|
||||
SendFilesLimits limits,
|
||||
SendFilesCheck check,
|
||||
not_null<PeerData*> toPeer,
|
||||
Api::SendType sendType,
|
||||
SendMenu::Type sendMenuType);
|
||||
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
|
||||
|
@ -221,6 +225,8 @@ private:
|
|||
void enqueueNextPrepare();
|
||||
void addPreparedAsyncFile(Ui::PreparedFile &&file);
|
||||
|
||||
void checkCharsLimitation();
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const style::ComposeControls &_st;
|
||||
const Api::SendType _sendType = Api::SendType();
|
||||
|
@ -233,7 +239,7 @@ private:
|
|||
|
||||
SendFilesLimits _limits = {};
|
||||
SendMenu::Type _sendMenuType = {};
|
||||
|
||||
PeerData *_captionToPeer = nullptr;
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
Fn<void()> _cancelledCallback;
|
||||
|
@ -244,6 +250,8 @@ private:
|
|||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
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> _sendImagesAsPhotos = { nullptr };
|
||||
|
|
|
@ -17,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "storage/storage_account.h"
|
||||
|
@ -38,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/painter.h"
|
||||
#include "ui/unread_badge_paint.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 "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
@ -99,7 +103,8 @@ public:
|
|||
Inner(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> megagroup);
|
||||
not_null<ChannelData*> megagroup,
|
||||
bool isEmoji);
|
||||
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
||||
|
@ -108,7 +113,7 @@ public:
|
|||
}
|
||||
void setInnerFocus();
|
||||
|
||||
void saveGroupSet();
|
||||
void saveGroupSet(Fn<void()> done);
|
||||
|
||||
void rebuild(bool masks);
|
||||
void updateSize(int newWidth = 0);
|
||||
|
@ -221,6 +226,7 @@ private:
|
|||
StickersSetsOrder collectSets(Check check) const;
|
||||
|
||||
void updateSelected();
|
||||
void checkGroupLevel(Fn<void()> done);
|
||||
|
||||
void checkLoadMore();
|
||||
void updateScrollbarWidth();
|
||||
|
@ -323,6 +329,8 @@ private:
|
|||
|
||||
int _scrollbar = 0;
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
bool _megagroupSetEmoji = false;
|
||||
bool _checkingGroupLevel = false;
|
||||
StickerSetIdentifier _megagroupSetInput;
|
||||
std::unique_ptr<Row> _megagroupSelectedSet;
|
||||
object_ptr<AddressField> _megagroupSetField = { nullptr };
|
||||
|
@ -429,15 +437,16 @@ StickersBox::StickersBox(
|
|||
StickersBox::StickersBox(
|
||||
QWidget*,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> megagroup)
|
||||
not_null<ChannelData*> megagroup,
|
||||
bool isEmoji)
|
||||
: _st(st::stickersRowItem)
|
||||
, _show(std::move(show))
|
||||
, _session(&_show->session())
|
||||
, _api(&_session->mtp())
|
||||
, _section(Section::Installed)
|
||||
, _isMasks(false)
|
||||
, _isEmoji(false)
|
||||
, _installed(0, this, _show, megagroup)
|
||||
, _isEmoji(isEmoji)
|
||||
, _installed(0, this, _show, megagroup, isEmoji)
|
||||
, _megagroupSet(megagroup) {
|
||||
_installed.widget()->scrollsToY(
|
||||
) | rpl::start_with_next([=](int y) {
|
||||
|
@ -581,7 +590,9 @@ void StickersBox::prepare() {
|
|||
session().local().readArchivedStickers();
|
||||
}
|
||||
} else {
|
||||
setTitle(tr::lng_stickers_group_set());
|
||||
setTitle(_isEmoji
|
||||
? tr::lng_emoji_group_set()
|
||||
: tr::lng_stickers_group_set());
|
||||
}
|
||||
} else if (_section == Section::Archived) {
|
||||
requestArchivedSets();
|
||||
|
@ -659,9 +670,11 @@ void StickersBox::prepare() {
|
|||
}
|
||||
|
||||
if (_megagroupSet) {
|
||||
addButton(
|
||||
tr::lng_settings_save(),
|
||||
[=] { _installed.widget()->saveGroupSet(); closeBox(); });
|
||||
addButton(tr::lng_settings_save(), [=] {
|
||||
_installed.widget()->saveGroupSet(crl::guard(this, [=] {
|
||||
closeBox();
|
||||
}));
|
||||
});
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
} else {
|
||||
const auto close = _section == Section::Attached;
|
||||
|
@ -1220,7 +1233,8 @@ StickersBox::Inner::Inner(
|
|||
StickersBox::Inner::Inner(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> megagroup)
|
||||
not_null<ChannelData*> megagroup,
|
||||
bool isEmoji)
|
||||
: RpWidget(parent)
|
||||
, _st(st::stickersRowItem)
|
||||
, _show(std::move(show))
|
||||
|
@ -1248,19 +1262,30 @@ StickersBox::Inner::Inner(
|
|||
})
|
||||
, _itemsTop(st::lineWidth)
|
||||
, _megagroupSet(megagroup)
|
||||
, _megagroupSetInput(_megagroupSet->mgInfo->stickerSet)
|
||||
, _megagroupSetEmoji(isEmoji)
|
||||
, _megagroupSetInput(isEmoji
|
||||
? _megagroupSet->mgInfo->emojiSet
|
||||
: _megagroupSet->mgInfo->stickerSet)
|
||||
, _megagroupSetField(
|
||||
this,
|
||||
st::groupStickersField,
|
||||
rpl::single(u"stickerset"_q),
|
||||
rpl::single(isEmoji ? u"emojipack"_q : u"stickerset"_q),
|
||||
QString(),
|
||||
_session->createInternalLink(QString()))
|
||||
, _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(
|
||||
_session->createInternalLink(u"addstickers/"_q));
|
||||
_session->createInternalLink(
|
||||
isEmoji ? u"addemoji/"_q : u"addstickers/"_q));
|
||||
_megagroupSetField->setPlaceholderHidden(false);
|
||||
_megagroupSetAddressChangedTimer.setCallback([this] { handleMegagroupSetAddressChange(); });
|
||||
_megagroupSetAddressChangedTimer.setCallback([this] {
|
||||
handleMegagroupSetAddressChange();
|
||||
});
|
||||
connect(
|
||||
_megagroupSetField,
|
||||
&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) {
|
||||
if (_dragging >= 0) mouseReleaseEvent(e);
|
||||
if (_dragging >= 0) {
|
||||
mouseReleaseEvent(e);
|
||||
}
|
||||
_mouse = e->globalPos();
|
||||
updateSelected();
|
||||
|
||||
|
@ -1979,18 +2006,64 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
setActionDown(-1);
|
||||
}
|
||||
|
||||
void StickersBox::Inner::saveGroupSet() {
|
||||
void StickersBox::Inner::saveGroupSet(Fn<void()> done) {
|
||||
Expects(_megagroupSet != nullptr);
|
||||
|
||||
auto oldId = _megagroupSet->mgInfo->stickerSet.id;
|
||||
auto oldId = _megagroupSetEmoji
|
||||
? _megagroupSet->mgInfo->emojiSet.id
|
||||
: _megagroupSet->mgInfo->stickerSet.id;
|
||||
auto newId = _megagroupSetInput.id;
|
||||
if (newId != oldId) {
|
||||
if (newId == oldId) {
|
||||
done();
|
||||
} else if (_megagroupSetEmoji) {
|
||||
checkGroupLevel(done);
|
||||
} else {
|
||||
session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput);
|
||||
session().data().stickers().notifyStickerSetInstalled(
|
||||
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) {
|
||||
const auto index = getRowIndex(setId);
|
||||
if (index >= 0) {
|
||||
|
@ -2233,9 +2306,13 @@ void StickersBox::Inner::rebuild(bool masks) {
|
|||
clear();
|
||||
const auto &order = ([&]() -> const StickersSetsOrder & {
|
||||
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()) {
|
||||
return session().data().stickers().featuredSetsOrder();
|
||||
return _megagroupSetEmoji
|
||||
? session().data().stickers().featuredEmojiSetsOrder()
|
||||
: session().data().stickers().featuredSetsOrder();
|
||||
}
|
||||
return result;
|
||||
} else if (_section == Section::Masks) {
|
||||
|
@ -2252,9 +2329,15 @@ void StickersBox::Inner::rebuild(bool masks) {
|
|||
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
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
|
||||
? 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));
|
||||
updateControlsGeometry();
|
||||
} else if (_isInstalledTab) {
|
||||
|
|
|
@ -66,7 +66,8 @@ public:
|
|||
StickersBox(
|
||||
QWidget*,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> megagroup);
|
||||
not_null<ChannelData*> megagroup,
|
||||
bool isEmoji);
|
||||
StickersBox(
|
||||
QWidget*,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
|
|
|
@ -968,6 +968,7 @@ historyComposeField: InputField(defaultInputField) {
|
|||
duration: 100;
|
||||
}
|
||||
historyComposeFieldMaxHeight: 224px;
|
||||
historyComposeFieldFadeHeight: 6px;
|
||||
// historyMinHeight: 56px;
|
||||
|
||||
historyAttach: IconButton(defaultIconButton) {
|
||||
|
@ -1070,6 +1071,13 @@ historyMessagesTTLLabel: FlatLabel(defaultFlatLabel) {
|
|||
textFg: windowSubTextFg;
|
||||
}
|
||||
|
||||
historyCharsLimitationLabel: FlatLabel(defaultFlatLabel) {
|
||||
// The same as a width of the historySendSize.
|
||||
minWidth: 44px;
|
||||
align: align(center);
|
||||
textFg: attentionButtonFg;
|
||||
}
|
||||
|
||||
historyRecordVoiceFg: historyComposeIconFg;
|
||||
historyRecordVoiceFgOver: historyComposeIconFgOver;
|
||||
historyRecordVoiceFgInactive: attentionButtonFg;
|
||||
|
|
|
@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "lang/lang_keys.h"
|
||||
#include "layout/layout_position.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer_values.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_helper.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "core/application.h"
|
||||
#include "settings/settings_premium.h"
|
||||
|
@ -467,6 +470,7 @@ EmojiListWidget::EmojiListWidget(
|
|||
, _show(std::move(descriptor.show))
|
||||
, _features(descriptor.features)
|
||||
, _mode(descriptor.mode)
|
||||
, _api(&session().mtp())
|
||||
, _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1)
|
||||
, _premiumIcon(_mode == Mode::EmojiStatus
|
||||
? std::make_unique<GradientPremiumStar>()
|
||||
|
@ -523,6 +527,15 @@ EmojiListWidget::EmojiListWidget(
|
|||
pickerHidden();
|
||||
}, 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(
|
||||
Data::StickersType::Emoji
|
||||
) | rpl::start_with_next([=] {
|
||||
|
@ -1693,6 +1706,13 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
}
|
||||
|
||||
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();
|
||||
auto it = sets.find(setId);
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -1799,6 +1847,13 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
|
|||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1825,7 +1880,9 @@ bool EmojiListWidget::hasAddButton(int index) const {
|
|||
return false;
|
||||
}
|
||||
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 {
|
||||
|
@ -1849,10 +1906,13 @@ QRect EmojiListWidget::unlockButtonRect(int index) const {
|
|||
}
|
||||
|
||||
bool EmojiListWidget::hasButton(int index) const {
|
||||
if (hasColorButton(index)
|
||||
|| (index >= _staticCount
|
||||
&& index < _staticCount + _custom.size())) {
|
||||
if (hasColorButton(index)) {
|
||||
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;
|
||||
}
|
||||
|
@ -1982,6 +2042,16 @@ void EmojiListWidget::setAllowWithoutPremium(bool allow) {
|
|||
resizeToWidth(width());
|
||||
}
|
||||
|
||||
void EmojiListWidget::showMegagroupSet(ChannelData *megagroup) {
|
||||
Expects(!megagroup || megagroup->isMegagroup());
|
||||
|
||||
if (_megagroupSet != megagroup) {
|
||||
_megagroupSet = megagroup;
|
||||
refreshCustom();
|
||||
resizeToWidth(width());
|
||||
}
|
||||
}
|
||||
|
||||
QString EmojiListWidget::tooltipText() const {
|
||||
if (_mode != Mode::Full) {
|
||||
return {};
|
||||
|
@ -2052,7 +2122,25 @@ void EmojiListWidget::refreshCustom() {
|
|||
const auto owner = &session->data();
|
||||
const auto &sets = owner->stickers().sets();
|
||||
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()
|
||||
|| it->second->stickers.isEmpty()
|
||||
|| (_mode == Mode::BackgroundEmoji && !it->second->textColor())
|
||||
|
@ -2060,12 +2148,13 @@ void EmojiListWidget::refreshCustom() {
|
|||
&& !it->second->channelStatus())) {
|
||||
return;
|
||||
}
|
||||
const auto canRemove = !!(it->second->flags
|
||||
& Data::StickersSetFlag::Installed);
|
||||
const auto canRemove = megagroup
|
||||
? (_megagroupSet->canEditEmoji() || installed)
|
||||
: !!(it->second->flags & Data::StickersSetFlag::Installed);
|
||||
const auto sortAsInstalled = canRemove
|
||||
&& (!(it->second->flags & Data::StickersSetFlag::Featured)
|
||||
|| !_localSetsManager->isInstalledLocally(setId));
|
||||
if (sortAsInstalled != installed) {
|
||||
|| !_localSetsManager->isInstalledLocally(lookupId));
|
||||
if (!megagroup && sortAsInstalled != installed) {
|
||||
return;
|
||||
}
|
||||
auto premium = false;
|
||||
|
@ -2078,7 +2167,7 @@ void EmojiListWidget::refreshCustom() {
|
|||
return false;
|
||||
}
|
||||
for (auto k = 0; k != count; ++k) {
|
||||
if (!premium && list[k]->isPremiumEmoji()) {
|
||||
if (!premium && !megagroup && list[k]->isPremiumEmoji()) {
|
||||
premium = true;
|
||||
}
|
||||
if (i->list[k].document != list[k]) {
|
||||
|
@ -2112,11 +2201,11 @@ void EmojiListWidget::refreshCustom() {
|
|||
continue;
|
||||
} else if (const auto sticker = document->sticker()) {
|
||||
set.push_back({
|
||||
.custom = resolveCustomEmoji(document, setId),
|
||||
.custom = resolveCustomEmoji(document, lookupId),
|
||||
.document = document,
|
||||
.emoji = Ui::Emoji::Find(sticker->alt),
|
||||
});
|
||||
if (!premium && document->isPremiumEmoji()) {
|
||||
if (!premium && !megagroup && document->isPremiumEmoji()) {
|
||||
premium = true;
|
||||
}
|
||||
}
|
||||
|
@ -2134,12 +2223,14 @@ void EmojiListWidget::refreshCustom() {
|
|||
.premiumRequired = premium && premiumMayBeBought,
|
||||
});
|
||||
};
|
||||
refreshMegagroupStickers(push, GroupStickersPlace::Visible);
|
||||
for (const auto setId : owner->stickers().emojiSetsOrder()) {
|
||||
push(setId, true);
|
||||
}
|
||||
for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) {
|
||||
push(setId, false);
|
||||
}
|
||||
refreshMegagroupStickers(push, GroupStickersPlace::Hidden);
|
||||
|
||||
_footer->refreshIcons(
|
||||
fillIcons(),
|
||||
|
@ -2232,6 +2323,60 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
|
|||
).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() {
|
||||
auto result = std::vector<StickerIcon>();
|
||||
result.reserve(2 + _custom.size());
|
||||
|
@ -2248,6 +2393,11 @@ std::vector<StickerIcon> EmojiListWidget::fillIcons() {
|
|||
}
|
||||
const auto esize = StickersListFooter::IconFrameSize();
|
||||
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;
|
||||
result.emplace_back(set, custom.thumbnailDocument, esize, esize);
|
||||
}
|
||||
|
|
|
@ -117,6 +117,7 @@ public:
|
|||
void showSet(uint64 setId);
|
||||
[[nodiscard]] uint64 currentSet(int yOffset) const;
|
||||
void setAllowWithoutPremium(bool allow);
|
||||
void showMegagroupSet(ChannelData *megagroup);
|
||||
|
||||
// Ui::AbstractTooltipShower interface.
|
||||
QString tooltipText() const override;
|
||||
|
@ -257,6 +258,13 @@ private:
|
|||
void colorChosen(EmojiChosen data);
|
||||
bool checkPickerHide();
|
||||
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 unloadAllCustom();
|
||||
void unloadCustomIn(const SectionInfo &info);
|
||||
|
@ -340,6 +348,7 @@ private:
|
|||
|
||||
void displaySet(uint64 setId);
|
||||
void removeSet(uint64 setId);
|
||||
void removeMegagroupSet(bool locally);
|
||||
|
||||
void initButton(RightButton &button, const QString &text, bool gradient);
|
||||
[[nodiscard]] std::unique_ptr<Ui::RippleAnimation> createButtonRipple(
|
||||
|
@ -370,10 +379,13 @@ private:
|
|||
const ComposeFeatures _features;
|
||||
Mode _mode = Mode::Full;
|
||||
std::unique_ptr<Ui::TabbedSearch> _search;
|
||||
MTP::Sender _api;
|
||||
const int _staticCount = 0;
|
||||
StickersListFooter *_footer = nullptr;
|
||||
std::unique_ptr<GradientPremiumStar> _premiumIcon;
|
||||
std::unique_ptr<LocalStickersManager> _localSetsManager;
|
||||
ChannelData *_megagroupSet = nullptr;
|
||||
uint64 _megagroupSetIdRequested = 0;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
DocumentId,
|
||||
Fn<void()>)> _customRecentFactory;
|
||||
|
|
|
@ -10,11 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_widget.h"
|
||||
#include "history/history.h" // History::session
|
||||
#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_url.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
|
@ -277,11 +278,9 @@ TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
|
|||
auto original = item->history()->session().supportMode()
|
||||
? StripSupportHashtag(item->originalText())
|
||||
: item->originalText();
|
||||
const auto dropCustomEmoji = !item->history()->session().premium()
|
||||
&& !item->history()->peer->isSelf();
|
||||
if (dropCustomEmoji) {
|
||||
original = DropCustomEmoji(std::move(original));
|
||||
}
|
||||
original = DropDisallowedCustomEmoji(
|
||||
item->history()->peer,
|
||||
std::move(original));
|
||||
return TextWithTags{
|
||||
original.text,
|
||||
TextUtilities::ConvertEntitiesToTextTags(original.entities)
|
||||
|
@ -442,6 +441,73 @@ bool HasSendText(not_null<const Ui::InputField*> field) {
|
|||
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(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<const Ui::InputField*> field) {
|
||||
|
|
|
@ -79,6 +79,10 @@ void InitSpellchecker(
|
|||
|
||||
bool HasSendText(not_null<const Ui::InputField*> field);
|
||||
|
||||
void InitMessageFieldFade(
|
||||
not_null<Ui::InputField*> field,
|
||||
const style::color &bg);
|
||||
|
||||
struct InlineBotQuery {
|
||||
QString query;
|
||||
QString username;
|
||||
|
|
|
@ -1790,7 +1790,8 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
removeSet(sets[button->section].id);
|
||||
}
|
||||
} 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) {
|
||||
if (setId == Data::Stickers::MegagroupSetId) {
|
||||
if (_megagroupSet->canEditStickers()) {
|
||||
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet));
|
||||
const auto isEmoji = false;
|
||||
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
|
||||
return;
|
||||
} else if (_megagroupSet->mgInfo->stickerSet.id) {
|
||||
setId = _megagroupSet->mgInfo->stickerSet.id;
|
||||
|
|
|
@ -966,6 +966,9 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
|
|||
}
|
||||
_currentPeer = peer;
|
||||
checkRestrictedPeer();
|
||||
if (hasEmojiTab()) {
|
||||
emoji()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
||||
}
|
||||
if (hasStickersTab()) {
|
||||
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QSessionManager>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/qpa/qplatformscreen.h>
|
||||
#include <ksandbox.h>
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
@ -517,8 +518,10 @@ void Sandbox::refreshGlobalProxy() {
|
|||
|| proxy.type == MTP::ProxyData::Type::Http) {
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
|
||||
} else if (!Core::IsAppLaunched()
|
||||
|| Core::App().settings().proxy().isSystem()) {
|
||||
} else if ((!Core::IsAppLaunched()
|
||||
|| 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);
|
||||
} else {
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 4014013;
|
||||
constexpr auto AppVersionStr = "4.14.13";
|
||||
constexpr auto AppVersion = 4015000;
|
||||
constexpr auto AppVersionStr = "4.15";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Data {
|
||||
|
||||
struct BoostsOverview final {
|
||||
bool group = false;
|
||||
int mine = 0;
|
||||
int level = 0;
|
||||
int boostCount = 0;
|
||||
|
|
|
@ -101,13 +101,14 @@ struct PeerUpdate {
|
|||
// For channels
|
||||
ChannelAmIn = (1ULL << 36),
|
||||
StickersSet = (1ULL << 37),
|
||||
ChannelLinkedChat = (1ULL << 38),
|
||||
ChannelLocation = (1ULL << 39),
|
||||
Slowmode = (1ULL << 40),
|
||||
GroupCall = (1ULL << 41),
|
||||
EmojiSet = (1ULL << 38),
|
||||
ChannelLinkedChat = (1ULL << 39),
|
||||
ChannelLocation = (1ULL << 40),
|
||||
Slowmode = (1ULL << 41),
|
||||
GroupCall = (1ULL << 42),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 41),
|
||||
LastUsedBit = (1ULL << 42),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -556,25 +556,16 @@ bool ChannelData::canDeleteMessages() const {
|
|||
}
|
||||
|
||||
bool ChannelData::canPostStories() const {
|
||||
if (!isBroadcast()) {
|
||||
return false;
|
||||
}
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::PostStories);
|
||||
}
|
||||
|
||||
bool ChannelData::canEditStories() const {
|
||||
if (!isBroadcast()) {
|
||||
return false;
|
||||
}
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::EditStories);
|
||||
}
|
||||
|
||||
bool ChannelData::canDeleteStories() const {
|
||||
if (!isBroadcast()) {
|
||||
return false;
|
||||
}
|
||||
return amCreator()
|
||||
|| (adminRights() & AdminRight::DeleteStories);
|
||||
}
|
||||
|
@ -648,6 +639,10 @@ bool ChannelData::canEditStickers() const {
|
|||
return (flags() & Flag::CanSetStickers);
|
||||
}
|
||||
|
||||
bool ChannelData::canEditEmoji() const {
|
||||
return amCreator() || (adminRights() & ChatAdminRight::ChangeInfo);
|
||||
}
|
||||
|
||||
bool ChannelData::canDelete() const {
|
||||
constexpr auto kDeleteChannelMembersLimit = 1000;
|
||||
return amCreator()
|
||||
|
@ -772,34 +767,93 @@ void ChannelData::setMigrateFromChat(ChatData *chat) {
|
|||
}
|
||||
|
||||
int ChannelData::slowmodeSeconds() const {
|
||||
return _slowmodeSeconds;
|
||||
if (const auto info = mgInfo.get()) {
|
||||
return info->slowmodeSeconds;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ChannelData::setSlowmodeSeconds(int seconds) {
|
||||
if (_slowmodeSeconds == seconds) {
|
||||
if (!mgInfo || slowmodeSeconds() == seconds) {
|
||||
return;
|
||||
}
|
||||
_slowmodeSeconds = seconds;
|
||||
mgInfo->slowmodeSeconds = seconds;
|
||||
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
||||
}
|
||||
|
||||
TimeId ChannelData::slowmodeLastMessage() const {
|
||||
return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage;
|
||||
return (hasAdminRights()
|
||||
|| amCreator()
|
||||
|| unrestrictedByBoosts()
|
||||
|| !mgInfo)
|
||||
? 0
|
||||
: mgInfo->slowmodeLastMessage;
|
||||
}
|
||||
|
||||
void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
||||
const auto info = mgInfo.get();
|
||||
const auto now = base::unixtime::now();
|
||||
accumulate_min(when, now);
|
||||
if (_slowmodeLastMessage > now) {
|
||||
_slowmodeLastMessage = when;
|
||||
} else if (_slowmodeLastMessage >= when) {
|
||||
if (!info) {
|
||||
return;
|
||||
} else if (info->slowmodeLastMessage > now) {
|
||||
info->slowmodeLastMessage = when;
|
||||
} else if (info->slowmodeLastMessage >= when) {
|
||||
return;
|
||||
} else {
|
||||
_slowmodeLastMessage = when;
|
||||
info->slowmodeLastMessage = when;
|
||||
}
|
||||
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) {
|
||||
if (!_invitePeek) {
|
||||
_invitePeek = std::make_unique<InvitePeek>();
|
||||
|
@ -1104,20 +1158,37 @@ void ApplyChannelUpdate(
|
|||
channel->owner().botCommandsChanged(channel);
|
||||
}
|
||||
const auto stickerSet = update.vstickerset();
|
||||
const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
|
||||
const auto newSetId = (set ? set->vid().v : 0);
|
||||
const auto oldSetId = channel->mgInfo->stickerSet.id;
|
||||
const auto sset = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
|
||||
const auto newStickerSetId = (sset ? sset->vid().v : 0);
|
||||
const auto oldStickerSetId = channel->mgInfo->stickerSet.id;
|
||||
const auto stickersChanged = (canEditStickers != channel->canEditStickers())
|
||||
|| (oldSetId != newSetId);
|
||||
if (oldSetId != newSetId) {
|
||||
|| (oldStickerSetId != newStickerSetId);
|
||||
if (oldStickerSetId != newStickerSetId) {
|
||||
channel->mgInfo->stickerSet = StickerSetIdentifier{
|
||||
.id = set ? set->vid().v : 0,
|
||||
.accessHash = set ? set->vaccess_hash().v : 0,
|
||||
.id = sset ? sset->vid().v : 0,
|
||||
.accessHash = sset ? sset->vaccess_hash().v : 0,
|
||||
};
|
||||
}
|
||||
if (stickersChanged) {
|
||||
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->setTranslationDisabled(update.is_translations_disabled());
|
||||
|
|
|
@ -114,6 +114,7 @@ public:
|
|||
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
|
||||
base::flat_set<not_null<PeerData*>> markupSenders;
|
||||
base::flat_set<not_null<UserData*>> bots;
|
||||
rpl::event_stream<bool> unrestrictedByBoostsChanges;
|
||||
|
||||
// For admin badges, full admins list with ranks.
|
||||
base::flat_map<UserId, QString> admins;
|
||||
|
@ -124,6 +125,7 @@ public:
|
|||
bool joinedMessageFound = false;
|
||||
bool adminsLoaded = false;
|
||||
StickerSetIdentifier stickerSet;
|
||||
StickerSetIdentifier emojiSet;
|
||||
|
||||
enum LastParticipantsStatus {
|
||||
LastParticipantsUpToDate = 0x00,
|
||||
|
@ -132,6 +134,11 @@ public:
|
|||
};
|
||||
mutable int lastParticipantsStatus = LastParticipantsUpToDate;
|
||||
int lastParticipantsCount = 0;
|
||||
int boostsApplied = 0;
|
||||
int boostsUnrestrict = 0;
|
||||
|
||||
int slowmodeSeconds = 0;
|
||||
TimeId slowmodeLastMessage = 0;
|
||||
|
||||
private:
|
||||
ChatData *_migratedFrom = nullptr;
|
||||
|
@ -354,6 +361,7 @@ public:
|
|||
[[nodiscard]] bool canViewBanned() const;
|
||||
[[nodiscard]] bool canEditSignatures() const;
|
||||
[[nodiscard]] bool canEditStickers() const;
|
||||
[[nodiscard]] bool canEditEmoji() const;
|
||||
[[nodiscard]] bool canDelete() const;
|
||||
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
|
||||
[[nodiscard]] bool canRestrictParticipant(
|
||||
|
@ -432,6 +440,12 @@ public:
|
|||
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
||||
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 clearInvitePeek();
|
||||
[[nodiscard]] TimeId invitePeekExpires() const;
|
||||
|
@ -519,9 +533,6 @@ private:
|
|||
std::unique_ptr<Data::GroupCall> _call;
|
||||
PeerId _callDefaultJoinAs = 0;
|
||||
|
||||
int _slowmodeSeconds = 0;
|
||||
TimeId _slowmodeLastMessage = 0;
|
||||
|
||||
};
|
||||
|
||||
namespace Data {
|
||||
|
|
|
@ -44,11 +44,9 @@ MTPInputReplyTo ReplyToForMTP(
|
|||
const auto owner = &history->owner();
|
||||
if (replyTo.storyId) {
|
||||
if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return MTP_inputReplyToStory(
|
||||
user->inputUser,
|
||||
MTP_int(replyTo.storyId.story));
|
||||
}
|
||||
return MTP_inputReplyToStory(
|
||||
peer->input,
|
||||
MTP_int(replyTo.storyId.story));
|
||||
}
|
||||
} else if (replyTo.messageId || replyTo.topicRootId) {
|
||||
const auto to = LookupReplyTo(history, replyTo.messageId);
|
||||
|
|
|
@ -302,20 +302,27 @@ bool UpdateExtendedMedia(
|
|||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextForMimeData WithCaptionClipboardText(
|
||||
const QString &attachType,
|
||||
TextForMimeData &&caption) {
|
||||
auto result = TextForMimeData();
|
||||
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));
|
||||
if (attachType.isEmpty()) {
|
||||
result.reserve(1 + caption.expanded.size());
|
||||
if (!caption.empty()) {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Invoice ComputeInvoiceData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaInvoice &data) {
|
||||
|
@ -767,9 +774,7 @@ QString MediaPhoto::pinnedTextSubstring() const {
|
|||
}
|
||||
|
||||
TextForMimeData MediaPhoto::clipboardText() const {
|
||||
return WithCaptionClipboardText(
|
||||
tr::lng_in_dlg_photo(tr::now),
|
||||
parent()->clipboardText());
|
||||
return TextForMimeData();
|
||||
}
|
||||
|
||||
bool MediaPhoto::allowsEditCaption() const {
|
||||
|
@ -1072,42 +1077,9 @@ QString MediaFile::pinnedTextSubstring() 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();
|
||||
|
||||
if (_document->isVoiceMessage()) {
|
||||
if (_document->isVoiceMessage() || _document->isVideoMessage()) {
|
||||
const auto &entry = _document->session().api().transcribes().entry(
|
||||
parent());
|
||||
if (!entry.requestId
|
||||
|
@ -1115,17 +1087,18 @@ TextForMimeData MediaFile::clipboardText() const {
|
|||
&& !entry.toolong
|
||||
&& !entry.failed
|
||||
&& (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.isEmpty() ? "" : " ")
|
||||
+ (entry.pending ? "[...]" : "")
|
||||
+ "\n}}"
|
||||
+ (caption.rich.text.isEmpty() ? "" : "\n");
|
||||
caption = TextForMimeData{ text, { text } }.append(std::move(caption));
|
||||
+ (hasCaption ? "\n}}\n" : "");
|
||||
caption = TextForMimeData{ text, { text } }.append(
|
||||
std::move(caption));
|
||||
}
|
||||
}
|
||||
|
||||
return WithCaptionClipboardText(attachType, std::move(caption));
|
||||
return caption;
|
||||
}
|
||||
|
||||
bool MediaFile::allowsEditCaption() const {
|
||||
|
|
|
@ -709,10 +709,6 @@ private:
|
|||
|
||||
};
|
||||
|
||||
[[nodiscard]] TextForMimeData WithCaptionClipboardText(
|
||||
const QString &attachType,
|
||||
TextForMimeData &&caption);
|
||||
|
||||
[[nodiscard]] Invoice ComputeInvoiceData(
|
||||
not_null<HistoryItem*> item,
|
||||
const MTPDmessageMediaInvoice &data);
|
||||
|
|
|
@ -164,7 +164,7 @@ struct FullReplyTo {
|
|||
int quoteOffset = 0;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return messageId || (storyId && peerIsUser(storyId.peer));
|
||||
return messageId || (storyId && storyId.peer);
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
|
|
|
@ -1122,7 +1122,8 @@ Data::RestrictionCheckResult PeerData::amRestricted(
|
|||
: ChatRestrictions(0));
|
||||
return (channel->amCreator() || allowByAdminRights(right, channel))
|
||||
? Result::Allowed()
|
||||
: (defaultRestrictions & right)
|
||||
: ((defaultRestrictions & right)
|
||||
&& !channel->unrestrictedByBoosts())
|
||||
? Result::WithEveryone()
|
||||
: (channel->restrictions() & right)
|
||||
? Result::Explicit()
|
||||
|
|
|
@ -271,11 +271,13 @@ inline auto DefaultRestrictionValue(
|
|||
AdminRightValue(
|
||||
channel,
|
||||
ChatAdminRight::PostMessages),
|
||||
channel->unrestrictedByBoostsValue(),
|
||||
RestrictionsValue(channel, rights),
|
||||
DefaultRestrictionsValue(channel, rights),
|
||||
[=](
|
||||
ChannelDataFlags flags,
|
||||
bool postMessagesRight,
|
||||
bool unrestrictedByBoosts,
|
||||
ChatRestrictions sendRestriction,
|
||||
ChatRestrictions defaultSendRestriction) {
|
||||
const auto notAmInFlags = Flag::Left | Flag::Forbidden;
|
||||
|
@ -285,7 +287,7 @@ inline auto DefaultRestrictionValue(
|
|||
|| ((flags & Flag::HasLink)
|
||||
&& !(flags & Flag::JoinToWrite));
|
||||
const auto restricted = sendRestriction
|
||||
| defaultSendRestriction;
|
||||
| (defaultSendRestriction && !unrestrictedByBoosts);
|
||||
return allowed
|
||||
&& !forumRestriction
|
||||
&& (postMessagesRight
|
||||
|
|
|
@ -67,6 +67,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
|||
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
MTPint(), // from_boosts_applied
|
||||
data.vpeer_id(),
|
||||
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
|
@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
|
|||
MTP_flags(flags),
|
||||
update.vid(),
|
||||
peerToMTP(local->from()->id),
|
||||
MTPint(), // from_boosts_applied
|
||||
peerToMTP(history->peer->id),
|
||||
MTPPeer(), // saved_peer_id
|
||||
MTPMessageFwdHeader(),
|
||||
|
|
|
@ -245,6 +245,12 @@ Session::Session(not_null<Main::Session*> session)
|
|||
, _bigFileCache(Core::App().databases().get(
|
||||
_session->local().cacheBigFilePath(),
|
||||
_session->local().cacheBigFileSettings()))
|
||||
, _groupFreeTranscribeLevel(session->account().appConfig().value(
|
||||
) | rpl::map([=] {
|
||||
return session->account().appConfig().get<int>(
|
||||
u"group_transcribe_level_min"_q,
|
||||
6);
|
||||
}))
|
||||
, _chatsList(
|
||||
session,
|
||||
FilterId(),
|
||||
|
@ -2189,8 +2195,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
|||
// We always use premium limit in the MainList limit producer,
|
||||
// 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.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
return _session->account().appConfig().value(
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return folder
|
||||
|
@ -2205,8 +2210,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
|||
// We always use premium limit in the MainList limit producer,
|
||||
// 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.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
return _session->account().appConfig().value(
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.dialogFiltersChatsPremium();
|
||||
|
@ -2215,8 +2219,7 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
|||
|
||||
rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
||||
not_null<Data::Forum*> forum) const {
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
return _session->account().appConfig().value(
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.topicsPinnedCurrent();
|
||||
|
@ -2229,14 +2232,17 @@ rpl::producer<int> Session::maxPinnedChatsLimitValue(
|
|||
// We always use premium limit in the MainList limit producer,
|
||||
// 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.
|
||||
return rpl::single(rpl::empty_value()) | rpl::then(
|
||||
_session->account().appConfig().refreshed()
|
||||
return _session->account().appConfig().value(
|
||||
) | rpl::map([=] {
|
||||
const auto limits = Data::PremiumLimits(_session);
|
||||
return limits.savedSublistsPinnedPremium();
|
||||
});
|
||||
}
|
||||
|
||||
int Session::groupFreeTranscribeLevel() const {
|
||||
return _groupFreeTranscribeLevel.current();
|
||||
}
|
||||
|
||||
const std::vector<Dialogs::Key> &Session::pinnedChatsOrder(
|
||||
Data::Folder *folder) const {
|
||||
return chatsList(folder)->pinned()->order();
|
||||
|
@ -4518,6 +4524,7 @@ void Session::insertCheckedServiceNotification(
|
|||
MTP_flags(flags),
|
||||
MTP_int(0), // Not used (would've been trimmed to 32 bits).
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
MTPint(), // from_boosts_applied
|
||||
peerToMTP(PeerData::kServiceNotificationsId),
|
||||
MTPPeer(), // saved_peer_id
|
||||
MTPMessageFwdHeader(),
|
||||
|
|
|
@ -367,6 +367,7 @@ public:
|
|||
not_null<Forum*> forum) const;
|
||||
[[nodiscard]] rpl::producer<int> maxPinnedChatsLimitValue(
|
||||
not_null<SavedMessages*> saved) const;
|
||||
[[nodiscard]] int groupFreeTranscribeLevel() const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
Folder *folder) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
|
@ -887,6 +888,7 @@ private:
|
|||
QPointer<Ui::BoxContent> _exportSuggestion;
|
||||
|
||||
rpl::variable<bool> _contactsLoaded = false;
|
||||
rpl::variable<int> _groupFreeTranscribeLevel;
|
||||
rpl::event_stream<Folder*> _chatsListLoadedEvents;
|
||||
rpl::event_stream<Folder*> _chatsListChanged;
|
||||
rpl::event_stream<not_null<UserData*>> _userIsBotChanges;
|
||||
|
|
|
@ -164,6 +164,15 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
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
|
||||
|
||||
class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask {
|
||||
|
@ -278,6 +287,7 @@ Story::Story(
|
|||
, _repostSourcePeer(RepostSourcePeer(&peer->owner(), data))
|
||||
, _repostSourceName(RepostSourceName(data))
|
||||
, _repostSourceId(RepostSourceId(data))
|
||||
, _fromPeer(FromPeer(&peer->owner(), data))
|
||||
, _date(data.vdate().v)
|
||||
, _expires(data.vexpire_date().v)
|
||||
, _repostModified(RepostModified(data)) {
|
||||
|
@ -890,6 +900,10 @@ StoryId Story::repostSourceId() const {
|
|||
return _repostSourceId;
|
||||
}
|
||||
|
||||
PeerData *Story::fromPeer() const {
|
||||
return _fromPeer;
|
||||
}
|
||||
|
||||
StoryPreload::StoryPreload(not_null<Story*> story, Fn<void()> done)
|
||||
: _story(story)
|
||||
, _done(std::move(done)) {
|
||||
|
|
|
@ -208,6 +208,8 @@ public:
|
|||
[[nodiscard]] QString repostSourceName() const;
|
||||
[[nodiscard]] StoryId repostSourceId() const;
|
||||
|
||||
[[nodiscard]] PeerData *fromPeer() const;
|
||||
|
||||
private:
|
||||
struct ViewsCounts {
|
||||
int views = 0;
|
||||
|
@ -234,6 +236,7 @@ private:
|
|||
PeerData * const _repostSourcePeer = nullptr;
|
||||
const QString _repostSourceName;
|
||||
const StoryId _repostSourceId = 0;
|
||||
PeerData * const _fromPeer = nullptr;
|
||||
Data::ReactionId _sentReactionId;
|
||||
StoryMedia _media;
|
||||
TextWithEntities _caption;
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.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_message_reactions.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_frame_generator.h"
|
||||
#include "ffmpeg/ffmpeg_frame_generator.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "storage/file_download.h" // kMaxFileInMemory
|
||||
#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_utilities.h"
|
||||
#include "ui/dynamic_thumbnails.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "apiwrap.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -93,6 +98,10 @@ private:
|
|||
return u"internal:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString UserpicEmojiPrefix() {
|
||||
return u"userpic:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString InternalPadding(QMargins value) {
|
||||
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
|
||||
).arg(value.left()
|
||||
|
@ -527,6 +536,10 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
|||
int sizeOverride) {
|
||||
if (data.startsWith(InternalPrefix())) {
|
||||
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);
|
||||
return parsed
|
||||
|
@ -574,6 +587,26 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::internal(
|
|||
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(
|
||||
QStringView data,
|
||||
not_null<Listener*> listener) {
|
||||
|
@ -954,6 +987,14 @@ QString CustomEmojiManager::registerInternalEmoji(
|
|||
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) {
|
||||
const auto emoji = EmojiSizeFromTag(tag);
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
|
@ -980,8 +1021,21 @@ TextWithEntities SingleCustomEmoji(not_null<DocumentData*> document) {
|
|||
return SingleCustomEmoji(document->id);
|
||||
}
|
||||
|
||||
bool AllowEmojiWithoutPremium(not_null<PeerData*> peer) {
|
||||
return peer->isSelf();
|
||||
bool AllowEmojiWithoutPremium(
|
||||
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(
|
||||
|
|
|
@ -92,6 +92,10 @@ public:
|
|||
QMargins padding = {},
|
||||
bool textColor = true);
|
||||
|
||||
[[nodiscard]] QString peerUserpicEmojiData(
|
||||
not_null<PeerData*> peer,
|
||||
QMargins padding = {});
|
||||
|
||||
[[nodiscard]] uint64 coloredSetId() const;
|
||||
|
||||
private:
|
||||
|
@ -146,6 +150,10 @@ private:
|
|||
LoaderFactory factory);
|
||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> internal(
|
||||
QStringView data);
|
||||
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> userpic(
|
||||
QStringView data,
|
||||
Fn<void()> update,
|
||||
int size);
|
||||
[[nodiscard]] static int SizeIndex(SizeTag tag);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
@ -201,7 +209,9 @@ private:
|
|||
[[nodiscard]] TextWithEntities SingleCustomEmoji(
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
[[nodiscard]] bool AllowEmojiWithoutPremium(not_null<PeerData*> peer);
|
||||
[[nodiscard]] bool AllowEmojiWithoutPremium(
|
||||
not_null<PeerData*> peer,
|
||||
DocumentData *exactEmoji = nullptr);
|
||||
|
||||
void InsertCustomEmoji(
|
||||
not_null<Ui::InputField*> field,
|
||||
|
|
|
@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "dialogs/dialogs_three_state_icon.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_stories_list.h"
|
||||
#include "dialogs/ui/dialogs_video_userpic.h"
|
||||
#include "dialogs/dialogs_indexed_list.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
|
|