Compare commits

...

87 Commits

Author SHA1 Message Date
AlexeyZavar bf5337259d fix: message shot replies in topics 2024-02-22 22:59:22 +03:00
AlexeyZavar 039795cab8 feat: disable custom backgrounds 2024-02-22 22:46:46 +03:00
AlexeyZavar e580640adc fix: crash 2024-02-20 23:26:31 +03:00
AlexeyZavar 3a9b470e47 fix: change ease 2024-02-20 22:47:27 +03:00
AlexeyZavar 5d145cc613 fix: crash 2024-02-20 22:36:10 +03:00
AlexeyZavar df0c1f814a fix: skill issue
Pro-tip: don't watch anime while coding.
2024-02-20 22:32:41 +03:00
ZavaruKitsu 7e62f8749d fix: prettify imports 2024-02-19 00:09:01 +03:00
ZavaruKitsu e592b21912 fix: update submodules 2024-02-18 23:28:34 +03:00
ZavaruKitsu e2fafc62fd fix: make it build
fix: `disableStories`
2024-02-18 23:25:12 +03:00
ZavaruKitsu 43b0df3f94 Merge tag 'v4.15.0' into dev
# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/dialogs/dialogs_row.cpp
#	Telegram/SourceFiles/dialogs/dialogs_widget.cpp
#	Telegram/SourceFiles/history/view/history_view_message.cpp
#	Telegram/SourceFiles/info/profile/info_profile_actions.cpp
#	Telegram/lib_ui
#	snap/snapcraft.yaml
2024-02-18 21:37:55 +03:00
ZavaruKitsu b8dda54e04 fix: a couple of crashes
fix: disable peer ids again
2024-02-18 21:30:48 +03:00
John Preston cb065146c2 Version 4.15: Fix build with GCC. 2024-02-18 21:00:10 +04:00
John Preston 3aab2a88aa Remove helper temporary code. 2024-02-18 20:56:51 +04:00
John Preston 9a012d8b9b Version 4.15: Fix build with Xcode. 2024-02-18 20:46:10 +04:00
John Preston eaad2034e6 Version 4.15.
- Stories from groups.
- Group appearance settings.
- Group emoji pack.
- Boost groups to unlock features.
2024-02-18 20:24:20 +04:00
John Preston 2a52eca5f5 Fix set icon in updating group emoji set. 2024-02-18 20:21:03 +04:00
John Preston 13ded3a82e Fix crash in removing group emoji set. 2024-02-18 20:15:55 +04:00
John Preston 9a0ea78e0f Don't show duplicated emoji set. 2024-02-18 20:11:57 +04:00
John Preston a2fe1506cc Fix build. 2024-02-18 17:55:07 +04:00
John Preston 608b4f30c5 Show thumbnail of selected group emoji pack. 2024-02-18 17:54:09 +04:00
Ilya Fedin 3af646a584 Disable system proxy support on Linux
Reading system proxy on Linux reads to massive crashes in system libraries.

Since those crashes are unreproducible in development environment, it's not possible to find the root cause.

Workarounds #27482.
2024-02-18 00:26:36 +04:00
John Preston 89c7147fd9 Improve group appearance box design. 2024-02-18 00:25:40 +04:00
John Preston d4ee45f9ff Use nice boost features icons. 2024-02-17 22:05:05 +04:00
John Preston df5454a0b0 Fix build. 2024-02-17 22:04:46 +04:00
John Preston ebf4e88a10 Fix crash in giveaway results view. 2024-02-17 21:49:07 +04:00
John Preston 0163ec314a Improve phrases for giveaways in groups. 2024-02-17 21:46:00 +04:00
23rd 46d5a93c96 Slightly improved fade effect for bottom side of input message field. 2024-02-17 20:33:59 +03:00
23rd dd6cbd2c36 Fixed edit of long text message without media. 2024-02-17 20:33:59 +03:00
Ilya Fedin 5bf6a93086 Replace usage of deprecated CRAFT_ARCH_TRIPLET 2024-02-17 21:11:45 +04:00
Ilya Fedin 1146ecfd45 Update macOS action to macOS 13 2024-02-17 21:11:45 +04:00
John Preston f59c46cd7c Remove "Boost Group" button in channels. 2024-02-17 21:06:07 +04:00
John Preston 20f3ef9a5b Fix stories likes in groups. 2024-02-17 21:06:07 +04:00
John Preston 41d3e16c7d Don't add peer saved / stories in topics profiles. 2024-02-17 21:06:07 +04:00
John Preston 46d11b6dcb Use correct phrases in group stories. 2024-02-17 21:06:07 +04:00
John Preston adc8966c61 Fix device pixel ratio in stories userpics. 2024-02-17 21:06:07 +04:00
John Preston d5353e8741 Fix hidden sender info display in Saved Messages.
Fixes #27463.
2024-02-17 21:06:07 +04:00
23rd e79be73025 Added ability to export peer ids from contact list. 2024-02-17 17:34:44 +03:00
23rd f20e393d77 Fixed fade effect for input message fields when theme is changed. 2024-02-17 17:34:44 +03:00
23rd 6336ed33fa Removed some include directives. 2024-02-17 17:34:44 +03:00
Ilya Fedin bcc864cbe1 Fix build without Wayland integration 2024-02-16 23:41:07 +04:00
Ilya Fedin 2dd1ab31b9 Update submodules 2024-02-16 19:06:33 +04:00
Ilya Fedin 9f58a5a4b0 Limit source depth for snapcraft desktop helpers 2024-02-16 19:06:33 +04:00
Ilya Fedin e31704d01e Update Qt to 6.6.2 on Linux 2024-02-16 19:06:33 +04:00
John Preston f1b7d9ebba Version 4.14.16.
- Boost groups.
2024-02-16 15:00:19 +04:00
23rd 991110b84d Fixed fade effect for input message fields in compose controls. 2024-02-16 15:00:19 +04:00
John Preston 18ca4a9eeb Add a couple of phrases for group wallpapers. 2024-02-16 12:22:38 +04:00
John Preston 9f7ee3cafd Correctly check webview init success.
Also correctly init recreated webview bottom bar.

