diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6ac646b3c..fe6d3752c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -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="" \ diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 2a570aef7..8e99d7162 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -40,7 +40,7 @@ jobs: macos: name: MacOS - runs-on: macos-12 + runs-on: macos-13 strategy: matrix: diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 46ff421e6..7b41428e1 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/animations/palette.tgs b/Telegram/Resources/animations/palette.tgs new file mode 100644 index 000000000..cc4887f94 Binary files /dev/null and b/Telegram/Resources/animations/palette.tgs differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names.png new file mode 100644 index 000000000..4acd54d82 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png new file mode 100644 index 000000000..f8dd406fe Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png new file mode 100644 index 000000000..1bd9d32ae Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_color_names@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg.png new file mode 100644 index 000000000..234f29434 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png new file mode 100644 index 000000000..0fb615748 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png new file mode 100644 index 000000000..571d1379e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_custombg@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png new file mode 100644 index 000000000..327d069c7 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png new file mode 100644 index 000000000..076b08433 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png new file mode 100644 index 000000000..4325db686 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_emoji_pack@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links.png b/Telegram/Resources/icons/settings/premium/features/feature_links.png new file mode 100644 index 000000000..11c44d03f Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2.png b/Telegram/Resources/icons/settings/premium/features/feature_links2.png new file mode 100644 index 000000000..8bfedd0f4 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png new file mode 100644 index 000000000..c89f73225 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png new file mode 100644 index 000000000..66c9d9d3a Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links2@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png new file mode 100644 index 000000000..78ebcd323 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png new file mode 100644 index 000000000..80b73df03 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_links@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions.png new file mode 100644 index 000000000..efbe55f2a Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png new file mode 100644 index 000000000..b73917126 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png new file mode 100644 index 000000000..c71f495be Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_reactions@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status.png b/Telegram/Resources/icons/settings/premium/features/feature_status.png new file mode 100644 index 000000000..b7969a460 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png new file mode 100644 index 000000000..10f2a645e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png new file mode 100644 index 000000000..5f93a1e33 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_status@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories.png b/Telegram/Resources/icons/settings/premium/features/feature_stories.png new file mode 100644 index 000000000..7420822b7 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png new file mode 100644 index 000000000..ec1d7e056 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png new file mode 100644 index 000000000..86e53f601 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_stories@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice.png b/Telegram/Resources/icons/settings/premium/features/feature_voice.png new file mode 100644 index 000000000..3f8f0a9c3 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png new file mode 100644 index 000000000..75ffbbf9d Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png new file mode 100644 index 000000000..176153370 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_voice@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png new file mode 100644 index 000000000..a88ef05cf Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png new file mode 100644 index 000000000..ad2bc2ed5 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png new file mode 100644 index 000000000..6e0c0765a Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/features/feature_wallpaper@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen.png b/Telegram/Resources/icons/settings/premium/lastseen.png new file mode 100644 index 000000000..9299ed216 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen@2x.png b/Telegram/Resources/icons/settings/premium/lastseen@2x.png new file mode 100644 index 000000000..994a90719 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/lastseen@3x.png b/Telegram/Resources/icons/settings/premium/lastseen@3x.png new file mode 100644 index 000000000..42b3f1865 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/lastseen@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy.png b/Telegram/Resources/icons/settings/premium/privacy.png new file mode 100644 index 000000000..9d2309768 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy@2x.png b/Telegram/Resources/icons/settings/premium/privacy@2x.png new file mode 100644 index 000000000..f82d78947 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/privacy@3x.png b/Telegram/Resources/icons/settings/premium/privacy@3x.png new file mode 100644 index 000000000..ce3c77543 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/privacy@3x.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini.png b/Telegram/Resources/icons/stories/boosts_mini.png new file mode 100644 index 000000000..80c667ecc Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini@2x.png b/Telegram/Resources/icons/stories/boosts_mini@2x.png new file mode 100644 index 000000000..95a806396 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@2x.png differ diff --git a/Telegram/Resources/icons/stories/boosts_mini@3x.png b/Telegram/Resources/icons/stories/boosts_mini@3x.png new file mode 100644 index 000000000..423de8209 Binary files /dev/null and b/Telegram/Resources/icons/stories/boosts_mini@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 51851db0e..7605dd583 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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."; diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 76ea1ef4a..a129237ca 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -13,5 +13,6 @@ ../../animations/stats.tgs ../../animations/voice_ttl_idle.tgs ../../animations/voice_ttl_start.tgs + ../../animations/palette.tgs diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index c3069c310..54c27edef 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.15.0.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index eaac34070..5b93e930d 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -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" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 8b304b6be..af1902b73 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -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" diff --git a/Telegram/SourceFiles/api/api_peer_colors.cpp b/Telegram/SourceFiles/api/api_peer_colors.cpp index 50c2382fd..5f3fe12d2 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.cpp +++ b/Telegram/SourceFiles/api/api_peer_colors.cpp @@ -55,19 +55,42 @@ rpl::producer> PeerColors::suggestedValue() const { auto PeerColors::indicesValue() const -> rpl::producer { - 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 &PeerColors::requiredLevelsGroup() const { + return _requiredLevelsGroup; +} + +const base::flat_map &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); diff --git a/Telegram/SourceFiles/api/api_peer_colors.h b/Telegram/SourceFiles/api/api_peer_colors.h index 0ad1a63c7..f8d379020 100644 --- a/Telegram/SourceFiles/api/api_peer_colors.h +++ b/Telegram/SourceFiles/api/api_peer_colors.h @@ -25,10 +25,19 @@ public: [[nodiscard]] std::vector suggested() const; [[nodiscard]] rpl::producer> suggestedValue() const; + [[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const; [[nodiscard]] auto indicesValue() const -> rpl::producer; - [[nodiscard]] int requiredLevelFor( + [[nodiscard]] auto requiredLevelsGroup() const + -> const base::flat_map &; + [[nodiscard]] auto requiredLevelsChannel() const + -> const base::flat_map &; + + [[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> _suggested; - base::flat_map _requiredLevels; + base::flat_map _requiredLevelsGroup; + base::flat_map _requiredLevelsChannel; rpl::event_stream<> _colorIndicesChanged; std::unique_ptr _colorIndicesCurrent; diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index f24e156d0..789211f33 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -604,7 +604,7 @@ rpl::producer 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 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( diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp index ac792883b..d7064abcd 100644 --- a/Telegram/SourceFiles/api/api_transcribes.cpp +++ b/Telegram/SourceFiles/api/api_transcribes.cpp @@ -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 api) , _api(&api->instance()) { } +bool Transcribes::freeFor(not_null 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( diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h index a5c5923ec..b074e42f1 100644 --- a/Telegram/SourceFiles/api/api_transcribes.h +++ b/Telegram/SourceFiles/api/api_transcribes.h @@ -36,6 +36,8 @@ public: void apply(const MTPDupdateTranscribedAudio &update); + [[nodiscard]] bool freeFor(not_null item) const; + [[nodiscard]] bool trialsSupport(); [[nodiscard]] TimeId trialsRefreshAt(); [[nodiscard]] int trialsCount(); diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 2548c5cbb..d10af3e42 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -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(), diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 89f2cc169..a722b99dd 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -2638,6 +2638,22 @@ void ApiWrap::setGroupStickerSet( _session->data().stickers().notifyUpdated(Data::StickersType::Stickers); } +void ApiWrap::setGroupEmojiSet( + not_null 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> *ApiWrap::stickersByEmoji( const QString &key) { const auto it = _stickersByEmoji.find(key); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 192e14f28..615960126 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -246,6 +246,9 @@ public: void setGroupStickerSet( not_null megagroup, const StickerSetIdentifier &set); + void setGroupEmojiSet( + not_null megagroup, + const StickerSetIdentifier &set); [[nodiscard]] std::vector> *stickersByEmoji( const QString &key); diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index db72d5690..6dadab562 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -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(); } 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 &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( diff --git a/Telegram/SourceFiles/boxes/background_preview_box.h b/Telegram/SourceFiles/boxes/background_preview_box.h index 3eb06d1a5..96150c6ba 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.h +++ b/Telegram/SourceFiles/boxes/background_preview_box.h @@ -95,6 +95,7 @@ private: [[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark); [[nodiscard]] bool forChannel() const; + [[nodiscard]] bool forGroup() const; void checkLevelForChannel(); void recreate(bool dark); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index dbba29270..8bde31e8d 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -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); diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp index defc4bd64..8e94beeb9 100644 --- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp @@ -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 emoji) { + return Data::AllowEmojiWithoutPremium(peer, emoji); }; InitMessageFieldHandlers( _controller, diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f6e7feb40..7e0e6b5dc 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -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( 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() }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 38f064cb2..2627d2342 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -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( "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 parent, + const style::SettingsButton &basicSt, + QMargins added) { + const auto st = parent->lifetime().make_state( + basicSt); + st->padding += added; + return st; +} + +struct ButtonWithEmoji { + not_null st; + int emojiWidth = 0; + int noneWidth = 0; + int added = 0; +}; + +[[nodiscard]] ButtonWithEmoji ButtonStyleWithRightEmoji( + not_null 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 CreateEmojiIconButton( not_null parent, std::shared_ptr show, @@ -677,22 +729,12 @@ int ColorSelector::resizeGetHeight(int newWidth) { rpl::producer colorIndexValue, rpl::producer emojiIdValue, Fn 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( - basicSt); - st->padding.setRight(rightPadding); - auto result = object_ptr( + 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(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 parent, std::shared_ptr show, rpl::producer statusIdValue, - Fn 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( - basicSt); - st->padding.setRight(rightPadding); - auto result = object_ptr( + Fn 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(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 CreateEmojiPackButton( + not_null parent, + std::shared_ptr show, + not_null 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 custom; + QImage cache; + }; + const auto state = parent->lifetime().make_state(); + + const auto right = Ui::CreateChild(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(show, channel, isEmoji)); + }); + + channel->session().changes().peerFlagsValue( + channel, + Data::PeerUpdate::Flag::EmojiSet + ) | rpl::map([=]() -> rpl::producer { + const auto id = channel->mgInfo->emojiSet.id; + if (!id) { + return rpl::single(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( + 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 peer, std::shared_ptr style, std::shared_ptr 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( - box, - style, - theme, - peer, - state->index.value(), - state->emojiId.value() - ), {}); + if (group) { + const auto divider = Ui::CreateChild( + box.get()); + const auto verticalLayout = box->verticalLayout()->add( + object_ptr(box.get())); - auto indices = peer->session().api().peerColors().suggestedValue(); - const auto margin = st::settingsColorRadioMargin; - const auto skip = st::settingsColorRadioSkip; - box->addRow( - object_ptr( + 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( + 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( 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( + 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( + 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 container, - std::shared_ptr show, - not_null 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, + not_null peer, + rpl::producer label, + std::shared_ptr 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( - peer->session().colorIndicesValue()); - const auto theme = std::shared_ptr( - Window::Theme::DefaultChatThemeOn(button->lifetime())); - style->apply(theme.get()); - const auto sample = Ui::CreateChild( button.get(), style, @@ -1098,6 +1304,30 @@ void AddPeerColorButton( }, sample->lifetime()); sample->setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void AddPeerColorButton( + not_null container, + std::shared_ptr show, + not_null 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( + peer->session().colorIndicesValue()); + const auto theme = std::shared_ptr( + 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)); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index fba0c7303..01bbb02f2 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -198,6 +198,37 @@ void SaveSlowmodeSeconds( api->registerModifyRequest(key, requestId); } +void SaveBoostsUnrestrict( + not_null channel, + int boostsUnrestrict, + Fn 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 navigation, not_null 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 Controller::createStickersEdit() { tr::lng_group_stickers_add(), rpl::single(QString()), //Empty count. [=, controller = _navigation->parentController()] { + const auto isEmoji = false; controller->show( - Box(controller->uiShow(), channel)); + Box(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())); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index beca4c910..b5ab58b9c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -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{ + auto first = std::vector{ { 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{ + { 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{ { 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{ { 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 void ApplyDependencies( const CheckboxesMap &checkboxes, @@ -770,14 +790,14 @@ void AddSlowmodeLabels( } } -Fn AddSlowmodeSlider( +rpl::producer AddSlowmodeSlider( not_null container, not_null 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 AddSlowmodeSlider( const auto secondsCount = lifetime.make_state>( channel ? channel->slowmodeSeconds() : 0); - container->add( - object_ptr(container), - { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); - container->add( object_ptr( container, @@ -858,7 +874,157 @@ Fn AddSlowmodeSlider( st::proxyAboutPadding), style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip)); - return [=] { return secondsCount->current(); }; + return secondsCount->value(); +} + +void AddBoostsUnrestrictLabels( + not_null container, + not_null session) { + const auto labels = container->add( + object_ptr(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( + 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 AddBoostsUnrestrictSlider( + not_null container, + not_null 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>( + channel ? channel->boostsUnrestrict() : 0); + + container->add( + object_ptr(container), + { 0, st::infoProfileSkip, 0, st::infoProfileSkip }); + + auto enabled = boostsUnrestrict->value( + ) | rpl::map(_1 > 0); + container->add(object_ptr( + 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>( + container, + object_ptr(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(inner, st::localStorageLimitSlider), + st::localStorageLimitMargin); + slider->resize(st::localStorageLimitSlider.seekSize); + slider->setPseudoDiscrete( + kBoostsUnrestrictValues, + BoostsUnrestrictByIndex, + boostsUnrestrict->current(), + [=](int boosts) { + (*boostsUnrestrict) = boosts; + }); + + inner->add( + object_ptr( + inner, + object_ptr( + 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 AddBoostsUnrestrictWrapped( + not_null container, + not_null peer, + rpl::producer shown) { + const auto wrap = container->add( + object_ptr>( + container, + object_ptr(container))); + wrap->toggleOn(rpl::duplicate(shown), anim::type::normal); + wrap->finishAnimating(); + + auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer); + const auto divider = container->add( + object_ptr>( + container, + object_ptr(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 slowmodeSeconds; + rpl::variable boostsUnrestrict; + rpl::variable 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->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(); }); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h index c1ce0eee1..05e1962d4 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.h @@ -37,6 +37,7 @@ class SessionNavigation; struct EditPeerPermissionsBoxResult final { ChatRestrictions rights; int slowmodeSeconds = 0; + int boostsUnrestrict = 0; }; void ShowEditPeerPermissionsBox( diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp index 0129b8596..ef99b3a90 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp @@ -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 row) { } } -object_ptr ReassignBoostFloodBox(int seconds) { +object_ptr 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 ReassignBoostFloodBox(int seconds) { object_ptr ReassignBoostSingleBox( not_null to, TakenBoostSlot from, - Fn slots, int sources)> reassign, + Fn slots, int groups, int channels)> reassign, Fn cancel) { const auto reassigned = std::make_shared(); const auto slot = from.id; const auto peer = to->owner().peer(from.peerId); + const auto group = peer->isMegagroup(); const auto confirmed = [=](Fn 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 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(key, fallback) : 0; + }; + + auto nameColorsByLevel = base::flat_map(); + auto linkStylesByLevel = base::flat_map(); + 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 session) { const auto key = u"boosts_per_sent_gift"_q; return session->account().appConfig().get(key, 0); } -[[nodiscard]] int SourcesCount( +struct Sources { + int groups = 0; + int channels = 0; +}; +[[nodiscard]] Sources SourcesCount( + not_null to, const std::vector &from, const std::vector &slots) { - auto checked = base::flat_set(); - checked.reserve(slots.size()); + auto groups = base::flat_set(); + groups.reserve(slots.size()); + auto channels = base::flat_set(); + 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 ReassignBoostsBox( not_null to, std::vector from, - Fn slots, int sources)> reassign, + Fn slots, int groups, int channels)> reassign, Fn 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 ReassignBoostsBox( ) | rpl::start_with_next([=](std::vector 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(), [=] { diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h index f15cf0b14..0f7d01106 100644 --- a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h +++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h @@ -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 channel); + [[nodiscard]] int BoostsForGift(not_null session); object_ptr ReassignBoostsBox( not_null to, std::vector from, - Fn slots, int sources)> reassign, + Fn slots, int groups, int channels)> reassign, Fn cancel); [[nodiscard]] object_ptr CreateBoostReplaceUserpics( diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 8baf8a535..93df20cfa 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -112,6 +112,10 @@ void PreloadSticker(const std::shared_ptr &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 &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 ""; }(); diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index ba6dae46b..b7fb7a40e 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -62,6 +62,8 @@ enum class PremiumPreview { RealTimeTranslation, Wallpapers, TagsForMessages, + LastSeen, + MessagePrivacy, kCount, }; diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index cd92a5177..2a51f8a4e 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -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 @@ -339,16 +332,16 @@ SendFilesBox::SendFilesBox( not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - SendFilesLimits limits, - SendFilesCheck check, + not_null 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 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( + 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); diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index b1db58787..af712eda1 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -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 show; Ui::PreparedList list; TextWithTags caption; + PeerData *captionToPeer = nullptr; SendFilesLimits limits = {}; SendFilesCheck check; Api::SendType sendType = {}; @@ -108,8 +113,7 @@ public: not_null controller, Ui::PreparedList &&list, const TextWithTags &caption, - SendFilesLimits limits, - SendFilesCheck check, + not_null 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 _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 _cancelledCallback; @@ -244,6 +250,8 @@ private: object_ptr _emojiToggle = { nullptr }; base::unique_qptr _emojiPanel; base::unique_qptr _emojiFilter; + using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; + base::unique_qptr _charsLimitation; object_ptr _groupFiles = { nullptr }; object_ptr _sendImagesAsPhotos = { nullptr }; diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 3f35e9591..8a88acfba 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -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 show, - not_null megagroup); + not_null megagroup, + bool isEmoji); [[nodiscard]] Main::Session &session() const; @@ -108,7 +113,7 @@ public: } void setInnerFocus(); - void saveGroupSet(); + void saveGroupSet(Fn 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 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 _megagroupSelectedSet; object_ptr _megagroupSetField = { nullptr }; @@ -429,15 +437,16 @@ StickersBox::StickersBox( StickersBox::StickersBox( QWidget*, std::shared_ptr show, - not_null megagroup) + not_null 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 show, - not_null megagroup) + not_null 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, 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 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 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(); + } + _checkingGroupLevel = false; + const auto appConfig = &peer->session().account().appConfig(); + const auto required = appConfig->get( + "group_emoji_stickers_level_min", + 4); + if (level >= required) { + save(); + return std::optional(); + } + 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) { diff --git a/Telegram/SourceFiles/boxes/stickers_box.h b/Telegram/SourceFiles/boxes/stickers_box.h index 255ed3936..89b31ccf5 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.h +++ b/Telegram/SourceFiles/boxes/stickers_box.h @@ -66,7 +66,8 @@ public: StickersBox( QWidget*, std::shared_ptr show, - not_null megagroup); + not_null megagroup, + bool isEmoji); StickersBox( QWidget*, std::shared_ptr show, diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 21f1ad3c3..a39019a93 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -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; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 212735fe2..3d1b2b13b 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -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() @@ -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 &&close) { + Expects(group->mgInfo != nullptr); + + if (group->mgInfo->emojiSet) { + session().api().setGroupEmojiSet(group, {}); + } + close(); + }), + .cancelled = [](Fn &&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 EmojiListWidget::resolveCustomRecent( ).first->second.emoji.get(); } +void EmojiListWidget::refreshMegagroupStickers( + Fn 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 EmojiListWidget::fillIcons() { auto result = std::vector(); result.reserve(2 + _custom.size()); @@ -2248,6 +2393,11 @@ std::vector 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); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 08d45c3cb..647a2c789 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -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 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 createButtonRipple( @@ -370,10 +379,13 @@ private: const ComposeFeatures _features; Mode _mode = Mode::Full; std::unique_ptr _search; + MTP::Sender _api; const int _staticCount = 0; StickersListFooter *_footer = nullptr; std::unique_ptr _premiumIcon; std::unique_ptr _localSetsManager; + ChannelData *_megagroupSet = nullptr; + uint64 _megagroupSetIdRequested = 0; Fn( DocumentId, Fn)> _customRecentFactory; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index ee3418394..0cef46221 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -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 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 field) { return false; } +void InitMessageFieldFade( + not_null 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(field.get()); + const auto bottomFade = Ui::CreateChild(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 session, not_null field) { diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 7bce7facf..a8f248e82 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -79,6 +79,10 @@ void InitSpellchecker( bool HasSendText(not_null field); +void InitMessageFieldFade( + not_null field, + const style::color &bg); + struct InlineBotQuery { QString query; QString username; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index ea4c71877..e1e176cf7 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -1790,7 +1790,8 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { removeSet(sets[button->section].id); } } else if (std::get_if(&pressed)) { - _show->showBox(Box(_show, _megagroupSet)); + const auto isEmoji = false; + _show->showBox(Box(_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(_show, _megagroupSet)); + const auto isEmoji = false; + checkHideWithBox(Box(_show, _megagroupSet, isEmoji)); return; } else if (_megagroupSet->mgInfo->stickerSet.id) { setId = _megagroupSet->mgInfo->stickerSet.id; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 6fc2c555a..5841f2c9e 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -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); } diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 6feacdb5b..fb145f7f1 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include 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); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 649c6ad45..d9af5939e 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h index bada02a91..a5924bc3d 100644 --- a/Telegram/SourceFiles/data/data_boosts.h +++ b/Telegram/SourceFiles/data/data_boosts.h @@ -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; diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 9de11be5b..2c29ef8fc 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index fecbe6287..469351af2 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -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 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(); @@ -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()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d55e8a4e6..afeb20cbd 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -114,6 +114,7 @@ public: base::flat_map, Restricted> lastRestricted; base::flat_set> markupSenders; base::flat_set> bots; + rpl::event_stream unrestrictedByBoostsChanges; // For admin badges, full admins list with ranks. base::flat_map 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 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 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 _call; PeerId _callDefaultJoinAs = 0; - int _slowmodeSeconds = 0; - TimeId _slowmodeLastMessage = 0; - }; namespace Data { diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index dfbd5f23b..9f6231652 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -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); diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index eee8ac7e1..7361373df 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -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 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 { diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index b2323af8a..1aae67928 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -709,10 +709,6 @@ private: }; -[[nodiscard]] TextForMimeData WithCaptionClipboardText( - const QString &attachType, - TextForMimeData &&caption); - [[nodiscard]] Invoice ComputeInvoiceData( not_null item, const MTPDmessageMediaInvoice &data); diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index 4555bda3b..d2790a288 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -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(); diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 1a0abf134..78c2051a0 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -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() diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index cffc40d3c..3071cddf2 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -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 diff --git a/Telegram/SourceFiles/data/data_scheduled_messages.cpp b/Telegram/SourceFiles/data/data_scheduled_messages.cpp index f122f05b9..9ea40996e 100644 --- a/Telegram/SourceFiles/data/data_scheduled_messages.cpp +++ b/Telegram/SourceFiles/data/data_scheduled_messages.cpp @@ -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(), diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 165fe27db..65fa78c8f 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -245,6 +245,12 @@ Session::Session(not_null session) , _bigFileCache(Core::App().databases().get( _session->local().cacheBigFilePath(), _session->local().cacheBigFileSettings())) +, _groupFreeTranscribeLevel(session->account().appConfig().value( +) | rpl::map([=] { + return session->account().appConfig().get( + u"group_transcribe_level_min"_q, + 6); +})) , _chatsList( session, FilterId(), @@ -2189,8 +2195,7 @@ rpl::producer 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 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 Session::maxPinnedChatsLimitValue( rpl::producer Session::maxPinnedChatsLimitValue( not_null 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 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 &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(), diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 0a779a4b8..d391d1d31 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -367,6 +367,7 @@ public: not_null forum) const; [[nodiscard]] rpl::producer maxPinnedChatsLimitValue( not_null saved) const; + [[nodiscard]] int groupFreeTranscribeLevel() const; [[nodiscard]] const std::vector &pinnedChatsOrder( Folder *folder) const; [[nodiscard]] const std::vector &pinnedChatsOrder( @@ -887,6 +888,7 @@ private: QPointer _exportSuggestion; rpl::variable _contactsLoaded = false; + rpl::variable _groupFreeTranscribeLevel; rpl::event_stream _chatsListLoadedEvents; rpl::event_stream _chatsListChanged; rpl::event_stream> _userIsBotChanges; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 7631892a3..d4b12de6a 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -164,6 +164,15 @@ using UpdateFlag = StoryUpdate::Flag; return false; } +[[nodiscard]] PeerData *FromPeer( + not_null 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, Fn done) : _story(story) , _done(std::move(done)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 4fb4796b2..41da0239e 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -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; diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 5b271b5b6..0ce9ccf78 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -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 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 CustomEmojiManager::internal( info.textColor); } +std::unique_ptr CustomEmojiManager::userpic( + QStringView data, + Fn 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( + data.toString(), + Ui::MakeUserpicThumbnail(_owner->peer(id)), + std::move(update), + padding, + size); +} + void CustomEmojiManager::resolve( QStringView data, not_null listener) { @@ -954,6 +987,14 @@ QString CustomEmojiManager::registerInternalEmoji( return result + InternalPadding(padding); } +[[nodiscard]] QString CustomEmojiManager::peerUserpicEmojiData( + not_null 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 document) { return SingleCustomEmoji(document->id); } -bool AllowEmojiWithoutPremium(not_null peer) { - return peer->isSelf(); +bool AllowEmojiWithoutPremium( + not_null 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( diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 947f8bfda..1cbbe0de9 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -92,6 +92,10 @@ public: QMargins padding = {}, bool textColor = true); + [[nodiscard]] QString peerUserpicEmojiData( + not_null peer, + QMargins padding = {}); + [[nodiscard]] uint64 coloredSetId() const; private: @@ -146,6 +150,10 @@ private: LoaderFactory factory); [[nodiscard]] std::unique_ptr internal( QStringView data); + [[nodiscard]] std::unique_ptr userpic( + QStringView data, + Fn update, + int size); [[nodiscard]] static int SizeIndex(SizeTag tag); const not_null _owner; @@ -201,7 +209,9 @@ private: [[nodiscard]] TextWithEntities SingleCustomEmoji( not_null document); -[[nodiscard]] bool AllowEmojiWithoutPremium(not_null peer); +[[nodiscard]] bool AllowEmojiWithoutPremium( + not_null peer, + DocumentData *exactEmoji = nullptr); void InsertCustomEmoji( not_null field, diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 3047c27e2..dab3e303a 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -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" diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index eae45defe..509f0a8a3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -454,7 +454,7 @@ void Row::paintUserpic( ? _cornerBadgeShown : !_cornerBadgeUserpic->layersManager.isDisplayedNone(); const auto storiesPeer = settings->disableStories ? nullptr : peer - ? ((peer->isUser() || peer->isBroadcast()) ? peer : nullptr) + ? ((peer->isUser() || peer->isChannel()) ? peer : nullptr) : nullptr; const auto storiesFolder = peer ? nullptr : _id.folder(); const auto storiesHas = storiesPeer diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 01864c354..fa45d5dfd 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -532,6 +532,16 @@ void Widget::chosenRow(const ChosenRow &row) { topic, row.message.fullId.msg, Window::SectionShow::Way::ClearStack); + } else if (history + && row.userpicClick + && (row.message.fullId.msg == ShowAtUnreadMsgId) + && history->peer->hasActiveStories() + && !history->peer->isSelf()) { + const auto settings = &AyuSettings::getInstance(); + if (!settings->disableStories) { + controller()->openPeerStories(history->peer->id); + } + return; } else if (history && history->isForum() && !row.message.fullId @@ -561,17 +571,7 @@ void Widget::chosenRow(const ChosenRow &row) { } return; } else if (history) { - const auto settings = &AyuSettings::getInstance(); const auto peer = history->peer; - if (row.message.fullId.msg == ShowAtUnreadMsgId) { - if (row.userpicClick - && peer->hasActiveStories() - && !peer->isSelf() - && !settings->disableStories) { - controller()->openPeerStories(peer->id); - return; - } - } const auto showAtMsgId = controller()->uniqueChatsInSearchResults() ? ShowAtUnreadMsgId : row.message.fullId.msg; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp index 25f996620..bea4a1b29 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.cpp @@ -22,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "info/info_memento.h" #include "main/main_session.h" #include "lang/lang_keys.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_menu_icons.h" @@ -31,99 +33,6 @@ namespace { constexpr auto kShownLastCount = 3; -class PeerUserpic final : public Thumbnail { -public: - explicit PeerUserpic(not_null peer); - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - struct Subscribed { - explicit Subscribed(Fn callback) - : callback(std::move(callback)) { - } - - Ui::PeerUserpicView view; - Fn callback; - InMemoryKey key; - rpl::lifetime photoLifetime; - rpl::lifetime downloadLifetime; - }; - - [[nodiscard]] bool waitingUserpicLoad() const; - void processNewPhoto(); - - const not_null _peer; - QImage _frame; - std::unique_ptr _subscribed; - -}; - -class StoryThumbnail : public Thumbnail { -public: - explicit StoryThumbnail(FullStoryId id); - virtual ~StoryThumbnail() = default; - - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -protected: - struct Thumb { - Image *image = nullptr; - bool blurred = false; - }; - [[nodiscard]] virtual Main::Session &session() = 0; - [[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0; - virtual void clear() = 0; - -private: - const FullStoryId _id; - QImage _full; - rpl::lifetime _subscription; - QImage _prepared; - bool _blurred = false; - -}; - -class PhotoThumbnail final : public StoryThumbnail { -public: - PhotoThumbnail(not_null photo, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _photo; - std::shared_ptr _media; - -}; - -class VideoThumbnail final : public StoryThumbnail { -public: - VideoThumbnail(not_null video, FullStoryId id); - -private: - Main::Session &session() override; - Thumb loaded(FullStoryId id) override; - void clear() override; - - const not_null _video; - std::shared_ptr _media; - -}; - -class EmptyThumbnail final : public Thumbnail { -public: - QImage image(int size) override; - void subscribeToUpdates(Fn callback) override; - -private: - QImage _cached; - -}; - class State final { public: State(not_null data, Data::StorySourcesList list); @@ -135,193 +44,10 @@ private: const Data::StorySourcesList _list; base::flat_map< not_null, - std::shared_ptr> _userpics; + std::shared_ptr> _userpics; }; -PeerUserpic::PeerUserpic(not_null peer) -: _peer(peer) { -} - -QImage PeerUserpic::image(int size) { - Expects(_subscribed != nullptr); - - const auto good = (_frame.width() == size * _frame.devicePixelRatio()); - const auto key = _peer->userpicUniqueKey(_subscribed->view); - if (!good || (_subscribed->key != key && !waitingUserpicLoad())) { - const auto ratio = style::DevicePixelRatio(); - _subscribed->key = key; - _frame = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _frame.setDevicePixelRatio(ratio); - _frame.fill(Qt::transparent); - - auto p = Painter(&_frame); - _peer->paintUserpic(p, _subscribed->view, 0, 0, size); - } - return _frame; -} - -bool PeerUserpic::waitingUserpicLoad() const { - return _peer->hasUserpic() && _peer->useEmptyUserpic(_subscribed->view); -} - -void PeerUserpic::subscribeToUpdates(Fn callback) { - if (!callback) { - _subscribed = nullptr; - return; - } - _subscribed = std::make_unique(std::move(callback)); - - _peer->session().changes().peerUpdates( - _peer, - Data::PeerUpdate::Flag::Photo - ) | rpl::start_with_next([=] { - _subscribed->callback(); - processNewPhoto(); - }, _subscribed->photoLifetime); - - processNewPhoto(); -} - -void PeerUserpic::processNewPhoto() { - Expects(_subscribed != nullptr); - - if (!waitingUserpicLoad()) { - _subscribed->downloadLifetime.destroy(); - return; - } - _peer->session().downloaderTaskFinished( - ) | rpl::filter([=] { - return !waitingUserpicLoad(); - }) | rpl::start_with_next([=] { - _subscribed->callback(); - _subscribed->downloadLifetime.destroy(); - }, _subscribed->downloadLifetime); -} - -StoryThumbnail::StoryThumbnail(FullStoryId id) -: _id(id) { -} - -QImage StoryThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_prepared.width() != size * ratio) { - if (_full.isNull()) { - _prepared = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _prepared.fill(Qt::black); - } else { - const auto width = _full.width(); - const auto skip = std::max((_full.height() - width) / 2, 0); - _prepared = _full.copy(0, skip, width, width).scaled( - QSize(size, size) * ratio, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } - _prepared = Images::Circle(std::move(_prepared)); - _prepared.setDevicePixelRatio(ratio); - } - return _prepared; -} - -void StoryThumbnail::subscribeToUpdates(Fn callback) { - _subscription.destroy(); - if (!callback) { - clear(); - return; - } else if (!_full.isNull() && !_blurred) { - return; - } - const auto thumbnail = loaded(_id); - if (const auto image = thumbnail.image) { - _full = image->original(); - } - _blurred = thumbnail.blurred; - if (!_blurred) { - _prepared = QImage(); - } else { - _subscription = session().downloaderTaskFinished( - ) | rpl::filter([=] { - const auto thumbnail = loaded(_id); - if (!thumbnail.blurred) { - _full = thumbnail.image->original(); - _prepared = QImage(); - _blurred = false; - return true; - } - return false; - }) | rpl::take(1) | rpl::start_with_next(callback); - } -} - -PhotoThumbnail::PhotoThumbnail(not_null photo, FullStoryId id) -: StoryThumbnail(id) -, _photo(photo) { -} - -Main::Session &PhotoThumbnail::session() { - return _photo->session(); -} - -StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _photo->createMediaView(); - _media->wanted(Data::PhotoSize::Small, id); - } - if (const auto small = _media->image(Data::PhotoSize::Small)) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void PhotoThumbnail::clear() { - _media = nullptr; -} - -VideoThumbnail::VideoThumbnail( - not_null video, - FullStoryId id) -: StoryThumbnail(id) -, _video(video) { -} - -Main::Session &VideoThumbnail::session() { - return _video->session(); -} - -StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) { - if (!_media) { - _media = _video->createMediaView(); - _media->thumbnailWanted(id); - } - if (const auto small = _media->thumbnail()) { - return { .image = small }; - } - return { .image = _media->thumbnailInline(), .blurred = true }; -} - -void VideoThumbnail::clear() { - _media = nullptr; -} - -QImage EmptyThumbnail::image(int size) { - const auto ratio = style::DevicePixelRatio(); - if (_cached.width() != size * ratio) { - _cached = QImage( - QSize(size, size) * ratio, - QImage::Format_ARGB32_Premultiplied); - _cached.fill(Qt::black); - _cached.setDevicePixelRatio(ratio); - } - return _cached; -} - -void EmptyThumbnail::subscribeToUpdates(Fn callback) { -} - State::State(not_null data, Data::StorySourcesList list) : _data(data) , _list(list) { @@ -335,12 +61,12 @@ Content State::next() { const auto source = _data->source(info.id); Assert(source != nullptr); - auto userpic = std::shared_ptr(); + auto userpic = std::shared_ptr(); const auto peer = source->peer; if (const auto i = _userpics.find(peer); i != end(_userpics)) { userpic = i->second; } else { - userpic = MakeUserpicThumbnail(peer); + userpic = Ui::MakeUserpicThumbnail(peer, true); _userpics.emplace(peer, userpic); } result.elements.push_back({ @@ -430,7 +156,7 @@ rpl::producer LastForPeer(not_null peer) { result.elements.reserve(ids.size()); result.elements.push_back({ .id = uint64(id), - .thumbnail = MakeStoryThumbnail(*maybe), + .thumbnail = Ui::MakeStoryThumbnail(*maybe), .count = 1U, .unreadCount = unread ? 1U : 0U, }); @@ -479,23 +205,6 @@ rpl::producer LastForPeer(not_null peer) { }) | rpl::flatten_latest(); } -std::shared_ptr MakeUserpicThumbnail(not_null peer) { - return std::make_shared(peer); -} - -std::shared_ptr MakeStoryThumbnail( - not_null story) { - using Result = std::shared_ptr; - const auto id = story->fullId(); - return v::match(story->media().data, [](v::null_t) -> Result { - return std::make_shared(); - }, [&](not_null photo) -> Result { - return std::make_shared(photo, id); - }, [&](not_null video) -> Result { - return std::make_shared(video, id); - }); -} - void FillSourceMenu( not_null controller, const ShowMenuRequest &request) { @@ -512,14 +221,19 @@ void FillSourceMenu( controller->showSection(Info::Stories::Make(peer)); }, &st::menuIconStoriesSavedSection); } else { + const auto group = peer->isMegagroup(); const auto channel = peer->isChannel(); - const auto showHistoryText = channel + const auto showHistoryText = group + ? tr::lng_context_open_group(tr::now) + : channel ? tr::lng_context_open_channel(tr::now) : tr::lng_profile_send_message(tr::now); add(showHistoryText, [=] { controller->showPeerHistory(peer); }, channel ? &st::menuIconChannel : &st::menuIconChatBubble); - const auto viewProfileText = channel + const auto viewProfileText = group + ? tr::lng_context_view_group(tr::now) + : channel ? tr::lng_context_view_channel(tr::now) : tr::lng_context_view_profile(tr::now); add(viewProfileText, [=] { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h index b42715d54..c38854f81 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_content.h @@ -23,7 +23,6 @@ class SessionController; namespace Dialogs::Stories { struct Content; -class Thumbnail; struct ShowMenuRequest; [[nodiscard]] rpl::producer ContentForSession( @@ -32,11 +31,6 @@ struct ShowMenuRequest; [[nodiscard]] rpl::producer LastForPeer(not_null peer); -[[nodiscard]] std::shared_ptr MakeUserpicThumbnail( - not_null peer); -[[nodiscard]] std::shared_ptr MakeStoryThumbnail( - not_null story); - void FillSourceMenu( not_null controller, const ShowMenuRequest &request); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp index e6a93af3a..f615f4fb3 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" #include "ui/painter.h" #include "styles/style_dialogs.h" diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h index 71d4aca42..869767745 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_stories_list.h @@ -10,9 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/qt/qt_compare.h" #include "base/timer.h" #include "base/weak_ptr.h" +#include "ui/effects/animations.h" +#include "ui/text/text_custom_emoji.h" #include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/rp_widget.h" -#include "ui/effects/animations.h" class QPainter; @@ -23,22 +24,17 @@ struct DialogsStoriesList; namespace Ui { class PopupMenu; +class DynamicImage; struct OutlineSegment; class ImportantTooltip; } // namespace Ui namespace Dialogs::Stories { -class Thumbnail { -public: - [[nodiscard]] virtual QImage image(int size) = 0; - virtual void subscribeToUpdates(Fn callback) = 0; -}; - struct Element { uint64 id = 0; QString name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; uint32 count : 15 = 0; uint32 unreadCount : 15 = 0; uint32 skipSmall : 1 = 0; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 7c9910e1e..f7928f006 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1458,6 +1458,10 @@ ServiceAction ParseServiceAction( content.winners = data.vwinners_count().v; content.unclaimed = data.vunclaimed_count().v; result.content = content; + }, [&](const MTPDmessageActionBoostApply &data) { + auto content = ActionBoostApply(); + content.boosts = data.vboosts().v; + result.content = content; }, [](const MTPDmessageActionEmpty &data) {}); return result; } diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 20bd15206..5f2c2da39 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -559,6 +559,10 @@ struct ActionGiveawayResults { int unclaimed = 0; }; +struct ActionBoostApply { + int boosts = 0; +}; + struct ServiceAction { std::variant< v::null_t, @@ -599,7 +603,8 @@ struct ServiceAction { ActionSetChatWallPaper, ActionGiftCode, ActionGiveawayLaunch, - ActionGiveawayResults> content; + ActionGiveawayResults, + ActionBoostApply> content; }; ServiceAction ParseServiceAction( diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index b23329c03..b50c105f3 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "export/output/export_output_file.h" #include "mtproto/mtproto_response.h" #include "base/bytes.h" +#include "base/options.h" #include "base/random.h" #include #include @@ -1069,7 +1070,35 @@ void ApiWrap::requestContacts(FnMut done) { mainRequest(MTPcontacts_GetSaved( )).done([=](const MTPVector &result) { _contactsProcess->result = Data::ParseContactsList(result); - requestTopPeersSlice(); + + const auto resolve = [=](int index, const auto &resolveNext) -> void { + if (index == _contactsProcess->result.list.size()) { + return requestTopPeersSlice(); + } + const auto &contact = _contactsProcess->result.list[index]; + mainRequest(MTPcontacts_ResolvePhone( + MTP_string(qs(contact.phoneNumber)) + )).done([=](const MTPcontacts_ResolvedPeer &result) { + auto &contact = _contactsProcess->result.list[index]; + contact.userId = result.data().vpeer().match([&]( + const MTPDpeerUser &user) { + return UserId(user.vuser_id()); + }, [](const auto &) { + return UserId(); + }); + resolveNext(index + 1, resolveNext); + }).fail([=](const MTP::Error &) { + resolveNext(index + 1, resolveNext); + return true; + }).send(); + }; + + if (base::options::lookup("show-peer-id-below-about").value()) { + resolve(0, resolve); + } else { + requestTopPeersSlice(); + } + }).send(); } diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index 74c1f1f3d..1aa595089 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -357,6 +357,7 @@ struct UserpicData { QString largeLink; QByteArray firstName; QByteArray lastName; + QByteArray tooltip; }; struct StoryData { @@ -743,9 +744,17 @@ QByteArray HtmlWriter::Wrap::pushUserpic(const UserpicData &userpic) { }, { "style", sizeStyle } })); - result.append(pushDiv( - "initials", - "line-height: " + size)); + if (userpic.tooltip.isEmpty()) { + result.append(pushDiv( + "initials", + "line-height: " + size)); + } else { + result.append(pushTag("div", { + { "class", "initials" }, + { "style", "line-height: " + size }, + { "title", userpic.tooltip }, + })); + } auto character = [](const QByteArray &from) { const auto utf = QString::fromUtf8(from).trimmed(); return utf.isEmpty() @@ -1301,6 +1310,11 @@ auto HtmlWriter::Wrap::pushMessage( return QByteArray::number(data.winners) + " of the giveaway were randomly selected by Telegram " "and received private messages with giftcodes."; + }, [&](const ActionBoostApply &data) { + return serviceFrom + + " boosted the group " + + QByteArray::number(data.boosts) + + (data.boosts > 1 ? " times" : " time"); }, [](v::null_t) { return QByteArray(); }); if (!serviceText.isEmpty()) { @@ -2488,6 +2502,10 @@ Result HtmlWriter::writeSavedContacts(const Data::ContactsList &data) { }; userpic.firstName = contact.firstName; userpic.lastName = contact.lastName; + if (contact.userId) { + const auto raw = contact.userId.bare & PeerId::kChatTypeMask; + userpic.tooltip = (u"ID: "_q + QString::number(raw)).toUtf8(); + } block.append(file->pushListEntry( userpic, ComposeName(userpic, "Deleted Account"), diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index dc23fd38f..dde36a5a3 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -617,6 +617,10 @@ QByteArray SerializeMessage( ? "set_same_chat_wallpaper" : "set_chat_wallpaper"); pushReplyToMsgId("message_id"); + }, [&](const ActionBoostApply &data) { + pushActor(); + pushAction("boost_apply"); + push("boosts", data.boosts); }, [](v::null_t) {}); if (v::is_null(message.action.content)) { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index d652d0f4c..b7bb4b9e0 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_text.h" #include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_filter.h" +#include "history/view/history_view_context_menu.h" #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_cursor_state.h" @@ -1251,6 +1252,12 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, lnkDocument] { saveDocumentToFile(lnkDocument); }), &st::menuIconDownload); + + HistoryView::AddCopyFilename( + _menu, + lnkDocument, + [] { return false; }); + if (lnkDocument->hasAttachedStickers()) { const auto controller = _controller; auto callback = [=] { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index f9ece93b0..0642a1568 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -116,6 +116,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, TimeId newDate) { MTP_flags(data.vflags().v & ~removeFlags), data.vid(), data.vfrom_id() ? *data.vfrom_id() : MTPPeer(), + MTPint(), // from_boosts_applied data.vpeer_id(), MTPPeer(), // saved_peer_id data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(), @@ -734,6 +735,7 @@ void GenerateItems( using LogBan = MTPDchannelAdminLogEventActionParticipantToggleBan; using LogPromote = MTPDchannelAdminLogEventActionParticipantToggleAdmin; using LogSticker = MTPDchannelAdminLogEventActionChangeStickerSet; + using LogEmoji = MTPDchannelAdminLogEventActionChangeEmojiStickerSet; using LogPreHistory = MTPDchannelAdminLogEventActionTogglePreHistoryHidden; using LogPermissions = MTPDchannelAdminLogEventActionDefaultBannedRights; @@ -1151,6 +1153,50 @@ void GenerateItems( } }; + const auto createChangeEmojiSet = [&](const LogEmoji &action) { + const auto set = action.vnew_stickerset(); + const auto removed = (set.type() == mtpc_inputStickerSetEmpty); + if (removed) { + const auto text = tr::lng_admin_log_removed_emoji_group( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); + } else { + const auto text = tr::lng_admin_log_changed_emoji_group( + tr::now, + lt_from, + fromLinkText, + lt_sticker_set, + Ui::Text::Link( + tr::lng_admin_log_changed_emoji_set(tr::now), + QString()), + Ui::Text::WithEntities); + const auto setLink = std::make_shared([=]( + ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->show( + Box( + controller->uiShow(), + Data::FromInputSet(set), + Data::StickersType::Emoji), + Ui::LayerOption::CloseOther); + } + }); + auto message = PreparedServiceText{ text }; + message.links.push_back(fromLink); + message.links.push_back(setLink); + addPart(history->makeMessage( + history->nextNonHistoryEntryId(), + MessageFlag::AdminLogEntry, + date, + std::move(message), + peerToUser(from->id))); + } + }; + const auto createTogglePreHistoryHidden = [&]( const LogPreHistory &action) { const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); @@ -2003,6 +2049,7 @@ void GenerateItems( createParticipantToggleBan, createParticipantToggleAdmin, createChangeStickerSet, + createChangeEmojiSet, createTogglePreHistoryHidden, createDefaultBannedRights, createStopPoll, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c2e889e06..ca841583b 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -360,6 +360,7 @@ HistoryInner::HistoryInner( _theme = std::move(theme); controller->setChatStyleTheme(_theme); }, lifetime()); + Assert(_theme != nullptr); setAttribute(Qt::WA_AcceptTouchEvents); @@ -2233,6 +2234,11 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lnkIsVideo ? tr::lng_context_save_video(tr::now) : (lnkIsVoice ? tr::lng_context_save_audio(tr::now) : (lnkIsAudio ? tr::lng_context_save_audio_file(tr::now) : tr::lng_context_save_file(tr::now))), base::fn_delayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [=] { saveDocumentToFile(itemId, document); }), &st::menuIconDownload); + + HistoryView::AddCopyFilename( + _menu, + document, + [=] { return showCopyRestrictionForSelected(); }); } if (document->hasAttachedStickers()) { _menu->addAction(tr::lng_context_attached_stickers(tr::now), [=] { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index b4948d2f0..4e471faef 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -145,6 +145,7 @@ struct HistoryItem::CreateConfig { UserId viaBotId = 0; int viewsCount = -1; int forwardsCount = -1; + int boostsApplied = 0; QString postAuthor; MsgId originalId = 0; @@ -358,6 +359,8 @@ HistoryItem::HistoryItem( FlagsFromMTP(id, data.vflags().v, localFlags), data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)) { + _boostsApplied = data.vfrom_boosts_applied().value_or_empty(); + const auto media = data.vmedia(); const auto checked = media ? CheckMessageMedia(*media) @@ -600,11 +603,8 @@ HistoryItem::HistoryItem( } } - const auto dropCustomEmoji = dropForwardInfo - && !history->session().premium() - && !history->peer->isSelf(); - setText(dropCustomEmoji - ? DropCustomEmoji(original->originalText()) + setText(dropForwardInfo + ? DropDisallowedCustomEmoji(history->peer, original->originalText()) : original->originalText()); } @@ -1526,8 +1526,9 @@ void HistoryItem::returnSavedMedia() { return; } const auto wasGrouped = history()->owner().groups().isGrouped(this); - _media = std::move(_savedLocalEditMediaData->media); - setText(_savedLocalEditMediaData->text); + const auto data = Get(); + _media = std::move(data->media); + setText(data->text); clearSavedMedia(); if (wasGrouped) { history()->owner().groups().refreshMessage(this, true); @@ -1540,19 +1541,18 @@ void HistoryItem::returnSavedMedia() { void HistoryItem::savePreviousMedia() { Expects(_media != nullptr); - using Data = SavedMediaData; - _savedLocalEditMediaData = std::make_unique(Data{ - .text = originalText(), - .media = _media->clone(this), - }); + AddComponents(HistoryMessageSavedMediaData::Bit()); + const auto data = Get(); + data->text = originalText(); + data->media = _media->clone(this); } bool HistoryItem::isEditingMedia() const { - return _savedLocalEditMediaData != nullptr; + return Has(); } void HistoryItem::clearSavedMedia() { - _savedLocalEditMediaData = nullptr; + RemoveComponents(HistoryMessageSavedMediaData::Bit()); } bool HistoryItem::definesReplyKeyboard() const { @@ -1704,9 +1704,10 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { // } //} + const auto editingMedia = isEditingMedia(); const auto updatingSavedLocalEdit = !edition.savePreviousMedia - && (_savedLocalEditMediaData != nullptr); - if (!_savedLocalEditMediaData && edition.savePreviousMedia) { + && editingMedia; + if (!editingMedia && edition.savePreviousMedia) { savePreviousMedia(); } Assert(!updatingSavedLocalEdit || !isLocalUpdateMedia()); @@ -1735,7 +1736,7 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setReplyMarkup(base::take(edition.replyMarkup)); } if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->media = edition.mtpMedia + Get()->media = edition.mtpMedia ? CreateMedia(this, *edition.mtpMedia) : nullptr; } else { @@ -1752,13 +1753,13 @@ void HistoryItem::applyEdition(HistoryMessageEdition &&edition) { setForwardsCount(edition.forwards); } const auto &checkedMedia = updatingSavedLocalEdit - ? _savedLocalEditMediaData->media + ? Get()->media : _media; auto updatedText = checkedMedia ? edition.textWithEntities : EnsureNonEmpty(edition.textWithEntities); if (updatingSavedLocalEdit) { - _savedLocalEditMediaData->text = std::move(updatedText); + Get()->text = std::move(updatedText); } else { setText(std::move(updatedText)); addToSharedMediaIndex(); @@ -1918,7 +1919,7 @@ void HistoryItem::applySentMessage( void HistoryItem::updateSentContent( const TextWithEntities &textWithEntities, const MTPMessageMedia *media) { - if (_savedLocalEditMediaData) { + if (isEditingMedia()) { return; } setText(textWithEntities); @@ -2050,10 +2051,9 @@ void HistoryItem::destroyHistoryEntry() { } Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const { - auto result = Storage::SharedMediaTypesMask {}; - const auto media = _savedLocalEditMediaData - ? _savedLocalEditMediaData->media.get() - : _media.get(); + auto result = Storage::SharedMediaTypesMask{}; + const auto saved = Get(); + const auto media = saved ? saved->media.get() : _media.get(); if (media) { result.set(media->sharedMediaTypes()); } @@ -2717,7 +2717,7 @@ TextWithEntities HistoryItem::translatedTextWithLocalEntities() const { TextForMimeData HistoryItem::clipboardText() const { return isService() ? TextForMimeData() - : TextForMimeData::WithExpandedLinks(_text); + : TextForMimeData::WithExpandedLinks(translatedText()); } bool HistoryItem::changeViewsCount(int count) { @@ -3497,6 +3497,12 @@ void HistoryItem::createComponents(CreateConfig &&config) { } else { _flags &= ~MessageFlag::HasReplyMarkup; } + + if (out() && isSending()) { + if (const auto channel = _history->peer->asMegagroup()) { + _boostsApplied = channel->mgInfo->boostsApplied; + } + } } bool HistoryItem::checkRepliesPts( @@ -4881,11 +4887,13 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareGiveawayLaunch = [&](const MTPDmessageActionGiveawayLaunch &action) { auto result = PreparedServiceText(); result.links.push_back(fromLink()); - result.text = tr::lng_action_giveaway_started( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); + result.text = (_history->peer->isMegagroup() + ? tr::lng_action_giveaway_started_group + : tr::lng_action_giveaway_started)( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); return result; }; @@ -4906,6 +4914,20 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { return result; }; + auto prepareBoostApply = [&](const MTPDmessageActionBoostApply &action) { + auto result = PreparedServiceText(); + const auto boosts = action.vboosts().v; + result.links.push_back(fromLink()); + result.text = tr::lng_action_boost_apply( + tr::now, + lt_count, + boosts, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + return result; + }; + setServiceText(action.match( prepareChatAddUserText, prepareChatJoinedByLink, @@ -4947,6 +4969,7 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { prepareGiftCode, prepareGiveawayLaunch, prepareGiveawayResults, + prepareBoostApply, PrepareErrorText)); // Additional information. diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index f19462758..689bca9b8 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -21,6 +21,7 @@ struct HistoryMessageMarkupData; struct HistoryMessageReplyMarkup; struct HistoryMessageTranslation; struct HistoryMessageForwarded; +struct HistoryMessageSavedMediaData; struct HistoryServiceDependentData; enum class HistorySelfDestructType; struct PreparedServiceText; @@ -537,16 +538,15 @@ public: return _ttlDestroyAt; } + [[nodiscard]] int boostsApplied() const { + return _boostsApplied; + } + MsgId id; private: struct CreateConfig; - struct SavedMediaData { - TextWithEntities text; - std::unique_ptr media; - }; - HistoryItem( not_null history, MsgId id, @@ -656,13 +656,13 @@ private: TextWithEntities _text; - std::unique_ptr _savedLocalEditMediaData; std::unique_ptr _media; std::unique_ptr _reactions; crl::time _reactionsLastRefreshed = 0; TimeId _date = 0; TimeId _ttlDestroyAt = 0; + int _boostsApplied = 0; HistoryView::Element *_mainView = nullptr; MessageGroupId _groupId = MessageGroupId(); @@ -673,3 +673,5 @@ private: friend class HistoryView::ServiceMessagePainter; }; + +constexpr auto kSize = int(sizeof(HistoryItem)); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index bdb9d6281..05e99d2a2 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -330,7 +330,7 @@ ReplyFields ReplyFieldsFromMTP( return result; }, [&](const MTPDmessageReplyStoryHeader &data) { return ReplyFields{ - .externalPeerId = peerFromUser(data.vuser_id()), + .externalPeerId = peerFromMTP(data.vpeer()), .storyId = data.vstory_id().v, }; }); @@ -362,9 +362,9 @@ FullReplyTo ReplyToFromMTP( result.quoteOffset = data.vquote_offset().value_or_empty(); return result; }, [&](const MTPDinputReplyToStory &data) { - if (const auto parsed = Data::UserFromInputMTP( + if (const auto parsed = Data::PeerFromInputMTP( &history->owner(), - data.vuser_id())) { + data.vpeer())) { return FullReplyTo{ .storyId = { parsed->id, data.vstory_id().v }, }; diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index bd0eb6a9d..4d60ff9a3 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -149,6 +149,11 @@ struct HistoryMessageForwarded : public RuntimeComponent { + TextWithEntities text; + std::unique_ptr media; +}; + struct HistoryMessageSaved : public RuntimeComponent { Data::SavedSublist *sublist = nullptr; }; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 9865790d5..6fdc3de55 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "boxes/premium_preview_box.h" #include "calls/calls_instance.h" +#include "data/stickers/data_custom_emoji.h" #include "data/notify/data_notify_settings.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -221,13 +222,40 @@ bool LookupReplyIsTopicPost(HistoryItem *replyTo) { && (replyTo->topicRootId() != Data::ForumTopic::kGeneralId); } -TextWithEntities DropCustomEmoji(TextWithEntities text) { - text.entities.erase( - ranges::remove( - text.entities, - EntityType::CustomEmoji, - &EntityInText::type), - text.entities.end()); +TextWithEntities DropDisallowedCustomEmoji( + not_null to, + TextWithEntities text) { + if (to->session().premium() || to->isSelf()) { + return text; + } + const auto channel = to->asMegagroup(); + const auto allowSetId = channel ? channel->mgInfo->emojiSet.id : 0; + if (!allowSetId) { + text.entities.erase( + ranges::remove( + text.entities, + EntityType::CustomEmoji, + &EntityInText::type), + text.entities.end()); + } else { + const auto predicate = [&](const EntityInText &entity) { + if (entity.type() != EntityType::CustomEmoji) { + return false; + } + if (const auto id = Data::ParseCustomEmojiData(entity.data())) { + const auto document = to->owner().document(id); + if (const auto sticker = document->sticker()) { + if (sticker->set.id == allowSetId) { + return false; + } + } + } + return true; + }; + text.entities.erase( + ranges::remove_if(text.entities, predicate), + text.entities.end()); + } return text; } @@ -393,7 +421,7 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { if (const auto replyTo = action.replyTo) { if (replyTo.storyId) { return MTP_messageReplyStoryHeader( - MTP_long(peerToUser(replyTo.storyId.peer).bare), + peerToMTP(replyTo.storyId.peer), MTP_int(replyTo.storyId.story)); } using Flag = MTPDmessageReplyHeader::Flag; diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 5ebb3f0de..e468f1497 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -111,7 +111,9 @@ struct SendingErrorRequest { not_null thread, SendingErrorRequest request); -[[nodiscard]] TextWithEntities DropCustomEmoji(TextWithEntities text); +[[nodiscard]] TextWithEntities DropDisallowedCustomEmoji( + not_null to, + TextWithEntities text); [[nodiscard]] Main::Session *SessionByUniqueId(uint64 sessionUniqueId); [[nodiscard]] HistoryItem *MessageByGlobalId(GlobalMsgId globalId); diff --git a/Telegram/SourceFiles/history/history_item_text.cpp b/Telegram/SourceFiles/history/history_item_text.cpp index 9bcbee704..1c66c59a4 100644 --- a/Telegram/SourceFiles/history/history_item_text.cpp +++ b/Telegram/SourceFiles/history/history_item_text.cpp @@ -17,15 +17,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_options.h" TextForMimeData HistoryItemText(not_null item) { - auto textResult = item->clipboardText(); + const auto media = item->media(); + + auto mediaResult = media ? media->clipboardText() : TextForMimeData(); + auto textResult = mediaResult.empty() + ? item->clipboardText() + : TextForMimeData(); auto logEntryOriginalResult = [&] { const auto entry = item->Get(); if (!entry) { return TextForMimeData(); } - const auto title = TextUtilities::SingleLine(entry->page->title.isEmpty() - ? entry->page->author - : entry->page->title); + const auto title = TextUtilities::SingleLine( + entry->page->title.isEmpty() + ? entry->page->author + : entry->page->title); auto titleResult = TextForMimeData::Rich( TextUtilities::ParseEntities( title, @@ -41,6 +47,11 @@ TextForMimeData HistoryItemText(not_null item) { return titleResult; }(); auto result = textResult; + if (result.empty()) { + result = std::move(mediaResult); + } else if (!mediaResult.empty()) { + result.append(qstr("\n\n")).append(std::move(mediaResult)); + } if (result.empty()) { result = std::move(logEntryOriginalResult); } else if (!logEntryOriginalResult.empty()) { @@ -78,7 +89,7 @@ TextForMimeData HistoryGroupText(not_null group) { return result; } } - auto caption = [&] { + return [&] { auto &&nonempty = ranges::views::all( group->items ) | ranges::views::filter( @@ -92,7 +103,4 @@ TextForMimeData HistoryGroupText(not_null group) { auto result = (*first)->clipboardText(); return (++first == end) ? result : TextForMimeData(); }(); - return Data::WithCaptionClipboardText( - tr::lng_in_dlg_album(tr::now), - std::move(caption)); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9c33163f2..a7a6bc461 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -71,6 +71,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_histories.h" #include "data/data_group_call.h" +#include "data/data_peer_values.h" // Data::AmPremiumValue. +#include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" #include "history/history.h" @@ -80,6 +82,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_inner_widget.h" #include "history/history_item_components.h" #include "history/history_unread_things.h" +#include "history/view/controls/history_view_characters_limit.h" #include "history/view/controls/history_view_compose_search.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" @@ -400,12 +403,13 @@ HistoryWidget::HistoryWidget( InitMessageField(controller, _field, [=]( not_null document) { - if (_peer && Data::AllowEmojiWithoutPremium(_peer)) { + if (_peer && Data::AllowEmojiWithoutPremium(_peer, document)) { return true; } showPremiumToast(document); return false; }); + InitMessageFieldFade(_field, st::historyComposeField.textBg); _keyboard->sendCommandRequests( ) | rpl::start_with_next([=](Bot::SendCommandRequest r) { @@ -1099,7 +1103,10 @@ void HistoryWidget::initTabbedSelector() { ; info && info->setType == Data::StickersType::Emoji) { if (data.document->isPremiumEmoji() && !session().premium() - && (!_peer || !Data::AllowEmojiWithoutPremium(_peer))) { + && (!_peer + || !Data::AllowEmojiWithoutPremium( + _peer, + data.document))) { showPremiumToast(data.document); } else if (!_field->isHidden()) { Data::InsertCustomEmoji(_field.data(), data.document); @@ -1642,6 +1649,8 @@ void HistoryWidget::fieldChanged() { } }); + checkCharsLimitation(); + updateSendButtonType(); if (!HasSendText(_field)) { _fieldIsEmpty = true; @@ -3858,6 +3867,18 @@ void HistoryWidget::windowIsVisibleChanged() { }); } +TextWithEntities HistoryWidget::prepareTextForEditMsg() const { + const auto textWithTags = _field->getTextWithAppliedMarkdown(); + const auto prepareFlags = Ui::ItemTextOptions( + _history, + session().user()).flags; + auto left = TextWithEntities { + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; + TextUtilities::PrepareForSending(left, prepareFlags); + return left; +} + void HistoryWidget::saveEditMsg() { Expects(_history != nullptr); @@ -3871,28 +3892,27 @@ void HistoryWidget::saveEditMsg() { return; } const auto webPageDraft = _preview->draft(); - const auto textWithTags = _field->getTextWithAppliedMarkdown(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; + auto left = prepareTextForEditMsg(); auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); - const auto media = item->media(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) + const auto originalLeftSize = left.text.size(); + const auto hasMediaWithCaption = item + && item->media() + && item->media()->allowsEditCaption(); + const auto maxCaptionSize = !hasMediaWithCaption + ? MaxMessageSize + : Data::PremiumLimits(&session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) && (webPageDraft.removed || webPageDraft.url.isEmpty() || !webPageDraft.manual) - && (!media || !media->allowsEditCaption())) { + && !hasMediaWithCaption) { const auto suggestModerateActions = false; controller()->show( Box(item, suggestModerateActions)); return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; @@ -5495,8 +5515,7 @@ bool HistoryWidget::confirmSendingFiles( controller(), std::move(list), text, - DefaultLimitsForPeer(_peer), - DefaultCheckForPeer(controller(), _peer), + _peer, Api::SendType::Normal, sendMenuType()); _field->setTextWithTags({}); @@ -7326,6 +7345,38 @@ void HistoryWidget::showPremiumToast(not_null document) { _stickerToast->showFor(document); } +void HistoryWidget::checkCharsLimitation() { + if (!_history || !_editMsgId) { + _charsLimitation = nullptr; + return; + } + const auto item = session().data().message(_history->peer, _editMsgId); + if (!item || !item->media() || !item->media()->allowsEditCaption()) { + _charsLimitation = nullptr; + return; + } + const auto limits = Data::PremiumLimits(&session()); + const auto left = prepareTextForEditMsg(); + const auto remove = left.text.size() - limits.captionLengthCurrent(); + if (remove > 0) { + if (!_charsLimitation) { + _charsLimitation = base::make_unique_q( + this, + _send.get(), + style::al_bottom); + _charsLimitation->show(); + Data::AmPremiumValue( + &session() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _charsLimitation->lifetime()); + } + _charsLimitation->setLeft(remove); + } else { + _charsLimitation = nullptr; + } +} + void HistoryWidget::setFieldText( const TextWithTags &textWithTags, TextUpdateEvents events, @@ -7338,6 +7389,8 @@ void HistoryWidget::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; + checkCharsLimitation(); + if (_preview) { _preview->checkNow(false); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 8b1a73202..1be92a2f4 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -101,6 +101,7 @@ class VoiceRecordBar; class ForwardPanel; class TTLButton; class WebpageProcessor; +class CharactersLimitLabel; } // namespace HistoryView::Controls class BotKeyboard; @@ -544,6 +545,7 @@ private: [[nodiscard]] bool insideJumpToEndInsteadOfToUnread() const; void createUnreadBarAndResize(); + [[nodiscard]] TextWithEntities prepareTextForEditMsg() const; void saveEditMsg(); void setupPreview(); @@ -644,6 +646,8 @@ private: void switchToSearch(QString query); + void checkCharsLimitation(); + MTP::Sender _api; FullReplyTo _replyTo; Ui::Text::String _replyToName; @@ -764,6 +768,8 @@ private: object_ptr _field; base::unique_qptr _fieldDisabled; base::unique_qptr _sendRestriction; + using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel; + base::unique_qptr _charsLimitation; QString _sendRestrictionKey; Ui::Animations::Simple _inPhotoEditOver; bool _inDetails = false; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp new file mode 100644 index 000000000..c3f273674 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.cpp @@ -0,0 +1,45 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_characters_limit.h" + +#include "ui/rect.h" +#include "styles/style_chat_helpers.h" + +namespace HistoryView::Controls { + +CharactersLimitLabel::CharactersLimitLabel( + not_null parent, + not_null widgetToAlign, + style::align align) +: Ui::FlatLabel(parent, st::historyCharsLimitationLabel) { + Expects((align == style::al_top) || align == style::al_bottom); + const auto w = st::historyCharsLimitationLabel.minWidth; + using F = Fn; + const auto position = (align == style::al_top) + ? F([=](int height, const QRect &g) { + move(g.x() + (g.width() - w) / 2, rect::bottom(g)); + }) + : F([=](int height, const QRect &g) { + move(g.x() + (g.width() - w) / 2, g.y() - height); + }); + rpl::combine( + Ui::RpWidget::heightValue(), + widgetToAlign->geometryValue() + ) | rpl::start_with_next(position, lifetime()); +} + +void CharactersLimitLabel::setLeft(int value) { + if (value <= 0) { + return; + } + constexpr auto kMinus = QChar(0x2212); + constexpr auto kLimit = int(999); + Ui::FlatLabel::setText(kMinus + QString::number(std::min(value, kLimit))); +} + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h new file mode 100644 index 000000000..3b6054e19 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_characters_limit.h @@ -0,0 +1,25 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/labels.h" + +namespace HistoryView::Controls { + +class CharactersLimitLabel final : public Ui::FlatLabel { +public: + CharactersLimitLabel( + not_null parent, + not_null widgetToAlign, + style::align align); + + void setLeft(int value); + +}; + +} // namespace HistoryView::Controls diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 05675a2fe..c8d47451c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/platform/base_platform_info.h" #include "base/qt_signal_producer.h" +#include "base/timer_rpl.h" #include "base/unixtime.h" #include "boxes/edit_caption_box.h" #include "chat_helpers/compose/compose_show.h" @@ -36,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_peer_values.h" #include "data/data_photo_media.h" +#include "data/data_premium_limits.h" // Data::PremiumLimits. #include "data/stickers/data_stickers.h" #include "data/stickers/data_custom_emoji.h" #include "data/data_web_page.h" @@ -47,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/power_saving.h" #include "history/history.h" #include "history/history_item.h" +#include "history/view/controls/history_view_characters_limit.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" @@ -63,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio_capture.h" #include "media/audio/media_audio.h" #include "settings/settings_premium.h" +#include "ui/item_text_options.h" #include "ui/text/text_options.h" #include "ui/text/text_utilities.h" #include "ui/ui_utility.h" @@ -92,6 +96,7 @@ constexpr auto kMouseEvents = { QEvent::MouseButtonPress, QEvent::MouseButtonRelease }; +constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); constexpr auto kCommonModifiers = 0 | Qt::ShiftModifier @@ -1205,6 +1210,8 @@ void ComposeControls::setFieldText( _textUpdateEvents = TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping; + checkCharsLimitation(); + if (_preview) { _preview->checkNow(false); } @@ -1567,7 +1574,8 @@ void ComposeControls::initField() { }, _field->lifetime()); #endif // Q_OS_MAC InitMessageField(_show, _field, [=](not_null emoji) { - if (_history && Data::AllowEmojiWithoutPremium(_history->peer)) { + if (_history + && Data::AllowEmojiWithoutPremium(_history->peer, emoji)) { return true; } if (_unavailableEmojiPasted) { @@ -1575,11 +1583,13 @@ void ComposeControls::initField() { } return false; }); + InitMessageFieldFade(_field, _st.field.textBg); _field->setEditLinkCallback( DefaultEditLinkCallback(_show, _field, &_st.boxField)); initAutocomplete(); - const auto allow = [=](const auto &) { - return _history && Data::AllowEmojiWithoutPremium(_history->peer); + const auto allow = [=](not_null emoji) { + return _history + && Data::AllowEmojiWithoutPremium(_history->peer, emoji); }; const auto suggestions = Ui::Emoji::SuggestionsController::Init( _parent, @@ -1787,6 +1797,8 @@ void ComposeControls::fieldChanged() { } }); + checkCharsLimitation(); + _saveCloudDraftTimer.cancel(); if (!(_textUpdateEvents & TextUpdateEvent::SaveDraft)) { return; @@ -2020,6 +2032,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) { _preview->setDisabled(false); } } + checkCharsLimitation(); } void ComposeControls::cancelForward() { @@ -2078,7 +2091,9 @@ void ComposeControls::initTabbedSelector() { if (data.document->isPremiumEmoji() && !session().premium() && (!_history - || !Data::AllowEmojiWithoutPremium(_history->peer))) { + || !Data::AllowEmojiWithoutPremium( + _history->peer, + data.document))) { if (_unavailableEmojiPasted) { _unavailableEmojiPasted(data.document); } @@ -3283,4 +3298,94 @@ Fn ComposeControls::restoreTextCallback( }); } +TextWithEntities ComposeControls::prepareTextForEditMsg() const { + if (!_history) { + return {}; + } + const auto textWithTags = getTextWithAppliedMarkdown(); + const auto prepareFlags = Ui::ItemTextOptions( + _history, + session().user()).flags; + auto left = TextWithEntities { + textWithTags.text, + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; + TextUtilities::PrepareForSending(left, prepareFlags); + return left; +} + +void ComposeControls::checkCharsLimitation() { + if (!_history || !isEditingMessage()) { + _charsLimitation = nullptr; + return; + } + const auto item = _history->owner().message(_header->editMsgId()); + if (!item || !item->media() || !item->media()->allowsEditCaption()) { + _charsLimitation = nullptr; + return; + } + const auto limits = Data::PremiumLimits(&session()); + const auto left = prepareTextForEditMsg(); + const auto remove = left.text.size() - limits.captionLengthCurrent(); + if (remove > 0) { + if (!_charsLimitation) { + using namespace Controls; + _charsLimitation = base::make_unique_q( + _wrap.get(), + _send.get(), + style::al_bottom); + _charsLimitation->show(); + Data::AmPremiumValue( + &session() + ) | rpl::start_with_next([=] { + checkCharsLimitation(); + }, _charsLimitation->lifetime()); + } + _charsLimitation->setLeft(remove); + } else { + _charsLimitation = nullptr; + } +} + +rpl::producer SlowmodeSecondsLeft(not_null peer) { + return peer->session().changes().peerFlagsValue( + peer, + Data::PeerUpdate::Flag::Slowmode + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::map([=](int delay) -> rpl::producer { + auto start = rpl::single(delay); + if (!delay) { + return start; + } + return std::move( + start + ) | rpl::then(base::timer_each( + kRefreshSlowmodeLabelTimeout + ) | rpl::map([=] { + return peer->slowmodeSecondsLeft(); + }) | rpl::take_while([=](int delay) { + return delay > 0; + })) | rpl::then(rpl::single(0)); + }) | rpl::flatten_latest(); +} + +rpl::producer SendDisabledBySlowmode(not_null peer) { + const auto history = peer->owner().history(peer); + auto hasSendingMessage = peer->session().changes().historyFlagsValue( + history, + Data::HistoryUpdate::Flag::ClientSideMessages + ) | rpl::map([=] { + return history->latestSendingMessage() != nullptr; + }) | rpl::distinct_until_changed(); + + using namespace rpl::mappers; + const auto channel = peer->asChannel(); + return (!channel || channel->amCreator()) + ? (rpl::single(false) | rpl::type_erased()) + : rpl::combine( + channel->slowmodeAppliedValue(), + std::move(hasSendingMessage), + _1 && _2); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index 795e25760..b1bc012ca 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -83,6 +83,7 @@ namespace HistoryView::Controls { class VoiceRecordBar; class TTLButton; class WebpageProcessor; +class CharactersLimitLabel; } // namespace HistoryView::Controls namespace HistoryView { @@ -228,6 +229,8 @@ public: [[nodiscard]] rpl::producer fieldMenuShownValue() const; [[nodiscard]] not_null likeAnimationTarget() const; + [[nodiscard]] TextWithEntities prepareTextForEditMsg() const; + void applyCloudDraft(); void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); @@ -334,6 +337,8 @@ private: void registerDraftSource(); void changeFocusedControl(); + void checkCharsLimitation(); + const style::ComposeControls &_st; const ChatHelpers::ComposeFeatures _features; const not_null _parent; @@ -373,6 +378,7 @@ private: std::unique_ptr _sendAs; std::unique_ptr _silent; std::unique_ptr _ttlInfo; + base::unique_qptr _charsLimitation; std::unique_ptr _inlineResults; std::unique_ptr _tabbedPanel; @@ -433,4 +439,9 @@ private: }; +[[nodiscard]] rpl::producer SlowmodeSecondsLeft( + not_null peer); +[[nodiscard]] rpl::producer SendDisabledBySlowmode( + not_null peer); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp index 13c6dace6..ca783f07a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp @@ -194,12 +194,8 @@ void ForwardPanel::updateTexts() { .generateImages = false, .ignoreGroup = true, }).text; - const auto history = item->history(); - const auto dropCustomEmoji = !history->session().premium() - && !_to->peer()->isSelf() - && (item->computeDropForwardedInfo() || !keepNames); - if (dropCustomEmoji) { - text = DropCustomEmoji(std::move(text)); + if (item->computeDropForwardedInfo() || !keepNames) { + text = DropDisallowedCustomEmoji(_to->peer(), std::move(text)); } } else { text = Ui::Text::Colorized( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp index 4dbc3cd10..84d565639 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_button.cpp @@ -13,6 +13,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" +#include + namespace HistoryView::Controls { namespace { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 63dab7b96..26fdff30c 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/menu/menu_multiline_action.h" #include "ui/image/image.h" #include "ui/toast/toast.h" +#include "ui/text/format_song_document_name.h" #include "ui/text/text_utilities.h" #include "ui/controls/delete_message_context_action.h" #include "ui/controls/who_reacted_context_action.h" @@ -334,6 +335,10 @@ void AddDocumentActions( AddSaveSoundForNotifications(menu, item, document, controller); } AddSaveDocumentAction(menu, item, document, list); + AddCopyFilename( + menu, + document, + [=] { return list->showCopyRestrictionForSelected(); }); } void AddPostLinkAction( @@ -1579,6 +1584,33 @@ void ShowTagInListMenu( (*menu)->popup(position); } +void AddCopyFilename( + not_null menu, + not_null document, + Fn showCopyRestrictionForSelected) { + const auto filenameToCopy = [&] { + if (document->isAudioFile()) { + return TextForMimeData().append( + Ui::Text::FormatSongNameFor(document).string()); + } else if (document->sticker() + || document->isAnimation() + || document->isVideoMessage() + || document->isVideoFile() + || document->isVoiceMessage()) { + return TextForMimeData(); + } else { + return TextForMimeData().append(document->filename()); + } + }(); + if (!filenameToCopy.empty()) { + menu->addAction(tr::lng_context_copy_filename(tr::now), [=] { + if (!showCopyRestrictionForSelected()) { + TextUtilities::SetClipboardText(filenameToCopy); + } + }, &st::menuIconCopy); + } +} + void ShowWhoReactedMenu( not_null*> menu, QPoint position, diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h index 65afd007b..8f00f4da8 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.h +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h @@ -94,6 +94,10 @@ void ShowTagInListMenu( not_null context, const Data::ReactionId &id, not_null controller); +void AddCopyFilename( + not_null menu, + not_null document, + Fn showCopyRestrictionForSelected); enum class EmojiPacksSource { Message, diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a9bede89c..1b1c80626 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -459,21 +459,22 @@ void Message::setReactions(std::unique_ptr list) { } void Message::refreshRightBadge() { + const auto item = data(); const auto text = [&] { - if (data()->isDiscussionPost()) { + if (item->isDiscussionPost()) { return (delegate()->elementContext() == Context::Replies) ? QString() : tr::lng_channel_badge(tr::now); - } else if (data()->author()->isMegagroup()) { - if (const auto msgsigned = data()->Get()) { + } else if (item->author()->isMegagroup()) { + if (const auto msgsigned = item->Get()) { Assert(msgsigned->isAnonymousRank); return msgsigned->postAuthor; } } else if (data()->history()->peer->isMegagroup() && data()->author()->isChannel() && !data()->out()) { return tr::lng_channel_badge(tr::now); } - const auto channel = data()->history()->peer->asMegagroup(); - const auto user = data()->author()->asUser(); + const auto channel = item->history()->peer->asMegagroup(); + const auto user = item->author()->asUser(); if (!channel || !user) { return QString(); } @@ -492,13 +493,41 @@ void Message::refreshRightBadge() { ? tr::lng_admin_badge(tr::now) : QString(); }(); - const auto badge = text.isEmpty() - ? delegate()->elementAuthorRank(this) - : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text)); - if (badge.isEmpty()) { + auto badge = TextWithEntities{ + (text.isEmpty() + ? delegate()->elementAuthorRank(this) + : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text))) + }; + _rightBadgeHasBoosts = 0; + if (const auto boosts = item->boostsApplied()) { + _rightBadgeHasBoosts = 1; + + const auto many = (boosts > 1); + const auto &icon = many + ? st::boostsMessageIcon + : st::boostMessageIcon; + const auto padding = many + ? st::boostsMessageIconPadding + : st::boostMessageIconPadding; + const auto owner = &item->history()->owner(); + auto added = Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().registerInternalEmoji(icon, padding) + ).append(many ? QString::number(boosts) : QString()); + badge.append(' ').append(Ui::Text::Colorized(added, 1)); + } + if (badge.empty()) { _rightBadge.clear(); } else { - _rightBadge.setText(st::defaultTextStyle, badge); + const auto context = Core::MarkedTextContext{ + .session = &item->history()->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _rightBadge.setMarkedText( + st::defaultTextStyle, + badge, + Ui::NameTextOptions(), + context); } } @@ -1499,10 +1528,10 @@ void Message::paintFromName( } if (rightWidth) { p.setPen(stm->msgDateFg); - p.setFont(ClickHandler::showAsActive(_fastReplyLink) - ? st::msgFont->underline() - : st::msgFont); if (replyWidth) { + p.setFont(ClickHandler::showAsActive(_fastReplyLink) + ? st::msgFont->underline() + : st::msgFont); p.drawText( trect.left() + trect.width() - rightWidth, trect.top() + st::msgFont->ascent, @@ -1515,11 +1544,21 @@ void Message::paintFromName( trect.top() + (_rightBadge.minHeight() - stm->channelBadgeIcon.height()) / 2, rightWidth); } else { - _rightBadge.draw( - p, - trect.left() + trect.width() - rightWidth, - trect.top(), - rightWidth); + const auto shift = QPoint(trect.width() - rightWidth, 0); + const auto pen = !_rightBadgeHasBoosts + ? QPen() + : !context.outbg + ? QPen(FromNameFg(context, colorIndex())) + : stm->msgServiceFg->p; + auto colored = std::array{ + { { &pen, &pen } }, + }; + _rightBadge.draw(p, { + .position = trect.topLeft() + shift, + .availableWidth = rightWidth, + .colors = colored, + .now = context.now, + }); } } } @@ -3272,8 +3311,10 @@ bool Message::displayForwardedFrom() const { if (forwarded->story) { return true; } else if (item->showForwardsFromSender(forwarded)) { - return forwarded->savedFromSender - && (forwarded->savedFromSender != forwarded->originalSender); + return forwarded->savedFromHiddenSenderInfo + || (forwarded->savedFromSender + && (forwarded->savedFromSender + != forwarded->originalSender)); } if (const auto sender = item->discussionPostOriginalSender()) { if (sender == forwarded->originalSender) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 1f13056e8..62ab6c02b 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -305,9 +305,10 @@ private: mutable std::unique_ptr _fromNameStatus; Ui::Text::String _rightBadge; mutable int _fromNameVersion = 0; - uint32 _bubbleWidthLimit : 30 = 0; + uint32 _bubbleWidthLimit : 29 = 0; uint32 _invertMedia : 1 = 0; uint32 _hideReply : 1 = 0; + uint32 _rightBadgeHasBoosts : 1 = 0; BottomInfo _bottomInfo; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index da13799aa..4d3d16d9a 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -8,12 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_replies_section.h" #include "history/view/controls/history_view_compose_controls.h" -#include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" #include "history/view/history_view_top_bar_widget.h" -#include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" -#include "history/view/history_view_pinned_bar.h" #include "history/view/history_view_sticker_toast.h" #include "history/view/history_view_cursor_state.h" #include "history/view/history_view_contact_status.h" @@ -25,43 +22,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_components.h" -#include "history/history_item.h" #include "history/history_item_helpers.h" // GetErrorTextForSending. -#include "menu/menu_send.h" // SendMenu::Type. -#include "ui/chat/attach/attach_prepare.h" -#include "ui/chat/attach/attach_send_files_way.h" #include "ui/chat/pinned_bar.h" #include "ui/chat/chat_style.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/shadow.h" #include "ui/widgets/popup_menu.h" -#include "ui/wrap/slide_wrap.h" -#include "ui/layers/generic_box.h" -#include "ui/item_text_options.h" #include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/effects/message_sending_animation_controller.h" -#include "ui/ui_utility.h" #include "base/timer_rpl.h" #include "api/api_bot.h" -#include "api/api_common.h" #include "api/api_editing.h" #include "api/api_sending.h" #include "apiwrap.h" #include "ui/boxes/confirm_box.h" #include "chat_helpers/tabbed_selector.h" #include "boxes/delete_messages_box.h" -#include "boxes/edit_caption_box.h" #include "boxes/send_files_box.h" #include "boxes/premium_limits_box.h" -#include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" -#include "base/event_filter.h" #include "base/call_delayed.h" #include "base/qt/qt_key_modifiers.h" -#include "core/file_utilities.h" #include "core/application.h" #include "core/shortcuts.h" #include "core/click_handler_types.h" @@ -79,8 +62,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_shared_media.h" #include "data/data_send_action.h" +#include "data/data_premium_limits.h" #include "storage/storage_media_prepare.h" -#include "storage/storage_shared_media.h" #include "storage/storage_account.h" #include "storage/localimageloader.h" #include "inline_bots/inline_bot_result.h" @@ -89,7 +72,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" -#include "styles/style_info.h" #include "styles/style_boxes.h" #include "styles/style_layers.h" @@ -102,8 +84,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200); - rpl::producer RootViewContent( not_null history, MsgId rootId, @@ -638,45 +618,6 @@ bool RepliesWidget::computeAreComments() const { } void RepliesWidget::setupComposeControls() { - auto slowmodeSecondsLeft = session().changes().peerFlagsValue( - _history->peer, - Data::PeerUpdate::Flag::Slowmode - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::map([=](int delay) -> rpl::producer { - auto start = rpl::single(delay); - if (!delay) { - return start; - } - return std::move( - start - ) | rpl::then(base::timer_each( - kRefreshSlowmodeLabelTimeout - ) | rpl::map([=] { - return _history->peer->slowmodeSecondsLeft(); - }) | rpl::take_while([=](int delay) { - return delay > 0; - })) | rpl::then(rpl::single(0)); - }) | rpl::flatten_latest(); - - const auto channel = _history->peer->asChannel(); - Assert(channel != nullptr); - - auto hasSendingMessage = session().changes().historyFlagsValue( - _history, - Data::HistoryUpdate::Flag::ClientSideMessages - ) | rpl::map([=] { - return _history->latestSendingMessage() != nullptr; - }) | rpl::distinct_until_changed(); - - using namespace rpl::mappers; - auto sendDisabledBySlowmode = (!channel || channel->amCreator()) - ? (rpl::single(false) | rpl::type_erased()) - : rpl::combine( - channel->slowmodeAppliedValue(), - std::move(hasSendingMessage), - _1 && _2); - auto topicWriteRestrictions = rpl::single( ) | rpl::then(session().changes().topicUpdates( Data::TopicUpdate::Flag::Closed @@ -726,8 +667,8 @@ void RepliesWidget::setupComposeControls() { .topicRootId = _topic ? _topic->rootId() : MsgId(0), .showSlowmodeError = [=] { return showSlowmodeError(); }, .sendActionFactory = [=] { return prepareSendAction({}); }, - .slowmodeSecondsLeft = std::move(slowmodeSecondsLeft), - .sendDisabledBySlowmode = std::move(sendDisabledBySlowmode), + .slowmodeSecondsLeft = SlowmodeSecondsLeft(_history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(_history->peer), .writeRestriction = std::move(writeRestriction), }); @@ -971,8 +912,7 @@ bool RepliesWidget::confirmSendingFiles( controller(), std::move(list), _composeControls->getTextWithAppliedMarkdown(), - DefaultLimitsForPeer(_history->peer), - DefaultCheckForPeer(controller(), _history->peer), + _history->peer, Api::SendType::Normal, SendMenu::Type::SilentOnly); // #TODO replies schedule @@ -1237,21 +1177,19 @@ void RepliesWidget::edit( if (*saveEditMsgRequestId) { return; } - const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); const auto webpage = _composeControls->webPageDraft(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); + auto left = _composeControls->prepareTextForEditMsg(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) - && (!item - || !item->media() - || !item->media()->allowsEditCaption())) { + const auto originalLeftSize = left.text.size(); + const auto hasMediaWithCaption = item + && item->media() + && item->media()->allowsEditCaption(); + const auto maxCaptionSize = !hasMediaWithCaption + ? MaxMessageSize + : Data::PremiumLimits(&session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) + && !hasMediaWithCaption) { if (item) { controller()->show(Box(item, false)); } else { @@ -1259,7 +1197,7 @@ void RepliesWidget::edit( } return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index fef818acb..56798905b 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -10,49 +10,36 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_compose_controls.h" #include "history/view/history_view_empty_list_bubble.h" #include "history/view/history_view_top_bar_widget.h" -#include "history/view/history_view_list_widget.h" #include "history/view/history_view_schedule_box.h" #include "history/view/history_view_sticker_toast.h" #include "history/history.h" #include "history/history_drag_area.h" -#include "history/history_item.h" #include "history/history_item_helpers.h" // GetErrorTextForSending. #include "menu/menu_send.h" // SendMenu::Type. #include "ui/widgets/scroll_area.h" #include "ui/widgets/shadow.h" -#include "ui/layers/generic_box.h" -#include "ui/item_text_options.h" #include "ui/chat/chat_style.h" -#include "ui/chat/attach/attach_prepare.h" -#include "ui/chat/attach/attach_send_files_way.h" -#include "ui/ui_utility.h" #include "ui/text/text_utilities.h" -#include "api/api_common.h" #include "api/api_editing.h" #include "api/api_sending.h" #include "apiwrap.h" -#include "ui/boxes/confirm_box.h" #include "boxes/delete_messages_box.h" -#include "boxes/edit_caption_box.h" #include "boxes/send_files_box.h" #include "boxes/premium_limits_box.h" -#include "window/window_adaptive.h" #include "window/window_session_controller.h" #include "window/window_peer_menu.h" -#include "base/event_filter.h" #include "base/call_delayed.h" #include "base/qt/qt_key_modifiers.h" -#include "core/file_utilities.h" #include "core/mime_type.h" #include "chat_helpers/tabbed_selector.h" #include "main/main_session.h" -#include "data/data_chat_participant_status.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_scheduled_messages.h" #include "data/data_user.h" #include "data/data_message_reactions.h" #include "data/data_peer_values.h" +#include "data/data_premium_limits.h" #include "storage/storage_media_prepare.h" #include "storage/storage_account.h" #include "storage/localimageloader.h" @@ -60,8 +47,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" -#include "styles/style_window.h" -#include "styles/style_info.h" #include "styles/style_boxes.h" #include @@ -423,8 +408,7 @@ bool ScheduledWidget::confirmSendingFiles( controller(), std::move(list), _composeControls->getTextWithAppliedMarkdown(), - DefaultLimitsForPeer(_history->peer), - DefaultCheckForPeer(controller(), _history->peer), + _history->peer, (CanScheduleUntilOnline(_history->peer) ? Api::SendType::ScheduledToUser : Api::SendType::Scheduled), @@ -657,21 +641,19 @@ void ScheduledWidget::edit( if (*saveEditMsgRequestId) { return; } - const auto textWithTags = _composeControls->getTextWithAppliedMarkdown(); const auto webpage = _composeControls->webPageDraft(); - const auto prepareFlags = Ui::ItemTextOptions( - _history, - session().user()).flags; auto sending = TextWithEntities(); - auto left = TextWithEntities { - textWithTags.text, - TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) }; - TextUtilities::PrepareForSending(left, prepareFlags); + auto left = _composeControls->prepareTextForEditMsg(); - if (!TextUtilities::CutPart(sending, left, MaxMessageSize) - && (!item - || !item->media() - || !item->media()->allowsEditCaption())) { + const auto originalLeftSize = left.text.size(); + const auto hasMediaWithCaption = item + && item->media() + && item->media()->allowsEditCaption(); + const auto maxCaptionSize = !hasMediaWithCaption + ? MaxMessageSize + : Data::PremiumLimits(&session()).captionLengthCurrent(); + if (!TextUtilities::CutPart(sending, left, maxCaptionSize) + && !hasMediaWithCaption) { if (item) { controller()->show(Box(item, false)); } else { @@ -679,7 +661,7 @@ void ScheduledWidget::edit( } return; } else if (!left.text.isEmpty()) { - const auto remove = left.text.size(); + const auto remove = originalLeftSize - maxCaptionSize; controller()->showToast( tr::lng_edit_limit_reached(tr::now, lt_count, remove)); return; diff --git a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp index 523cd5ce1..dccca0f3a 100644 --- a/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_transcribe_button.cpp @@ -221,14 +221,14 @@ void TranscribeButton::paint( } bool TranscribeButton::hasLock() const { - if (_item->history()->session().premium()) { + const auto session = &_item->history()->session(); + const auto transcribes = &session->api().transcribes(); + if (session->premium() + || transcribes->freeFor(_item) + || transcribes->trialsCount()) { return false; } - if (_item->history()->session().api().transcribes().trialsCount()) { - return false; - } - const auto until = _item->history()->session().api().transcribes() - .trialsRefreshAt(); + const auto until = transcribes->trialsRefreshAt(); if (!until || base::unixtime::now() >= until) { return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index f5c8cbe6e..d312489f4 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -446,10 +446,13 @@ QSize Document::countOptimalSize() { auto hasTranscribe = false; const auto voice = Get(); if (voice) { - const auto session = &_realParent->history()->session(); + const auto history = _realParent->history(); + const auto session = &history->session(); + const auto transcribes = &session->api().transcribes(); if (_parent->data()->media()->ttlSeconds() || (!session->premium() - && !session->api().transcribes().trialsSupport())) { + && !transcribes->freeFor(_realParent) + && !transcribes->trialsSupport())) { voice->transcribe = nullptr; voice->transcribeText = {}; } else { @@ -459,8 +462,7 @@ QSize Document::countOptimalSize() { _realParent, false); } - const auto &entry = session->api().transcribes().entry( - _realParent); + const auto &entry = transcribes->entry(_realParent); const auto update = [=] { repaint(); }; voice->transcribe->setLoading( entry.shown && (entry.requestId || entry.pending), diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index e0172434f..bd27f2514 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_document.h" #include "data/data_media_types.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/history.h" #include "history/history_item.h" #include "history/history_item_components.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/widgets/tooltip.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "ui/round_rect.h" #include "styles/style_chat.h" @@ -446,7 +446,7 @@ PeerBubbleListPart::PeerBubbleListPart( peer->name(), kDefaultTextOptions, st::msgMinWidth), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(peer), + .thumbnail = Ui::MakeUserpicThumbnail(peer), .link = peer->openLink(), .colorIndex = peer->colorIndex(), }); @@ -698,12 +698,27 @@ auto GenerateGiveawayStart( Ui::Text::Bold(tr::lng_prizes_participants(tr::now)), st::chatGiveawayPrizesTitleMargin); + const auto hasChannel = ranges::any_of( + data->channels, + &ChannelData::isBroadcast); + const auto hasGroup = ranges::any_of( + data->channels, + &ChannelData::isMegagroup); + const auto mixed = (hasChannel && hasGroup); pushText({ (data->all - ? tr::lng_prizes_participants_all - : tr::lng_prizes_participants_new)( - tr::now, - lt_count, - data->channels.size()), + ? (mixed + ? tr::lng_prizes_participants_all_mixed + : hasGroup + ? tr::lng_prizes_participants_all_group + : tr::lng_prizes_participants_all) + : (mixed + ? tr::lng_prizes_participants_new_mixed + : hasGroup + ? tr::lng_prizes_participants_new_group + : tr::lng_prizes_participants_new))( + tr::now, + lt_count, + data->channels.size()), }, st::chatGiveawayParticipantsMargin); auto list = ranges::views::all( diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h index 534886fc7..1aae9340d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h @@ -15,11 +15,8 @@ struct GiveawayStart; struct GiveawayResults; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -208,10 +205,9 @@ public: private: int layout(int x, int y, int available); - using Thumbnail = Dialogs::Stories::Thumbnail; struct Peer { Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; QRect geometry; ClickHandlerPtr link; mutable std::unique_ptr ripple; diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp index 919a76cb0..05a03df4d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.cpp @@ -14,8 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_premium_limits.h" #include "data/data_session.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "history/view/history_view_element.h" #include "history/view/history_view_cursor_state.h" #include "history/history.h" @@ -30,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "window/window_session_controller.h" #include "styles/style_chat.h" @@ -373,7 +373,7 @@ void SimilarChannels::fillMoreThumbnails() const { if (similar.list.size() <= _channels.size() + i) { break; } - _moreThumbnails[i] = Dialogs::Stories::MakeUserpicThumbnail( + _moreThumbnails[i] = Ui::MakeUserpicThumbnail( similar.list[_channels.size() + i]); } } @@ -556,7 +556,7 @@ QSize SimilarChannels::countOptimalSize() { : channel->name()), kDefaultTextOptions, st::chatSimilarChannelPhoto), - .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), + .thumbnail = Ui::MakeUserpicThumbnail(channel), .more = uint32(moreCounter), .moreLocked = uint32((moreCounter && !premium) ? 1 : 0), }); diff --git a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h index a7f30b2a7..138373914 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h +++ b/Telegram/SourceFiles/history/view/media/history_view_similar_channels.h @@ -9,11 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories - namespace Ui { +class DynamicImage; class RippleAnimation; } // namespace Ui @@ -58,11 +55,10 @@ public: bool consumeHorizontalScroll(QPoint position, int delta) override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; struct Channel { QRect geometry; Ui::Text::String name; - std::shared_ptr thumbnail; + std::shared_ptr thumbnail; ClickHandlerPtr link; QString counter; mutable QRect counterRect; @@ -99,7 +95,7 @@ private: mutable uint32 _hasHeavyPart : 1 = 0; std::vector _channels; - mutable std::array, 2> _moreThumbnails; + mutable std::array, 2> _moreThumbnails; mutable ClickHandlerPtr _viewAllLink; mutable ClickHandlerPtr _toggleLink; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp index 2d9089f37..a2b2d65ba 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.cpp @@ -15,8 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_click_handler.h" #include "data/data_session.h" #include "data/data_stories.h" -#include "dialogs/ui/dialogs_stories_content.h" -#include "dialogs/ui/dialogs_stories_list.h" #include "editor/photo_editor_common.h" #include "editor/photo_editor_layer_widget.h" #include "history/history.h" @@ -31,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/outline_segments.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" +#include "ui/dynamic_image.h" +#include "ui/dynamic_thumbnails.h" #include "ui/painter.h" #include "mainwidget.h" #include "apiwrap.h" @@ -102,12 +102,11 @@ void StoryMention::draw( const QRect &geometry) { const auto showStory = _story->forbidsForward() ? 0 : 1; if (!_thumbnail || _thumbnailFromStory != showStory) { - using namespace Dialogs::Stories; const auto item = _parent->data(); const auto history = item->history(); _thumbnail = showStory - ? MakeStoryThumbnail(_story) - : MakeUserpicThumbnail(item->out() + ? Ui::MakeStoryThumbnail(_story) + : Ui::MakeUserpicThumbnail(item->out() ? history->session().user() : history->peer); _thumbnailFromStory = showStory; diff --git a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h index 376926f3b..601823460 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_story_mention.h +++ b/Telegram/SourceFiles/history/view/media/history_view_story_mention.h @@ -15,9 +15,9 @@ namespace Data { class Story; } // namespace Data -namespace Dialogs::Stories { -class Thumbnail; -} // namespace Dialogs::Stories +namespace Ui { +class DynamicImage; +} // namespace Ui namespace HistoryView { @@ -53,13 +53,11 @@ public: void unloadHeavyPart() override; private: - using Thumbnail = Dialogs::Stories::Thumbnail; - bool changeSubscribedTo(uint32 value); const not_null _parent; const not_null _story; - std::shared_ptr _thumbnail; + std::shared_ptr _thumbnail; QBrush _unreadBrush; uint32 _paletteVersion : 29 = 0; uint32 _thumbnailFromStory : 1 = 0; diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index 01d6d5c5e..6c5583723 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -89,7 +89,8 @@ constexpr auto kAdditionalPrizeLengthMax = 128; void AddPremiumTopBarWithDefaultTitleBar( not_null box, rpl::producer<> showFinished, - rpl::producer titleText) { + rpl::producer titleText, + bool group) { struct State final { Ui::Animations::Simple animation; Ui::Text::String title; @@ -175,7 +176,9 @@ void AddPremiumTopBarWithDefaultTitleBar( st::startGiveawayCover, nullptr, tr::lng_giveaway_new_title(), - tr::lng_giveaway_new_about(Ui::Text::RichLangValue), + (group + ? tr::lng_giveaway_new_about_group + : tr::lng_giveaway_new_about)(Ui::Text::RichLangValue), true, false); bar->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -265,6 +268,7 @@ void CreateGiveawayBox( rpl::variable confirmButtonBusy = true; }; + const auto group = peer->isMegagroup(); const auto state = box->lifetime().make_state(peer); const auto typeGroup = std::make_shared(); @@ -276,7 +280,8 @@ void CreateGiveawayBox( state->typeValue.value( ) | rpl::map(rpl::mappers::_1 == GiveawayType::Random), tr::lng_giveaway_start(), - tr::lng_giveaway_award())); + tr::lng_giveaway_award()), + peer->isMegagroup()); { const auto &padding = st::giveawayGiftCodeCoverDividerPadding; Ui::AddSkip(box->verticalLayout(), padding.bottom()); @@ -329,7 +334,8 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::Random, - tr::lng_giveaway_create_subtitle())); + tr::lng_giveaway_create_subtitle(), + group)); row->addRadio(typeGroup); row->setClickedCallback([=] { state->typeValue.force_assign(GiveawayType::Random); @@ -351,7 +357,8 @@ void CreateGiveawayBox( : tr::lng_giveaway_award_chosen( lt_count, rpl::single(selected.size()) | tr::to_count()); - }) | rpl::flatten_latest())); + }) | rpl::flatten_latest(), + group)); row->addRadio(typeGroup); row->setClickedCallback([=] { auto initBox = [=](not_null peersBox) { @@ -534,12 +541,14 @@ void CreateGiveawayBox( auto &list = state->selectedToSubscribe; list.erase(ranges::remove(list, peer), end(list)); }, box->lifetime()); - listState->controller.setTopStatus(tr::lng_giveaway_channels_this( - lt_count, - state->sliderValue.value( - ) | rpl::map([=](int v) -> float64 { - return state->apiOptions.giveawayBoostsPerPremium() * v; - }))); + listState->controller.setTopStatus((peer->isMegagroup() + ? tr::lng_giveaway_channels_this_group + : tr::lng_giveaway_channels_this)( + lt_count, + state->sliderValue.value( + ) | rpl::map([=](int v) -> float64 { + return state->apiOptions.giveawayBoostsPerPremium() * v; + }))); using IconType = Settings::IconType; Settings::AddButtonWithIcon( @@ -638,7 +647,8 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::AllMembers, - rpl::duplicate(subtitle))); + rpl::duplicate(subtitle), + group)); row->addRadio(membersGroup); row->setClickedCallback(createCallback(GiveawayType::AllMembers)); } @@ -646,14 +656,17 @@ void CreateGiveawayBox( object_ptr( box, GiveawayType::OnlyNewMembers, - std::move(subtitle))); + std::move(subtitle), + group)); row->addRadio(membersGroup); row->setClickedCallback(createCallback(GiveawayType::OnlyNewMembers)); Ui::AddSkip(countriesContainer); Ui::AddDividerText( countriesContainer, - tr::lng_giveaway_users_about()); + (group + ? tr::lng_giveaway_users_about_group() + : tr::lng_giveaway_users_about())); Ui::AddSkip(countriesContainer); } @@ -892,9 +905,11 @@ void CreateGiveawayBox( auto terms = object_ptr(dateContainer); terms->add(object_ptr( terms, - tr::lng_giveaway_date_about( - lt_count, - state->sliderValue.value() | tr::to_count()), + (group + ? tr::lng_giveaway_date_about_group + : tr::lng_giveaway_date_about)( + lt_count, + state->sliderValue.value() | tr::to_count()), st::boxDividerLabel)); Ui::AddSkip(terms.data()); Ui::AddSkip(terms.data()); @@ -907,9 +922,11 @@ void CreateGiveawayBox( } else { Ui::AddDividerText( dateContainer, - tr::lng_giveaway_date_about( - lt_count, - state->sliderValue.value() | tr::to_count())); + (group + ? tr::lng_giveaway_date_about_group + : tr::lng_giveaway_date_about)( + lt_count, + state->sliderValue.value() | tr::to_count())); Ui::AddSkip(dateContainer); } } @@ -1036,12 +1053,17 @@ void CreateGiveawayBox( } return false; }; + const auto group = peer->isMegagroup(); const auto title = isSpecific ? tr::lng_giveaway_awarded_title : tr::lng_giveaway_created_title; const auto body = isSpecific - ? tr::lng_giveaway_awarded_body - : tr::lng_giveaway_created_body; + ? (group + ? tr::lng_giveaway_awarded_body_group + : tr::lng_giveaway_awarded_body) + : (group + ? tr::lng_giveaway_created_body_group + : tr::lng_giveaway_created_body); show->showToast({ .text = Ui::Text::Bold( title(tr::now)).append('\n').append( diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp index 975dab5b3..4b5c7ac60 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp @@ -293,14 +293,13 @@ void MyChannelsListController::setCheckError(Fn callback) { std::unique_ptr MyChannelsListController::createRow( not_null channel) const { - if (channel->isMegagroup()) { - return nullptr; - } auto row = std::make_unique(channel); - row->setCustomStatus(tr::lng_chat_status_subscribers( - tr::now, - lt_count, - channel->membersCount())); + row->setCustomStatus((channel->isBroadcast() + ? tr::lng_chat_status_subscribers + : tr::lng_chat_status_members)( + tr::now, + lt_count, + channel->membersCount())); return row; } @@ -358,19 +357,18 @@ void SelectedChannelsListController::prepare() { std::unique_ptr SelectedChannelsListController::createRow( not_null channel) const { - if (channel->isMegagroup()) { - return nullptr; - } const auto isYourChannel = (_peer->asChannel() == channel); auto row = isYourChannel ? std::make_unique(channel) : std::make_unique(channel); row->setCustomStatus(isYourChannel ? QString() - : tr::lng_chat_status_subscribers( - tr::now, - lt_count, - channel->membersCount())); + : (channel->isMegagroup() + ? tr::lng_chat_status_members + : tr::lng_chat_status_subscribers)( + tr::now, + lt_count, + channel->membersCount())); return row; } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp index 4417bfb25..c66a636ab 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.cpp @@ -24,7 +24,8 @@ constexpr auto kColorIndexRandom = int(2); GiveawayTypeRow::GiveawayTypeRow( not_null parent, Type type, - rpl::producer subtitle) + rpl::producer subtitle, + bool group) : GiveawayTypeRow( parent, type, @@ -34,8 +35,12 @@ GiveawayTypeRow::GiveawayTypeRow( : (type == Type::Random) ? tr::lng_giveaway_create_option() : (type == Type::AllMembers) - ? tr::lng_giveaway_users_all() - : tr::lng_giveaway_users_new(), + ? (group + ? tr::lng_giveaway_users_all_group() + : tr::lng_giveaway_users_all()) + : (group + ? tr::lng_giveaway_users_new_group() + : tr::lng_giveaway_users_new()), std::move(subtitle), QImage()) { } diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h index 9bb96812b..6cf12411d 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_type_row.h @@ -32,7 +32,8 @@ public: GiveawayTypeRow( not_null parent, Type type, - rpl::producer subtitle); + rpl::producer subtitle, + bool group); GiveawayTypeRow( not_null parent, diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index fac7ff9f2..eb0805c49 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "styles/style_giveaway.h" #include "styles/style_info.h" +#include "styles/style_premium.h" #include "styles/style_statistics.h" #include @@ -128,7 +129,9 @@ void FillOverview( addSub( topRightLabel, stats.premiumMemberPercentage, - tr::lng_boosts_premium_audience); + (stats.group + ? tr::lng_boosts_premium_members + : tr::lng_boosts_premium_audience)); addSub( bottomLeftLabel, 0, @@ -248,7 +251,9 @@ void FillGetBoostsButton( (st.height + rect::m::sum::v(st.padding) - icon.height()) / 2, })->show(); Ui::AddSkip(content); - Ui::AddDividerText(content, tr::lng_boosts_get_boosts_subtext()); + Ui::AddDividerText(content, peer->isMegagroup() + ? tr::lng_boosts_get_boosts_subtext_group() + : tr::lng_boosts_get_boosts_subtext()); } } // namespace @@ -297,6 +302,9 @@ void InnerWidget::fill() { { auto dividerContent = object_ptr(inner); + dividerContent->add(object_ptr( + dividerContent, + st::boostSkipTop)); Ui::FillBoostLimit( fakeShowed->events(), dividerContent.data(), @@ -479,7 +487,9 @@ void InnerWidget::fill() { Ui::AddSkip(inner); Ui::AddSkip(inner); - Ui::AddDividerText(inner, tr::lng_boosts_list_subtext()); + Ui::AddDividerText(inner, status.overview.group + ? tr::lng_boosts_list_subtext_group() + : tr::lng_boosts_list_subtext()); } Ui::AddSkip(inner); @@ -488,7 +498,9 @@ void InnerWidget::fill() { Ui::AddSkip(inner, st::boostsLinkSkip); FillShareLink(inner, _show, status.link, _peer); Ui::AddSkip(inner); - Ui::AddDividerText(inner, tr::lng_boosts_link_subtext()); + Ui::AddDividerText(inner, status.overview.group + ? tr::lng_boosts_link_subtext_group() + : tr::lng_boosts_link_subtext()); FillGetBoostsButton(inner, _controller, _show, _peer, reloadOnDone); diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index dea60361c..a8b57bc55 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/info_top_bar.h" -#include -#include -#include "dialogs/ui/dialogs_stories_content.h" #include "dialogs/ui/dialogs_stories_list.h" #include "lang/lang_keys.h" #include "lang/lang_numbers_animation.h" diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index fd2f75a05..6f62370b2 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -80,7 +80,8 @@ namespace { base::options::toggle ShowPeerIdBelowAbout({ .id = kOptionShowPeerIdBelowAbout, .name = "Show Peer IDs in Profile", - .description = "Show peer IDs from API below their Bio / Description.", + .description = "Show peer IDs from API below their Bio / Description." + " Add contact IDs to exported data.", .scope = static_cast(0), }); diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index eb6718a12..e58c6af43 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -241,8 +241,10 @@ object_ptr InnerWidget::setupSharedMedia( st::infoSharedMediaButtonIconPosition); }; - addStoriesButton(_peer, st::infoIconMediaStories); - addSavedSublistButton(_peer, st::infoIconMediaSaved); + if (!_topic) { + addStoriesButton(_peer, st::infoIconMediaStories); + addSavedSublistButton(_peer, st::infoIconMediaSaved); + } addMediaButton(MediaType::Photo, st::infoIconMediaPhoto); addMediaButton(MediaType::Video, st::infoIconMediaVideo); addMediaButton(MediaType::File, st::infoIconMediaFile); diff --git a/Telegram/SourceFiles/intro/intro_code_input.cpp b/Telegram/SourceFiles/intro/intro_code_input.cpp index 5c0005b20..968cd87d4 100644 --- a/Telegram/SourceFiles/intro/intro_code_input.cpp +++ b/Telegram/SourceFiles/intro/intro_code_input.cpp @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ #include "intro/intro_code_input.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/intro/intro_code_input.h b/Telegram/SourceFiles/intro/intro_code_input.h index f929142df..9b5094aa8 100644 --- a/Telegram/SourceFiles/intro/intro_code_input.h +++ b/Telegram/SourceFiles/intro/intro_code_input.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ #pragma once #include "ui/rp_widget.h" diff --git a/Telegram/SourceFiles/lang/lang_pch.h b/Telegram/SourceFiles/lang/lang_pch.h index 53a811432..175adb938 100644 --- a/Telegram/SourceFiles/lang/lang_pch.h +++ b/Telegram/SourceFiles/lang/lang_pch.h @@ -1,9 +1,10 @@ -// This file is part of Desktop App Toolkit, -// a set of libraries for developing nice desktop applications. -// -// For license and copyright information please follow this link: -// https://github.com/desktop-app/legal/blob/master/LEGAL -// +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ #include #include diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 769cd6998..cf451a20b 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -44,7 +44,9 @@ QByteArray SessionSettings::serialize() const { + sizeof(qint32) + (_mutePeriods.size() * sizeof(quint64)) + sizeof(qint32) * 2 - + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3); + + _hiddenPinnedMessages.size() * (sizeof(quint64) * 3) + + sizeof(qint32) + + _groupEmojiSectionHidden.size() * sizeof(quint64); auto result = QByteArray(); result.reserve(size); @@ -91,6 +93,11 @@ QByteArray SessionSettings::serialize() const { << qint64(key.topicRootId.bare) << qint64(value.bare); } + stream + << qint32(_groupEmojiSectionHidden.size()); + for (const auto &peerId : _groupEmojiSectionHidden) { + stream << SerializePeerId(peerId); + } } return result; } @@ -114,6 +121,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 appFloatPlayerCorner = static_cast(RectPart::TopRight); base::flat_map appSoundOverrides; base::flat_set groupStickersSectionHidden; + base::flat_set groupEmojiSectionHidden; qint32 appThirdSectionInfoEnabled = 0; qint32 legacySmallDialogsList = 0; float64 appDialogsWidthRatio = app.dialogsWidthRatio(); @@ -410,6 +418,22 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { } } } + if (!stream.atEnd()) { + auto count = qint32(0); + stream >> count; + if (stream.status() == QDataStream::Ok) { + for (auto i = 0; i != count; ++i) { + quint64 peerId; + stream >> peerId; + if (stream.status() != QDataStream::Ok) { + LOG(("App Error: " + "Bad data for SessionSettings::addFromSerialized()")); + return; + } + groupEmojiSectionHidden.emplace(DeserializePeerId(peerId)); + } + } + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for SessionSettings::addFromSerialized()")); @@ -433,6 +457,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { case ChatHelpers::SelectorTab::Gifs: _selectorTab = uncheckedTab; break; } _groupStickersSectionHidden = std::move(groupStickersSectionHidden); + _groupEmojiSectionHidden = std::move(groupEmojiSectionHidden); auto uncheckedSupportSwitch = static_cast( supportSwitch); switch (uncheckedSupportSwitch) { diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index 44d9e9a4a..222a94ead 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -75,6 +75,16 @@ public: _groupStickersSectionHidden.remove(peerId); } + void setGroupEmojiSectionHidden(PeerId peerId) { + _groupEmojiSectionHidden.insert(peerId); + } + [[nodiscard]] bool isGroupEmojiSectionHidden(PeerId peerId) const { + return _groupEmojiSectionHidden.contains(peerId); + } + void removeGroupEmojiSectionHidden(PeerId peerId) { + _groupEmojiSectionHidden.remove(peerId); + } + void setMediaLastPlaybackPosition(DocumentId id, crl::time time); [[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const; @@ -137,6 +147,7 @@ private: ChatHelpers::SelectorTab _selectorTab; // per-window base::flat_set _groupStickersSectionHidden; + base::flat_set _groupEmojiSectionHidden; bool _hadLegacyCallsPeerToPeerNobody = false; Data::AutoDownload::Full _autoDownload; rpl::variable _archiveCollapsed = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 14c2d0bad..62404da81 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -299,7 +299,9 @@ Controller::Controller(not_null delegate) _reactions->chosen( ) | rpl::start_with_next([=](Reactions::Chosen chosen) { - reactionChosen(chosen.mode, chosen.reaction); + if (reactionChosen(chosen.mode, chosen.reaction)) { + _reactions->animateAndProcess(std::move(chosen)); + } }, _lifetime); _delegate->storiesLayerShown( @@ -628,13 +630,15 @@ void Controller::toggleLiked() { _reactions->toggleLiked(); } -void Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { +bool Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) { + auto result = true; if (mode == ReactionsMode::Message) { - _replyArea->sendReaction(chosen.id); + result = _replyArea->sendReaction(chosen.id); } else if (const auto peer = shownPeer()) { peer->owner().stories().sendReaction(_shown, chosen.id); } unfocusReply(); + return result; } void Controller::showFullCaption() { @@ -879,6 +883,7 @@ void Controller::show( const auto document = story->document(); _header->show({ .peer = peer, + .fromPeer = story->fromPeer(), .repostPeer = _repostView ? _repostView->fromPeer() : nullptr, .repostFrom = _repostView ? _repostView->fromName() : nullptr, .date = story->date(), @@ -907,7 +912,7 @@ void Controller::show( .views = story->views(), .total = story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }, _reactions->likedValue()); if (const auto nowLikeButton = _recentViews->likeButton()) { if (wasLikeButton != nowLikeButton) { @@ -915,7 +920,7 @@ void Controller::show( } } - if (peer->isSelf() || peer->isChannel() || peer->isServiceUser()) { + if (peer->isSelf() || peer->isBroadcast() || peer->isServiceUser()) { _reactions->setReactionIconWidget(_recentViews->likeIconWidget()); } else if (const auto like = _replyArea->likeAnimationTarget()) { _reactions->setReactionIconWidget(like); @@ -1007,7 +1012,7 @@ void Controller::subscribeToSession() { .views = update.story->views(), .total = update.story->interactions(), .type = RecentViewsTypeFor(peer), - .canViewReactions = CanViewReactionsFor(peer), + .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(), }); updateAreas(update.story); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 0a6c4a8fe..b8ad1e20b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -260,7 +260,7 @@ private: [[nodiscard]] int repostSkipTop() const; void updateAreas(Data::Story *story); - void reactionChosen(ReactionsMode mode, ChosenReaction chosen); + bool reactionChosen(ReactionsMode mode, ChosenReaction chosen); const not_null _delegate; diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp index 8b14a984b..b247acf81 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -255,21 +255,37 @@ struct MadePrivacyBadge { result.text.append( QString::fromUtf8(" \xE2\x80\xA2 ") + tr::lng_edited(tr::now)); } - if (!data.repostFrom.isEmpty()) { + if (data.fromPeer || !data.repostFrom.isEmpty()) { result.text = QString::fromUtf8("\xE2\x80\xA2 ") + result.text; } return result; } +[[nodiscard]] TextWithEntities FromNameValue(not_null from) { + auto result = Ui::Text::SingleCustomEmoji( + from->owner().customEmojiManager().peerUserpicEmojiData( + from, + st::storiesRepostUserpicPadding)); + result.append(from->name()); + return Ui::Text::Link(result); +} + [[nodiscard]] TextWithEntities RepostNameValue( not_null owner, + PeerData *peer, QString name) { - const auto result = Ui::Text::SingleCustomEmoji( + auto result = Ui::Text::SingleCustomEmoji( owner->customEmojiManager().registerInternalEmoji( st::storiesRepostIcon, - st::storiesRepostIconPadding) - ).append(name); + st::storiesRepostIconPadding)); + if (peer) { + result.append(Ui::Text::SingleCustomEmoji( + owner->customEmojiManager().peerUserpicEmojiData( + peer, + st::storiesRepostUserpicPadding))); + } + result.append(name); return Ui::Text::Link(result); } @@ -371,24 +387,28 @@ void Header::show(HeaderData data) { _date->widthValue( ) | rpl::start_with_next(updateInfoGeometry, _date->lifetime()); - if (data.repostFrom.isEmpty()) { + if (!data.fromPeer && data.repostFrom.isEmpty()) { _repost = nullptr; } else { _repost = std::make_unique( _widget.get(), st::storiesHeaderDate); - const auto repostName = RepostNameValue( - &data.peer->owner(), - data.repostFrom); + const auto prefixName = data.fromPeer + ? FromNameValue(data.fromPeer) + : RepostNameValue( + &data.peer->owner(), + data.repostPeer, + data.repostFrom); + const auto prefix = data.fromPeer ? data.fromPeer : data.repostPeer; _repost->setMarkedText( - data.repostPeer ? Ui::Text::Link(repostName) : repostName, + (prefix ? Ui::Text::Link(prefixName) : prefixName), Core::MarkedTextContext{ .session = &data.peer->session(), .customEmojiRepaint = [=] { _repost->update(); }, }); - if (const auto peer = data.repostPeer) { + if (prefix) { _repost->setClickHandlerFilter([=](const auto &...) { - _controller->uiShow()->show(PrepareShortInfoBox(peer)); + _controller->uiShow()->show(PrepareShortInfoBox(prefix)); return false; }); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h index cf360cc90..f02c4ff37 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_header.h +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -32,6 +32,7 @@ enum class PauseState; struct HeaderData { not_null peer; + PeerData *fromPeer = nullptr; PeerData *repostPeer = nullptr; QString repostFrom; TimeId date = 0; diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp index bea75eb90..f401f6b37 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.cpp @@ -793,7 +793,7 @@ Reactions::Reactions(not_null controller) : _controller(controller) , _panel(std::make_unique(_controller)) { _panel->chosen() | rpl::start_with_next([=](Chosen &&chosen) { - animateAndProcess(std::move(chosen)); + _chosen.fire(std::move(chosen)); }, _lifetime); } @@ -887,7 +887,7 @@ auto Reactions::attachToMenu( selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) { menu->hideMenu(); - animateAndProcess({ reaction, ReactionsMode::Reaction }); + _chosen.fire({ reaction, ReactionsMode::Reaction }); }, selector->lifetime()); return AttachSelectorResult::Attached; @@ -933,7 +933,7 @@ void Reactions::toggleLiked() { void Reactions::applyLike(Data::ReactionId id) { if (_liked.current() != id) { - animateAndProcess({ { .id = id }, ReactionsMode::Reaction }); + _chosen.fire({ { .id = id }, ReactionsMode::Reaction }); } } @@ -971,8 +971,6 @@ void Reactions::animateAndProcess(Chosen &&chosen) { .scaleOutTarget = scaleOutTarget, }, target, std::move(done)); } - - _chosen.fire(std::move(chosen)); } void Reactions::assignLikedId(Data::ReactionId id) { diff --git a/Telegram/SourceFiles/media/stories/media_stories_reactions.h b/Telegram/SourceFiles/media/stories/media_stories_reactions.h index d27761f46..de9f0e0ce 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reactions.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reactions.h @@ -87,6 +87,8 @@ public: void attachToReactionButton(not_null button); void setReactionIconWidget(Ui::RpWidget *widget); + void animateAndProcess(Chosen &&chosen); + using AttachStripResult = HistoryView::Reactions::AttachSelectorResult; [[nodiscard]] AttachStripResult attachToMenu( not_null menu, @@ -95,8 +97,6 @@ public: private: class Panel; - void animateAndProcess(Chosen &&chosen); - void assignLikedId(Data::ReactionId id); [[nodiscard]] Fn setLikedIdIconInit( not_null owner, diff --git a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp index 442199300..e6b53d277 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_recent_views.cpp @@ -139,7 +139,7 @@ constexpr auto kLoadViewsPages = 2; RecentViewsType RecentViewsTypeFor(not_null peer) { return peer->isSelf() ? RecentViewsType::Self - : peer->isChannel() + : peer->isBroadcast() ? RecentViewsType::Channel : peer->isServiceUser() ? RecentViewsType::Changelog diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp index d06f32dbb..d6959d1a6 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_account.h" #include "storage/storage_media_prepare.h" #include "ui/chat/attach/attach_prepare.h" +#include "ui/text/format_values.h" #include "ui/round_rect.h" #include "window/section_widget.h" #include "styles/style_boxes.h" // sendMediaPreviewSize. @@ -49,11 +50,15 @@ namespace Media::Stories { namespace { [[nodiscard]] rpl::producer PlaceholderText( - const std::shared_ptr &show) { - return show->session().data().stories().stealthModeValue( - ) | rpl::map([](Data::StealthMode value) { - return value.enabledTill; - }) | rpl::distinct_until_changed() | rpl::map([](TimeId till) { + const std::shared_ptr &show, + rpl::producer isComment) { + return rpl::combine( + show->session().data().stories().stealthModeValue(), + std::move(isComment) + ) | rpl::map([](Data::StealthMode value, bool isComment) { + return std::tuple(value.enabledTill, isComment); + }) | rpl::distinct_until_changed( + ) | rpl::map([](TimeId till, bool isComment) { return rpl::single( rpl::empty ) | rpl::then( @@ -64,11 +69,13 @@ namespace { return left > 0; }) | rpl::then( rpl::single(0) - ) | rpl::map([](TimeId left) { + ) | rpl::map([=](TimeId left) { return left ? tr::lng_stealth_mode_countdown( lt_left, rpl::single(TimeLeftText(left))) + : isComment + ? tr::lng_story_comment_ph() : tr::lng_story_reply_ph(); }) | rpl::flatten_latest(); }) | rpl::flatten_latest(); @@ -118,7 +125,9 @@ ReplyArea::ReplyArea(not_null controller) .mode = HistoryView::ComposeControlsMode::Normal, .sendMenuType = SendMenu::Type::SilentOnly, .stickerOrEmojiChosen = _controller->stickerOrEmojiChosen(), - .customPlaceholder = PlaceholderText(_controller->uiShow()), + .customPlaceholder = PlaceholderText( + _controller->uiShow(), + rpl::deferred([=] { return _isComment.value(); })), .voiceCustomCancelText = tr::lng_record_cancel_stories(tr::now), .voiceLockFromBottom = true, .features = { @@ -171,7 +180,7 @@ void ReplyArea::initGeometry() { }, _lifetime); } -void ReplyArea::sendReaction(const Data::ReactionId &id) { +bool ReplyArea::sendReaction(const Data::ReactionId &id) { Expects(_data.peer != nullptr); auto message = Api::MessageToSend(prepareSendAction({})); @@ -188,9 +197,8 @@ void ReplyArea::sendReaction(const Data::ReactionId &id) { }; } } - if (!message.textWithTags.empty()) { - send(std::move(message), {}, true); - } + return !message.textWithTags.empty() + && send(std::move(message), {}, true); } void ReplyArea::send(Api::SendOptions options) { @@ -203,10 +211,14 @@ void ReplyArea::send(Api::SendOptions options) { send(std::move(message), options); } -void ReplyArea::send( +bool ReplyArea::send( Api::MessageToSend message, Api::SendOptions options, bool skipToast) { + if (!options.scheduled && showSlowmodeError()) { + return false; + } + const auto error = GetErrorTextForSending( _data.peer, { @@ -216,12 +228,14 @@ void ReplyArea::send( }); if (!error.isEmpty()) { _controller->uiShow()->showToast(error); + return false; } session().api().sendMessage(std::move(message)); finishSending(skipToast); _controls->clear(); + return true; } void ReplyArea::sendVoice(VoiceToSend &&data) { @@ -249,7 +263,8 @@ bool ReplyArea::sendExistingDocument( if (error) { show->showToast(*error); return false; - } else if (Window::ShowSendPremiumError(show, document)) { + } else if (showSlowmodeError() + || Window::ShowSendPremiumError(show, document)) { return false; } @@ -279,6 +294,8 @@ bool ReplyArea::sendExistingPhoto( if (error) { show->showToast(*error); return false; + } else if (showSlowmodeError()) { + return false; } Api::SendExistingPhoto( @@ -409,6 +426,8 @@ void ReplyArea::chooseAttach( if (const auto error = Data::AnyFileRestrictionError(peer)) { _controller->uiShow()->showToast(*error); return; + } else if (showSlowmodeError()) { + return; } const auto filter = (overrideSendImagesAsPhotos == true) @@ -505,6 +524,7 @@ bool ReplyArea::confirmSendingFiles( .show = show, .list = std::move(list), .caption = _controls->getTextWithAppliedMarkdown(), + .captionToPeer = _data.peer, .limits = DefaultLimitsForPeer(_data.peer), .check = DefaultCheckForPeer(show, _data.peer), .sendType = Api::SendType::Normal, @@ -665,6 +685,7 @@ void ReplyArea::show( const auto peer = data.peer; const auto history = peer ? peer->owner().history(peer).get() : nullptr; const auto user = peer->asUser(); + _isComment = peer->isMegagroup(); auto writeRestriction = Data::CanSendAnythingValue( peer ) | rpl::map([=](bool can) { @@ -680,8 +701,13 @@ void ReplyArea::show( .type = WriteRestrictionType::PremiumRequired, }; }); + using namespace HistoryView; _controls->setHistory({ .history = history, + .showSlowmodeError = [=] { return showSlowmodeError(); }, + .sendActionFactory = [=] { return prepareSendAction({}); }, + .slowmodeSecondsLeft = SlowmodeSecondsLeft(history->peer), + .sendDisabledBySlowmode = SendDisabledBySlowmode(history->peer), .liked = std::move( likedValue ) | rpl::map([](const Data::ReactionId &id) { @@ -691,7 +717,7 @@ void ReplyArea::show( }); _controls->clear(); const auto hidden = peer - && (!peer->isUser() || peer->isSelf() || peer->isServiceUser()); + && (peer->isBroadcast() || peer->isSelf() || peer->isServiceUser()); const auto cant = !peer; if (!hidden && !cant) { _controls->show(); @@ -713,6 +739,33 @@ void ReplyArea::show( } } +bool ReplyArea::showSlowmodeError() { + const auto text = [&] { + const auto story = _controller->story(); + if (!story) { + return QString(); + } + const auto peer = story->peer(); + if (const auto left = peer->slowmodeSecondsLeft()) { + return tr::lng_slowmode_enabled( + tr::now, + lt_left, + Ui::FormatDurationWordsSlowmode(left)); + } else if (peer->slowmodeApplied()) { + const auto history = peer->owner().history(peer); + if (const auto item = history->latestSendingMessage()) { + return tr::lng_slowmode_no_many(tr::now); + } + } + return QString(); + }(); + if (text.isEmpty()) { + return false; + } + _controller->uiShow()->showToast(text); + return true; +} + Main::Session &ReplyArea::session() const { Expects(_data.peer != nullptr); diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h index bb5a58e70..90214b421 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_reply.h +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -64,7 +64,7 @@ public: void show( ReplyAreaData data, rpl::producer likedValue); - void sendReaction(const Data::ReactionId &id); + bool sendReaction(const Data::ReactionId &id); [[nodiscard]] bool focused() const; [[nodiscard]] rpl::producer focusedValue() const; @@ -84,7 +84,7 @@ private: [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null history() const; - void send( + bool send( Api::MessageToSend message, Api::SendOptions options, bool skipToast = false); @@ -142,8 +142,11 @@ private: void chooseAttach(std::optional overrideSendImagesAsPhotos); void showPremiumToast(not_null emoji); + [[nodiscard]] bool showSlowmodeError(); const not_null _controller; + rpl::variable _isComment; + const std::unique_ptr _controls; std::unique_ptr _cant; diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index d144eba5c..5da7f93a8 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -1046,4 +1046,5 @@ storiesRepostSimpleStyle: QuoteStyle(defaultQuoteStyle) { radius: 10px; } storiesRepostIcon: icon {{ "mediaview/mini_repost", windowFg }}; -storiesRepostIconPadding: margins(0px, 4px, 4px, 0px); +storiesRepostIconPadding: margins(0px, 4px, 2px, 0px); +storiesRepostUserpicPadding: margins(0px, 1px, 4px, 0px); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index d22012e17..1f17c1a7d 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -101,7 +101,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2bcb6f flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper = ChatFull; +channelFull#44c054a7 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -114,7 +114,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#76bec211 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#1e4c8a69 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -175,6 +175,7 @@ messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags. messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long = MessageAction; messageActionGiveawayLaunch#332ba9ed = MessageAction; messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction; +messageActionBoostApply#cc02aa6d boosts:int = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -980,6 +981,7 @@ channelAdminLogEventActionChangePeerColor#5796e780 prev_value:PeerColor new_valu channelAdminLogEventActionChangeProfilePeerColor#5e477b25 prev_value:PeerColor new_value:PeerColor = ChannelAdminLogEventAction; channelAdminLogEventActionChangeWallpaper#31bb5d52 prev_value:WallPaper new_value:WallPaper = ChannelAdminLogEventAction; channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_value:EmojiStatus = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1275,7 +1277,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector quote_offset:flags.10?int = MessageReplyHeader; -messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; +messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1554,7 +1556,7 @@ storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int forwards_co storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#af6365a1 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; +storyItem#79b26a24 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int from_id:flags.18?Peer fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; stories.allStories#6efc5e81 flags:# has_more:flags.0?true count:int state:string peer_stories:Vector chats:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; @@ -1570,7 +1572,7 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int = InputReplyTo; -inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; +inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1627,7 +1629,7 @@ peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long = help.peerColorSet#26219a58 colors:Vector = help.PeerColorSet; help.peerColorProfileSet#767d61eb palette_colors:Vector bg_colors:Vector story_colors:Vector = help.PeerColorSet; -help.peerColorOption#ef8430ab flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int = help.PeerColorOption; +help.peerColorOption#adec6ebe flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int group_min_level:flags.4?int = help.PeerColorOption; help.peerColorsNotModified#2ba1f5ce = help.PeerColors; help.peerColors#f8ed08 hash:int colors:Vector = help.PeerColors; @@ -2111,6 +2113,8 @@ channels.updateColor#d8aa3671 flags:# for_profile:flags.1?true channel:InputChan channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates; channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats; channels.updateEmojiStatus#f0d3e6a8 channel:InputChannel emoji_status:EmojiStatus = Updates; +channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int = Updates; +channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2248,4 +2252,4 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 173 +// LAYER 174 diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index bc2817d52..a672f81fc 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -619,6 +619,10 @@ postEvent: function(eventType, eventData) { } };)"); + if (!_webview) { + return false; + } + setupProgressGeometry(); return true; diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index b3ca397ac..2299bea98 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -280,6 +280,11 @@ bool GenerateDesktopFile( } } + if (!args.isEmpty() + && target->has_key("Desktop Entry", "DBusActivatable")) { + target->remove_key("Desktop Entry", "DBusActivatable"); + } + target->save_to_file(targetFile.toStdString()); } catch (const std::exception &e) { if (!silent) { diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index d61f61738..f45d68c0b 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -92,6 +92,8 @@ settingsPremiumIconVoice: icon {{ "settings/premium/voice", settingsIconFg }}; settingsPremiumIconFiles: icon {{ "settings/premium/files", settingsIconFg }}; settingsPremiumIconTranslations: icon {{ "settings/premium/translations", settingsIconFg }}; settingsPremiumIconTags: icon {{ "settings/premium/tags", settingsIconFg }}; +settingsPremiumIconLastSeen: icon {{ "settings/premium/lastseen", settingsIconFg }}; +settingsPremiumIconPrivacy: icon {{ "settings/premium/privacy", settingsIconFg }}; settingsStoriesIconOrder: icon {{ "settings/premium/stories_order", premiumButtonBg1 }}; settingsStoriesIconStealth: icon {{ "menu/stealth", premiumButtonBg1 }}; @@ -572,3 +574,14 @@ messagePrivacySubscribe: SettingsButton(settingsButtonLight) { iconLeft: 20px; } messagePrivacyLock: icon {{ "info/info_rights_lock", checkboxFg }}; + +peerAppearanceButton: SettingsButton(settingsButtonLight) { + padding: margins(60px, 8px, 22px, 8px); + iconLeft: 20px; +} +peerAppearanceCoverLabel: FlatLabel(boxDividerLabel) { + align: align(top); +} +peerAppearanceCoverLabelMargin: margins(22px, 0px, 22px, 17px); +peerAppearanceIconPadding: margins(0px, 15px, 0px, 5px); +peerAppearanceDividerTextMargin: margins(22px, 8px, 22px, 11px); diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index 9cea22884..69a70b46e 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -181,22 +181,26 @@ using Order = std::vector; [[nodiscard]] Order FallbackOrder() { return Order{ - u"wallpapers"_q, u"stories"_q, - u"double_limits"_q, u"more_upload"_q, - u"faster_download"_q, + u"double_limits"_q, + u"last_seen"_q, u"voice_to_text"_q, - u"no_ads"_q, + u"faster_download"_q, + u"translations"_q, + u"animated_emoji"_q, u"emoji_status"_q, u"saved_tags"_q, - u"infinite_reactions"_q, - u"premium_stickers"_q, - u"animated_emoji"_q, - u"advanced_chat_management"_q, + //u"peer_colors"_q, + u"wallpapers"_q, u"profile_badge"_q, + u"message_privacy"_q, + u"advanced_chat_management"_q, + u"no_ads"_q, + //u"app_icons"_q, + u"infinite_reactions"_q, u"animated_userpics"_q, - u"translations"_q, + u"premium_stickers"_q, }; } @@ -212,6 +216,26 @@ using Order = std::vector; true, }, }, + { + u"last_seen"_q, + Entry{ + &st::settingsPremiumIconLastSeen, + tr::lng_premium_summary_subtitle_last_seen(), + tr::lng_premium_summary_about_last_seen(), + PremiumPreview::LastSeen, + true, + }, + }, + { + u"message_privacy"_q, + Entry{ + &st::settingsPremiumIconPrivacy, + tr::lng_premium_summary_subtitle_message_privacy(), + tr::lng_premium_summary_about_message_privacy(), + PremiumPreview::MessagePrivacy, + true, + }, + }, { u"wallpapers"_q, Entry{ @@ -1532,6 +1556,10 @@ not_null CreateSubscribeButton( return PremiumPreview::InfiniteReactions; } else if (s == u"saved_tags"_q) { return PremiumPreview::TagsForMessages; + } else if (s == u"last_seen"_q) { + return PremiumPreview::LastSeen; + } else if (s == u"message_privacy"_q) { + return PremiumPreview::MessagePrivacy; } else if (s == u"premium_stickers"_q) { return PremiumPreview::Stickers; } else if (s == u"animated_emoji"_q) { diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index a8466de5e..6620c4bbb 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -172,6 +172,7 @@ AdminLog::OwnedItem GenerateForwardedItem( MTP_flags(flags), MTP_int(0), // Not used (would've been trimmed to 32 bits). peerToMTP(history->peer->id), + MTPint(), // from_boosts_applied peerToMTP(history->peer->id), MTPPeer(), // saved_peer_id MTP_messageFwdHeader( diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index 5a226eea4..f4676dcc5 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/boxes/boost_box.h" +#include "info/profile/info_profile_icon.h" #include "lang/lang_keys.h" #include "ui/boxes/confirm_box.h" #include "ui/effects/fireworks_animation.h" @@ -14,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" +#include "ui/wrap/fade_wrap.h" #include "ui/painter.h" #include "styles/style_giveaway.h" #include "styles/style_layers.h" @@ -45,10 +47,11 @@ namespace { } [[nodiscard]] object_ptr MakeTitle( - not_null box, + not_null parent, rpl::producer title, - rpl::producer repeated) { - auto result = object_ptr(box); + rpl::producer repeated, + bool centered = true) { + auto result = object_ptr(parent); struct State { not_null title; @@ -57,7 +60,7 @@ namespace { const auto notEmpty = [](const QString &text) { return !text.isEmpty(); }; - const auto state = box->lifetime().make_state(State{ + const auto state = parent->lifetime().make_state(State{ .title = Ui::CreateChild( result.data(), rpl::duplicate(title), @@ -83,7 +86,9 @@ namespace { const auto available = outer - repeated - skip; const auto use = std::min(state->title->textMaxWidth(), available); state->title->resizeToWidth(use); - const auto left = (outer - use - skip - repeated) / 2; + const auto left = centered + ? (outer - use - skip - repeated) / 2 + : 0; state->title->moveToLeft(left, 0); const auto mleft = st::boostTitleBadge.margin.left(); const auto mtop = st::boostTitleBadge.margin.top(); @@ -103,6 +108,171 @@ namespace { return result; } +[[nodiscard]] object_ptr MakeFeaturesBadge( + not_null parent, + rpl::producer text) { + auto result = object_ptr( + parent, + std::move(text), + st::boostLevelBadge); + const auto label = result.data(); + + label->show(); + label->paintRequest() | rpl::start_with_next([=] { + const auto size = label->textMaxWidth(); + const auto rect = QRect( + (label->width() - size) / 2, + st::boostLevelBadge.margin.top(), + size, + st::boostLevelBadge.style.font->height + ).marginsAdded(st::boostLevelBadge.margin); + auto p = QPainter(label); + auto gradient = QLinearGradient( + rect.topLeft(), + rect.topRight()); + gradient.setStops(Ui::Premium::GiftGradientStops()); + p.setBrush(gradient); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, rect.height() / 2., rect.height() / 2.); + + const auto &lineFg = st::windowBgRipple; + const auto line = st::boostLevelBadgeLine; + const auto top = st::boostLevelBadge.margin.top() + + ((st::boostLevelBadge.style.font->height - line) / 2); + const auto left = 0; + const auto skip = st::boostLevelBadgeSkip; + if (const auto right = rect.x() - skip; right > left) { + p.fillRect(left, top, right - left, line, lineFg); + } + const auto right = label->width(); + if (const auto left = rect.x() + rect.width() + skip + ; left < right) { + p.fillRect(left, top, right - left, line, lineFg); + } + }, label->lifetime()); + + return result; +} + +void AddFeaturesList( + not_null container, + const Ui::BoostFeatures &features, + int startFromLevel, + bool group) { + const auto add = [&]( + rpl::producer text, + const style::icon &st) { + const auto label = container->add( + object_ptr( + container, + std::move(text), + st::boostFeatureLabel), + st::boostFeaturePadding); + object_ptr( + label, + st, + st::boostFeatureIconPosition); + }; + const auto proj = &Ui::Text::RichLangValue; + const auto max = std::max({ + features.linkLogoLevel, + features.transcribeLevel, + features.emojiPackLevel, + features.emojiStatusLevel, + features.wallpaperLevel, + features.customWallpaperLevel, + (features.nameColorsByLevel.empty() + ? 0 + : features.nameColorsByLevel.back().first), + (features.linkStylesByLevel.empty() + ? 0 + : features.linkStylesByLevel.back().first), + }); + auto nameColors = 0; + auto linkStyles = 0; + for (auto i = std::max(startFromLevel, 1); i <= max; ++i) { + const auto unlocks = (i == startFromLevel); + container->add( + MakeFeaturesBadge( + container, + (unlocks + ? tr::lng_boost_level_unlocks + : tr::lng_boost_level)( + lt_count, + rpl::single(float64(i)))), + st::boostLevelBadgePadding); + add( + tr::lng_feature_stories(lt_count, rpl::single(float64(i)), proj), + st::boostFeatureStories); + if (!group) { + add(tr::lng_feature_reactions( + lt_count, + rpl::single(float64(i)), + proj + ), st::boostFeatureCustomReactions); + if (const auto j = features.nameColorsByLevel.find(i) + ; j != end(features.nameColorsByLevel)) { + nameColors += j->second; + } + if (nameColors > 0) { + add(tr::lng_feature_name_color_channel( + lt_count, + rpl::single(float64(nameColors)), + proj + ), st::boostFeatureName); + } + if (const auto j = features.linkStylesByLevel.find(i) + ; j != end(features.linkStylesByLevel)) { + linkStyles += j->second; + } + if (linkStyles > 0) { + add(tr::lng_feature_link_style_channel( + lt_count, + rpl::single(float64(linkStyles)), + proj + ), st::boostFeatureLink); + } + if (i >= features.linkLogoLevel) { + add( + tr::lng_feature_link_emoji(proj), + st::boostFeatureCustomLink); + } + } + if (group && i >= features.emojiPackLevel) { + add( + tr::lng_feature_custom_emoji_pack(proj), + st::boostFeatureCustomEmoji); + } + if (group && i >= features.transcribeLevel) { + add( + tr::lng_feature_transcribe(proj), + st::boostFeatureTranscribe); + } + if (i >= features.emojiStatusLevel) { + add( + tr::lng_feature_emoji_status(proj), + st::boostFeatureEmojiStatus); + } + if (i >= features.wallpaperLevel) { + add( + (group + ? tr::lng_feature_backgrounds_group + : tr::lng_feature_backgrounds_channel)( + lt_count, + rpl::single(float64(features.wallpapersCount)), + proj), + st::boostFeatureBackground); + } + if (i >= features.customWallpaperLevel) { + add( + (group + ? tr::lng_feature_custom_background_group + : tr::lng_feature_custom_background_channel)(proj), + st::boostFeatureCustomBackground); + } + } +} + } // namespace void StartFireworks(not_null parent) { @@ -153,7 +323,10 @@ void BoostBox( state->data.value(), st::boxRowPadding); - box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); + box->setMaxHeight(st::boostBoxMaxHeight); + const auto close = box->addTopButton( + st::boxTitleClose, + [=] { box->closeBox(); }); const auto name = data.name; @@ -165,9 +338,13 @@ void BoostBox( rpl::single(name)) : !counters.nextLevelBoosts ? tr::lng_boost_channel_title_max() - : !counters.level - ? tr::lng_boost_channel_title_first() - : tr::lng_boost_channel_title_more(); + : counters.level + ? (data.group + ? tr::lng_boost_channel_title_more_group() + : tr::lng_boost_channel_title_more()) + : (data.group + ? tr::lng_boost_channel_title_first_group() + : tr::lng_boost_channel_title_first()); }) | rpl::flatten_latest(); auto repeated = state->data.value( ) | rpl::map([=](BoostCounters counters) { @@ -188,43 +365,57 @@ void BoostBox( Ui::Text::RichLangValue); return (counters.mine || full) ? (left - ? (!counters.level - ? tr::lng_boost_channel_you_first( - lt_count, - rpl::single(float64(left)), - Ui::Text::RichLangValue) - : tr::lng_boost_channel_you_more( - lt_count, - rpl::single(float64(left)), - lt_post, - std::move(post), - Ui::Text::RichLangValue)) + ? tr::lng_boost_channel_needs_unlock( + lt_count, + rpl::single(float64(left)), + lt_channel, + rpl::single(bold), + Ui::Text::RichLangValue) : (!counters.level - ? tr::lng_boost_channel_reached_first( - Ui::Text::RichLangValue) - : tr::lng_boost_channel_reached_more( - lt_count, - rpl::single(float64(counters.level)), - lt_post, - std::move(post), - Ui::Text::RichLangValue))) - : !counters.level - ? tr::lng_boost_channel_needs_first( + ? (data.group + ? tr::lng_boost_channel_reached_first_group + : tr::lng_boost_channel_reached_first)( + Ui::Text::RichLangValue) + : (data.group + ? tr::lng_boost_channel_reached_more_group + : tr::lng_boost_channel_reached_more)( + lt_count, + rpl::single(float64(counters.level)), + lt_post, + std::move(post), + Ui::Text::RichLangValue))) + : tr::lng_boost_channel_needs_unlock( lt_count, rpl::single(float64(left)), lt_channel, rpl::single(bold), - Ui::Text::RichLangValue) - : tr::lng_boost_channel_needs_more( - lt_count, - rpl::single(float64(left)), - lt_channel, - rpl::single(bold), - lt_post, - std::move(post), Ui::Text::RichLangValue); }) | rpl::flatten_latest(); + auto faded = object_ptr>( + close->parentWidget(), + MakeTitle( + box, + rpl::duplicate(title), + rpl::duplicate(repeated), + false)); + const auto titleInner = faded.data(); + titleInner->move(st::boxTitlePosition); + titleInner->resizeToWidth(st::boxWideWidth + - st::boxTitleClose.width + - st::boxTitlePosition.x()); + titleInner->hide(anim::type::instant); + crl::on_main(titleInner, [=] { + titleInner->raise(); + titleInner->toggleOn(rpl::single( + rpl::empty + ) | rpl::then( + box->scrolls() + ) | rpl::map([=] { + return box->scrollTop() > 0; + })); + }); + box->addRow( MakeTitle(box, std::move(title), std::move(repeated)), st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); @@ -237,6 +428,14 @@ void BoostBox( (st::boxRowPadding + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + const auto current = state->data.current(); + box->setTitle(rpl::single(QString())); + AddFeaturesList( + box->verticalLayout(), + data.features, + current.level + (current.nextLevelBoosts ? 1 : 0), + data.group); + const auto allowMulti = data.allowMulti; auto submit = state->data.value( ) | rpl::map([=](BoostCounters counters) { @@ -244,6 +443,8 @@ void BoostBox( ? tr::lng_box_ok() : (counters.mine > 0) ? tr::lng_boost_again_button() + : data.group + ? tr::lng_boost_group_button() : tr::lng_boost_channel_button(); }) | rpl::flatten_latest(); @@ -408,9 +609,11 @@ object_ptr MakeLinkLabel( return result; } -void BoostBoxAlready(not_null box) { +void BoostBoxAlready(not_null box, bool group) { ConfirmBox(box, { - .text = tr::lng_boost_error_already_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_already_text_group + : tr::lng_boost_error_already_text)(Text::RichLangValue), .title = tr::lng_boost_error_already_title(), .inform = true, }); @@ -435,16 +638,23 @@ void GiftForBoostsBox( }); } -void GiftedNoBoostsBox(not_null box) { +void GiftedNoBoostsBox(not_null box, bool group) { InformBox(box, { - .text = tr::lng_boost_error_gifted_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_gifted_text_group + : tr::lng_boost_error_gifted_text)(Text::RichLangValue), .title = tr::lng_boost_error_gifted_title(), }); } -void PremiumForBoostsBox(not_null box, Fn buyPremium) { +void PremiumForBoostsBox( + not_null box, + bool group, + Fn buyPremium) { ConfirmBox(box, { - .text = tr::lng_boost_error_premium_text(Text::RichLangValue), + .text = (group + ? tr::lng_boost_error_premium_text_group + : tr::lng_boost_error_premium_text)(Text::RichLangValue), .confirmed = buyPremium, .confirmText = tr::lng_boost_error_premium_yes(), .title = tr::lng_boost_error_premium_title(), @@ -474,6 +684,8 @@ void AskBoostBox( return tr::lng_boost_channel_title_wallpaper(); }, [&](AskBoostEmojiStatus data) { return tr::lng_boost_channel_title_status(); + }, [&](AskBoostEmojiPack data) { + return tr::lng_boost_group_title_emoji(); }, [&](AskBoostCustomReactions data) { return tr::lng_boost_channel_title_reactions(); }); @@ -484,12 +696,21 @@ void AskBoostBox( rpl::single(float64(data.requiredLevel)), Ui::Text::RichLangValue); }, [&](AskBoostWallpaper data) { - return tr::lng_boost_channel_needs_level_wallpaper( - lt_count, - rpl::single(float64(data.requiredLevel)), - Ui::Text::RichLangValue); + return (data.group + ? tr::lng_boost_group_needs_level_wallpaper + : tr::lng_boost_channel_needs_level_wallpaper)( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue); }, [&](AskBoostEmojiStatus data) { - return tr::lng_boost_channel_needs_level_status( + return (data.group + ? tr::lng_boost_group_needs_level_status + : tr::lng_boost_channel_needs_level_status)( + lt_count, + rpl::single(float64(data.requiredLevel)), + Ui::Text::RichLangValue); + }, [&](AskBoostEmojiPack data) { + return tr::lng_boost_group_needs_level_emoji( lt_count, rpl::single(float64(data.requiredLevel)), Ui::Text::RichLangValue); @@ -556,8 +777,6 @@ void FillBoostLimit( container->add(object_ptr(container, skip)); }; - addSkip(st::boostSkipTop); - const auto ratio = [=](BoostCounters counters) { const auto min = counters.thisLevelBoosts; const auto max = counters.nextLevelBoosts; diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index 68ff1d771..78e046cba 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -30,10 +30,24 @@ struct BoostCounters { BoostCounters) = default; }; +struct BoostFeatures { + base::flat_map nameColorsByLevel; + base::flat_map linkStylesByLevel; + int linkLogoLevel = 0; + int transcribeLevel = 0; + int emojiPackLevel = 0; + int emojiStatusLevel = 0; + int wallpaperLevel = 0; + int wallpapersCount = 0; + int customWallpaperLevel = 0; +}; + struct BoostBoxData { QString name; BoostCounters boost; + BoostFeatures features; bool allowMulti = false; + bool group = false; }; void BoostBox( @@ -41,14 +55,17 @@ void BoostBox( BoostBoxData data, Fn)> boost); -void BoostBoxAlready(not_null box); +void BoostBoxAlready(not_null box, bool group); void GiftForBoostsBox( not_null box, QString channel, int receive, bool again); -void GiftedNoBoostsBox(not_null box); -void PremiumForBoostsBox(not_null box, Fn buyPremium); +void GiftedNoBoostsBox(not_null box, bool group); +void PremiumForBoostsBox( + not_null box, + bool group, + Fn buyPremium); struct AskBoostChannelColor { int requiredLevel = 0; @@ -56,10 +73,16 @@ struct AskBoostChannelColor { struct AskBoostWallpaper { int requiredLevel = 0; + bool group = false; }; struct AskBoostEmojiStatus { int requiredLevel = 0; + bool group = false; +}; + +struct AskBoostEmojiPack { + int requiredLevel = 0; }; struct AskBoostCustomReactions { @@ -71,6 +94,7 @@ struct AskBoostReason { AskBoostChannelColor, AskBoostWallpaper, AskBoostEmojiStatus, + AskBoostEmojiPack, AskBoostCustomReactions> data; }; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index c34db9164..ed57fdf2b 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -494,6 +494,7 @@ bool Panel::showWebview( const QString &url, const Webview::ThemeParams ¶ms, rpl::producer bottomText) { + _bottomText = std::move(bottomText); if (!_webview && !createWebview(params)) { return false; } @@ -503,24 +504,6 @@ bool Panel::showWebview( updateThemeParams(params); _webview->window.navigate(url); _widget->setBackAllowed(allowBack); - if (bottomText) { - const auto &padding = st::paymentsPanelPadding; - const auto label = CreateChild( - _webviewBottom.get(), - std::move(bottomText), - st::paymentsWebviewBottom); - const auto height = padding.top() - + label->heightNoMargins() - + padding.bottom(); - rpl::combine( - _webviewBottom->widthValue(), - label->widthValue() - ) | rpl::start_with_next([=](int outerWidth, int width) { - label->move((outerWidth - width) / 2, padding.top()); - }, label->lifetime()); - label->show(); - _webviewBottom->resize(_webviewBottom->width(), height); - } _widget->setMenuAllowed([=](const Ui::Menu::MenuCallback &callback) { if (_hasSettingsButton) { callback(tr::lng_bot_settings(tr::now), [=] { @@ -533,7 +516,7 @@ bool Panel::showWebview( }, &st::menuIconLeave); } callback(tr::lng_bot_reload_page(tr::now), [=] { - if (_webview) { + if (_webview && _webview->window.widget()) { _webview->window.reload(); } else if (const auto params = _delegate->botThemeParams() ; createWebview(params)) { @@ -562,16 +545,28 @@ bool Panel::showWebview( return true; } -bool Panel::createWebview(const Webview::ThemeParams ¶ms) { - auto outer = base::make_unique_q(_widget.get()); - const auto container = outer.get(); - _widget->showInner(std::move(outer)); - _webviewParent = container; - +void Panel::createWebviewBottom() { _webviewBottom = std::make_unique(_widget.get()); const auto bottom = _webviewBottom.get(); bottom->show(); + const auto &padding = st::paymentsPanelPadding; + const auto label = CreateChild( + _webviewBottom.get(), + _bottomText.value(), + st::paymentsWebviewBottom); + const auto height = padding.top() + + label->heightNoMargins() + + padding.bottom(); + rpl::combine( + _webviewBottom->widthValue(), + label->widthValue() + ) | rpl::start_with_next([=](int outerWidth, int width) { + label->move((outerWidth - width) / 2, padding.top()); + }, label->lifetime()); + label->show(); + _webviewBottom->resize(_webviewBottom->width(), height); + bottom->heightValue( ) | rpl::start_with_next([=](int height) { const auto inner = _widget->innerGeometry(); @@ -579,11 +574,22 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { height = _mainButton->height(); } bottom->move(inner.x(), inner.y() + inner.height() - height); - container->resize(inner.width(), inner.height() - height); + if (const auto container = _webviewParent.data()) { + container->setFixedSize(inner.width(), inner.height() - height); + } bottom->resizeToWidth(inner.width()); }, bottom->lifetime()); - container->show(); +} +bool Panel::createWebview(const Webview::ThemeParams ¶ms) { + auto outer = base::make_unique_q(_widget.get()); + const auto container = outer.get(); + _widget->showInner(std::move(outer)); + _webviewParent = container; + + createWebviewBottom(); + + container->show(); _webview = std::make_unique( container, Webview::WindowConfig{ @@ -592,6 +598,7 @@ bool Panel::createWebview(const Webview::ThemeParams ¶ms) { }); const auto raw = &_webview->window; + const auto bottom = _webviewBottom.get(); QObject::connect(container, &QObject::destroyed, [=] { if (_webview && &_webview->window == raw) { base::take(_webview); @@ -703,6 +710,10 @@ postEvent: function(eventType, eventData) { } };)"); + if (!_webview) { + return false; + } + setupProgressGeometry(); return true; @@ -1177,7 +1188,7 @@ void Panel::createMainButton() { } button->move(inner.x(), inner.y() + inner.height() - height); if (const auto raw = _webviewParent.data()) { - raw->resize(inner.width(), inner.height() - height); + raw->setFixedSize(inner.width(), inner.height() - height); } button->resizeToWidth(inner.width()); _webviewBottom->setVisible(!shown); diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index 20a9a4f72..9c4be46d0 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -106,6 +106,7 @@ private: struct WebviewWithLifetime; bool createWebview(const Webview::ThemeParams ¶ms); + void createWebviewBottom(); void showWebviewProgress(); void hideWebviewProgress(); void setTitle(rpl::producer title); @@ -150,6 +151,7 @@ private: std::unique_ptr _widget; std::unique_ptr _webview; std::unique_ptr _webviewBottom; + rpl::variable _bottomText; QPointer _webviewParent; std::unique_ptr