Fixes #27481, fixes #27479.
2024-02-16 12:14:13 +04:00
John Preston 137155afd8 Use round thumbnail in forum stories. 2024-02-16 11:27:09 +04:00
John Preston 38e082422a Show story sender / repost userpic under story source. 2024-02-15 13:51:41 +04:00
John Preston 0fd8ceca6b Use Ui::DynamicImage and ui/dynamic_thumbnails module. 2024-02-15 13:51:41 +04:00
John Preston f674ace805 Send comments to group stories. 2024-02-15 13:51:41 +04:00
John Preston 11f0847295 Update API scheme on layer 174. 2024-02-15 13:51:41 +04:00
John Preston 964846f1bb Add stories rights to group admins edit. 2024-02-15 13:51:41 +04:00
John Preston fc6f2520b7 Support free transcribes in groups. 2024-02-15 13:51:41 +04:00
John Preston cfaef4c441 Add Boost Group in group menu. 2024-02-15 13:51:40 +04:00
John Preston 571f1a5179 Implement free megagroup emoji set. 2024-02-15 13:51:40 +04:00
John Preston 08efa73b2b Allow editing group wallpaper / status / emoji set. 2024-02-15 13:51:40 +04:00
John Preston 680171226c Boost community features list. 2024-02-15 13:51:40 +04:00
John Preston ea12c2f62c Respect boosts restrictions lifting. 2024-02-15 13:51:40 +04:00
John Preston 180b14ea36 Allow saving boosts to lift restrictions. 2024-02-15 13:51:40 +04:00
John Preston f6a8c1e996 Allow boosts / giveaways in groups. 2024-02-15 13:51:40 +04:00
John Preston cb174cb0bf Add some more premium promo sections. 2024-02-15 13:51:40 +04:00
John Preston 33207b78d5 Show applied boosts in message bubbles. 2024-02-15 13:51:40 +04:00
John Preston e32cbf468b Update API scheme to layer 174. 2024-02-15 13:51:40 +04:00
John Preston c891ee3a45 Fix build on Windows. 2024-02-15 13:51:40 +04:00
23rd 6de471db17 Added counter label of characters limit to send files box. 2024-02-15 09:37:11 +03:00
23rd d1eaf284b1 Fixed text suggestion in toast when media caption is too long. 2024-02-15 08:22:16 +03:00
23rd 5cd0d82ffb Added counter label of characters limit for long media captions. 2024-02-15 08:21:35 +03:00
23rd 113c8a797f Added fade effect to input message fields. 2024-02-14 21:33:17 +03:00
23rd dcf4f45a36 Fixed ability to copy entire translated text from context menu. 2024-02-14 21:33:17 +03:00
Ilya Fedin 8b6a7a443e Switch from libproxy to GProxyResolver 2024-02-14 20:01:31 +04:00
Ilya Fedin c5db2b8175 Ensure custom arguments get applied in generated .desktop file 2024-02-14 09:31:19 +04:00
Ilya Fedin 00f98793b1 Update lib_webview 2024-02-12 19:52:08 +04:00
ZavaruKitsu 77d129a738 fix: crash 2024-02-12 00:58:52 +03:00
ZavaruKitsu af18da81f4 fix: crash 2024-02-12 00:39:01 +03:00
John Preston e5f90cd40d Version 4.14.15.
- Fix webview regression on Linux X11. (2nd attempt)
2024-02-10 00:32:40 +04:00
ZavaruKitsu 5bb633d982 fix: crashes & infinite loop 2024-02-09 20:00:14 +03:00
John Preston a10d668131 Version 4.14.14.
- Fix webview regression on Linux X11.
2024-02-09 20:18:40 +04:00
John Preston 8a62bacaa6 Fix anti-aliasing in emoji categories search. 2024-02-09 20:15:02 +04:00
100backslash001 bacab01f7e Remove duplicates from configure.py 2024-02-09 10:49:18 +04:00
23rd 5d3400033a Fixed ability to copy whole transcribed text and copy album captions. 2024-02-08 17:35:29 +03:00
23rd 0e571ea679 Added ability to copy filename of named documents. 2024-02-08 17:32:25 +03:00
23rd b959262140 Fixed legal link in some files. 2024-02-08 15:20:42 +03:00
Ilya Fedin fd3ce905c0 Update lib_webview 2024-02-08 11:45:09 +04:00
Ilya Fedin 29debc07c4 Let specify arbitrary build configuration via Docker
Default to RelWithDebInfo for both optimizations and debug information
2024-02-06 20:31:44 +04:00
Ilya Fedin 5334096d68 Fix bot webview height with fractional scaling 2024-02-06 10:25:27 +04:00
Ilya Fedin bb3f8fbbe8 Check whether webview is destructed after Webview::Window::init 2024-02-05 16:22:52 +04:00
213 changed files with 3731 additions and 1312 deletions
+1 -1
View File
@@ -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="" \
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
macos:
name: MacOS
runs-on: macos-12
runs-on: macos-13
strategy:
matrix:
+2 -2
View File
@@ -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
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+141 -21
View File
@@ -867,6 +867,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_background_sure_delete" = "Are you sure you want to delete this background?";
"lng_background_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.";
@@ -4721,12 +4835,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"ayu_KeepAliveService" = "AyuGram Push Service";
"ayu_DisableAds" = "Disable ads";
"ayu_DisableStories" = "Disable stories";
"ayu_DisableCustomBackgrounds" = "Disable custom backgrounds";
"ayu_DisableNotificationsDelay" = "Disable notify delay";
"ayu_LocalPremium" = "Local Telegram Premium";
"ayu_CopyUsernameAsLink" = "Copy username as link";
"ayu_CustomizationHeader" = "Customization";
"ayu_DeletedMarkText" = "Deleted mark";
"ayu_DeletedMarkNothing" = "Nothing";
"ayu_DeletedMarkTrashBin" = "Trash Bin";
"ayu_DeletedMarkCross" = "Cross";
"ayu_DeletedMarkEyeCrossed" = "Eye crossed";
"ayu_EditedMarkText" = "Edited mark";
"ayu_SemiTransparentDeletedMessages" = "Semi-transparent deleted messages";
"ayu_HideNotificationCounters" = "Hide notification counters";
"ayu_HideAllChats" = "Hide \"All chats\" folder";
"ayu_ShowGhostToggleInDrawer" = "Show ghost mode toggle";
@@ -13,5 +13,6 @@
<file alias="stats.tgs">../../animations/stats.tgs</file>
<file alias="voice_ttl_idle.tgs">../../animations/voice_ttl_idle.tgs</file>
<file alias="voice_ttl_start.tgs">../../animations/voice_ttl_start.tgs</file>
<file alias="palette.tgs">../../animations/palette.tgs</file>
</qresource>
</RCC>
+1 -1
View File
@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.14.13.0" />
Version="4.15.0.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
+4 -4
View File
@@ -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"
+4 -4
View File
@@ -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"
+36 -9
View File
@@ -55,19 +55,42 @@ rpl::producer<std::vector<uint8>> PeerColors::suggestedValue() const {
auto PeerColors::indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed> {
return rpl::single(_colorIndicesCurrent
? *_colorIndicesCurrent
: Ui::ColorIndicesCompressed()
return rpl::single(
indicesCurrent()
) | rpl::then(_colorIndicesChanged.events() | rpl::map([=] {
return *_colorIndicesCurrent;
return indicesCurrent();
}));
}
int PeerColors::requiredLevelFor(PeerId channel, uint8 index) const {
Ui::ColorIndicesCompressed PeerColors::indicesCurrent() const {
return _colorIndicesCurrent
? *_colorIndicesCurrent
: Ui::ColorIndicesCompressed();
}
const base::flat_map<uint8, int> &PeerColors::requiredLevelsGroup() const {
return _requiredLevelsGroup;
}
const base::flat_map<uint8, int> &PeerColors::requiredLevelsChannel() const {
return _requiredLevelsChannel;
}
int PeerColors::requiredGroupLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevels.find(index)
; i != end(_requiredLevels)) {
} else if (const auto i = _requiredLevelsGroup.find(index)
; i != end(_requiredLevelsGroup)) {
return i->second;
}
return 1;
}
int PeerColors::requiredChannelLevelFor(PeerId channel, uint8 index) const {
if (Data::DecideColorIndex(channel) == index) {
return 0;
} else if (const auto i = _requiredLevelsChannel.find(index)
; i != end(_requiredLevelsChannel)) {
return i->second;
}
return 1;
@@ -100,7 +123,8 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
};
const auto &list = data.vcolors().v;
_requiredLevels.clear();
_requiredLevelsGroup.clear();
_requiredLevelsChannel.clear();
suggested.reserve(list.size());
for (const auto &color : list) {
const auto &data = color.data();
@@ -110,8 +134,11 @@ void PeerColors::apply(const MTPDhelp_peerColors &data) {
continue;
}
const auto colorIndex = uint8(colorIndexBare);
if (const auto min = data.vgroup_min_level()) {
_requiredLevelsGroup[colorIndex] = min->v;
}
if (const auto min = data.vchannel_min_level()) {
_requiredLevels[colorIndex] = min->v;
_requiredLevelsChannel[colorIndex] = min->v;
}
if (!data.is_hidden()) {
suggested.push_back(colorIndex);
+12 -2
View File
@@ -25,10 +25,19 @@ public:
[[nodiscard]] std::vector<uint8> suggested() const;
[[nodiscard]] rpl::producer<std::vector<uint8>> suggestedValue() const;
[[nodiscard]] Ui::ColorIndicesCompressed indicesCurrent() const;
[[nodiscard]] auto indicesValue() const
-> rpl::producer<Ui::ColorIndicesCompressed>;
[[nodiscard]] int requiredLevelFor(
[[nodiscard]] auto requiredLevelsGroup() const
-> const base::flat_map<uint8, int> &;
[[nodiscard]] auto requiredLevelsChannel() const
-> const base::flat_map<uint8, int> &;
[[nodiscard]] int requiredGroupLevelFor(
PeerId channel,
uint8 index) const;
[[nodiscard]] int requiredChannelLevelFor(
PeerId channel,
uint8 index) const;
@@ -42,7 +51,8 @@ private:
mtpRequestId _requestId = 0;
base::Timer _timer;
rpl::variable<std::vector<uint8>> _suggested;
base::flat_map<uint8, int> _requiredLevels;
base::flat_map<uint8, int> _requiredLevelsGroup;
base::flat_map<uint8, int> _requiredLevelsChannel;
rpl::event_stream<> _colorIndicesChanged;
std::unique_ptr<Ui::ColorIndicesCompressed> _colorIndicesCurrent;
+2 -1
View File
@@ -604,7 +604,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto channel = _peer->asChannel();
if (!channel || channel->isMegagroup()) {
if (!channel) {
return lifetime;
}
@@ -628,6 +628,7 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
const auto slots = data.vmy_boost_slots();
_boostStatus.overview = Data::BoostsOverview{
.group = channel->isMegagroup(),
.mine = slots ? int(slots->v.size()) : 0,
.level = std::max(data.vlevel().v, 0),
.boostCount = std::max(
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_transcribes.h"
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer.h"
#include "data/data_session.h"
@@ -25,6 +26,14 @@ Transcribes::Transcribes(not_null<ApiWrap*> api)
, _api(&api->instance()) {
}
bool Transcribes::freeFor(not_null<HistoryItem*> item) const {
if (const auto channel = item->history()->peer->asMegagroup()) {
const auto owner = &channel->owner();
return channel->levelHint() >= owner->groupFreeTranscribeLevel();
}
return false;
}
bool Transcribes::trialsSupport() {
if (!_trialsSupport) {
const auto count = _session->account().appConfig().get<int>(
@@ -36,6 +36,8 @@ public:
void apply(const MTPDupdateTranscribedAudio &update);
[[nodiscard]] bool freeFor(not_null<HistoryItem*> item) const;
[[nodiscard]] bool trialsSupport();
[[nodiscard]] TimeId trialsRefreshAt();
[[nodiscard]] int trialsCount();
+2
View File
@@ -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(),
+16
View File
@@ -2638,6 +2638,22 @@ void ApiWrap::setGroupStickerSet(
_session->data().stickers().notifyUpdated(Data::StickersType::Stickers);
}
void ApiWrap::setGroupEmojiSet(
not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set) {
Expects(megagroup->mgInfo != nullptr);
megagroup->mgInfo->emojiSet = set;
request(MTPchannels_SetEmojiStickers(
megagroup->inputChannel,
Data::InputStickerSet(set)
)).send();
_session->changes().peerUpdated(
megagroup,
Data::PeerUpdate::Flag::EmojiSet);
_session->data().stickers().notifyUpdated(Data::StickersType::Emoji);
}
std::vector<not_null<DocumentData*>> *ApiWrap::stickersByEmoji(
const QString &key) {
const auto it = _stickersByEmoji.find(key);
+3
View File
@@ -246,6 +246,9 @@ public:
void setGroupStickerSet(
not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set);
void setGroupEmojiSet(
not_null<ChannelData*> megagroup,
const StickerSetIdentifier &set);
[[nodiscard]] std::vector<not_null<DocumentData*>> *stickersByEmoji(
const QString &key);
+7 -2
View File
@@ -200,6 +200,7 @@ AyuGramSettings::AyuGramSettings() {
// ~ QoL toggles
disableAds = true;
disableStories = false;
disableCustomBackgrounds = true;
collapseSimilarChannels = true;
hideSimilarChannels = false;
@@ -214,7 +215,7 @@ AyuGramSettings::AyuGramSettings() {
simpleQuotesAndReplies = true;
deletedMark = "🧹";
editedMark = Core::IsAppLaunched() ? tr::lng_edited(tr::now) : QString("edited");
recentStickersCount = 50;
recentStickersCount = 100;
// context menu items
// 0 - hide
@@ -226,7 +227,7 @@ AyuGramSettings::AyuGramSettings() {
showUserMessagesInContextMenu = 2;
showMessageDetailsInContextMenu = 2;
showLReadToggleInDrawer = true;
showLReadToggleInDrawer = false;
showSReadToggleInDrawer = true;
showGhostToggleInDrawer = true;
showStreamerToggleInDrawer = false;
@@ -326,6 +327,10 @@ void AyuGramSettings::set_disableStories(bool val) {
disableStories = val;
}
void AyuGramSettings::set_disableCustomBackgrounds(bool val) {
disableCustomBackgrounds = val;
}
void AyuGramSettings::set_collapseSimilarChannels(bool val) {
collapseSimilarChannels = val;
}
+3
View File
@@ -33,6 +33,7 @@ public:
bool disableAds;
bool disableStories;
bool disableCustomBackgrounds;
bool collapseSimilarChannels;
bool hideSimilarChannels;
@@ -94,6 +95,7 @@ public:
void set_disableAds(bool val);
void set_disableStories(bool val);
void set_disableCustomBackgrounds(bool val);
void set_collapseSimilarChannels(bool val);
void set_hideSimilarChannels(bool val);
@@ -153,6 +155,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
saveMessagesHistory,
disableAds,
disableStories,
disableCustomBackgrounds,
collapseSimilarChannels,
hideSimilarChannels,
uploadSpeedBoost,
+1 -1
View File
@@ -34,7 +34,7 @@ void lateInit() {
}
void runOnce() {
if (!Core::App().domain().started()) {
if (!Core::IsAppLaunched() || !Core::App().domain().started()) {
return;
}
+19 -10
View File
@@ -182,9 +182,13 @@ void initialize() {
}
void addEditedMessage(const EditedMessage &message) {
storage.begin_transaction();
storage.insert(message);
storage.commit();
try {
storage.begin_transaction();
storage.insert(message);
storage.commit();
} catch (std::exception& ex) {
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
}
}
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId) {
@@ -198,13 +202,18 @@ std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageI
}
bool hasRevisions(ID userId, ID dialogId, ID messageId) {
return storage.count<EditedMessage>(
where(
c(&EditedMessage::userId) == userId and
c(&EditedMessage::dialogId) == dialogId and
c(&EditedMessage::messageId) == messageId
)
) > 0;
try {
return storage.count<EditedMessage>(
where(
c(&EditedMessage::userId) == userId and
c(&EditedMessage::dialogId) == dialogId and
c(&EditedMessage::messageId) == messageId
)
) > 0;
} catch (std::exception& ex) {
LOG(("Failed to check if message has revisions: %1").arg(ex.what()));
return false;
}
}
}
@@ -15,6 +15,7 @@
#include "data/data_cloud_themes.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "history/history.h"
#include "history/history_inner_widget.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
@@ -150,7 +151,8 @@ public:
MessageShotDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update);
Fn<void()> update,
bool isTopic);
bool elementAnimationsPaused() override;
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
@@ -160,14 +162,17 @@ public:
private:
const not_null<QWidget*> _parent;
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
HistoryView::Context _context;
};
MessageShotDelegate::MessageShotDelegate(
not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st,
Fn<void()> update)
Fn<void()> update,
bool isTopic)
: _parent(parent)
, _pathGradient(HistoryView::MakePathShiftGradient(st, update)) {
_context = isTopic ? HistoryView::Context::Replies : HistoryView::Context::AdminLog;
}
bool MessageShotDelegate::elementAnimationsPaused() {
@@ -255,12 +260,14 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
takingShot = true;
auto delegate = std::make_unique<MessageShotDelegate>(box,
st.get(),
[=]
{
box->update();
});
auto delegate = std::make_unique<MessageShotDelegate>(
box,
st.get(),
[=]
{
box->update();
},
messages.front()->history()->asTopic());
// remove deleted messages
messages.erase(
@@ -361,7 +368,7 @@ QImage Make(not_null<QWidget*> box, const ShotConfig &config) {
const auto picY = y + view->height() - st::msgPhotoSize;
auto userpicView =
!message->displayFrom()->activeUserpicView().null()
message->displayFrom()->hasUserpic()
? message->displayFrom()->activeUserpicView()
: message->displayFrom()->createUserpicView();
message->displayFrom()->paintUserpic(p, userpicView, picX, picY, st::msgPhotoSize);
@@ -413,4 +420,5 @@ void Wrapper(not_null<HistoryView::ListWidget*> widget) {
auto box = Box<MessageShotBox>(config);
Ui::show(std::move(box));
}
}
@@ -595,6 +595,25 @@ void SetupQoLToggles(not_null<Ui::VerticalLayout*> container) {
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_DisableCustomBackgrounds(),
st::settingsButtonNoIcon
)->toggleOn(
rpl::single(settings->disableCustomBackgrounds)
)->toggledValue(
) | rpl::filter(
[=](bool enabled)
{
return (enabled != settings->disableCustomBackgrounds);
}) | start_with_next(
[=](bool enabled)
{
settings->set_disableCustomBackgrounds(enabled);
AyuSettings::save();
},
container->lifetime());
AddButtonWithIcon(
container,
tr::ayu_SimpleQuotesAndReplies(),
@@ -467,6 +467,10 @@ bool BackgroundPreviewBox::forChannel() const {
return _forPeer && _forPeer->isChannel();
}
bool BackgroundPreviewBox::forGroup() const {
return forChannel() && _forPeer->isMegagroup();
}
void BackgroundPreviewBox::generateBackground() {
if (_paper.backgroundColors().empty()) {
return;
@@ -492,7 +496,9 @@ void BackgroundPreviewBox::resetTitle() {
void BackgroundPreviewBox::rebuildButtons(bool dark) {
clearButtons();
addButton(forChannel()
addButton(forGroup()
? tr::lng_background_apply_group()
: forChannel()
? tr::lng_background_apply_channel()
: _forPeer
? tr::lng_background_apply_button()
@@ -708,7 +714,7 @@ void BackgroundPreviewBox::checkLevelForChannel() {
return std::optional<Ui::AskBoostReason>();
}
return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostWallpaper{ required }
Ui::AskBoostWallpaper{ required, _forPeer->isMegagroup()}
});
}, [=] { _forPeerLevelCheck = false; });
}
@@ -1083,7 +1089,9 @@ void BackgroundPreviewBox::updateServiceBg(const std::vector<QColor> &bg) {
_service = GenerateServiceItem(
delegate(),
_serviceHistory,
(forChannel()
(forGroup()
? tr::lng_background_other_group(tr::now)
: forChannel()
? tr::lng_background_other_channel(tr::now)
: (_forPeer && !_fromMessageId)
? tr::lng_background_other_info(
@@ -95,6 +95,7 @@ private:
[[nodiscard]] OverridenStyle prepareOverridenStyle(bool dark);
[[nodiscard]] bool forChannel() const;
[[nodiscard]] bool forGroup() const;
void checkLevelForChannel();
void recreate(bool dark);
+3
View File
@@ -791,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
slowmodeLabel: LabelSimple(defaultLabelSimple) {
textFg: windowSubTextFg;
}
boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
customBadgeField: InputField(defaultInputField) {
textMargins: margins(2px, 7px, 2px, 0px);
@@ -470,8 +470,8 @@ void EditCaptionBox::rebuildPreview() {
void EditCaptionBox::setupField() {
const auto peer = _historyItem->history()->peer;
const auto allow = [=](const auto&) {
return Data::AllowEmojiWithoutPremium(peer);
const auto allow = [=](not_null<DocumentData*> emoji) {
return Data::AllowEmojiWithoutPremium(peer, emoji);
};
InitMessageFieldHandlers(
_controller,
+53 -32
View File
@@ -1363,20 +1363,26 @@ void GiveawayInfoBox(
? start->quantity
: (results->winnersCount + results->unclaimedCount);
const auto months = start ? start->months : results->months;
const auto group = results
? results->channel->isMegagroup()
: (!start->channels.empty()
&& start->channels.front()->isMegagroup());
text.append((finished
? tr::lng_prizes_end_text
: tr::lng_prizes_how_text)(
tr::now,
lt_admins,
tr::lng_prizes_admins(
tr::now,
lt_count,
quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
(group
? tr::lng_prizes_admins_group
: tr::lng_prizes_admins)(
tr::now,
lt_count,
quantity,
lt_channel,
Ui::Text::Bold(first),
lt_duration,
TextWithEntities{ GiftDuration(months) },
Ui::Text::RichLangValue),
Ui::Text::RichLangValue));
const auto many = start
? (start->channels.size() > 1)
@@ -1387,8 +1393,12 @@ void GiveawayInfoBox(
const auto all = start ? start->all : results->all;
auto winners = all
? (many
? tr::lng_prizes_winners_all_of_many
: tr::lng_prizes_winners_all_of_one)(
? (group
? tr::lng_prizes_winners_all_of_many_group
: tr::lng_prizes_winners_all_of_many)
: (group
? tr::lng_prizes_winners_all_of_one_group
: tr::lng_prizes_winners_all_of_one))(
tr::now,
lt_count,
count,
@@ -1411,15 +1421,17 @@ void GiveawayInfoBox(
? results->additionalPrize
: start->additionalPrize;
if (!additionalPrize.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_additional_added(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_prize,
TextWithEntities{ additionalPrize },
Ui::Text::RichLangValue));
text.append("\n\n").append((group
? tr::lng_prizes_additional_added_group
: tr::lng_prizes_additional_added)(
tr::now,
lt_count,
count,
lt_channel,
Ui::Text::Bold(first),
lt_prize,
TextWithEntities{ additionalPrize },
Ui::Text::RichLangValue));
}
const auto untilDate = start
? start->untilDate
@@ -1448,18 +1460,25 @@ void GiveawayInfoBox(
if (info.adminChannelId) {
const auto channel = controller->session().data().channel(
info.adminChannelId);
text.append("\n\n").append(tr::lng_prizes_how_no_admin(
tr::now,
lt_channel,
Ui::Text::Bold(channel->name()),
Ui::Text::RichLangValue));
text.append("\n\n").append((channel->isMegagroup()
? tr::lng_prizes_how_no_admin_group
: tr::lng_prizes_how_no_admin)(
tr::now,
lt_channel,
Ui::Text::Bold(channel->name()),
Ui::Text::RichLangValue));
} else if (info.tooEarlyDate) {
text.append("\n\n").append(tr::lng_prizes_how_no_joined(
tr::now,
lt_date,
Ui::Text::Bold(
langDateTime(base::unixtime::parse(info.tooEarlyDate))),
Ui::Text::RichLangValue));
const auto channel = controller->session().data().channel(
info.adminChannelId);
text.append("\n\n").append((channel->isMegagroup()
? tr::lng_prizes_how_no_joined_group
: tr::lng_prizes_how_no_joined)(
tr::now,
lt_date,
Ui::Text::Bold(
langDateTime(
base::unixtime::parse(info.tooEarlyDate))),
Ui::Text::RichLangValue));
} else if (!info.disallowedCountry.isEmpty()) {
text.append("\n\n").append(tr::lng_prizes_how_no_country(
tr::now,
@@ -1499,7 +1518,9 @@ void GiveawayInfoBox(
box.get(),
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_prizes_cancelled(),
(group
? tr::lng_prizes_cancelled_group()
: tr::lng_prizes_cancelled()),
st::giveawayRefundedLabel),
st::giveawayRefundedPadding),
{ padding.left(), 0, padding.right(), padding.bottom() });
@@ -13,11 +13,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
#include "boxes/background_box.h"
#include "boxes/stickers_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
@@ -28,6 +32,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_emoji_status_panel.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "lottie/lottie_single_player.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
@@ -478,6 +484,7 @@ void Set(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId)));
} else if (peer->isMegagroup()) {
} else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor(
@@ -527,9 +534,13 @@ void Apply(
} else {
CheckBoostLevel(show, peer, [=](int level) {
const auto peerColors = &peer->session().api().peerColors();
const auto colorRequired = peerColors->requiredLevelFor(
peer->id,
values.colorIndex);
const auto colorRequired = peer->isMegagroup()
? peerColors->requiredGroupLevelFor(
peer->id,
values.colorIndex)
: peerColors->requiredChannelLevelFor(
peer->id,
values.colorIndex);
const auto iconRequired = values.backgroundEmojiId
? session->account().appConfig().get<int>(
"channel_bg_icon_level_min",
@@ -553,7 +564,10 @@ void Apply(
}
const auto reason = [&]() -> Ui::AskBoostReason {
if (level < statusRequired) {
return { Ui::AskBoostEmojiStatus{ statusRequired } };
return { Ui::AskBoostEmojiStatus{
statusRequired,
peer->isMegagroup()
} };
} else if (level < iconRequired) {
return { Ui::AskBoostChannelColor{ iconRequired } };
}
@@ -670,6 +684,44 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return (top - skip) + ((count % columns) ? (isize + skip) : 0);
}
[[nodiscard]] auto ButtonStyleWithAddedPadding(
not_null<Ui::RpWidget*> parent,
const style::SettingsButton &basicSt,
QMargins added) {
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding += added;
return st;
}
struct ButtonWithEmoji {
not_null<const style::SettingsButton*> st;
int emojiWidth = 0;
int noneWidth = 0;
int added = 0;
};
[[nodiscard]] ButtonWithEmoji ButtonStyleWithRightEmoji(
not_null<Ui::RpWidget*> parent) {
const auto ratio = style::DevicePixelRatio();
const auto emojiWidth = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = st::normalFont->width(
tr::lng_settings_color_emoji_off(tr::now));
const auto added = st::normalFont->spacew;
const auto rightAdded = std::max(noneWidth, emojiWidth);
return {
.st = ButtonStyleWithAddedPadding(
parent,
st::peerAppearanceButton,
QMargins(0, 0, added + rightAdded, 0)),
.emojiWidth = emojiWidth,
.noneWidth = noneWidth,
.added = added,
};
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiIconButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
@@ -677,22 +729,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
rpl::producer<uint8> colorIndexValue,
rpl::producer<DocumentId> emojiIdValue,
Fn<void(DocumentId)> emojiIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
const auto button = ButtonStyleWithRightEmoji(parent);
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_settings_color_emoji(),
*st);
*button.st,
{ &st::menuBlueIconColorNames });
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
@@ -719,6 +761,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
}, right->lifetime());
const auto session = &show->session();
const auto added = st::normalFont->spacew;
std::move(emojiIdValue) | rpl::start_with_next([=](DocumentId emojiId) {
state->emojiId = emojiId;
state->emoji = emojiId
@@ -727,7 +770,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
[=] { right->update(); })
: nullptr;
right->resize(
(emojiId ? emojiWidth : noneWidth) + added,
(emojiId ? button.emojiWidth : button.noneWidth) + button.added,
right->height());
right->update();
}, right->lifetime());
@@ -738,7 +781,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
@@ -752,7 +795,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto colors = style->coloredValues(false, state->index);
state->emoji->paint(p, {
.textColor = colors.name,
.position = QPoint(added, (height - emojiSize) / 2),
.position = QPoint(added, (height - button.emojiWidth) / 2),
.internal = {
.forceFirstFrame = true,
},
@@ -791,23 +834,16 @@ int ColorSelector::resizeGetHeight(int newWidth) {
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
Fn<void(DocumentId,TimeId)> statusIdChosen,
bool group) {
const auto button = ButtonStyleWithRightEmoji(parent);
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_edit_channel_status(),
*st);
(group
? tr::lng_edit_channel_status_group()
: tr::lng_edit_channel_status()),
*button.st,
{ &st::menuBlueIconEmojiStatus });
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
@@ -834,7 +870,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
[=] { right->update(); })
: nullptr;
right->resize(
(id ? emojiWidth : noneWidth) + added,
(id ? button.emojiWidth : button.noneWidth) + button.added,
right->height());
right->update();
}, right->lifetime());
@@ -845,7 +881,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
@@ -861,14 +897,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
st::stickerPanPremium1,
st::stickerPanPremium2,
0.5),
.position = QPoint(added, (height - emojiSize) / 2),
.position = QPoint(
button.added,
(height - button.emojiWidth) / 2),
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(st::windowActiveTextFg);
p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent),
QPoint(
button.added,
(height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now));
}
}, right->lifetime());
@@ -889,6 +929,116 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result;
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiPackButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> channel) {
Expects(channel->mgInfo != nullptr);
const auto button = ButtonStyleWithRightEmoji(parent);
auto result = Settings::CreateButtonWithIcon(
parent,
tr::lng_group_emoji(),
*button.st,
{ &st::menuBlueIconEmojiPack });
const auto raw = result.data();
struct State {
DocumentData *icon = nullptr;
std::unique_ptr<Ui::Text::CustomEmoji> custom;
QImage cache;
};
const auto state = parent->lifetime().make_state<State>();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
right->resize(
button.emojiWidth + button.added,
right->height());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - button.added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::filter([=] {
return state->icon != nullptr;
}) | rpl::start_with_next([=] {
auto p = QPainter(right);
const auto x = button.added;
const auto y = (right->height() - button.emojiWidth) / 2;
const auto active = right->window()->isActiveWindow();
if (const auto emoji = state->icon) {
if (!state->custom
&& emoji->sticker()
&& emoji->sticker()->setType == Data::StickersType::Emoji) {
auto &manager = emoji->owner().customEmojiManager();
state->custom = manager.create(
emoji->id,
[=] { right->update(); },
{});
}
if (state->custom) {
state->custom->paint(p, Ui::Text::CustomEmoji::Context{
.textColor = st::windowFg->c,
.now = crl::now(),
.position = { x, y },
.paused = !active,
});
}
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto isEmoji = true;
show->showBox(Box<StickersBox>(show, channel, isEmoji));
});
channel->session().changes().peerFlagsValue(
channel,
Data::PeerUpdate::Flag::EmojiSet
) | rpl::map([=]() -> rpl::producer<DocumentData*> {
const auto id = channel->mgInfo->emojiSet.id;
if (!id) {
return rpl::single<DocumentData*>(nullptr);
}
const auto sets = &channel->owner().stickers().sets();
auto wrapLoaded = [=](Data::StickersSets::const_iterator it) {
return it->second->lookupThumbnailDocument();
};
const auto it = sets->find(id);
if (it != sets->cend()
&& !(it->second->flags & Data::StickersSetFlag::NotLoaded)) {
return rpl::single(wrapLoaded(it));
}
return rpl::single<DocumentData*>(
nullptr
) | rpl::then(channel->owner().stickers().updated(
Data::StickersType::Emoji
) | rpl::filter([=] {
const auto it = sets->find(id);
return (it != sets->cend())
&& !(it->second->flags & Data::StickersSetFlag::NotLoaded);
}) | rpl::map([=] {
return wrapLoaded(sets->find(id));
}));
}) | rpl::flatten_latest(
) | rpl::start_with_next([=](DocumentData *icon) {
if (state->icon != icon) {
state->icon = icon;
state->custom = nullptr;
right->update();
}
}, right->lifetime());
return result;
}
} // namespace
void EditPeerColorBox(
@@ -897,7 +1047,12 @@ void EditPeerColorBox(
not_null<PeerData*> peer,
std::shared_ptr<Ui::ChatStyle> style,
std::shared_ptr<Ui::ChatTheme> theme) {
box->setTitle(tr::lng_settings_color_title());
const auto group = peer->isMegagroup();
const auto container = box->verticalLayout();
box->setTitle(peer->isSelf()
? tr::lng_settings_color_title()
: tr::lng_edit_channel_color());
box->setWidth(st::boxWideWidth);
struct State {
@@ -914,53 +1069,94 @@ void EditPeerColorBox(
state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>(
box,
style,
theme,
peer,
state->index.value(),
state->emojiId.value()
), {});
if (group) {
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(
box.get());
const auto verticalLayout = box->verticalLayout()->add(
object_ptr<Ui::VerticalLayout>(box.get()));
auto indices = peer->session().api().peerColors().suggestedValue();
const auto margin = st::settingsColorRadioMargin;
const auto skip = st::settingsColorRadioSkip;
box->addRow(
object_ptr<ColorSelector>(
auto icon = CreateLottieIcon(
verticalLayout,
{
.name = u"palette"_q,
.sizeOverride = {
st::settingsCloudPasswordIconSize,
st::settingsCloudPasswordIconSize,
},
},
st::peerAppearanceIconPadding);
box->setShowFinishedCallback([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
});
verticalLayout->add(std::move(icon.widget));
verticalLayout->add(
object_ptr<Ui::FlatLabel>(
verticalLayout,
tr::lng_boost_group_about(),
st::peerAppearanceCoverLabel),
st::peerAppearanceCoverLabelMargin);
verticalLayout->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
divider->setGeometry(r);
}, divider->lifetime());
} else {
box->addRow(object_ptr<PreviewWrap>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
theme,
peer,
state->index.value(),
state->emojiId.value()
), {});
const auto container = box->verticalLayout();
Ui::AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel());
auto indices = peer->session().api().peerColors().suggestedValue();
const auto margin = st::settingsColorRadioMargin;
const auto skip = st::settingsColorRadioSkip;
box->addRow(
object_ptr<ColorSelector>(
box,
style,
std::move(indices),
state->index.current(),
[=](uint8 index) { state->index = index; }),
{ margin, skip, margin, skip });
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
(peer->isSelf()
? tr::lng_settings_color_about()
: tr::lng_settings_color_about_channel()),
st::peerAppearanceDividerTextMargin);
container->add(CreateEmojiIconButton(
container,
show,
style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, peer->isSelf()
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
container->add(CreateEmojiIconButton(
container,
show,
style,
state->index.value(),
state->emojiId.value(),
[=](DocumentId id) { state->emojiId = id; }));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
(peer->isSelf()
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel()),
st::peerAppearanceDividerTextMargin);
}
if (const auto channel = peer->asChannel()) {
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(object_ptr<Ui::SettingsButton>(
Settings::AddButtonWithIcon(
container,
tr::lng_edit_channel_wallpaper(),
st::settingsButtonNoIcon)
(group
? tr::lng_edit_channel_wallpaper_group()
: tr::lng_edit_channel_wallpaper()),
st::peerAppearanceButton,
{ &st::menuBlueIconWallpaper }
)->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto strong = show->resolveWindow(usage)) {
@@ -971,7 +1167,25 @@ void EditPeerColorBox(
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
tr::lng_edit_channel_wallpaper_about());
(group
? tr::lng_edit_channel_wallpaper_about_group()
: tr::lng_edit_channel_wallpaper_about()),
st::peerAppearanceDividerTextMargin);
if (group) {
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiPackButton(
container,
show,
channel));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(
container,
tr::lng_group_emoji_description(),
st::peerAppearanceDividerTextMargin);
}
// Preload exceptions list.
const auto peerPhoto = &channel->session().api().peerPhoto();
@@ -992,10 +1206,16 @@ void EditPeerColorBox(
state->statusId = id;
state->statusUntil = until;
state->statusChanged = true;
}));
},
group));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
Ui::AddDividerText(
container,
(group
? tr::lng_edit_channel_status_about_group()
: tr::lng_edit_channel_status_about()),
st::peerAppearanceDividerTextMargin);
}
box->addButton(tr::lng_settings_apply(), [=] {
@@ -1020,19 +1240,11 @@ void EditPeerColorBox(
});
}
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
auto label = peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
void SetupPeerColorSample(
not_null<Button*> button,
not_null<PeerData*> peer,
rpl::producer<QString> label,
std::shared_ptr<Ui::ChatStyle> style) {
auto colorIndexValue = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::Color
@@ -1041,12 +1253,6 @@ void AddPeerColorButton(
});
const auto name = peer->shortName();
const auto style = std::make_shared<Ui::ChatStyle>(
peer->session().colorIndicesValue());
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(button->lifetime()));
style->apply(theme.get());
const auto sample = Ui::CreateChild<ColorSample>(
button.get(),
style,
@@ -1098,6 +1304,30 @@ void AddPeerColorButton(
}, sample->lifetime());
sample->setAttribute(Qt::WA_TransparentForMouseEvents);
}
void AddPeerColorButton(
not_null<Ui::VerticalLayout*> container,
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer) {
auto label = peer->isSelf()
? tr::lng_settings_theme_name_color()
: tr::lng_edit_channel_color();
const auto button = AddButtonWithIcon(
container,
rpl::duplicate(label),
st::settingsColorButton,
{ &st::menuIconChangeColors });
const auto style = std::make_shared<Ui::ChatStyle>(
peer->session().colorIndicesValue());
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(button->lifetime()));
style->apply(theme.get());
if (!peer->isMegagroup()) {
SetupPeerColorSample(button, peer, rpl::duplicate(label), style);
}
button->setClickedCallback([=] {
show->show(Box(EditPeerColorBox, show, peer, style, theme));
@@ -198,6 +198,37 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId);
}
void SaveBoostsUnrestrict(
not_null<ChannelData*> channel,
int boostsUnrestrict,
Fn<void()> done) {
const auto api = &channel->session().api();
const auto key = Api::RequestKey("boosts_unrestrict", channel->id);
const auto requestId = api->request(
MTPchannels_SetBoostsToUnblockRestrictions(
channel->inputChannel,
MTP_int(boostsUnrestrict))
).done([=](const MTPUpdates &result) {
api->clearModifyRequest(key);
api->applyUpdates(result);
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).fail([=](const MTP::Error &error) {
api->clearModifyRequest(key);
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
return;
}
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).send();
api->registerModifyRequest(key, requestId);
}
void ShowEditPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
@@ -215,6 +246,10 @@ void ShowEditPermissions(
close);
if (const auto channel = peer->asChannel()) {
SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
SaveBoostsUnrestrict(
channel,
result.boostsUnrestrict,
close);
}
};
auto done = [=](EditPeerPermissionsBoxResult result) {
@@ -225,7 +260,8 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat();
if (!result.slowmodeSeconds || !chat) {
if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
save(saveFor, result);
return;
}
@@ -595,8 +631,9 @@ object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
tr::lng_group_stickers_add(),
rpl::single(QString()), //Empty count.
[=, controller = _navigation->parentController()] {
const auto isEmoji = false;
controller->show(
Box<StickersBox>(controller->uiShow(), channel));
Box<StickersBox>(controller->uiShow(), channel, isEmoji));
},
{ &st::menuIconStickers });
@@ -1057,9 +1094,7 @@ void Controller::fillManageSection() {
&& (channel->hasAdminRights() || channel->amCreator());
const auto canEditStickers = isChannel && channel->canEditStickers();
const auto canDeleteChannel = isChannel && channel->canDelete();
const auto canEditColorIndex = isChannel
&& !channel->isMegagroup()
&& channel->canEditInformation();
const auto canEditColorIndex = isChannel && channel->canEditEmoji();
const auto canViewOrEditLinkedChat = isChannel
&& (channel->linkedChat()
|| (channel->isBroadcast() && channel->canEditInformation()));
@@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_permissions_box.h"
#include "lang/lang_keys.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_session.h"
#include "ui/effects/toggle_arrow.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
@@ -34,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
#include "styles/style_window.h"
@@ -42,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kSlowmodeValues = 7;
constexpr auto kBoostsUnrestrictValues = 5;
constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
@@ -99,7 +104,7 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
using Flag = ChatAdminRight;
if (options.isGroup) {
auto result = std::vector<AdminRightLabel>{
auto first = std::vector<AdminRightLabel>{
{ Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
{ Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) },
{ Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
@@ -108,19 +113,30 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
: tr::lng_rights_group_invite(tr::now) },
{ Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) },
{ Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
};
auto stories = std::vector<AdminRightLabel>{
{ Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
{ Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
{ Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
};
auto second = std::vector<AdminRightLabel>{
{ Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) },
{ Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
};
if (!options.isForum) {
result.erase(
first.erase(
ranges::remove(
result,
first,
Flag::ManageTopics | Flag(),
&AdminRightLabel::flags),
end(result));
end(first));
}
return { { std::nullopt, std::move(result) } };
return {
{ std::nullopt, std::move(first) },
{ tr::lng_rights_channel_manage_stories(), std::move(stories) },
{ std::nullopt, std::move(second) },
};
}
auto first = std::vector<AdminRightLabel>{
{ Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) },
@@ -163,6 +179,10 @@ int SlowmodeDelayByIndex(int index) {
Unexpected("Index in SlowmodeDelayByIndex.");
}
[[nodiscard]] int BoostsUnrestrictByIndex(int index) {
return index + 1;
}
template <typename CheckboxesMap, typename DependenciesMap>
void ApplyDependencies(
const CheckboxesMap &checkboxes,
@@ -770,14 +790,14 @@ void AddSlowmodeLabels(
}
}
Fn<int()> AddSlowmodeSlider(
rpl::producer<int> AddSlowmodeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer) {
using namespace rpl::mappers;
if (const auto chat = peer->asChat()) {
if (!chat->amCreator()) {
return [] { return 0; };
return rpl::single(0);
}
}
const auto channel = peer->asChannel();
@@ -785,10 +805,6 @@ Fn<int()> AddSlowmodeSlider(
const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
channel ? channel->slowmodeSeconds() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
container->add(
object_ptr<Ui::FlatLabel>(
container,
@@ -858,7 +874,157 @@ Fn<int()> AddSlowmodeSlider(
st::proxyAboutPadding),
style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
return [=] { return secondsCount->current(); };
return secondsCount->value();
}
void AddBoostsUnrestrictLabels(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session) {
const auto labels = container->add(
object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
st::slowmodeLabelsMargin);
const auto manager = &session->data().customEmojiManager();
const auto one = Ui::Text::SingleCustomEmoji(
manager->registerInternalEmoji(
st::boostMessageIcon,
st::boostMessageIconPadding));
const auto many = Ui::Text::SingleCustomEmoji(
manager->registerInternalEmoji(
st::boostsMessageIcon,
st::boostsMessageIconPadding));
const auto context = Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = [] {},
.customEmojiLoopLimit = 1,
};
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(
labels,
st::boostsUnrestrictLabel);
label->setMarkedText(
TextWithEntities(i ? many : one).append(QString::number(i + 1)),
context);
rpl::combine(
labels->widthValue(),
label->widthValue()
) | rpl::start_with_next([=](int outer, int inner) {
const auto skip = st::localStorageLimitMargin;
const auto size = st::localStorageLimitSlider.seekSize;
const auto available = outer
- skip.left()
- skip.right()
- size.width();
const auto shift = (i == 0)
? -(size.width() / 2)
: (i + 1 == kBoostsUnrestrictValues)
? (size.width() - (size.width() / 2) - inner)
: (-inner / 2);
const auto left = skip.left()
+ (size.width() / 2)
+ (i * available) / (kBoostsUnrestrictValues - 1)
+ shift;
label->moveToLeft(left, 0, outer);
}, label->lifetime());
}
}
rpl::producer<int> AddBoostsUnrestrictSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer) {
using namespace rpl::mappers;
if (const auto chat = peer->asChat()) {
if (!chat->amCreator()) {
return rpl::single(0);
}
}
const auto channel = peer->asChannel();
auto &lifetime = container->lifetime();
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
channel ? channel->boostsUnrestrict() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
auto enabled = boostsUnrestrict->value(
) | rpl::map(_1 > 0);
container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_rights_boosts_no_restrict(),
st::defaultSettingsButton
))->toggleOn(rpl::duplicate(enabled))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
if (toggled && !boostsUnrestrict->current()) {
*boostsUnrestrict = 1;
} else if (!toggled && boostsUnrestrict->current()) {
*boostsUnrestrict = 0;
}
}, container->lifetime());
const auto outer = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
outer->toggleOn(rpl::duplicate(enabled), anim::type::normal);
outer->finishAnimating();
const auto inner = outer->entity();
AddBoostsUnrestrictLabels(inner, &peer->session());
const auto slider = inner->add(
object_ptr<Ui::MediaSlider>(inner, st::localStorageLimitSlider),
st::localStorageLimitMargin);
slider->resize(st::localStorageLimitSlider.seekSize);
slider->setPseudoDiscrete(
kBoostsUnrestrictValues,
BoostsUnrestrictByIndex,
boostsUnrestrict->current(),
[=](int boosts) {
(*boostsUnrestrict) = boosts;
});
inner->add(
object_ptr<Ui::DividerLabel>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
rpl::conditional(
boostsUnrestrict->value() | rpl::map(_1 > 0),
tr::lng_rights_boosts_about_on(),
tr::lng_rights_boosts_about()),
st::boxDividerLabel),
st::proxyAboutPadding),
style::margins(0, st::infoProfileSkip, 0, 0));
return boostsUnrestrict->value();
}
rpl::producer<int> AddBoostsUnrestrictWrapped(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
rpl::producer<bool> shown) {
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
wrap->finishAnimating();
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
const auto divider = container->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container,
object_ptr<Ui::BoxContentDivider>(container),
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
divider->toggleOn(rpl::combine(
std::move(shown),
rpl::duplicate(result),
!rpl::mappers::_1 || !rpl::mappers::_2));
divider->finishAnimating();
return result;
}
void AddSuggestGigagroup(
@@ -985,7 +1151,40 @@ void ShowEditPeerPermissionsBox(
inner->add(std::move(checkboxes));
const auto getSlowmodeSeconds = AddSlowmodeSlider(inner, peer);
struct State {
rpl::variable<int> slowmodeSeconds;
rpl::variable<int> boostsUnrestrict;
rpl::variable<bool> hasSendRestrictions;
};
static constexpr auto kSendRestrictions = Flag::EmbedLinks
| Flag::SendGames
| Flag::SendGifs
| Flag::SendInline
| Flag::SendPolls
| Flag::SendStickers
| Flag::SendPhotos
| Flag::SendVideos
| Flag::SendVideoMessages
| Flag::SendMusic
| Flag::SendVoiceMessages
| Flag::SendFiles
| Flag::SendOther;
const auto state = inner->lifetime().make_state<State>();
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
inner,
peer,
state->hasSendRestrictions.value());
state->slowmodeSeconds = AddSlowmodeSlider(inner, peer);
state->hasSendRestrictions = rpl::combine(
rpl::single(
restrictions
) | rpl::then(std::move(changes)),
state->slowmodeSeconds.value()
) | rpl::map([](ChatRestrictions restrictions, int slowmodeSeconds) {
return ((restrictions & kSendRestrictions) != 0) || slowmodeSeconds;
});
if (const auto channel = peer->asChannel()) {
if (channel->amCreator()
@@ -1001,7 +1200,18 @@ void ShowEditPeerPermissionsBox(
AddBannedButtons(inner, navigation, peer);
box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] {
done({ rights(), getSlowmodeSeconds() });
const auto restrictions = rights();
const auto slowmodeSeconds = state->slowmodeSeconds.current();
const auto hasRestrictions = (slowmodeSeconds > 0)
|| ((restrictions & kSendRestrictions) != 0);
const auto boostsUnrestrict = hasRestrictions
? state->boostsUnrestrict.current()
: 0;
done({
restrictions,
slowmodeSeconds,
boostsUnrestrict,
});
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
@@ -37,6 +37,7 @@ class SessionNavigation;
struct EditPeerPermissionsBoxResult final {
ChatRestrictions rights;
int slowmodeSeconds = 0;
int boostsUnrestrict = 0;
};
void ShowEditPeerPermissionsBox(
@@ -7,10 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/replace_boost_box.h"
#include "api/api_peer_colors.h"
#include "apiwrap.h"
#include "base/event_filter.h"
#include "base/unixtime.h"
#include "boxes/peer_list_box.h"
#include "data/data_channel.h"
#include "data/data_cloud_themes.h"
#include "data/data_session.h"
#include "lang/lang_keys.h"
#include "main/main_account.h"
@@ -313,21 +316,23 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
}
}
object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds, bool group) {
const auto days = seconds / 86400;
const auto hours = seconds / 3600;
const auto minutes = seconds / 60;
return Ui::MakeInformBox({
.text = tr::lng_boost_error_flood_text(
lt_left,
rpl::single(Ui::Text::Bold((days > 1)
? tr::lng_days(tr::now, lt_count, days)
: (hours > 1)
? tr::lng_hours(tr::now, lt_count, hours)
: (minutes > 1)
? tr::lng_minutes(tr::now, lt_count, minutes)
: tr::lng_seconds(tr::now, lt_count, seconds))),
Ui::Text::RichLangValue),
.text = (group
? tr::lng_boost_error_flood_text_group
: tr::lng_boost_error_flood_text)(
lt_left,
rpl::single(Ui::Text::Bold((days > 1)
? tr::lng_days(tr::now, lt_count, days)
: (hours > 1)
? tr::lng_hours(tr::now, lt_count, hours)
: (minutes > 1)
? tr::lng_minutes(tr::now, lt_count, minutes)
: tr::lng_seconds(tr::now, lt_count, seconds))),
Ui::Text::RichLangValue),
.title = tr::lng_boost_error_flood_title(),
});
}
@@ -335,14 +340,15 @@ object_ptr<Ui::BoxContent> ReassignBoostFloodBox(int seconds) {
object_ptr<Ui::BoxContent> ReassignBoostSingleBox(
not_null<ChannelData*> to,
TakenBoostSlot from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel) {
const auto reassigned = std::make_shared<bool>();
const auto slot = from.id;
const auto peer = to->owner().peer(from.peerId);
const auto group = peer->isMegagroup();
const auto confirmed = [=](Fn<void()> close) {
*reassigned = true;
reassign({ slot }, 1);
reassign({ slot }, group ? 1 : 0, group ? 0 : 1);
close();
};
@@ -417,35 +423,94 @@ Ui::BoostCounters ParseBoostCounters(
};
}
Ui::BoostFeatures LookupBoostFeatures(not_null<ChannelData*> channel) {
const auto group = channel->isMegagroup();
const auto appConfig = &channel->session().account().appConfig();
const auto get = [&](const QString &key, int fallback, bool ok = true) {
return ok ? appConfig->get<int>(key, fallback) : 0;
};
auto nameColorsByLevel = base::flat_map<int, int>();
auto linkStylesByLevel = base::flat_map<int, int>();
const auto peerColors = &channel->session().api().peerColors();
const auto &list = group
? peerColors->requiredLevelsGroup()
: peerColors->requiredLevelsChannel();
const auto indices = peerColors->indicesCurrent();
for (const auto &[index, level] : list) {
if (!Ui::ColorPatternIndex(indices, index, false)) {
++nameColorsByLevel[level];
}
++linkStylesByLevel[level];
}
const auto &themes = channel->owner().cloudThemes().chatThemes();
if (themes.empty()) {
channel->owner().cloudThemes().refreshChatThemes();
}
return Ui::BoostFeatures{
.nameColorsByLevel = std::move(nameColorsByLevel),
.linkStylesByLevel = std::move(linkStylesByLevel),
.linkLogoLevel = get(u"channel_bg_icon_level_min"_q, 4, !group),
.transcribeLevel = get(u"group_transcribe_level_min"_q, 6, group),
.emojiPackLevel = get(u"group_emoji_stickers_level_min"_q, 4, group),
.emojiStatusLevel = get(group
? u"group_emoji_status_level_min"_q
: u"channel_emoji_status_level_min"_q, 8),
.wallpaperLevel = get(group
? u"group_wallpaper_level_min"_q
: u"channel_wallpaper_level_min"_q, 9),
.wallpapersCount = themes.empty() ? 8 : int(themes.size()),
.customWallpaperLevel = get(group
? u"channel_custom_wallpaper_level_min"_q
: u"group_custom_wallpaper_level_min"_q, 10),
};
}
int BoostsForGift(not_null<Main::Session*> session) {
const auto key = u"boosts_per_sent_gift"_q;
return session->account().appConfig().get<int>(key, 0);
}
[[nodiscard]] int SourcesCount(
struct Sources {
int groups = 0;
int channels = 0;
};
[[nodiscard]] Sources SourcesCount(
not_null<ChannelData*> to,
const std::vector<TakenBoostSlot> &from,
const std::vector<int> &slots) {
auto checked = base::flat_set<PeerId>();
checked.reserve(slots.size());
auto groups = base::flat_set<PeerId>();
groups.reserve(slots.size());
auto channels = base::flat_set<PeerId>();
channels.reserve(slots.size());
const auto owner = &to->owner();
for (const auto slot : slots) {
const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
Assert(i != end(from));
checked.emplace(i->peerId);
const auto id = i->peerId;
if (!groups.contains(id) && !channels.contains(id)) {
(owner->peer(id)->isMegagroup() ? groups : channels).insert(id);
}
}
return checked.size();
return {
.groups = int(groups.size()),
.channels = int(channels.size()),
};
}
object_ptr<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel) {
Expects(!from.empty());
const auto now = base::unixtime::now();
if (from.size() == 1 && from.front().cooldown > now) {
cancel();
return ReassignBoostFloodBox(from.front().cooldown - now);
return ReassignBoostFloodBox(
from.front().cooldown - now,
to->owner().peer(from.front().peerId)->isMegagroup());
} else if (from.size() == 1 && from.front().peerId) {
return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
}
@@ -457,10 +522,10 @@ object_ptr<Ui::BoxContent> ReassignBoostsBox(
) | rpl::start_with_next([=](std::vector<int> slots) {
box->clearButtons();
if (!slots.empty()) {
const auto sources = SourcesCount(from, slots);
const auto sources = SourcesCount(to, from, slots);
box->addButton(tr::lng_boost_reassign_button(), [=] {
*reassigned = true;
reassign(slots, sources);
reassign(slots, sources.groups, sources.channels);
});
}
box->addButton(tr::lng_cancel(), [=] {
@@ -9,12 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
class ChannelData;
namespace Main {
class Session;
} // namespace Main
namespace Ui {
struct BoostCounters;
struct BoostFeatures;
class BoxContent;
class RpWidget;
} // namespace Ui
@@ -39,12 +42,15 @@ struct ForChannelBoostSlots {
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
const MTPpremium_BoostsStatus &status);
[[nodiscard]] Ui::BoostFeatures LookupBoostFeatures(
not_null<ChannelData*> channel);
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
object_ptr<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void(std::vector<int> slots, int groups, int channels)> reassign,
Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
@@ -112,6 +112,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_subtitle_tags_for_messages();
case PremiumPreview::LastSeen:
return tr::lng_premium_summary_subtitle_last_seen();
case PremiumPreview::MessagePrivacy:
return tr::lng_premium_summary_subtitle_message_privacy();
case PremiumPreview::Stickers:
return tr::lng_premium_summary_subtitle_premium_stickers();
case PremiumPreview::AnimatedEmoji:
@@ -150,6 +154,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_about_tags_for_messages();
case PremiumPreview::LastSeen:
return tr::lng_premium_summary_about_last_seen();
case PremiumPreview::MessagePrivacy:
return tr::lng_premium_summary_about_message_privacy();
case PremiumPreview::Stickers:
return tr::lng_premium_summary_about_premium_stickers();
case PremiumPreview::AnimatedEmoji:
@@ -480,6 +488,8 @@ struct VideoPreviewDocument {
case PremiumPreview::AnimatedUserpics: return "animated_userpics";
case PremiumPreview::RealTimeTranslation: return "translations";
case PremiumPreview::Wallpapers: return "wallpapers";
case PremiumPreview::LastSeen: return "last_seen";
case PremiumPreview::MessagePrivacy: return "message_privacy";
}
return "";
}();
@@ -62,6 +62,8 @@ enum class PremiumPreview {
RealTimeTranslation,
Wallpapers,
TagsForMessages,
LastSeen,
MessagePrivacy,
kCount,
};
+51 -17
View File
@@ -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,23 +42,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_single_player.h"
#include "data/data_document.h"
#include "data/data_user.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue.
#include "data/data_premium_limits.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "api/api_common.h"
#include "window/window_session_controller.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h"
#include <QtCore/QMimeData>
// AyuGram includes
#include "ayu/ayu_settings.h"
#include "base/unixtime.h"
#include "styles/style_menu_icons.h"
#include <QBuffer>
@@ -339,16 +333,16 @@ SendFilesBox::SendFilesBox(
not_null<Window::SessionController*> controller,
Ui::PreparedList &&list,
const TextWithTags &caption,
SendFilesLimits limits,
SendFilesCheck check,
not_null<PeerData*> toPeer,
Api::SendType sendType,
SendMenu::Type sendMenuType)
: SendFilesBox(nullptr, {
.show = controller->uiShow(),
.list = std::move(list),
.caption = caption,
.limits = limits,
.check = check,
.captionToPeer = toPeer,
.limits = DefaultLimitsForPeer(toPeer),
.check = DefaultCheckForPeer(controller, toPeer),
.sendType = sendType,
.sendMenuType = sendMenuType,
}) {
@@ -364,6 +358,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 +1029,10 @@ void SendFilesBox::updateSendWayControls() {
}
void SendFilesBox::setupCaption() {
const auto allow = [=](const auto &) {
return (_limits & SendFilesAllow::EmojiWithoutPremium);
const auto allow = [=](not_null<DocumentData*> emoji) {
return _captionToPeer
? Data::AllowEmojiWithoutPremium(_captionToPeer, emoji)
: (_limits & SendFilesAllow::EmojiWithoutPremium);
};
const auto show = _show;
InitMessageFieldHandlers(
@@ -1096,6 +1093,39 @@ void SendFilesBox::setupCaption() {
updateCaptionPlaceholder();
setupEmojiPanel();
rpl::single(rpl::empty_value()) | rpl::then(
_caption->changes()
) | rpl::start_with_next([=] {
checkCharsLimitation();
}, _caption->lifetime());
}
void SendFilesBox::checkCharsLimitation() {
const auto limits = Data::PremiumLimits(&_show->session());
const auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
const auto remove = caption.text.size() - limits.captionLengthCurrent();
if ((remove > 0) && _emojiToggle) {
if (!_charsLimitation) {
_charsLimitation = base::make_unique_q<CharactersLimitLabel>(
this,
_emojiToggle.data(),
style::al_top);
_charsLimitation->show();
Data::AmPremiumValue(
&_show->session()
) | rpl::start_with_next([=] {
checkCharsLimitation();
}, _charsLimitation->lifetime());
}
_charsLimitation->setLeft(remove);
} else {
if (_charsLimitation) {
_charsLimitation = nullptr;
}
}
}
void SendFilesBox::setupEmojiPanel() {
@@ -1114,7 +1144,6 @@ void SendFilesBox::setupEmojiPanel() {
.level = Window::GifPauseReason::Layer,
.mode = ChatHelpers::TabbedSelector::Mode::EmojiOnly,
.features = {
.megagroupSet = false,
.stickersSettings = false,
.openStickerSets = false,
},
@@ -1125,6 +1154,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 +1167,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);
+11 -3
View File
@@ -50,6 +50,10 @@ namespace SendMenu {
enum class Type;
} // namespace SendMenu
namespace HistoryView::Controls {
class CharactersLimitLabel;
} // namespace HistoryView::Controls
enum class SendFilesAllow {
OnlyOne = (1 << 0),
Photos = (1 << 1),
@@ -88,6 +92,7 @@ struct SendFilesBoxDescriptor {
std::shared_ptr<ChatHelpers::Show> show;
Ui::PreparedList list;
TextWithTags caption;
PeerData *captionToPeer = nullptr;
SendFilesLimits limits = {};
SendFilesCheck check;
Api::SendType sendType = {};
@@ -108,8 +113,7 @@ public:
not_null<Window::SessionController*> controller,
Ui::PreparedList &&list,
const TextWithTags &caption,
SendFilesLimits limits,
SendFilesCheck check,
not_null<PeerData*> toPeer,
Api::SendType sendType,
SendMenu::Type sendMenuType);
SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor);
@@ -221,6 +225,8 @@ private:
void enqueueNextPrepare();
void addPreparedAsyncFile(Ui::PreparedFile &&file);
void checkCharsLimitation();
const std::shared_ptr<ChatHelpers::Show> _show;
const style::ComposeControls &_st;
const Api::SendType _sendType = Api::SendType();
@@ -233,7 +239,7 @@ private:
SendFilesLimits _limits = {};
SendMenu::Type _sendMenuType = {};
PeerData *_captionToPeer = nullptr;
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
Fn<void()> _cancelledCallback;
@@ -244,6 +250,8 @@ private:
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
base::unique_qptr<QObject> _emojiFilter;
using CharactersLimitLabel = HistoryView::Controls::CharactersLimitLabel;
base::unique_qptr<CharactersLimitLabel> _charsLimitation;
object_ptr<Ui::Checkbox> _groupFiles = { nullptr };
object_ptr<Ui::Checkbox> _sendImagesAsPhotos = { nullptr };
+106 -23
View File
@@ -17,7 +17,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "ui/boxes/boost_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/sticker_set_box.h"
#include "apiwrap.h"
#include "storage/storage_account.h"
@@ -38,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/unread_badge_paint.h"
#include "media/clip/media_clip_reader.h"
#include "main/main_account.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -99,7 +103,8 @@ public:
Inner(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup);
not_null<ChannelData*> megagroup,
bool isEmoji);
[[nodiscard]] Main::Session &session() const;
@@ -108,7 +113,7 @@ public:
}
void setInnerFocus();
void saveGroupSet();
void saveGroupSet(Fn<void()> done);
void rebuild(bool masks);
void updateSize(int newWidth = 0);
@@ -221,6 +226,7 @@ private:
StickersSetsOrder collectSets(Check check) const;
void updateSelected();
void checkGroupLevel(Fn<void()> done);
void checkLoadMore();
void updateScrollbarWidth();
@@ -323,6 +329,8 @@ private:
int _scrollbar = 0;
ChannelData *_megagroupSet = nullptr;
bool _megagroupSetEmoji = false;
bool _checkingGroupLevel = false;
StickerSetIdentifier _megagroupSetInput;
std::unique_ptr<Row> _megagroupSelectedSet;
object_ptr<AddressField> _megagroupSetField = { nullptr };
@@ -429,15 +437,16 @@ StickersBox::StickersBox(
StickersBox::StickersBox(
QWidget*,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup)
not_null<ChannelData*> megagroup,
bool isEmoji)
: _st(st::stickersRowItem)
, _show(std::move(show))
, _session(&_show->session())
, _api(&_session->mtp())
, _section(Section::Installed)
, _isMasks(false)
, _isEmoji(false)
, _installed(0, this, _show, megagroup)
, _isEmoji(isEmoji)
, _installed(0, this, _show, megagroup, isEmoji)
, _megagroupSet(megagroup) {
_installed.widget()->scrollsToY(
) | rpl::start_with_next([=](int y) {
@@ -581,7 +590,9 @@ void StickersBox::prepare() {
session().local().readArchivedStickers();
}
} else {
setTitle(tr::lng_stickers_group_set());
setTitle(_isEmoji
? tr::lng_emoji_group_set()
: tr::lng_stickers_group_set());
}
} else if (_section == Section::Archived) {
requestArchivedSets();
@@ -659,9 +670,11 @@ void StickersBox::prepare() {
}
if (_megagroupSet) {
addButton(
tr::lng_settings_save(),
[=] { _installed.widget()->saveGroupSet(); closeBox(); });
addButton(tr::lng_settings_save(), [=] {
_installed.widget()->saveGroupSet(crl::guard(this, [=] {
closeBox();
}));
});
addButton(tr::lng_cancel(), [=] { closeBox(); });
} else {
const auto close = _section == Section::Attached;
@@ -1220,7 +1233,8 @@ StickersBox::Inner::Inner(
StickersBox::Inner::Inner(
QWidget *parent,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup)
not_null<ChannelData*> megagroup,
bool isEmoji)
: RpWidget(parent)
, _st(st::stickersRowItem)
, _show(std::move(show))
@@ -1248,19 +1262,30 @@ StickersBox::Inner::Inner(
})
, _itemsTop(st::lineWidth)
, _megagroupSet(megagroup)
, _megagroupSetInput(_megagroupSet->mgInfo->stickerSet)
, _megagroupSetEmoji(isEmoji)
, _megagroupSetInput(isEmoji
? _megagroupSet->mgInfo->emojiSet
: _megagroupSet->mgInfo->stickerSet)
, _megagroupSetField(
this,
st::groupStickersField,
rpl::single(u"stickerset"_q),
rpl::single(isEmoji ? u"emojipack"_q : u"stickerset"_q),
QString(),
_session->createInternalLink(QString()))
, _megagroupDivider(this)
, _megagroupSubTitle(this, tr::lng_stickers_group_from_your(tr::now), st::boxTitle) {
, _megagroupSubTitle(
this,
(isEmoji
? tr::lng_emoji_group_from_your
: tr::lng_stickers_group_from_your)(tr::now),
st::boxTitle) {
_megagroupSetField->setLinkPlaceholder(
_session->createInternalLink(u"addstickers/"_q));
_session->createInternalLink(
isEmoji ? u"addemoji/"_q : u"addstickers/"_q));
_megagroupSetField->setPlaceholderHidden(false);
_megagroupSetAddressChangedTimer.setCallback([this] { handleMegagroupSetAddressChange(); });
_megagroupSetAddressChangedTimer.setCallback([this] {
handleMegagroupSetAddressChange();
});
connect(
_megagroupSetField,
&Ui::MaskedInputField::changed,
@@ -1689,7 +1714,9 @@ void StickersBox::Inner::paintFakeButton(Painter &p, not_null<Row*> row, int ind
}
void StickersBox::Inner::mousePressEvent(QMouseEvent *e) {
if (_dragging >= 0) mouseReleaseEvent(e);
if (_dragging >= 0) {
mouseReleaseEvent(e);
}
_mouse = e->globalPos();
updateSelected();
@@ -1979,18 +2006,64 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
setActionDown(-1);
}
void StickersBox::Inner::saveGroupSet() {
void StickersBox::Inner::saveGroupSet(Fn<void()> done) {
Expects(_megagroupSet != nullptr);
auto oldId = _megagroupSet->mgInfo->stickerSet.id;
auto oldId = _megagroupSetEmoji
? _megagroupSet->mgInfo->emojiSet.id
: _megagroupSet->mgInfo->stickerSet.id;
auto newId = _megagroupSetInput.id;
if (newId != oldId) {
if (newId == oldId) {
done();
} else if (_megagroupSetEmoji) {
checkGroupLevel(done);
} else {
session().api().setGroupStickerSet(_megagroupSet, _megagroupSetInput);
session().data().stickers().notifyStickerSetInstalled(
Data::Stickers::MegagroupSetId);
}
}
void StickersBox::Inner::checkGroupLevel(Fn<void()> done) {
Expects(_megagroupSet != nullptr);
Expects(_megagroupSetEmoji);
const auto peer = _megagroupSet;
const auto save = [=] {
session().api().setGroupEmojiSet(peer, _megagroupSetInput);
session().data().stickers().notifyEmojiSetInstalled(
Data::Stickers::MegagroupSetId);
done();
};
if (!_megagroupSetInput) {
save();
return;
} else if (_checkingGroupLevel) {
return;
}
_checkingGroupLevel = true;
const auto weak = Ui::MakeWeak(this);
CheckBoostLevel(_show, peer, [=](int level) {
if (!weak) {
return std::optional<Ui::AskBoostReason>();
}
_checkingGroupLevel = false;
const auto appConfig = &peer->session().account().appConfig();
const auto required = appConfig->get<int>(
"group_emoji_stickers_level_min",
4);
if (level >= required) {
save();
return std::optional<Ui::AskBoostReason>();
}
return std::make_optional(Ui::AskBoostReason{
Ui::AskBoostEmojiPack{ required }
});
}, [=] { _checkingGroupLevel = false; });
}
void StickersBox::Inner::setRowRemovedBySetId(uint64 setId, bool removed) {
const auto index = getRowIndex(setId);
if (index >= 0) {
@@ -2233,9 +2306,13 @@ void StickersBox::Inner::rebuild(bool masks) {
clear();
const auto &order = ([&]() -> const StickersSetsOrder & {
if (_section == Section::Installed) {
auto &result = session().data().stickers().setsOrder();
auto &result = _megagroupSetEmoji
? session().data().stickers().emojiSetsOrder()
: session().data().stickers().setsOrder();
if (_megagroupSet && result.empty()) {
return session().data().stickers().featuredSetsOrder();
return _megagroupSetEmoji
? session().data().stickers().featuredEmojiSetsOrder()
: session().data().stickers().featuredSetsOrder();
}
return result;
} else if (_section == Section::Masks) {
@@ -2252,9 +2329,15 @@ void StickersBox::Inner::rebuild(bool masks) {
const auto &sets = session().data().stickers().sets();
if (_megagroupSet) {
auto usingFeatured = session().data().stickers().setsOrder().empty();
auto usingFeatured = _megagroupSetEmoji
? session().data().stickers().emojiSetsOrder().empty()
: session().data().stickers().setsOrder().empty();
_megagroupSubTitle->setText(usingFeatured
? tr::lng_stickers_group_from_featured(tr::now)
? (_megagroupSetEmoji
? tr::lng_stickers_group_from_featured(tr::now)
: tr::lng_emoji_group_from_featured(tr::now))
: _megagroupSetEmoji
? tr::lng_emoji_group_from_your(tr::now)
: tr::lng_stickers_group_from_your(tr::now));
updateControlsGeometry();
} else if (_isInstalledTab) {
+2 -1
View File
@@ -66,7 +66,8 @@ public:
StickersBox(
QWidget*,
std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> megagroup);
not_null<ChannelData*> megagroup,
bool isEmoji);
StickersBox(
QWidget*,
std::shared_ptr<ChatHelpers::Show> show,
@@ -968,6 +968,7 @@ historyComposeField: InputField(defaultInputField) {
duration: 100;
}
historyComposeFieldMaxHeight: 224px;
historyComposeFieldFadeHeight: 6px;
// historyMinHeight: 56px;
historyAttach: IconButton(defaultIconButton) {
@@ -1070,6 +1071,13 @@ historyMessagesTTLLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
historyCharsLimitationLabel: FlatLabel(defaultFlatLabel) {
// The same as a width of the historySendSize.
minWidth: 44px;
align: align(center);
textFg: attentionButtonFg;
}
historyRecordVoiceFg: historyComposeIconFg;
historyRecordVoiceFgOver: historyComposeIconFgOver;
historyRecordVoiceFgInactive: attentionButtonFg;
@@ -31,6 +31,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "layout/layout_position.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h"
@@ -41,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "emoji_suggestions_data.h"
#include "emoji_suggestions_helper.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "core/core_settings.h"
#include "core/application.h"
#include "settings/settings_premium.h"
@@ -467,6 +470,7 @@ EmojiListWidget::EmojiListWidget(
, _show(std::move(descriptor.show))
, _features(descriptor.features)
, _mode(descriptor.mode)
, _api(&session().mtp())
, _staticCount(_mode == Mode::Full ? kEmojiSectionCount : 1)
, _premiumIcon(_mode == Mode::EmojiStatus
? std::make_unique<GradientPremiumStar>()
@@ -523,6 +527,15 @@ EmojiListWidget::EmojiListWidget(
pickerHidden();
}, lifetime());
session().changes().peerUpdates(
Data::PeerUpdate::Flag::EmojiSet
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == _megagroupSet);
}) | rpl::start_with_next([=] {
refreshCustom();
resizeToWidth(width());
}, lifetime());
session().data().stickers().updated(
Data::StickersType::Emoji
) | rpl::start_with_next([=] {
@@ -1693,6 +1706,13 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
}
void EmojiListWidget::displaySet(uint64 setId) {
if (setId == Data::Stickers::MegagroupSetId) {
if (_megagroupSet->mgInfo->emojiSet.id) {
setId = _megagroupSet->mgInfo->emojiSet.id;
} else {
return;
}
}
const auto &sets = session().data().stickers().sets();
auto it = sets.find(setId);
if (it != sets.cend()) {
@@ -1700,9 +1720,37 @@ void EmojiListWidget::displaySet(uint64 setId) {
}
}
void EmojiListWidget::removeMegagroupSet(bool locally) {
if (locally) {
session().settings().setGroupEmojiSectionHidden(_megagroupSet->id);
session().saveSettings();
refreshCustom();
return;
}
checkHideWithBox(Ui::MakeConfirmBox({
.text = tr::lng_emoji_remove_group_set(),
.confirmed = crl::guard(this, [this, group = _megagroupSet](
Fn<void()> &&close) {
Expects(group->mgInfo != nullptr);
if (group->mgInfo->emojiSet) {
session().api().setGroupEmojiSet(group, {});
}
close();
}),
.cancelled = [](Fn<void()> &&close) { close(); },
.labelStyle = &st().boxLabel,
}));
}
void EmojiListWidget::removeSet(uint64 setId) {
const auto &labelSt = st().boxLabel;
if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
if (setId == Data::Stickers::MegagroupSetId) {
const auto i = ranges::find(_custom, setId, &CustomSet::id);
Assert(i != end(_custom));
const auto removeLocally = !_megagroupSet->canEditEmoji();
removeMegagroupSet(removeLocally);
} else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
checkHideWithBox(std::move(box));
}
}
@@ -1799,6 +1847,13 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
return false;
}
const auto &set = _custom[index - _staticCount];
if (set.id == Data::Stickers::MegagroupSetId) {
Assert(_megagroupSet != nullptr);
if (index + 1 != _staticCount + _custom.size()) {
return true;
}
return !set.list.empty() && _megagroupSet->canEditEmoji();
}
return set.canRemove && !set.premiumRequired;
}
@@ -1825,7 +1880,9 @@ bool EmojiListWidget::hasAddButton(int index) const {
return false;
}
const auto &set = _custom[index - _staticCount];
return !set.canRemove && !set.premiumRequired;
return !set.canRemove
&& !set.premiumRequired
&& set.id != Data::Stickers::MegagroupSetId;
}
QRect EmojiListWidget::addButtonRect(int index) const {
@@ -1849,10 +1906,13 @@ QRect EmojiListWidget::unlockButtonRect(int index) const {
}
bool EmojiListWidget::hasButton(int index) const {
if (hasColorButton(index)
|| (index >= _staticCount
&& index < _staticCount + _custom.size())) {
if (hasColorButton(index)) {
return true;
} else if (index >= _staticCount
&& index < _staticCount + _custom.size()) {
const auto &custom = _custom[index - _staticCount];
return (custom.id != Data::Stickers::MegagroupSetId)
|| custom.canRemove;
}
return false;
}
@@ -1982,6 +2042,16 @@ void EmojiListWidget::setAllowWithoutPremium(bool allow) {
resizeToWidth(width());
}
void EmojiListWidget::showMegagroupSet(ChannelData *megagroup) {
Expects(!megagroup || megagroup->isMegagroup());
if (_megagroupSet != megagroup) {
_megagroupSet = megagroup;
refreshCustom();
resizeToWidth(width());
}
}
QString EmojiListWidget::tooltipText() const {
if (_mode != Mode::Full) {
return {};
@@ -2052,7 +2122,25 @@ void EmojiListWidget::refreshCustom() {
const auto owner = &session->data();
const auto &sets = owner->stickers().sets();
const auto push = [&](uint64 setId, bool installed) {
auto it = sets.find(setId);
const auto megagroup = _megagroupSet
&& (setId == Data::Stickers::MegagroupSetId);
const auto lookupId = megagroup
? _megagroupSet->mgInfo->emojiSet.id
: setId;
if (!lookupId) {
return;
} else if (!megagroup
&& !_custom.empty()
&& _custom.front().id == Data::Stickers::MegagroupSetId
&& _megagroupSet->mgInfo->emojiSet.id == setId) {
// Skip the set that is already added as a megagroup set.
return;
} else if (megagroup
&& ranges::contains(_custom, lookupId, &CustomSet::id)) {
// Skip the set that is already added as a custom set.
return;
}
auto it = sets.find(lookupId);
if (it == sets.cend()
|| it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji && !it->second->textColor())
@@ -2060,12 +2148,13 @@ void EmojiListWidget::refreshCustom() {
&& !it->second->channelStatus())) {
return;
}
const auto canRemove = !!(it->second->flags
& Data::StickersSetFlag::Installed);
const auto canRemove = megagroup
? (_megagroupSet->canEditEmoji() || installed)
: !!(it->second->flags & Data::StickersSetFlag::Installed);
const auto sortAsInstalled = canRemove
&& (!(it->second->flags & Data::StickersSetFlag::Featured)
|| !_localSetsManager->isInstalledLocally(setId));
if (sortAsInstalled != installed) {
|| !_localSetsManager->isInstalledLocally(lookupId));
if (!megagroup && sortAsInstalled != installed) {
return;
}
auto premium = false;
@@ -2078,7 +2167,7 @@ void EmojiListWidget::refreshCustom() {
return false;
}
for (auto k = 0; k != count; ++k) {
if (!premium && list[k]->isPremiumEmoji()) {
if (!premium && !megagroup && list[k]->isPremiumEmoji()) {
premium = true;
}
if (i->list[k].document != list[k]) {
@@ -2112,11 +2201,11 @@ void EmojiListWidget::refreshCustom() {
continue;
} else if (const auto sticker = document->sticker()) {
set.push_back({
.custom = resolveCustomEmoji(document, setId),
.custom = resolveCustomEmoji(document, lookupId),
.document = document,
.emoji = Ui::Emoji::Find(sticker->alt),
});
if (!premium && document->isPremiumEmoji()) {
if (!premium && !megagroup && document->isPremiumEmoji()) {
premium = true;
}
}
@@ -2134,12 +2223,14 @@ void EmojiListWidget::refreshCustom() {
.premiumRequired = premium && premiumMayBeBought,
});
};
refreshMegagroupStickers(push, GroupStickersPlace::Visible);
for (const auto setId : owner->stickers().emojiSetsOrder()) {
push(setId, true);
}
for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) {
push(setId, false);
}
refreshMegagroupStickers(push, GroupStickersPlace::Hidden);
_footer->refreshIcons(
fillIcons(),
@@ -2232,6 +2323,60 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
).first->second.emoji.get();
}
void EmojiListWidget::refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place) {
if (!_features.megagroupSet
|| !_megagroupSet
|| !_megagroupSet->mgInfo->emojiSet) {
return;
}
auto canEdit = _megagroupSet->canEditEmoji();
auto isShownHere = [place](bool hidden) {
return (hidden == (place == GroupStickersPlace::Hidden));
};
auto hidden = session().settings().isGroupEmojiSectionHidden(_megagroupSet->id);
auto removeHiddenForGroup = [this, &hidden] {
if (hidden) {
session().settings().removeGroupEmojiSectionHidden(_megagroupSet->id);
session().saveSettings();
hidden = false;
}
};
if (canEdit && hidden) {
removeHiddenForGroup();
}
const auto &set = _megagroupSet->mgInfo->emojiSet;
if (!set.id || !isShownHere(hidden)) {
return;
}
push(Data::Stickers::MegagroupSetId, !hidden);
if (!_custom.empty()
&& _custom.back().id == Data::Stickers::MegagroupSetId) {
return;
} else if (_megagroupSetIdRequested == set.id) {
return;
}
_megagroupSetIdRequested = set.id;
_api.request(MTPmessages_GetStickerSet(
Data::InputStickerSet(set),
MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) {
result.match([&](const MTPDmessages_stickerSet &data) {
if (const auto set = session().data().stickers().feedSetFull(data)) {
refreshCustom();
if (set->id == _megagroupSetIdRequested) {
_megagroupSetIdRequested = 0;
} else {
LOG(("API Error: Got different set."));
}
}
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).send();
}
std::vector<StickerIcon> EmojiListWidget::fillIcons() {
auto result = std::vector<StickerIcon>();
result.reserve(2 + _custom.size());
@@ -2248,6 +2393,11 @@ std::vector<StickerIcon> EmojiListWidget::fillIcons() {
}
const auto esize = StickersListFooter::IconFrameSize();
for (const auto &custom : _custom) {
if (custom.id == Data::Stickers::MegagroupSetId) {
result.emplace_back(Data::Stickers::MegagroupSetId);
result.back().megagroup = _megagroupSet;
continue;
}
const auto set = custom.set;
result.emplace_back(set, custom.thumbnailDocument, esize, esize);
}
@@ -117,6 +117,7 @@ public:
void showSet(uint64 setId);
[[nodiscard]] uint64 currentSet(int yOffset) const;
void setAllowWithoutPremium(bool allow);
void showMegagroupSet(ChannelData *megagroup);
// Ui::AbstractTooltipShower interface.
QString tooltipText() const override;
@@ -257,6 +258,13 @@ private:
void colorChosen(EmojiChosen data);
bool checkPickerHide();
void refreshCustom();
enum class GroupStickersPlace {
Visible,
Hidden,
};
void refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place);
void unloadNotSeenCustom(int visibleTop, int visibleBottom);
void unloadAllCustom();
void unloadCustomIn(const SectionInfo &info);
@@ -340,6 +348,7 @@ private:
void displaySet(uint64 setId);
void removeSet(uint64 setId);
void removeMegagroupSet(bool locally);
void initButton(RightButton &button, const QString &text, bool gradient);
[[nodiscard]] std::unique_ptr<Ui::RippleAnimation> createButtonRipple(
@@ -370,10 +379,13 @@ private:
const ComposeFeatures _features;
Mode _mode = Mode::Full;
std::unique_ptr<Ui::TabbedSearch> _search;
MTP::Sender _api;
const int _staticCount = 0;
StickersListFooter *_footer = nullptr;
std::unique_ptr<GradientPremiumStar> _premiumIcon;
std::unique_ptr<LocalStickersManager> _localSetsManager;
ChannelData *_megagroupSet = nullptr;
uint64 _megagroupSetIdRequested = 0;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
DocumentId,
Fn<void()>)> _customRecentFactory;
@@ -10,11 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_widget.h"
#include "history/history.h" // History::session
#include "history/history_item.h" // HistoryItem::originalText
#include "history/history_item_helpers.h" // DropCustomEmoji
#include "history/history_item_helpers.h" // DropDisallowedCustomEmoji
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "base/event_filter.h"
#include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "core/shortcuts.h"
#include "core/application.h"
#include "core/core_settings.h"
@@ -277,11 +278,9 @@ TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
auto original = item->history()->session().supportMode()
? StripSupportHashtag(item->originalText())
: item->originalText();
const auto dropCustomEmoji = !item->history()->session().premium()
&& !item->history()->peer->isSelf();
if (dropCustomEmoji) {
original = DropCustomEmoji(std::move(original));
}
original = DropDisallowedCustomEmoji(
item->history()->peer,
std::move(original));
return TextWithTags{
original.text,
TextUtilities::ConvertEntitiesToTextTags(original.entities)
@@ -442,6 +441,73 @@ bool HasSendText(not_null<const Ui::InputField*> field) {
return false;
}
void InitMessageFieldFade(
not_null<Ui::InputField*> field,
const style::color &bg) {
class Fade final : public Ui::RpWidget {
public:
using Ui::RpWidget::RpWidget;
void setFade(QPixmap &&fade) {
_fade = std::move(fade);
}
int resizeGetHeight(int newWidth) override {
return st::historyComposeFieldFadeHeight;
}
private:
void paintEvent(QPaintEvent *event) override {
auto p = QPainter(this);
p.drawTiledPixmap(rect(), _fade);
}
QPixmap _fade;
};
const auto topFade = Ui::CreateChild<Fade>(field.get());
const auto bottomFade = Ui::CreateChild<Fade>(field.get());
const auto generateFade = [=] {
const auto size = QSize(1, st::historyComposeFieldFadeHeight);
auto fade = QPixmap(size * style::DevicePixelRatio());
fade.setDevicePixelRatio(style::DevicePixelRatio());
fade.fill(Qt::transparent);
{
auto p = QPainter(&fade);
auto gradient = QLinearGradient(0, 1, 0, size.height());
gradient.setStops({ { 0., bg->c }, { .9, Qt::transparent } });
p.setPen(Qt::NoPen);
p.setBrush(gradient);
p.drawRect(Rect(size));
}
bottomFade->setFade(fade.transformed(QTransform().scale(1, -1)));
topFade->setFade(std::move(fade));
};
generateFade();
style::PaletteChanged(
) | rpl::start_with_next([=] {
generateFade();
}, topFade->lifetime());
field->sizeValue(
) | rpl::start_with_next_done([=](const QSize &size) {
topFade->resizeToWidth(size.width());
bottomFade->resizeToWidth(size.width());
bottomFade->move(
0,
size.height() - st::historyComposeFieldFadeHeight);
}, [t = Ui::MakeWeak(topFade), b = Ui::MakeWeak(bottomFade)] {
Ui::DestroyChild(t.data());
Ui::DestroyChild(b.data());
}, topFade->lifetime());
topFade->show();
bottomFade->show();
}
InlineBotQuery ParseInlineBotQuery(
not_null<Main::Session*> session,
not_null<const Ui::InputField*> field) {
@@ -79,6 +79,10 @@ void InitSpellchecker(
bool HasSendText(not_null<const Ui::InputField*> field);
void InitMessageFieldFade(
not_null<Ui::InputField*> field,
const style::color &bg);
struct InlineBotQuery {
QString query;
QString username;
@@ -1790,7 +1790,8 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
removeSet(sets[button->section].id);
}
} else if (std::get_if<OverGroupAdd>(&pressed)) {
_show->showBox(Box<StickersBox>(_show, _megagroupSet));
const auto isEmoji = false;
_show->showBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
}
}
}
@@ -2642,7 +2643,8 @@ void StickersListWidget::setupSearch() {
void StickersListWidget::displaySet(uint64 setId) {
if (setId == Data::Stickers::MegagroupSetId) {
if (_megagroupSet->canEditStickers()) {
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet));
const auto isEmoji = false;
checkHideWithBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
return;
} else if (_megagroupSet->mgInfo->stickerSet.id) {
setId = _megagroupSet->mgInfo->stickerSet.id;
@@ -966,6 +966,9 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
}
_currentPeer = peer;
checkRestrictedPeer();
if (hasEmojiTab()) {
emoji()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
}
if (hasStickersTab()) {
stickers()->showMegagroupSet(peer ? peer->asMegagroup() : nullptr);
}
+5 -2
View File
@@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QSessionManager>
#include <QtGui/QScreen>
#include <QtGui/qpa/qplatformscreen.h>
#include <ksandbox.h>
namespace Core {
namespace {
@@ -517,8 +518,10 @@ void Sandbox::refreshGlobalProxy() {
|| proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
} else if (!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem()) {
} else if ((!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem())
// this works stable only in sandboxed environment where it works through portal
&& (!Platform::IsLinux() || KSandbox::isInside() || cDebugMode())) {
QNetworkProxyFactory::setUseSystemConfiguration(true);
} else {
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
+2 -2
View File
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 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;
+1
View File
@@ -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;
+6 -5
View File
@@ -101,13 +101,14 @@ struct PeerUpdate {
// For channels
ChannelAmIn = (1ULL << 36),
StickersSet = (1ULL << 37),
ChannelLinkedChat = (1ULL << 38),
ChannelLocation = (1ULL << 39),
Slowmode = (1ULL << 40),
GroupCall = (1ULL << 41),
EmojiSet = (1ULL << 38),
ChannelLinkedChat = (1ULL << 39),
ChannelLocation = (1ULL << 40),
Slowmode = (1ULL << 41),
GroupCall = (1ULL << 42),
// For iteration
LastUsedBit = (1ULL << 41),
LastUsedBit = (1ULL << 42),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
+95 -24
View File
@@ -556,25 +556,16 @@ bool ChannelData::canDeleteMessages() const {
}
bool ChannelData::canPostStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator()
|| (adminRights() & AdminRight::PostStories);
}
bool ChannelData::canEditStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator()
|| (adminRights() & AdminRight::EditStories);
}
bool ChannelData::canDeleteStories() const {
if (!isBroadcast()) {
return false;
}
return amCreator()
|| (adminRights() & AdminRight::DeleteStories);
}
@@ -648,6 +639,10 @@ bool ChannelData::canEditStickers() const {
return (flags() & Flag::CanSetStickers);
}
bool ChannelData::canEditEmoji() const {
return amCreator() || (adminRights() & ChatAdminRight::ChangeInfo);
}
bool ChannelData::canDelete() const {
constexpr auto kDeleteChannelMembersLimit = 1000;
return amCreator()
@@ -772,34 +767,93 @@ void ChannelData::setMigrateFromChat(ChatData *chat) {
}
int ChannelData::slowmodeSeconds() const {
return _slowmodeSeconds;
if (const auto info = mgInfo.get()) {
return info->slowmodeSeconds;
}
return 0;
}
void ChannelData::setSlowmodeSeconds(int seconds) {
if (_slowmodeSeconds == seconds) {
if (!mgInfo || slowmodeSeconds() == seconds) {
return;
}
_slowmodeSeconds = seconds;
mgInfo->slowmodeSeconds = seconds;
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
}
TimeId ChannelData::slowmodeLastMessage() const {
return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage;
return (hasAdminRights()
|| amCreator()
|| unrestrictedByBoosts()
|| !mgInfo)
? 0
: mgInfo->slowmodeLastMessage;
}
void ChannelData::growSlowmodeLastMessage(TimeId when) {
const auto info = mgInfo.get();
const auto now = base::unixtime::now();
accumulate_min(when, now);
if (_slowmodeLastMessage > now) {
_slowmodeLastMessage = when;
} else if (_slowmodeLastMessage >= when) {
if (!info) {
return;
} else if (info->slowmodeLastMessage > now) {
info->slowmodeLastMessage = when;
} else if (info->slowmodeLastMessage >= when) {
return;
} else {
_slowmodeLastMessage = when;
info->slowmodeLastMessage = when;
}
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
}
int ChannelData::boostsApplied() const {
if (const auto info = mgInfo.get()) {
return info->boostsApplied;
}
return 0;
}
int ChannelData::boostsUnrestrict() const {
if (const auto info = mgInfo.get()) {
return info->boostsUnrestrict;
}
return 0;
}
bool ChannelData::unrestrictedByBoosts() const {
if (const auto info = mgInfo.get()) {
return (info->boostsUnrestrict > 0)
&& (info->boostsApplied >= info->boostsUnrestrict);
}
return 0;
}
rpl::producer<bool> ChannelData::unrestrictedByBoostsValue() const {
return mgInfo
? mgInfo->unrestrictedByBoostsChanges.events_starting_with(
unrestrictedByBoosts())
: (rpl::single(false) | rpl::type_erased());
}
void ChannelData::setBoostsUnrestrict(int applied, int unrestrict) {
if (const auto info = mgInfo.get()) {
if (info->boostsApplied == applied
&& info->boostsUnrestrict == unrestrict) {
return;
}
const auto wasUnrestricted = unrestrictedByBoosts();
info->boostsApplied = applied;
info->boostsUnrestrict = unrestrict;
const auto nowUnrestricted = unrestrictedByBoosts();
if (wasUnrestricted != nowUnrestricted) {
info->unrestrictedByBoostsChanges.fire_copy(nowUnrestricted);
session().changes().peerUpdated(
this,
UpdateFlag::Rights | UpdateFlag::Slowmode);
}
}
}
void ChannelData::setInvitePeek(const QString &hash, TimeId expires) {
if (!_invitePeek) {
_invitePeek = std::make_unique<InvitePeek>();
@@ -1104,20 +1158,37 @@ void ApplyChannelUpdate(
channel->owner().botCommandsChanged(channel);
}
const auto stickerSet = update.vstickerset();
const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
const auto newSetId = (set ? set->vid().v : 0);
const auto oldSetId = channel->mgInfo->stickerSet.id;
const auto sset = stickerSet ? &stickerSet->c_stickerSet() : nullptr;
const auto newStickerSetId = (sset ? sset->vid().v : 0);
const auto oldStickerSetId = channel->mgInfo->stickerSet.id;
const auto stickersChanged = (canEditStickers != channel->canEditStickers())
|| (oldSetId != newSetId);
if (oldSetId != newSetId) {
|| (oldStickerSetId != newStickerSetId);
if (oldStickerSetId != newStickerSetId) {
channel->mgInfo->stickerSet = StickerSetIdentifier{
.id = set ? set->vid().v : 0,
.accessHash = set ? set->vaccess_hash().v : 0,
.id = sset ? sset->vid().v : 0,
.accessHash = sset ? sset->vaccess_hash().v : 0,
};
}
if (stickersChanged) {
session->changes().peerUpdated(channel, UpdateFlag::StickersSet);
}
const auto emojiSet = update.vemojiset();
const auto eset = emojiSet ? &emojiSet->c_stickerSet() : nullptr;
const auto newEmojiSetId = (eset ? eset->vid().v : 0);
const auto oldEmojiSetId = channel->mgInfo->emojiSet.id;
const auto emojiChanged = (oldEmojiSetId != newEmojiSetId);
if (oldEmojiSetId != newEmojiSetId) {
channel->mgInfo->emojiSet = StickerSetIdentifier{
.id = eset ? eset->vid().v : 0,
.accessHash = eset ? eset->vaccess_hash().v : 0,
};
}
if (emojiChanged) {
session->changes().peerUpdated(channel, UpdateFlag::EmojiSet);
}
channel->setBoostsUnrestrict(
update.vboosts_applied().value_or_empty(),
update.vboosts_unrestrict().value_or_empty());
}
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
channel->setTranslationDisabled(update.is_translations_disabled());
+14 -3
View File
@@ -114,6 +114,7 @@ public:
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
base::flat_set<not_null<PeerData*>> markupSenders;
base::flat_set<not_null<UserData*>> bots;
rpl::event_stream<bool> unrestrictedByBoostsChanges;
// For admin badges, full admins list with ranks.
base::flat_map<UserId, QString> admins;
@@ -124,6 +125,7 @@ public:
bool joinedMessageFound = false;
bool adminsLoaded = false;
StickerSetIdentifier stickerSet;
StickerSetIdentifier emojiSet;
enum LastParticipantsStatus {
LastParticipantsUpToDate = 0x00,
@@ -132,6 +134,11 @@ public:
};
mutable int lastParticipantsStatus = LastParticipantsUpToDate;
int lastParticipantsCount = 0;
int boostsApplied = 0;
int boostsUnrestrict = 0;
int slowmodeSeconds = 0;
TimeId slowmodeLastMessage = 0;
private:
ChatData *_migratedFrom = nullptr;
@@ -354,6 +361,7 @@ public:
[[nodiscard]] bool canViewBanned() const;
[[nodiscard]] bool canEditSignatures() const;
[[nodiscard]] bool canEditStickers() const;
[[nodiscard]] bool canEditEmoji() const;
[[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canEditAdmin(not_null<UserData*> user) const;
[[nodiscard]] bool canRestrictParticipant(
@@ -432,6 +440,12 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when);
[[nodiscard]] int boostsApplied() const;
[[nodiscard]] int boostsUnrestrict() const;
[[nodiscard]] bool unrestrictedByBoosts() const;
[[nodiscard]] rpl::producer<bool> unrestrictedByBoostsValue() const;
void setBoostsUnrestrict(int applied, int unrestrict);
void setInvitePeek(const QString &hash, TimeId expires);
void clearInvitePeek();
[[nodiscard]] TimeId invitePeekExpires() const;
@@ -519,9 +533,6 @@ private:
std::unique_ptr<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0;
int _slowmodeSeconds = 0;
TimeId _slowmodeLastMessage = 0;
};
namespace Data {
@@ -775,7 +775,10 @@ FilterId ChatFilters::defaultId() const {
}
FilterId ChatFilters::lookupId(int index) const {
Expects(index >= 0 && index < _list.size());
// Expects(index >= 0 && index < _list.size());
if (!(index >= 0 && index < _list.size())) {
return FilterId(); // AyuGram: fix crash when using `hideAllChatsFolder`
}
if (_owner->session().user()->isPremium() || !_list.front().id()) {
return _list[index].id();
+3 -5
View File
@@ -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);
+21 -48
View File
@@ -302,20 +302,27 @@ bool UpdateExtendedMedia(
});
}
} // namespace
TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption) {
auto result = TextForMimeData();
result.reserve(5 + attachType.size() + caption.expanded.size());
result.append(u"[ "_q).append(attachType).append(u" ]"_q);
if (!caption.empty()) {
result.append('\n').append(std::move(caption));
if (attachType.isEmpty()) {
result.reserve(1 + caption.expanded.size());
if (!caption.empty()) {
result.append(std::move(caption));
}
} else {
result.reserve(5 + attachType.size() + caption.expanded.size());
result.append(u"[ "_q).append(attachType).append(u" ]"_q);
if (!caption.empty()) {
result.append('\n').append(std::move(caption));
}
}
return result;
}
} // namespace
Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data) {
@@ -767,9 +774,7 @@ QString MediaPhoto::pinnedTextSubstring() const {
}
TextForMimeData MediaPhoto::clipboardText() const {
return WithCaptionClipboardText(
tr::lng_in_dlg_photo(tr::now),
parent()->clipboardText());
return TextForMimeData();
}
bool MediaPhoto::allowsEditCaption() const {
@@ -1072,42 +1077,9 @@ QString MediaFile::pinnedTextSubstring() const {
}
TextForMimeData MediaFile::clipboardText() const {
const auto attachType = [&] {
const auto name = Ui::Text::FormatSongNameFor(_document).string();
const auto addName = !name.isEmpty()
? u" : "_q + name
: QString();
if (const auto sticker = _document->sticker()) {
if (!_emoji.isEmpty()) {
return tr::lng_in_dlg_sticker_emoji(
tr::now,
lt_emoji,
_emoji);
}
return tr::lng_in_dlg_sticker(tr::now);
} else if (_document->isAnimation()) {
if (_document->isVideoMessage()) {
const auto media = parent()->media();
return (media && media->ttlSeconds())
? tr::lng_in_dlg_video_message_ttl(tr::now)
: tr::lng_in_dlg_video_message(tr::now);
}
return u"GIF"_q;
} else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now);
} else if (_document->isVoiceMessage()) {
const auto media = parent()->media();
return ((media && media->ttlSeconds())
? tr::lng_in_dlg_voice_message_ttl
: tr::lng_in_dlg_audio)(tr::now) + addName;;
} else if (_document->isSong()) {
return tr::lng_in_dlg_audio_file(tr::now) + addName;
}
return tr::lng_in_dlg_file(tr::now) + addName;
}();
auto caption = parent()->clipboardText();
if (_document->isVoiceMessage()) {
if (_document->isVoiceMessage() || _document->isVideoMessage()) {
const auto &entry = _document->session().api().transcribes().entry(
parent());
if (!entry.requestId
@@ -1115,17 +1087,18 @@ TextForMimeData MediaFile::clipboardText() const {
&& !entry.toolong
&& !entry.failed
&& (entry.pending || !entry.result.isEmpty())) {
const auto text = "{{\n"
const auto hasCaption = !caption.rich.text.isEmpty();
const auto text = (hasCaption ? "{{\n" : "")
+ entry.result
+ (entry.result.isEmpty() ? "" : " ")
+ (entry.pending ? "[...]" : "")
+ "\n}}"
+ (caption.rich.text.isEmpty() ? "" : "\n");
caption = TextForMimeData{ text, { text } }.append(std::move(caption));
+ (hasCaption ? "\n}}\n" : "");
caption = TextForMimeData{ text, { text } }.append(
std::move(caption));
}
}
return WithCaptionClipboardText(attachType, std::move(caption));
return caption;
}
bool MediaFile::allowsEditCaption() const {
@@ -709,10 +709,6 @@ private:
};
[[nodiscard]] TextForMimeData WithCaptionClipboardText(
const QString &attachType,
TextForMimeData &&caption);
[[nodiscard]] Invoice ComputeInvoiceData(
not_null<HistoryItem*> item,
const MTPDmessageMediaInvoice &data);
+1 -1
View File
@@ -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();
+2 -1
View File
@@ -1122,7 +1122,8 @@ Data::RestrictionCheckResult PeerData::amRestricted(
: ChatRestrictions(0));
return (channel->amCreator() || allowByAdminRights(right, channel))
? Result::Allowed()
: (defaultRestrictions & right)
: ((defaultRestrictions & right)
&& !channel->unrestrictedByBoosts())
? Result::WithEveryone()
: (channel->restrictions() & right)
? Result::Explicit()
@@ -271,11 +271,13 @@ inline auto DefaultRestrictionValue(
AdminRightValue(
channel,
ChatAdminRight::PostMessages),
channel->unrestrictedByBoostsValue(),
RestrictionsValue(channel, rights),
DefaultRestrictionsValue(channel, rights),
[=](
ChannelDataFlags flags,
bool postMessagesRight,
bool unrestrictedByBoosts,
ChatRestrictions sendRestriction,
ChatRestrictions defaultSendRestriction) {
const auto notAmInFlags = Flag::Left | Flag::Forbidden;
@@ -285,7 +287,7 @@ inline auto DefaultRestrictionValue(
|| ((flags & Flag::HasLink)
&& !(flags & Flag::JoinToWrite));
const auto restricted = sendRestriction
| defaultSendRestriction;
| (defaultSendRestriction && !unrestrictedByBoosts);
return allowed
&& !forumRestriction
&& (postMessagesRight
@@ -67,6 +67,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
MTP_flags(data.vflags().v | MTPDmessage::Flag::f_from_scheduled),
data.vid(),
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
MTPint(), // from_boosts_applied
data.vpeer_id(),
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
@@ -216,6 +217,7 @@ void ScheduledMessages::sendNowSimpleMessage(
MTP_flags(flags),
update.vid(),
peerToMTP(local->from()->id),
MTPint(), // from_boosts_applied
peerToMTP(history->peer->id),
MTPPeer(), // saved_peer_id
MTPMessageFwdHeader(),

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