diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 8c04e71228..24fbcb9080 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Clone.
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
submodules: recursive
diff --git a/.gitignore b/.gitignore
index 3db1a60aed..0ae30de9fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,5 +54,16 @@ stage
.idea/
cmake-build-debug/
+# Local configuration files
+settings.local.json
+*.local.json
+.env
+.env.local
+.env.*.local
+
+# Cursor IDE local settings (but keep .cursor/rules/)
+.cursor/*
+!.cursor/rules/
+
# Project specific
Telegram/SourceFiles/_other/packer_private.h
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index e25ce766d1..be68112aec 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -659,6 +659,8 @@ PRIVATE
data/notify/data_notify_settings.h
data/notify/data_peer_notify_settings.cpp
data/notify/data_peer_notify_settings.h
+ data/notify/data_peer_notify_volume.cpp
+ data/notify/data_peer_notify_volume.h
data/stickers/data_custom_emoji.cpp
data/stickers/data_custom_emoji.h
data/stickers/data_stickers_set.cpp
@@ -748,6 +750,7 @@ PRIVATE
data/data_peer_bot_command.h
data/data_peer_bot_commands.cpp
data/data_peer_bot_commands.h
+ data/data_peer_common.h
data/data_peer_id.cpp
data/data_peer_id.h
data/data_peer_values.cpp
@@ -769,6 +772,8 @@ PRIVATE
data/data_report.h
data/data_saved_messages.cpp
data/data_saved_messages.h
+ data/data_saved_music.cpp
+ data/data_saved_music.h
data/data_saved_sublist.cpp
data/data_saved_sublist.h
data/data_search_controller.cpp
@@ -781,6 +786,7 @@ PRIVATE
data/data_shared_media.h
data/data_sparse_ids.cpp
data/data_sparse_ids.h
+ data/data_star_gift.cpp
data/data_star_gift.h
data/data_statistics.h
data/data_stories.cpp
@@ -843,6 +849,8 @@ PRIVATE
dialogs/dialogs_search_from_controllers.h
dialogs/dialogs_search_tags.cpp
dialogs/dialogs_search_tags.h
+ dialogs/dialogs_search_posts.cpp
+ dialogs/dialogs_search_posts.h
dialogs/dialogs_top_bar_suggestion.cpp
dialogs/dialogs_top_bar_suggestion.h
dialogs/dialogs_widget.cpp
@@ -945,6 +953,8 @@ PRIVATE
history/view/media/history_view_poll.h
history/view/media/history_view_premium_gift.cpp
history/view/media/history_view_premium_gift.h
+ history/view/media/history_view_save_document_action.cpp
+ history/view/media/history_view_save_document_action.h
history/view/media/history_view_service_box.cpp
history/view/media/history_view_service_box.h
history/view/media/history_view_similar_channels.cpp
@@ -1008,6 +1018,8 @@ PRIVATE
history/view/history_view_fake_items.h
history/view/history_view_group_call_bar.cpp
history/view/history_view_group_call_bar.h
+ history/view/history_view_group_members_widget.cpp
+ history/view/history_view_group_members_widget.h
history/view/history_view_item_preview.h
history/view/history_view_list_widget.cpp
history/view/history_view_list_widget.h
@@ -1142,6 +1154,8 @@ PRIVATE
info/media/info_media_widget.h
info/members/info_members_widget.cpp
info/members/info_members_widget.h
+ info/peer_gifts/info_peer_gifts_collections.cpp
+ info/peer_gifts/info_peer_gifts_collections.h
info/peer_gifts/info_peer_gifts_common.cpp
info/peer_gifts/info_peer_gifts_common.h
info/peer_gifts/info_peer_gifts_widget.cpp
@@ -1176,6 +1190,11 @@ PRIVATE
info/reactions_list/info_reactions_list_widget.h
info/requests_list/info_requests_list_widget.cpp
info/requests_list/info_requests_list_widget.h
+ info/saved/info_saved_music_common.h
+ info/saved/info_saved_music_provider.cpp
+ info/saved/info_saved_music_provider.h
+ info/saved/info_saved_music_widget.cpp
+ info/saved/info_saved_music_widget.h
info/saved/info_saved_sublists_widget.cpp
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp
@@ -1192,6 +1211,9 @@ PRIVATE
info/statistics/info_statistics_tag.h
info/statistics/info_statistics_widget.cpp
info/statistics/info_statistics_widget.h
+ info/stories/info_stories_albums.cpp
+ info/stories/info_stories_albums.h
+ info/stories/info_stories_common.h
info/stories/info_stories_inner_widget.cpp
info/stories/info_stories_inner_widget.h
info/stories/info_stories_provider.cpp
@@ -1246,6 +1268,8 @@ PRIVATE
inline_bots/inline_results_widget.h
intro/intro_code.cpp
intro/intro_code.h
+ intro/intro_email.cpp
+ intro/intro_email.h
intro/intro_password_check.cpp
intro/intro_password_check.h
intro/intro_phone.cpp
@@ -1399,6 +1423,8 @@ PRIVATE
menu/menu_antispam_validator.h
menu/menu_item_download_files.cpp
menu/menu_item_download_files.h
+ menu/menu_item_rate_transcribe_session.cpp
+ menu/menu_item_rate_transcribe_session.h
menu/menu_mute.cpp
menu/menu_mute.h
menu/menu_send.cpp
@@ -1432,6 +1458,8 @@ PRIVATE
mtproto/special_config_request.cpp
mtproto/special_config_request.h
mtproto/type_utils.h
+ overview/overview_checkbox.cpp
+ overview/overview_checkbox.h
overview/overview_layout.cpp
overview/overview_layout.h
overview/overview_layout_delegate.h
@@ -1552,10 +1580,6 @@ PRIVATE
platform/platform_window_title.h
profile/profile_back_button.cpp
profile/profile_back_button.h
- profile/profile_block_group_members.cpp
- profile/profile_block_group_members.h
- profile/profile_block_peer_list.cpp
- profile/profile_block_peer_list.h
profile/profile_block_widget.cpp
profile/profile_block_widget.h
profile/profile_cover_drop_area.cpp
@@ -1750,8 +1774,6 @@ PRIVATE
ui/text/format_song_document_name.h
ui/widgets/expandable_peer_list.cpp
ui/widgets/expandable_peer_list.h
- ui/widgets/label_with_custom_emoji.cpp
- ui/widgets/label_with_custom_emoji.h
ui/widgets/chat_filters_tabs_strip.cpp
ui/widgets/chat_filters_tabs_strip.h
ui/widgets/peer_bubble.cpp
@@ -1784,6 +1806,8 @@ PRIVATE
window/window_adaptive.h
window/window_chat_preview.cpp
window/window_chat_preview.h
+ window/window_chat_switch_process.cpp
+ window/window_chat_switch_process.h
window/window_connecting_widget.cpp
window/window_connecting_widget.h
window/window_controller.cpp
diff --git a/Telegram/Resources/animations/transcribe_loading.tgs b/Telegram/Resources/animations/transcribe_loading.tgs
new file mode 100644
index 0000000000..f63b6e9a02
Binary files /dev/null and b/Telegram/Resources/animations/transcribe_loading.tgs differ
diff --git a/Telegram/Resources/icons/chat/mini_ton_bold.svg b/Telegram/Resources/icons/chat/mini_ton_bold.svg
new file mode 100644
index 0000000000..64dc4fa576
--- /dev/null
+++ b/Telegram/Resources/icons/chat/mini_ton_bold.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level10_inner.svg b/Telegram/Resources/icons/levels/level10_inner.svg
new file mode 100644
index 0000000000..8ce8b2c9b2
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level10_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level1_inner.svg b/Telegram/Resources/icons/levels/level1_inner.svg
new file mode 100644
index 0000000000..37959bcb5d
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level1_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level20_inner.svg b/Telegram/Resources/icons/levels/level20_inner.svg
new file mode 100644
index 0000000000..055e99e5c3
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level20_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level2_inner.svg b/Telegram/Resources/icons/levels/level2_inner.svg
new file mode 100644
index 0000000000..2cd67761cc
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level2_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level30_inner.svg b/Telegram/Resources/icons/levels/level30_inner.svg
new file mode 100644
index 0000000000..b74a2e6efb
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level30_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level3_inner.svg b/Telegram/Resources/icons/levels/level3_inner.svg
new file mode 100644
index 0000000000..3bc0ef3505
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level3_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level40_inner.svg b/Telegram/Resources/icons/levels/level40_inner.svg
new file mode 100644
index 0000000000..f7fef2c167
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level40_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level4_inner.svg b/Telegram/Resources/icons/levels/level4_inner.svg
new file mode 100644
index 0000000000..b23971ce55
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level4_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level50_inner.svg b/Telegram/Resources/icons/levels/level50_inner.svg
new file mode 100644
index 0000000000..23aa9e3107
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level50_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level5_inner.svg b/Telegram/Resources/icons/levels/level5_inner.svg
new file mode 100644
index 0000000000..e28cd650ab
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level5_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level60_inner.svg b/Telegram/Resources/icons/levels/level60_inner.svg
new file mode 100644
index 0000000000..b006ec3280
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level60_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level6_inner.svg b/Telegram/Resources/icons/levels/level6_inner.svg
new file mode 100644
index 0000000000..1b8a68d0da
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level6_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level70_inner.svg b/Telegram/Resources/icons/levels/level70_inner.svg
new file mode 100644
index 0000000000..d6bc1b8b4a
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level70_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level7_inner.svg b/Telegram/Resources/icons/levels/level7_inner.svg
new file mode 100644
index 0000000000..d0a0c79299
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level7_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level80_inner.svg b/Telegram/Resources/icons/levels/level80_inner.svg
new file mode 100644
index 0000000000..a469b74988
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level80_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level8_inner.svg b/Telegram/Resources/icons/levels/level8_inner.svg
new file mode 100644
index 0000000000..f4a0d3b7d1
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level8_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level90_inner.svg b/Telegram/Resources/icons/levels/level90_inner.svg
new file mode 100644
index 0000000000..d25a731d93
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level90_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level9_inner.svg b/Telegram/Resources/icons/levels/level9_inner.svg
new file mode 100644
index 0000000000..27fe8c25a2
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level9_inner.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/levels/level_warning.svg b/Telegram/Resources/icons/levels/level_warning.svg
new file mode 100644
index 0000000000..cba1db0076
--- /dev/null
+++ b/Telegram/Resources/icons/levels/level_warning.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/limits/filled_rating_crown.svg b/Telegram/Resources/icons/limits/filled_rating_crown.svg
new file mode 100644
index 0000000000..e7cc2e124f
--- /dev/null
+++ b/Telegram/Resources/icons/limits/filled_rating_crown.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/limits/filled_understood.svg b/Telegram/Resources/icons/limits/filled_understood.svg
new file mode 100644
index 0000000000..337b43cc71
--- /dev/null
+++ b/Telegram/Resources/icons/limits/filled_understood.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/limits/mini_gift_lock.svg b/Telegram/Resources/icons/limits/mini_gift_lock.svg
new file mode 100644
index 0000000000..2887a84d4c
--- /dev/null
+++ b/Telegram/Resources/icons/limits/mini_gift_lock.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/menu/rating_gifts.svg b/Telegram/Resources/icons/menu/rating_gifts.svg
new file mode 100644
index 0000000000..ed3aa9d4b1
--- /dev/null
+++ b/Telegram/Resources/icons/menu/rating_gifts.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/menu/rating_refund.svg b/Telegram/Resources/icons/menu/rating_refund.svg
new file mode 100644
index 0000000000..05434da66a
--- /dev/null
+++ b/Telegram/Resources/icons/menu/rating_refund.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/menu/users_stars.svg b/Telegram/Resources/icons/menu/users_stars.svg
new file mode 100644
index 0000000000..0a068f774a
--- /dev/null
+++ b/Telegram/Resources/icons/menu/users_stars.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/icons/payments/ton_emoji.svg b/Telegram/Resources/icons/payments/ton_emoji.svg
new file mode 100644
index 0000000000..7de741c10e
--- /dev/null
+++ b/Telegram/Resources/icons/payments/ton_emoji.svg
@@ -0,0 +1,15 @@
+
diff --git a/Telegram/Resources/icons/settings/filled_verify_age.svg b/Telegram/Resources/icons/settings/filled_verify_age.svg
new file mode 100644
index 0000000000..2b13be56ca
--- /dev/null
+++ b/Telegram/Resources/icons/settings/filled_verify_age.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/Telegram/Resources/langs/cloud_lang.strings b/Telegram/Resources/langs/cloud_lang.strings
index c7e33531c0..3776c023a4 100644
--- a/Telegram/Resources/langs/cloud_lang.strings
+++ b/Telegram/Resources/langs/cloud_lang.strings
@@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"cloud_lng_topup_purpose_subs" = "Buy **Stars** to keep your channel subscriptions.";
+"cloud_lng_age_verify_about_gb#one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law.";
+"cloud_lng_age_verify_about_gb#other" = "To access such content, you must confirm that you are at least **{count}** years old as required by UK law.";
+
"cloud_lng_passport_in_ar" = "Arabic";
"cloud_lng_passport_in_az" = "Azerbaijani";
"cloud_lng_passport_in_bg" = "Bulgarian";
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 17adb92724..9d59b42ca8 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -377,6 +377,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment.";
"lng_intro_fragment_button" = "Open Fragment";
+"lng_intro_email_setup_title" = "Choose a login email";
+"lng_intro_email_confirm_subtitle" = "Please check your email {email} (don't forget the spam folder) and enter the code we just sent you.";
+
"lng_phone_title" = "Your Phone Number";
"lng_phone_desc" = "Please confirm your country code\nand enter your phone number.";
"lng_phone_to_qr" = "Quick log in using QR code";
@@ -512,6 +515,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_notify_global" = "Global settings";
"lng_settings_notify_title" = "Notifications for chats";
"lng_settings_desktop_notify" = "Desktop notifications";
+"lng_settings_master_volume_notifications" = "Volume";
"lng_settings_native_title" = "System integration";
"lng_settings_use_windows" = "Use Windows notifications";
"lng_settings_skip_in_focus" = "Respect system Focus mode";
@@ -552,12 +556,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_notification_title_private_chats" = "Notifications for private chats";
"lng_notification_about_private_chats#one" = "Please note that **{count} chat** is listed as an exception and won't be affected by this change.";
"lng_notification_about_private_chats#other" = "Please note that **{count} chats** are listed as exceptions and won't be affected by this change.";
+"lng_notification_volume_private_chats" = "Notifications volume for private chats";
"lng_notification_title_groups" = "Notifications for groups";
"lng_notification_about_groups#one" = "Please note that **{count} group** is listed as an exception and won't be affected by this change.";
"lng_notification_about_groups#other" = "Please note that **{count} groups** are listed as exceptions and won't be affected by this change.";
+"lng_notification_volume_groups" = "Notifications volume for groups";
"lng_notification_title_channels" = "Notifications for channels";
"lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change.";
"lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change.";
+"lng_notification_volume_channel" = "Notifications volume for channels";
"lng_notification_exceptions_view" = "View exceptions";
"lng_notification_enable" = "Enable notifications";
"lng_notification_sound" = "Sound";
@@ -687,6 +694,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_shortcuts_media_fullscreen" = "Toggle video fullscreen";
"lng_shortcuts_show_chat_menu" = "Show chat menu";
"lng_shortcuts_show_chat_preview" = "Show chat preview";
+"lng_shortcuts_record_voice_message" = "Record Voice Message";
+"lng_shortcuts_record_round_message" = "Record Round Message";
"lng_settings_chat_reactions_title" = "Quick Reaction";
"lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction";
@@ -1238,6 +1247,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_quick_dialog_action_toast_archive_success" = "The chat has been archived.";
"lng_quick_dialog_action_toast_unarchive_success" = "The chat has been unarchived.";
+"lng_archive_hint_title" = "This is your Archive";
+"lng_archive_hint_about" = "Archived chats will remain in the Archive when you receive a new message. {link}";
+"lng_archive_hint_about_unmuted" = "When you receive a new message, muted chats will remain in the Archive, while unmuted chats will be moved to Chats. {link}";
+"lng_archive_hint_about_link" = "Tap to change {emoji}";
+"lng_archive_hint_section_1" = "Archived Chats";
+"lng_archive_hint_section_1_info" = "Move any chat into your Archive and back by swiping on it.";
+"lng_archive_hint_section_2" = "Hiding Archive";
+"lng_archive_hint_section_2_info" = "Hide the Archive from your Main screen by swiping on it.";
+"lng_archive_hint_section_3" = "Stories";
+"lng_archive_hint_section_3_info" = "Archive Stories from your contacts separately from chats with them.";
+"lng_archive_hint_button" = "Got it";
+
"lng_settings_generic_subscribe" = "Subscribe to {link} to use this setting.";
"lng_settings_generic_subscribe_link" = "Telegram Premium";
@@ -1638,6 +1659,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_type_audios" = "Voice messages";
"lng_media_type_links" = "Shared links";
"lng_media_type_rounds" = "Video messages";
+"lng_media_saved_music_your" = "Your playlist";
+"lng_media_saved_music_title" = "Playlist";
"lng_profile_common_groups_section" = "Groups in common";
"lng_info_edit_contact" = "Edit contact";
"lng_info_delete_contact" = "Delete contact";
@@ -1876,6 +1899,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_star_ref_revoked_title" = "Link removed";
"lng_star_ref_revoked_text" = "It will no longer work.";
+"lng_stars_rating_title" = "Rating";
+"lng_stars_rating_future" = "Future Rating";
+"lng_stars_rating_updates#one" = "in {count} day";
+"lng_stars_rating_updates#other" = "in {count} days";
+"lng_stars_rating_pending#one" = "The rating will update {when}.\n{count} point is pending. {link}";
+"lng_stars_rating_pending#other" = "The rating will update {when}.\n{count} points are pending. {link}";
+"lng_stars_rating_pending_preview" = "Preview {arrow}";
+"lng_stars_rating_pending_back" = "Back {arrow}";
+"lng_stars_rating_negative_label" = "Negative Rating";
+"lng_stars_rating_negative" = "A negative rating indicates that **{name}'s** payments are unreliable.";
+"lng_stars_rating_negative_your#one" = "A negative rating indicates that your payments are unreliable. Spend **{count} Star** to fix this issue.";
+"lng_stars_rating_negative_your#other" = "A negative rating indicates that your payments are unreliable. Spend **{count} Stars** to fix this issue.";
+"lng_stars_rating_about" = "This rating reflects **{name}'s** activity on Telegram. What affects it:";
+"lng_stars_rating_about_your" = "This rating reflects your activity on Telegram. What affects it:";
+"lng_stars_title_gifts_telegram" = "Gifts from Telegram";
+"lng_stars_about_gifts_telegram" = "{emoji} 100% of the Stars spent on gifts purchased from Telegram.";
+"lng_stars_title_gifts_users" = "Gifts and Posts from Users";
+"lng_stars_about_gifts_users" = "{emoji} 20% of the Stars spent on resold gifts, paid messages and channel posts.";
+"lng_stars_title_refunds" = "Refunds and Conversions";
+"lng_stars_about_refunds" = "{emoji} 10x of refunded Stars and 85% of bought gifts converted to Stars.";
+"lng_stars_rating_added" = "Added";
+"lng_stars_rating_deducted" = "Deducted";
+"lng_stars_rating_understood" = "Understood";
+
"lng_manage_discussion_group" = "Discussion";
"lng_manage_discussion_group_add" = "Add a group";
"lng_manage_linked_channel" = "Linked channel";
@@ -1903,6 +1950,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_monoforum_price" = "Price for each message";
"lng_manage_monoforum_about" = "Allow users to send messages to your channel, with the option to charge a fee for each message.";
"lng_manage_monoforum_price_about" = "Your channel will receive {percent} of the selected fee ({amount}) for each incoming message.";
+"lng_manage_monoforum_link_subtitle" = "Link to direct messages";
"lng_manage_history_visibility_title" = "Chat history for new members";
"lng_manage_history_visibility_shown" = "Visible";
@@ -2031,6 +2079,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sure_delete_contact" = "Are you sure you want to delete {contact} from your contact list?";
"lng_sure_delete_history" = "Are you sure you want to delete all message history with {contact}?\n\nThis action cannot be undone.";
"lng_sure_delete_group_history" = "Are you sure you want to delete all messages in \"{group}\"?\n\nThis action cannot be undone.";
+"lng_sure_delete_channel_history" = "Are you sure you want to delete all messages in \"{channel}\"?\n\n**This action cannot be undone.**";
"lng_sure_delete_and_exit" = "Are you sure you want to delete all message history and leave «{group}»?\n\nThis action cannot be undone.";
"lng_sure_leave_channel" = "Are you sure you want to leave\nthis channel?";
"lng_sure_delete_channel" = "Are you sure you want to delete this channel? All subscribers will be removed and all messages will be lost.";
@@ -2143,6 +2192,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_you_proximity_reached" = "You are now within {distance} from {user}";
"lng_action_you_theme_changed" = "You changed the chat theme to {emoji}";
"lng_action_theme_changed" = "{from} changed the chat theme to {emoji}";
+"lng_action_you_gift_theme_changed" = "You set {name} as a new theme for this chat.";
+"lng_action_gift_theme_changed" = "{from} set {name} as a new theme for this chat.";
"lng_action_you_theme_disabled" = "You disabled the chat theme";
"lng_action_theme_disabled" = "{from} disabled the chat theme";
"lng_action_proximity_distance_m#one" = "{count} meter";
@@ -2159,6 +2210,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible";
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
+"lng_action_gift_sent_upgrade_other" = "{from} sent an upgrade worth {cost} for the gift you received from {user}.";
+"lng_action_gift_sent_upgrade_self_other" = "You sent an upgrade worth {cost} for the gift {name} received from {user}.";
+"lng_action_gift_sent_upgrade" = "{from} sent an upgrade worth {cost} for your gift.";
+"lng_action_gift_sent_upgrade_self" = "You sent an upgrade worth {cost} for this gift.";
+"lng_action_gift_sent_upgrade_self_channel" = "You sent an upgrade worth {cost} for your gift to {name}.";
+"lng_action_gift_upgraded_helped" = "{user} unpacked the gift that you helped to upgrade.";
+"lng_action_gift_upgraded_helped_self" = "You unpacked the gift that {user} helped to upgrade.";
"lng_action_gift_transferred" = "{user} transferred you a gift";
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
"lng_action_gift_transferred_unknown" = "Someone transferred you a gift";
@@ -2179,6 +2237,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible.";
"lng_action_gift_for_stars#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars";
+"lng_action_gift_for_ton#one" = "{count} TON";
+"lng_action_gift_for_ton#other" = "{count} TON";
"lng_action_gift_got_subtitle" = "Gift from {user}";
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
@@ -2313,6 +2373,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_peer_gifts_filter_by_value" = "Sort by Value";
"lng_peer_gifts_filter_by_date" = "Sort by Date";
"lng_peer_gifts_filter_unlimited" = "Unlimited";
+"lng_peer_gifts_filter_upgradable" = "Upgradeable";
"lng_peer_gifts_filter_limited" = "Limited";
"lng_peer_gifts_filter_unique" = "Unique";
"lng_peer_gifts_filter_saved" = "Displayed";
@@ -2589,6 +2650,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
"lng_premium_unlock_status" = "Unlock Emoji Status";
+"lng_premium_subscribe_months_24" = "2-Year";
"lng_premium_subscribe_months_12" = "Annual";
"lng_premium_subscribe_months_6" = "Semiannual";
"lng_premium_subscribe_months_1" = "Monthly";
@@ -2875,6 +2937,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
"lng_credits_box_history_entry_currency_in" = "TON Top-Up";
+"lng_credits_box_history_entry_posts_search" = "Posts Search";
"lng_credits_box_history_entry_api" = "Paid Broadcast";
"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message";
"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages";
@@ -2939,6 +3002,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}.";
+"lng_credits_small_balance_for_search" = "Buy **Stars** to search through public posts.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}";
@@ -3508,6 +3572,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_ton_amount#one" = "{count} TON";
"lng_gift_ton_amount#other" = "{count} TON";
+"lng_gift_premium_title" = "Premium Gift";
+"lng_gift_premium_text#one" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features.";
+"lng_gift_premium_text#other" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features.";
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
"lng_gift_premium_subtitle" = "Gift Premium";
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
@@ -3521,6 +3588,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_stars_sold_out" = "sold out";
"lng_gift_stars_resale" = "resale";
"lng_gift_stars_on_sale" = "on sale";
+"lng_gift_stars_premium" = "premium";
"lng_gift_stars_tabs_all" = "All Gifts";
"lng_gift_stars_tabs_my" = "My Gifts";
"lng_gift_stars_tabs_limited" = "Limited";
@@ -3549,6 +3617,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
"lng_gift_buy_resale_title" = "Buy {name}";
"lng_gift_buy_resale_button" = "Buy for {cost}";
+"lng_gift_buy_resale_equals" = "Equals to {cost}";
+"lng_gift_buy_resale_only_ton" = "The seller only accepts TON as payment.";
+"lng_gift_buy_resale_pay_stars" = "Pay in Stars";
+"lng_gift_buy_resale_pay_ton" = "Pay in TON";
"lng_gift_buy_resale_confirm" = "Do you want to buy {name} for {price} and gift it to {user}?";
"lng_gift_buy_resale_confirm_self" = "Do you want to buy {name} for {price}?";
"lng_gift_buy_price_change_title" = "Price change!";
@@ -3558,6 +3630,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sent_resale_done_self" = "{gift} is now yours.";
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
+"lng_gift_sent_finished#one" = "You've already sent **{count}** of these gifts, and it's the limit.";
+"lng_gift_sent_finished#other" = "You've already sent **{count}** of these gifts, and it's the limit.";
+"lng_gift_sent_remains#one" = "You can send **{count}** more.";
+"lng_gift_sent_remains#other" = "You can send **{count}** more.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift";
@@ -3570,8 +3646,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel.";
"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts.";
"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}";
-"lng_gift_in_blockchain_link" = "View >";
-"lng_gift_visible_hide" = "Hide >";
+"lng_gift_in_blockchain_link_arrow" = "View {arrow}";
+"lng_gift_visible_hide_arrow" = "Hide {arrow}";
+"lng_gift_visible_show_arrow" = "Show {arrow}";
"lng_gift_show_on_page" = "Display on my Page";
"lng_gift_show_on_channel" = "Display in channel's Gifts";
"lng_gift_availability" = "Availability";
@@ -3590,7 +3667,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
"lng_gift_unique_status" = "Status";
"lng_gift_unique_status_non" = "Non-Unique";
-"lng_gift_unique_status_upgrade" = "upgrade";
+"lng_gift_unique_upgrade" = "Upgrade";
+"lng_gift_unique_upgrade_next" = "Upgrade Next Gift";
+"lng_gift_unique_gift_upgrade" = "Gift an Upgrade";
"lng_gift_unique_number" = "Collectible #{index}";
"lng_gift_unique_number_by" = "Collectible #{index} by {name}";
"lng_gift_unique_model" = "Model";
@@ -3600,6 +3679,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_unique_availability_label" = "Quantity";
"lng_gift_unique_availability#one" = "{count} of {amount} issued";
"lng_gift_unique_availability#other" = "{count} of {amount} issued";
+"lng_gift_unique_value" = "Value";
+"lng_gift_unique_value_learn_more" = "learn more";
"lng_gift_unique_info" = "Gifted to {recipient} on {date}.";
"lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}.";
"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\".";
@@ -3608,6 +3689,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_availability_left#one" = "{count} of {amount} left";
"lng_gift_availability_left#other" = "{count} of {amount} left";
"lng_gift_availability_none" = "None of {amount} left";
+"lng_gift_value_about_average" = "This is the average sale price of {gift} gifts on Telegram and Fragment over the past month.";
+"lng_gift_value_about_last" = "This is the price at which {gift} was last sold on {platform}.";
+"lng_gift_value_initial_sale" = "Initial Sale";
+"lng_gift_value_initial_price" = "Initial Price";
+"lng_gift_value_initial_price_value" = "{stars} ({amount})";
+"lng_gift_value_last_sale" = "Last Sale";
+"lng_gift_value_last_price" = "Last Price";
+"lng_gift_value_minimum_price" = "Minimum Price";
+"lng_gift_value_minimum_price_tooltip" = "{amount} is the floor price for {gift} gifts listed on Telegram and Fragment.";
+"lng_gift_vlaue_average_price" = "Average Price";
+"lng_gift_value_average_price_tooltip" = "{amount} is the average sale price of {gift} gifts on Telegram and Fragment over the past month.";
+"lng_gift_value_availability#one" = "{count} {emoji} for sale on {platform} {arrow}";
+"lng_gift_value_availability#other" = "{count} {emoji} for sale on {platform} {arrow}";
+"lng_gift_value_telegram" = "Telegram";
+"lng_gift_value_fragment" = "Fragment";
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
@@ -3643,10 +3739,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible.";
"lng_gift_upgrade_unique_title" = "Unique";
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
+"lng_gift_upgrade_unique_about_user" = "{name} will get a unique number, model, backdrop and symbol for your gift.";
+"lng_gift_upgrade_unique_about_channel" = "Admins of {name} will get a unique number, model, backdrop and symbol for your gift.";
"lng_gift_upgrade_transferable_title" = "Transferable";
"lng_gift_upgrade_transferable_about" = "Send your upgraded gift to any of your friends on Telegram.";
+"lng_gift_upgrade_transferable_about_user" = "{name} will be able to send the gift to anyone on Telegram.";
+"lng_gift_upgrade_transferable_about_channel" = "Admins of {name} will be able to send the gift to anyone on Telegram.";
"lng_gift_upgrade_tradable_title" = "Tradable";
"lng_gift_upgrade_tradable_about" = "Sell or auction your gift on third-party NFT marketplaces.";
+"lng_gift_upgrade_tradable_about_user" = "{name} will be able to sell the gift on Telegram and NFT marketplaces.";
+"lng_gift_upgrade_tradable_about_channel" = "Admins of {name} will be able to sell the gift on Telegram and NFT marketplaces.";
"lng_gift_upgrade_button" = "Upgrade for {price}";
"lng_gift_upgrade_free" = "Upgrade for Free";
"lng_gift_upgrade_confirm" = "Confirm";
@@ -3656,6 +3758,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_upgrade_add_comment" = "Add sender's name and comment";
"lng_gift_upgraded_title" = "Gift Upgraded";
"lng_gift_upgraded_about" = "Your gift {name} now has unique attributes and can be transferred to others";
+"lng_gift_upgrade_gifted_title" = "Upgrade Gifted";
+"lng_gift_upgrade_gifted_about" = "Now {name} can turn your gift into a unique collectible.";
+"lng_gift_upgrade_gifted_about_channel" = "Now the admins of {name} can turn your gift into a unique collectible.";
"lng_gift_transferred_title" = "Gift Transferred";
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
"lng_gift_transfer_title" = "Transfer {name}";
@@ -3682,6 +3787,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
"lng_gift_transfer_button" = "Transfer";
"lng_gift_transfer_button_for" = "Transfer for {price}";
+"lng_gift_transfer_set_theme" = "Set as Theme in...";
+"lng_gift_transfer_choose" = "Choose Chat";
"lng_gift_transfer_wear" = "Wear";
"lng_gift_transfer_take_off" = "Take Off";
"lng_gift_transfer_sell" = "Sell";
@@ -3690,12 +3797,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sell_unlist_title" = "Unlist {name}";
"lng_gift_sell_unlist_sure" = "Are you sure you want to unlist your gift?";
"lng_gift_sell_title" = "Price in Stars";
-"lng_gift_sell_placeholder" = "Enter price in Stars";
"lng_gift_sell_about" = "You will receive {percent} of the selected amount.";
"lng_gift_sell_amount#one" = "You will receive **{count}** Star.";
"lng_gift_sell_amount#other" = "You will receive **{count}** Stars.";
"lng_gift_sell_min_price#one" = "Minimum price is {count} Star.";
"lng_gift_sell_min_price#other" = "Minimum price is {count} Stars.";
+"lng_gift_sell_only_ton" = "Only Accept TON";
+"lng_gift_sell_only_ton_about" = "If the buyer pays you in TON, there's no risk of refunds, unlike Stars payments.";
+"lng_gift_sell_amount_ton#one" = "You will receive **{count}** TON.";
+"lng_gift_sell_amount_ton#other" = "You will receive **{count}** TON.";
+"lng_gift_sell_min_price_ton#one" = "Minimum price is {count} TON.";
+"lng_gift_sell_min_price_ton#other" = "Minimum price is {count} TON.";
+"lng_gift_sell_title_ton" = "Price in TON";
"lng_gift_sell_put" = "Put for Sale";
"lng_gift_sell_update" = "Update the Price";
"lng_gift_sell_toast" = "{name} is now for sale!";
@@ -3735,9 +3848,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_resale_symbol" = "Symbol";
"lng_gift_resale_symbols#one" = "{count} Symbol";
"lng_gift_resale_symbols#other" = "{count} Symbols";
+"lng_gift_resale_switch_to_ton" = "Switch to Ton";
+"lng_gift_resale_switch_to_stars" = "Switch to Stars";
"lng_gift_resale_early" = "You will be able to resell this gift in {duration}.";
"lng_gift_transfer_early" = "You will be able to transfer this gift in {duration}.";
"lng_gift_resale_transfer_early_title" = "Try Later";
+"lng_gift_collection_add" = "Add Collection";
+"lng_gift_collection_new_title" = "Create a New Collection";
+"lng_gift_collection_new_button" = "New Collection";
+"lng_gift_collection_new_text" = "Choose a name for your collection and start adding your gifts there.";
+"lng_gift_collection_new_ph" = "Title";
+"lng_gift_collection_new_create" = "Create";
+"lng_gift_collection_empty_title" = "Organize Your Gifts";
+"lng_gift_collection_empty_text" = "Add some of your gifts to this collection.";
+"lng_gift_collection_all" = "All Gifts";
+"lng_gift_collection_add_title" = "Add Gifts";
+"lng_gift_collection_add_button" = "Add Gifts";
+"lng_gift_collection_share" = "Share Collection";
+"lng_gift_collection_edit" = "Edit Name";
+"lng_gift_collection_limit_title" = "Limit Reached";
+"lng_gift_collection_limit_text" = "Please remove one of the existing collections to add a new one.";
+"lng_gift_collection_delete" = "Delete Collection";
+"lng_gift_collection_delete_sure" = "Are you sure you want to delete this collection?";
+"lng_gift_collection_delete_button" = "Delete";
+"lng_gift_collection_add_to" = "Add to Collection";
+"lng_gift_locked_title" = "Gift Locked";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
@@ -4056,6 +4191,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dialogs_suggestions_credits_sub_low_title#other" = "{emoji} {count} Stars needed for {channels}";
"lng_dialogs_suggestions_credits_sub_low_about" = "Insufficient funds to cover your subscription.";
+"lng_unconfirmed_auth_title" = "Someone just got access to your messages!";
+"lng_unconfirmed_auth_confirm" = "Yes, it’s me";
+"lng_unconfirmed_auth_deny" = "No, it’s not me!";
+"lng_unconfirmed_auth_single" = "We detected a new login to your account from {from}, {country}. Is it you?";
+"lng_unconfirmed_auth_multiple#one" = "We detected new {count} login to your account. Is it you?";
+"lng_unconfirmed_auth_multiple#other" = "We detected new {count} logins to your account. Is it you?";
+"lng_unconfirmed_auth_multiple_from#one" = "We detected new {count} login to your account from {country}. Is it you?";
+"lng_unconfirmed_auth_multiple_from#other" = "We detected new {count} logins to your account from {country}. Is it you?";
+"lng_unconfirmed_auth_denied_title#one" = "New Login Prevented";
+"lng_unconfirmed_auth_denied_title#other" = "New Logins Prevented";
+"lng_unconfirmed_auth_denied_single" = "We have terminated the login attempt from {country}.";
+"lng_unconfirmed_auth_denied_multiple" = "We have terminated the login attempts from: {country}";
+"lng_unconfirmed_auth_denied_warning" = "Never send your login code to anyone or you can lose your Telegram account!";
+"lng_unconfirmed_auth_confirmed" = "New Login Allowed";
+"lng_unconfirmed_auth_confirmed_message" = "You can check the list of your active logins in {link}.";
+
"lng_about_random" = "Send a {emoji} emoji to any chat to try your luck.";
"lng_about_random_send" = "Send";
@@ -4223,6 +4374,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_archive_to_list" = "Move to chat list";
"lng_context_archive_to_menu_info" = "Archive moved to the main menu!\nRight click the archive button to return the Archive to your chat list.";
"lng_context_archive_settings" = "Archive settings";
+"lng_context_archive_how_does_it_work" = "How does it work?";
"lng_context_mute" = "Mute notifications";
"lng_context_unmute" = "Unmute";
@@ -4234,6 +4386,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_remove_from_group" = "Remove from group";
"lng_context_add_to_group" = "Add to group";
+"lng_context_rate_transcription" = "Rate transcription";
+"lng_toast_sent_rate_transcription" = "Thank you for your feedback!";
+
"lng_context_copy_link" = "Copy Link";
"lng_context_copy_message_link" = "Copy Message Link";
"lng_context_copy_post_link" = "Copy Post Link";
@@ -4253,6 +4408,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_pack_info" = "View Sticker Set";
"lng_context_pack_add" = "Add Stickers";
"lng_context_save_file" = "Save As...";
+"lng_context_save_music_to" = "Save to...";
+"lng_context_save_music_profile" = "... Profile";
+"lng_context_save_music_saved" = "... Saved Messages";
+"lng_context_save_music_folder" = "... Downloads";
+"lng_context_save_music_about" = "Choose where you want this audio to be saved.";
"lng_context_copy_text" = "Copy Text";
"lng_context_open_gif" = "Open GIF";
"lng_context_save_gif" = "Add to GIFs";
@@ -4667,6 +4827,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_selected_delete_sure_this" = "Do you want to delete this message?";
"lng_selected_delete_sure#one" = "Do you want to delete {count} message?";
"lng_selected_delete_sure#other" = "Do you want to delete {count} messages?";
+"lng_selected_remove_saved_music" = "Do you want to remove this file from your profile?";
+"lng_saved_music_added" = "Audio added to your Profile.";
+"lng_saved_music_removed" = "Audio removed from your Profile.";
"lng_delete_photo_sure" = "Do you want to delete this photo?";
"lng_delete_for_everyone_hint#one" = "This will delete it for everyone in this chat.";
"lng_delete_for_everyone_hint#other" = "This will delete them for everyone in this chat.";
@@ -6192,6 +6355,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_chat_theme_change_wallpaper" = "Change Wallpaper";
"lng_chat_theme_title" = "Select theme";
"lng_chat_theme_cant_voice" = "Sorry, you can't change the chat theme while you have an unsent voice message.";
+"lng_chat_theme_gift_replace" = "This gift is already your theme in the chat with {name}. Remove it there and use it here instead?";
"lng_photo_editor_menu_delete" = "Delete";
"lng_photo_editor_menu_flip" = "Flip";
@@ -6224,10 +6388,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_view_button_emojipack" = "View emoji";
"lng_view_button_collectible" = "View collectible";
"lng_view_button_call" = "Join call";
+"lng_view_button_storyalbum" = "View Album";
+"lng_view_button_collection" = "View Collection";
"lng_sponsored_hide_ads" = "Hide";
"lng_sponsored_title" = "What are sponsored messages?";
-"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
+"lng_sponsored_info_description1_linked" = "Unlike other apps, Telegram never uses your private data to target ads. {link}\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
+"lng_sponsored_info_description1_link" = "Learn more in the Privacy Policy";
+"lng_sponsored_info_description1_url" = "https://telegram.org/privacy#5-6-no-ads-based-on-user-data";
"lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
"lng_sponsored_info_menu" = "Advertiser info";
"lng_sponsored_info_submenu" = "Advertiser: {text}";
@@ -6264,6 +6432,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_ringtones_box_title" = "Notification Sound";
"lng_ringtones_box_cloud_subtitle" = "Choose your tone";
+"lng_ringtones_box_volume" = "Volume";
"lng_ringtones_box_upload_choose" = "Choose a tone";
"lng_ringtones_box_upload_button" = "Upload Sound";
"lng_ringtones_box_about" = "Right click on any short voice note or MP3 file in chat and select \"Save for Notifications\". It will appear here.";
@@ -6388,6 +6557,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_archive_title" = "Story Archive";
"lng_stories_archive_about" = "Only you can see archived stories unless you choose to post them to your profile.";
"lng_stories_channel_archive_about" = "Only admins of the channel can see archived stories unless they are posted to the channel page.";
+"lng_stories_empty" = "Your stories will be here.";
+"lng_stories_empty_channel" = "Channel posts will be here.";
"lng_stories_reply_sent" = "Message sent.";
"lng_stories_hidden_to_contacts" = "Stories from {user} were moved to the **Archive**.";
"lng_stories_shown_in_chats" = "Stories from {user} were returned from the **Archive**.";
@@ -6423,6 +6594,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_save_promo" = "Subscribe to {link} to save other people's stories that are not protected.";
"lng_stories_reaction_as_message" = "Send reaction as a private message";
+"lng_stories_album_add" = "Add Album";
+"lng_stories_album_new_title" = "Create a New Album";
+"lng_stories_album_new_button" = "New Album";
+"lng_stories_album_new_text" = "Choose a name for your album and start adding your stories there.";
+"lng_stories_album_new_ph" = "Title";
+"lng_stories_album_new_create" = "Create";
+"lng_stories_album_empty_title" = "Organize Your Stories";
+"lng_stories_album_empty_text" = "Add some of your stories to this album.";
+"lng_stories_album_all" = "All Stories";
+"lng_stories_album_add_title" = "Add Stories";
+"lng_stories_album_add_button" = "Add Stories";
+"lng_stories_album_share" = "Share Album";
+"lng_stories_album_edit" = "Edit Name";
+"lng_stories_album_limit_title" = "Limit Reached";
+"lng_stories_album_limit_text" = "Please remove one of the existing albums to add a new one.";
+"lng_stories_album_delete" = "Delete Album";
+"lng_stories_album_delete_sure" = "Are you sure you want to delete this album?";
+"lng_stories_album_delete_button" = "Delete";
+"lng_stories_album_add_to" = "Add to Album";
+
"lng_stealth_mode_menu_item" = "Hide My View";
"lng_stealth_mode_title" = "Stealth Mode";
"lng_stealth_mode_unlock_about" = "Subscribe to Telegram Premium to watch stories without appearing in the list of viewers.";
@@ -6679,6 +6870,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recent_chats" = "Chats";
"lng_recent_channels" = "Channels";
"lng_recent_apps" = "Apps";
+"lng_recent_posts" = "Posts";
"lng_all_photos" = "Photos";
"lng_all_videos" = "Videos";
"lng_all_downloads" = "Downloads";
@@ -6696,6 +6888,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_apps_popular" = "Grossing apps";
"lng_bot_apps_which" = "Which apps are included here? {link}";
"lng_bot_apps_which_link" = "Learn >";
+"lng_posts_title" = "Global Search";
+"lng_posts_start" = "Type a keyword to search all posts from public channels.";
+"lng_posts_subscribe" = "Subscribe to Premium";
+"lng_posts_need_subscribe" = "Global search is a Premium feature.";
+"lng_posts_search_button" = "Search {query}";
+"lng_posts_remaining#one" = "{count} free search remaining today";
+"lng_posts_remaining#other" = "{count} free searches remaining today";
+"lng_posts_subtitle_empty" = "Telegram News";
+"lng_posts_subtitle" = "Public posts";
+"lng_posts_limit_reached" = "Limit Reached";
+"lng_posts_limit_about#one" = "You can make up to {count} search query per day.";
+"lng_posts_limit_about#other" = "You can make up to {count} search queries per day.";
+"lng_posts_limit_search_paid" = "Search for {cost}";
+"lng_posts_limit_unlocks" = "free search unlocks in {duration}";
+"lng_posts_paid_spent#one" = "**{count} Star** spent on extra search.";
+"lng_posts_paid_spent#other" = "**{count} Stars** spent on extra search.";
"lng_popular_apps_info_title" = "Top Mini Apps";
"lng_popular_apps_info_text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini apps in {bot} (as described {link}), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on the weekly average.";
@@ -6756,6 +6964,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted.";
"lng_frozen_appeal_button" = "Submit an Appeal";
+"lng_age_verify_title" = "Age Verification";
+"lng_age_verify_mobile" = "Please open this media in the official Telegram app for Android or iOS to verify your age.";
+"lng_age_verify_here" = "This is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
+"lng_age_verify_button" = "Verify My Age";
+"lng_age_verify_sorry_title" = "Age Check Failed";
+"lng_age_verify_sorry_text" = "Sorry, you can't view 18+ content.";
+
"lng_context_bank_card_copy" = "Copy Card Number";
"lng_context_bank_card_copied" = "Card number copied to clipboard.";
diff --git a/Telegram/Resources/picker_html/picker.js b/Telegram/Resources/picker_html/picker.js
index e44fd51a95..628a224637 100644
--- a/Telegram/Resources/picker_html/picker.js
+++ b/Telegram/Resources/picker_html/picker.js
@@ -51,8 +51,8 @@ var LocationPicker = {
},
init: function (params) {
mapboxgl.accessToken = params.token;
- if (params.protocol) {
- mapboxgl.config.API_URL = params.protocol + '://domain/api.mapbox.com';
+ if (location.hostname != 'desktop-app-resource') {
+ mapboxgl.config.API_URL = location.protocol + '//' + location.host + '/api.mapbox.com';
}
var options = { container: 'map', config: {
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index cc9c2313ed..4e206e01ca 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -39,6 +39,7 @@
../../animations/edit_peers/topics_list.tgs
../../animations/edit_peers/direct_messages.tgs
../../animations/no_chats.tgs
+ ../../animations/transcribe_loading.tgs
../../animations/dice/dice_idle.tgs
../../animations/dice/dart_idle.tgs
diff --git a/Telegram/Resources/qrc/telegram/sounds.qrc b/Telegram/Resources/qrc/telegram/sounds.qrc
index d9f7cf8b5a..1ffc48b052 100644
--- a/Telegram/Resources/qrc/telegram/sounds.qrc
+++ b/Telegram/Resources/qrc/telegram/sounds.qrc
@@ -10,5 +10,6 @@
../../sounds/group_call_connect.mp3
../../sounds/group_call_end.mp3
../../sounds/group_call_allowed.mp3
+ ../../sounds/group_call_recording_start.mp3
diff --git a/Telegram/Resources/sounds/group_call_recording_start.mp3 b/Telegram/Resources/sounds/group_call_recording_start.mp3
new file mode 100644
index 0000000000..0060e82c37
Binary files /dev/null and b/Telegram/Resources/sounds/group_call_recording_start.mp3 differ
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index d63d433f73..a95c99b9d6 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="6.1.3.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 218aa82970..580e404a52 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,16,4,0
- PRODUCTVERSION 5,16,4,0
+ FILEVERSION 6,1,3,0
+ PRODUCTVERSION 6,1,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.16.4.0"
+ VALUE "FileVersion", "6.1.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.16.4.0"
+ VALUE "ProductVersion", "6.1.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 81f44604c6..3b6df7a214 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,16,4,0
- PRODUCTVERSION 5,16,4,0
+ FILEVERSION 6,1,3,0
+ PRODUCTVERSION 6,1,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "5.16.4.0"
+ VALUE "FileVersion", "6.1.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.16.4.0"
+ VALUE "ProductVersion", "6.1.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp
index 6cd28824e6..01caa44e61 100644
--- a/Telegram/SourceFiles/api/api_authorizations.cpp
+++ b/Telegram/SourceFiles/api/api_authorizations.cpp
@@ -9,10 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
-#include "core/changelogs.h"
#include "core/application.h"
+#include "core/changelogs.h"
#include "core/core_settings.h"
#include "lang/lang_keys.h"
+#include "main/main_app_config.h"
+#include "main/main_session_settings.h"
+#include "main/main_session.h"
namespace Api {
namespace {
@@ -83,7 +86,19 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) {
} // namespace
Authorizations::Authorizations(not_null api)
-: _api(&api->instance()) {
+: _api(&api->instance())
+, _autoconfirmPeriod([=] {
+ constexpr auto kFallbackCount = 604800;
+ return api->session().appConfig().get(
+ u"authorization_autoconfirm_period"_q,
+ kFallbackCount);
+})
+, _saveUnreviewed([=] {
+ api->session().settings().setUnreviewed(_unreviewed);
+ api->session().saveSettingsDelayed();
+}) {
+ _unreviewed = api->session().settings().unreviewed();
+ removeExpiredUnreviewed();
Core::App().settings().deviceModelChanges(
) | rpl::start_with_next([=](const QString &model) {
auto changed = false;
@@ -119,6 +134,7 @@ void Authorizations::reload() {
) | ranges::views::transform([](const MTPAuthorization &auth) {
return ParseEntry(auth.data());
}) | ranges::to;
+ removeExpiredUnreviewed();
refreshCallsDisabledHereFromCloud();
_listChanges.fire({});
}).fail([=] {
@@ -217,11 +233,7 @@ void Authorizations::toggleCallsDisabled(uint64 hash, bool disabled) {
MTP_bool(disabled)
)).done([=] {
_toggleCallsDisabledRequests.remove(hash);
- }).fail([=](const MTP::Error &error) {
- LOG(("API Error: toggle calls %1. Hash: %2. %3.")
- .arg(disabled ? u"disabled"_q : u"enabled"_q)
- .arg(hash)
- .arg(error.type()));
+ }).fail([=] {
_toggleCallsDisabledRequests.remove(hash);
}).send();
_toggleCallsDisabledRequests.emplace(hash, id);
@@ -265,4 +277,129 @@ crl::time Authorizations::lastReceivedTime() {
return _lastReceived;
}
+const std::vector &Authorizations::unreviewed() {
+ removeExpiredUnreviewed();
+ return _unreviewed;
+}
+
+void Authorizations::removeExpiredUnreviewed() {
+ const auto now = base::unixtime::now();
+ const auto period = _autoconfirmPeriod();
+
+ const auto oldSize = _unreviewed.size();
+ _unreviewed.erase(
+ std::remove_if(_unreviewed.begin(), _unreviewed.end(),
+ [=](const auto &auth) {
+ return (now - auth.date) >= period;
+ }),
+ _unreviewed.end());
+
+ if (_unreviewed.size() != oldSize) {
+ _saveUnreviewed();
+ }
+}
+
+void Authorizations::review(const std::vector &hashes, bool confirm) {
+ for (const auto hash : hashes) {
+ if (const auto sent = _reviewRequests.take(hash)) {
+ _api.request(*sent).cancel();
+ }
+ }
+
+ const auto checkComplete = [=] {
+ if (_reviewRequests.empty()) {
+ _saveUnreviewed();
+ _unreviewedChanges.fire({});
+ }
+ };
+
+ for (const auto hash : hashes) {
+ const auto removeFromUnreviewed = [=] {
+ _unreviewed.erase(
+ std::remove_if(_unreviewed.begin(), _unreviewed.end(),
+ [hash](const auto &auth) { return auth.hash == hash; }),
+ _unreviewed.end());
+ _reviewRequests.remove(hash);
+ checkComplete();
+ };
+
+ if (confirm) {
+ using Flag = MTPaccount_ChangeAuthorizationSettings::Flag;
+ const auto id = _api.request(MTPaccount_ChangeAuthorizationSettings(
+ MTP_flags(Flag::f_confirmed),
+ MTP_long(hash),
+ MTPBool(), // encrypted_requests_disabled
+ MTPBool() // call_requests_disabled
+ )).done([=] {
+ removeFromUnreviewed();
+ }).fail([=] {
+ removeFromUnreviewed();
+ }).send();
+ _reviewRequests.emplace(hash, id);
+ } else {
+ const auto id = _api.request(MTPaccount_ResetAuthorization(
+ MTP_long(hash)
+ )).done([=](const MTPBool &result) {
+ if (mtpIsTrue(result)) {
+ _list.erase(
+ ranges::remove(_list, hash, &Entry::hash),
+ end(_list));
+ _listChanges.fire({});
+ }
+ removeFromUnreviewed();
+ }).fail([=] {
+ removeFromUnreviewed();
+ }).send();
+ _reviewRequests.emplace(hash, id);
+ }
+ }
+}
+
+rpl::producer<> Authorizations::unreviewedChanges() const {
+ return _unreviewedChanges.events();
+}
+
+void Authorizations::apply(const MTPUpdate &update) {
+ removeExpiredUnreviewed();
+ update.match([&](const MTPDupdateNewAuthorization &data) {
+ auto unreviewed = Data::UnreviewedAuth{
+ .hash = data.vhash().v,
+ .unconfirmed = data.is_unconfirmed(),
+ .date = data.vdate().value_or_empty(),
+ .device = qs(data.vdevice().value_or_empty()),
+ .location = qs(data.vlocation().value_or_empty())
+ };
+ if (!unreviewed.unconfirmed) {
+ const auto hash = unreviewed.hash;
+ const auto was = _unreviewed.size();
+ _unreviewed.erase(
+ std::remove_if(
+ _unreviewed.begin(),
+ _unreviewed.end(),
+ [hash](const auto &auth) { return auth.hash == hash; }),
+ _unreviewed.end());
+ if (was != _unreviewed.size()) {
+ _saveUnreviewed();
+ _unreviewedChanges.fire({});
+ }
+ return;
+ }
+
+ for (auto &auth : _unreviewed) {
+ if (auth.hash == unreviewed.hash) {
+ auth = std::move(unreviewed);
+ _saveUnreviewed();
+ _unreviewedChanges.fire({});
+ return;
+ }
+ }
+
+ _unreviewed.push_back(std::move(unreviewed));
+ _saveUnreviewed();
+ _unreviewedChanges.fire({});
+ }, [](auto&&) {
+ Unexpected("Update in Authorizations::apply.");
+ });
+}
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h
index 5e2a41c9f9..25c97f3945 100644
--- a/Telegram/SourceFiles/api/api_authorizations.h
+++ b/Telegram/SourceFiles/api/api_authorizations.h
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
+#include "data/data_authorization.h"
#include "mtproto/sender.h"
class ApiWrap;
@@ -35,6 +36,8 @@ public:
Fn &&fail,
std::optional hash = std::nullopt);
+ void apply(const MTPUpdate &update);
+
[[nodiscard]] crl::time lastReceivedTime();
[[nodiscard]] List list() const;
@@ -42,6 +45,11 @@ public:
[[nodiscard]] int total() const;
[[nodiscard]] rpl::producer totalValue() const;
+ [[nodiscard]] const std::vector &unreviewed();
+ [[nodiscard]] rpl::producer<> unreviewedChanges() const;
+
+ void review(const std::vector &hashes, bool confirm);
+
void updateTTL(int days);
[[nodiscard]] rpl::producer ttlDays() const;
@@ -57,6 +65,7 @@ public:
private:
void refreshCallsDisabledHereFromCloud();
+ void removeExpiredUnreviewed();
MTP::Sender _api;
mtpRequestId _requestId = 0;
@@ -64,10 +73,16 @@ private:
List _list;
rpl::event_stream<> _listChanges;
+ Fn _autoconfirmPeriod;
+ std::vector _unreviewed;
+ rpl::event_stream<> _unreviewedChanges;
+ Fn _saveUnreviewed;
+
mtpRequestId _ttlRequestId = 0;
rpl::variable _ttlDays = 0;
base::flat_map _toggleCallsDisabledRequests;
+ base::flat_map _reviewRequests;
rpl::variable _callsDisabledHere;
crl::time _lastReceived = 0;
diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp
index b86ce1a70c..05c0d46928 100644
--- a/Telegram/SourceFiles/api/api_bot.cpp
+++ b/Telegram/SourceFiles/api/api_bot.cpp
@@ -245,7 +245,7 @@ void SendBotCallbackDataWithPassword(
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = [=](
const Core::CloudPasswordResult &result,
- QPointer box) {
+ base::weak_qptr box) {
if (const auto button = getButton()) {
if (button->requestId) {
return;
diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
index fe96accd61..ffd3fb0f79 100644
--- a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
+++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
@@ -57,7 +57,7 @@ void RemoveChatFilter(
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
void RemoveComplexChatFilter::request(
- QPointer widget,
+ base::weak_qptr widget,
base::weak_ptr weak,
FilterId id) {
const auto session = &weak->session();
diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
index ce92b2df3c..6bd6ce7fa3 100644
--- a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
+++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
@@ -22,7 +22,7 @@ public:
RemoveComplexChatFilter();
void request(
- QPointer widget,
+ base::weak_qptr widget,
base::weak_ptr weak,
FilterId id);
diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp
index b67e148677..415c1a8296 100644
--- a/Telegram/SourceFiles/api/api_chat_invite.cpp
+++ b/Telegram/SourceFiles/api/api_chat_invite.cpp
@@ -142,13 +142,12 @@ void ConfirmSubscriptionBox(
const auto content = box->verticalLayout();
Ui::AddSkip(content, st::confirmInvitePhotoTop);
- const auto userpicWrap = content->add(
- object_ptr>(
- content,
- object_ptr(content)));
- const auto userpic = userpicWrap->entity();
+ const auto userpic = content->add(
+ object_ptr(content),
+ style::al_top);
const auto photoSize = st::confirmInvitePhotoSize;
userpic->resize(Size(photoSize));
+ userpic->setNaturalWidth(photoSize);
const auto creditsIconSize = photoSize / 3;
const auto creditsIconCallback =
Ui::PaintOutlinedColoredCreditsIconCallback(
@@ -188,8 +187,8 @@ void ConfirmSubscriptionBox(
}
auto p = QPainter(userpic);
p.drawImage(0, 0, state->frame);
- }, userpicWrap->lifetime());
- userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
+ }, userpic->lifetime());
+ userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
if (photo) {
state->photoMedia = photo->createMediaView();
state->photoMedia->wanted(Data::PhotoSize::Small, Data::FileOrigin());
@@ -197,7 +196,7 @@ void ConfirmSubscriptionBox(
session->downloaderTaskFinished(
) | rpl::start_with_next([=] {
userpic->update();
- }, userpicWrap->entity()->lifetime());
+ }, userpic->lifetime());
}
} else {
state->photoEmpty = std::make_unique(
@@ -215,43 +214,40 @@ void ConfirmSubscriptionBox(
2.);
box->addRow(
- object_ptr>(
+ object_ptr(
box,
- object_ptr(
- box,
- tr::lng_channel_invite_subscription_title(),
- st::inviteLinkSubscribeBoxTitle)));
+ tr::lng_channel_invite_subscription_title(),
+ st::inviteLinkSubscribeBoxTitle),
+ style::al_top);
box->addRow(
- object_ptr>(
+ object_ptr(
box,
- object_ptr(
- box,
- tr::lng_channel_invite_subscription_about(
- lt_channel,
- rpl::single(Ui::Text::Bold(name)),
- lt_price,
- tr::lng_credits_summary_options_credits(
- lt_count,
- rpl::single(amount) | tr::to_count(),
- Ui::Text::Bold),
- Ui::Text::WithEntities),
- st::inviteLinkSubscribeBoxAbout)));
+ tr::lng_channel_invite_subscription_about(
+ lt_channel,
+ rpl::single(Ui::Text::Bold(name)),
+ lt_price,
+ tr::lng_credits_summary_options_credits(
+ lt_count,
+ rpl::single(amount) | tr::to_count(),
+ Ui::Text::Bold),
+ Ui::Text::WithEntities),
+ st::inviteLinkSubscribeBoxAbout),
+ style::al_top);
Ui::AddSkip(content);
box->addRow(
- object_ptr>(
+ object_ptr(
box,
- object_ptr(
- box,
- tr::lng_channel_invite_subscription_terms(
- lt_link,
- rpl::combine(
- tr::lng_paid_react_agree_link(),
- tr::lng_group_invite_subscription_about_url()
- ) | rpl::map([](const QString &text, const QString &url) {
- return Ui::Text::Link(text, url);
- }),
- Ui::Text::RichLangValue),
- st::inviteLinkSubscribeBoxTerms)));
+ tr::lng_channel_invite_subscription_terms(
+ lt_link,
+ rpl::combine(
+ tr::lng_paid_react_agree_link(),
+ tr::lng_group_invite_subscription_about_url()
+ ) | rpl::map([](const QString &text, const QString &url) {
+ return Ui::Text::Link(text, url);
+ }),
+ Ui::Text::RichLangValue),
+ st::inviteLinkSubscribeBoxTerms),
+ style::al_top);
{
const auto balance = Settings::AddBalanceWidget(
@@ -272,7 +268,7 @@ void ConfirmSubscriptionBox(
}, balance->lifetime());
}
- const auto sendCredits = [=, weak = Ui::MakeWeak(box)] {
+ const auto sendCredits = [=, weak = base::make_weak(box)] {
const auto show = box->uiShow();
const auto buttonWidth = state->saveButton
? state->saveButton->width()
@@ -280,7 +276,7 @@ void ConfirmSubscriptionBox(
const auto finish = [=] {
state->api = std::nullopt;
state->loading.force_assign(false);
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->closeBox();
}
};
@@ -294,7 +290,7 @@ void ConfirmSubscriptionBox(
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
});
const auto refill = session->data().activeCreditsSubsRebuilder();
- const auto strong = weak.data();
+ const auto strong = weak.get();
if (!strong) {
return;
}
diff --git a/Telegram/SourceFiles/api/api_confirm_phone.cpp b/Telegram/SourceFiles/api/api_confirm_phone.cpp
index a3b41fefc9..7379681138 100644
--- a/Telegram/SourceFiles/api/api_confirm_phone.cpp
+++ b/Telegram/SourceFiles/api/api_confirm_phone.cpp
@@ -87,7 +87,7 @@ void ConfirmPhone::resolve(
sentCodeLength,
fragmentUrl,
timeout);
- const auto boxWeak = Ui::MakeWeak(box.data());
+ const auto boxWeak = base::make_weak(box.data());
using LoginCode = rpl::event_stream;
const auto codeHandles = box->lifetime().make_state();
controller->session().account().setHandleLoginCode([=](
diff --git a/Telegram/SourceFiles/api/api_credits_history_entry.cpp b/Telegram/SourceFiles/api/api_credits_history_entry.cpp
index c6acdfeb6d..dbf616471d 100644
--- a/Telegram/SourceFiles/api/api_credits_history_entry.cpp
+++ b/Telegram/SourceFiles/api/api_credits_history_entry.cpp
@@ -153,6 +153,7 @@ Data::CreditsHistoryEntry CreditsHistoryEntryFromTL(
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
.stargift = stargift.has_value(),
+ .postsSearch = tl.data().is_posts_search(),
.giftUpgraded = tl.data().is_stargift_upgrade(),
.giftResale = tl.data().is_stargift_resale(),
.reaction = tl.data().is_reaction(),
diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp
index 9df407aad2..3b8272fc4e 100644
--- a/Telegram/SourceFiles/api/api_earn.cpp
+++ b/Telegram/SourceFiles/api/api_earn.cpp
@@ -82,7 +82,7 @@ void HandleWithdrawalButton(
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = crl::guard(button, [=](
const Core::CloudPasswordResult &result,
- QPointer box) {
+ base::weak_qptr box) {
const auto done = [=](const QString &result) {
if (!result.isEmpty()) {
UrlClickHandler::Open(result);
diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp
index 5065541e27..545ef29be3 100644
--- a/Telegram/SourceFiles/api/api_premium.cpp
+++ b/Telegram/SourceFiles/api/api_premium.cpp
@@ -45,19 +45,45 @@ namespace {
auto options = PremiumSubscriptionOptionsFromTL(tlOptions);
for (auto i = 0; i < options.size(); i++) {
const auto &tlOption = tlOptions[i].data();
+ const auto currency = qs(tlOption.vcurrency());
const auto perUserText = Ui::FillAmountAndCurrency(
tlOption.vamount().v / float64(tlOption.vusers().v),
- qs(tlOption.vcurrency()),
+ currency,
false);
options[i].costPerMonth = perUserText
+ ' '
+ QChar(0x00D7)
+ ' '
+ QString::number(tlOption.vusers().v);
+ options[i].currency = currency;
}
return options;
}
+[[nodiscard]] int FindStarsForResale(const MTPVector *list) {
+ if (!list) {
+ return 0;
+ }
+ for (const auto &amount : list->v) {
+ if (amount.type() == mtpc_starsAmount) {
+ return int(amount.c_starsAmount().vamount().v);
+ }
+ }
+ return 0;
+}
+
+[[nodiscard]] int64 FindTonForResale(const MTPVector *list) {
+ if (!list) {
+ return 0;
+ }
+ for (const auto &amount : list->v) {
+ if (amount.type() == mtpc_starsTonAmount) {
+ return int64(amount.c_starsTonAmount().vamount().v);
+ }
+ }
+ return 0;
+}
+
} // namespace
Premium::Premium(not_null api)
@@ -589,24 +615,32 @@ std::vector PremiumGiftCodeOptions::optionsForPeer() const {
return result;
}
-Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
- const auto it = _subscriptionOptions.find(amount);
+Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::optionsForGiveaway(
+ int usersCount) {
+ const auto skipForStars = [&](Data::PremiumSubscriptionOptions options) {
+ const auto proj = &Data::PremiumSubscriptionOption::currency;
+ options.erase(
+ ranges::remove(options, Ui::kCreditsCurrency, proj),
+ end(options));
+ return options;
+ };
+ const auto it = _subscriptionOptions.find(usersCount);
if (it != end(_subscriptionOptions)) {
- return it->second;
+ return skipForStars(it->second);
} else {
auto tlOptions = QVector();
for (auto i = 0; i < _optionsForOnePerson.months.size(); i++) {
tlOptions.push_back(MTP_premiumGiftCodeOption(
MTP_flags(MTPDpremiumGiftCodeOption::Flags(0)),
- MTP_int(amount),
+ MTP_int(usersCount),
MTP_int(_optionsForOnePerson.months[i]),
MTPstring(),
MTPint(),
MTP_string(_optionsForOnePerson.currencies[i]),
- MTP_long(_optionsForOnePerson.totalCosts[i] * amount)));
+ MTP_long(_optionsForOnePerson.totalCosts[i] * usersCount)));
}
- _subscriptionOptions[amount] = GiftCodesFromTL(tlOptions);
- return _subscriptionOptions[amount];
+ _subscriptionOptions[usersCount] = GiftCodesFromTL(tlOptions);
+ return skipForStars(_subscriptionOptions[usersCount]);
}
}
@@ -825,8 +859,12 @@ std::optional FromTL(
.resellCount = int(data.vavailability_resale().value_or_empty()),
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
+ .perUserTotal = data.vper_user_total().value_or_empty(),
+ .perUserRemains = data.vper_user_remains().value_or_empty(),
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
+ .lockedUntilDate = data.vlocked_until_date().value_or_empty(),
+ .requirePremium = data.is_require_premium(),
.upgradable = data.vupgrade_stars().has_value(),
.birthday = data.is_birthday(),
.soldOut = data.is_sold_out(),
@@ -853,13 +891,20 @@ std::optional FromTL(
const auto releasedById = data.vreleased_by()
? peerFromMTP(*data.vreleased_by())
: PeerId();
+ const auto themeUserId = data.vtheme_peer()
+ ? peerFromMTP(*data.vtheme_peer())
+ : PeerId();
const auto releasedBy = releasedById
? session->data().peer(releasedById).get()
: nullptr;
+ const auto themeUser = themeUserId
+ ? session->data().peer(themeUserId).get()
+ : nullptr;
auto result = Data::StarGift{
- .id = uint64(data.vid().v),
+ .id = data.vid().v,
.unique = std::make_shared(Data::UniqueGift{
.id = data.vid().v,
+ .initialGiftId = data.vgift_id().v,
.slug = qs(data.vslug()),
.title = qs(data.vtitle()),
.ownerAddress = qs(data.vowner_address().value_or_empty()),
@@ -868,15 +913,30 @@ std::optional FromTL(
? peerFromMTP(*data.vowner_id())
: PeerId()),
.releasedBy = releasedBy,
+ .themeUser = themeUser,
+ .nanoTonForResale = FindTonForResale(data.vresell_amount()),
+ .starsForResale = FindStarsForResale(data.vresell_amount()),
.number = data.vnum().v,
- .starsForResale = int(data.vresell_stars().value_or_empty()),
+ .onlyAcceptTon = data.is_resale_ton_only(),
+ .canBeTheme = data.is_theme_available(),
.model = *model,
.pattern = *pattern,
+ .value = (data.vvalue_amount()
+ ? std::make_shared(
+ Data::UniqueGiftValue{
+ .currency = qs(
+ data.vvalue_currency().value_or_empty()),
+ .valuePrice = int64(
+ data.vvalue_amount().value_or_empty()),
+ })
+ : nullptr),
}),
.document = model->document,
.releasedBy = releasedBy,
.limitedLeft = (total - data.vavailability_issued().v),
.limitedCount = total,
+ .resellTonOnly = data.is_resale_ton_only(),
+ .requirePremium = data.is_require_premium(),
};
const auto unique = result.unique.get();
for (const auto &attribute : data.vattributes().v) {
@@ -913,21 +973,26 @@ std::optional FromTL(
.manageId = (to->isUser()
? Id::User(data.vmsg_id().value_or_empty())
: Id::Chat(to, data.vsaved_id().value_or_empty())),
+ .collectionIds = (data.vcollection_id()
+ ? (data.vcollection_id()->v
+ | ranges::views::transform(&MTPint::v)
+ | ranges::to_vector)
+ : std::vector()),
.message = (data.vmessage()
- ? TextWithEntities{
- .text = qs(data.vmessage()->data().vtext()),
- .entities = Api::EntitiesFromMTP(
- session,
- data.vmessage()->data().ventities().v),
- }
+ ? Api::ParseTextWithEntities(
+ session,
+ *data.vmessage())
: TextWithEntities()),
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
.starsUpgradedBySender = int64(
data.vupgrade_stars().value_or_empty()),
+ .giftPrepayUpgradeHash = qs(
+ data.vprepaid_upgrade_hash().value_or_empty()),
.fromId = (data.vfrom_id()
? peerFromMTP(*data.vfrom_id())
: PeerId()),
.date = data.vdate().v,
+ .upgradeSeparate = data.is_upgrade_separate(),
.upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(),
.pinned = data.is_pinned_to_top() && hasUnique,
diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h
index 2b692a4849..e3eb8f1e45 100644
--- a/Telegram/SourceFiles/api/api_premium.h
+++ b/Telegram/SourceFiles/api/api_premium.h
@@ -180,7 +180,8 @@ public:
[[nodiscard]] rpl::producer request();
[[nodiscard]] std::vector optionsForPeer() const;
- [[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
+ [[nodiscard]] Data::PremiumSubscriptionOptions optionsForGiveaway(
+ int usersCount);
[[nodiscard]] const std::vector &availablePresets() const;
[[nodiscard]] int monthsFromPreset(int monthsIndex);
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
diff --git a/Telegram/SourceFiles/api/api_sensitive_content.cpp b/Telegram/SourceFiles/api/api_sensitive_content.cpp
index 31f83f5eba..e1e25950e8 100644
--- a/Telegram/SourceFiles/api/api_sensitive_content.cpp
+++ b/Telegram/SourceFiles/api/api_sensitive_content.cpp
@@ -25,7 +25,7 @@ SensitiveContent::SensitiveContent(not_null api)
}
void SensitiveContent::preload() {
- if (!_loaded) {
+ if (!_loaded && !_loadRequestId) {
reload();
}
}
@@ -37,7 +37,6 @@ void SensitiveContent::reload(bool force) {
}
return;
}
- _loaded = true;
_loadRequestId = _api.request(MTPaccount_GetContentSettings(
)).done([=](const MTPaccount_ContentSettings &result) {
_loadRequestId = 0;
@@ -50,6 +49,10 @@ void SensitiveContent::reload(bool force) {
_enabled = enabled;
_canChange = canChange;
}
+ if (!_loaded) {
+ _loaded = true;
+ _loadedChanged.fire({});
+ }
if (base::take(_appConfigReloadForce) || changed) {
_appConfigReloadTimer.callOnce(kRefreshAppConfigTimeout);
}
@@ -61,6 +64,19 @@ void SensitiveContent::reload(bool force) {
}).send();
}
+bool SensitiveContent::loaded() const {
+ return _loaded;
+}
+
+rpl::producer SensitiveContent::loadedValue() const {
+ if (_loaded) {
+ return rpl::single(true);
+ }
+ return rpl::single(false) | rpl::then(
+ _loadedChanged.events() | rpl::map_to(true)
+ );
+}
+
bool SensitiveContent::enabledCurrent() const {
return _enabled.current();
}
@@ -69,6 +85,10 @@ rpl::producer SensitiveContent::enabled() const {
return _enabled.value();
}
+bool SensitiveContent::canChangeCurrent() const {
+ return _canChange.current();
+}
+
rpl::producer SensitiveContent::canChange() const {
return _canChange.value();
}
diff --git a/Telegram/SourceFiles/api/api_sensitive_content.h b/Telegram/SourceFiles/api/api_sensitive_content.h
index 576bf275f3..e35f8cbf46 100644
--- a/Telegram/SourceFiles/api/api_sensitive_content.h
+++ b/Telegram/SourceFiles/api/api_sensitive_content.h
@@ -26,12 +26,16 @@ public:
void reload(bool force = false);
void update(bool enabled);
+ [[nodiscard]] bool loaded() const;
+ [[nodiscard]] rpl::producer loadedValue() const;
[[nodiscard]] bool enabledCurrent() const;
[[nodiscard]] rpl::producer enabled() const;
+ [[nodiscard]] bool canChangeCurrent() const;
[[nodiscard]] rpl::producer canChange() const;
private:
const not_null _session;
+ rpl::event_stream<> _loadedChanged;
MTP::Sender _api;
mtpRequestId _loadRequestId = 0;
mtpRequestId _saveRequestId = 0;
diff --git a/Telegram/SourceFiles/api/api_suggest_post.cpp b/Telegram/SourceFiles/api/api_suggest_post.cpp
index 7af4fbc84d..05d0304452 100644
--- a/Telegram/SourceFiles/api/api_suggest_post.cpp
+++ b/Telegram/SourceFiles/api/api_suggest_post.cpp
@@ -223,7 +223,7 @@ void ConfirmApproval(
.confirmText = tr::lng_suggest_accept_send(),
.title = tr::lng_suggest_accept_title(),
});
- *callback = [=, weak = Ui::MakeWeak(box)] {
+ *callback = [=, weak = base::make_weak(box)] {
if (const auto onstack = accepted) {
onstack();
}
@@ -232,7 +232,7 @@ void ConfirmApproval(
return;
}
SendApproval(show, item, scheduleDate);
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->closeBox();
}
};
@@ -283,9 +283,9 @@ void RequestApprovalDate(
std::shared_ptr show,
not_null item) {
const auto id = item->fullId();
- const auto weak = std::make_shared>();
+ const auto weak = std::make_shared>();
const auto close = [=] {
- if (const auto strong = weak->data()) {
+ if (const auto strong = weak->get()) {
strong->closeBox();
}
};
@@ -339,13 +339,13 @@ void RequestDeclineComment(
box->setFocusCallback([=] {
reason->setFocusFast();
});
- *callback = [=, weak = Ui::MakeWeak(box)] {
+ *callback = [=, weak = base::make_weak(box)] {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
SendDecline(show, item, reason->getLastText().trimmed());
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->closeBox();
}
};
@@ -422,14 +422,14 @@ void SuggestApprovalDate(
}
const auto id = item->fullId();
const auto state = std::make_shared();
- const auto weak = std::make_shared>();
+ const auto weak = std::make_shared>();
const auto done = [=](TimeId result) {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
- if (const auto strong = weak->data()) {
+ if (const auto strong = weak->get()) {
strong->closeBox();
}
};
@@ -458,14 +458,14 @@ void SuggestOfferForMessage(
HistoryView::SuggestMode mode) {
const auto id = item->fullId();
const auto state = std::make_shared();
- const auto weak = std::make_shared>();
+ const auto weak = std::make_shared>();
const auto done = [=](SuggestPostOptions result) {
const auto item = show->session().data().message(id);
if (!item) {
return;
}
const auto close = [=] {
- if (const auto strong = weak->data()) {
+ if (const auto strong = weak->get()) {
strong->closeBox();
}
};
diff --git a/Telegram/SourceFiles/api/api_transcribes.cpp b/Telegram/SourceFiles/api/api_transcribes.cpp
index dd983dc37b..17681cc849 100644
--- a/Telegram/SourceFiles/api/api_transcribes.cpp
+++ b/Telegram/SourceFiles/api/api_transcribes.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
+#include "main/main_session_settings.h"
namespace Api {
@@ -25,6 +26,32 @@ Transcribes::Transcribes(not_null api)
, _api(&api->instance()) {
}
+bool Transcribes::isRated(not_null item) const {
+ const auto fullId = item->fullId();
+ for (const auto &[transcribeId, id] : _ids) {
+ if (id == fullId) {
+ return _session->settings().isTranscriptionRated(transcribeId);
+ }
+ }
+ return false;
+}
+
+void Transcribes::rate(not_null item, bool isGood) {
+ const auto fullId = item->fullId();
+ for (const auto &[transcribeId, id] : _ids) {
+ if (id == fullId) {
+ _api.request(MTPmessages_RateTranscribedAudio(
+ item->history()->peer->input,
+ MTP_int(item->id),
+ MTP_long(transcribeId),
+ MTP_bool(isGood))).send();
+ _session->settings().markTranscriptionAsRated(transcribeId);
+ _session->saveSettings();
+ return;
+ }
+ }
+}
+
bool Transcribes::freeFor(not_null item) const {
if (const auto channel = item->history()->peer->asMegagroup()) {
const auto owner = &channel->owner();
diff --git a/Telegram/SourceFiles/api/api_transcribes.h b/Telegram/SourceFiles/api/api_transcribes.h
index b074e42f13..93b42401df 100644
--- a/Telegram/SourceFiles/api/api_transcribes.h
+++ b/Telegram/SourceFiles/api/api_transcribes.h
@@ -37,6 +37,8 @@ public:
void apply(const MTPDupdateTranscribedAudio &update);
[[nodiscard]] bool freeFor(not_null item) const;
+ [[nodiscard]] bool isRated(not_null item) const;
+ void rate(not_null item, bool isGood);
[[nodiscard]] bool trialsSupport();
[[nodiscard]] TimeId trialsRefreshAt();
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index 1a5d0b9f6e..35ac496c51 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -2221,6 +2221,10 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
} break;
+ case mtpc_updateNewAuthorization: {
+ session().api().authorizations().apply(update);
+ } break;
+
case mtpc_updateServiceNotification: {
const auto &d = update.c_updateServiceNotification();
const auto text = TextWithEntities {
diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp
index bb8aa13441..91c94e7c07 100644
--- a/Telegram/SourceFiles/api/api_who_reacted.cpp
+++ b/Telegram/SourceFiles/api/api_who_reacted.cpp
@@ -240,13 +240,13 @@ struct State {
[[nodiscard]] rpl::producer WhoReadIds(
not_null item,
not_null context) {
- auto weak = QPointer(context.get());
+ auto weak = base::make_weak(context);
const auto session = &item->history()->session();
return [=](auto consumer) {
if (!weak) {
return rpl::lifetime();
}
- const auto context = PreparedContextAt(weak.data(), session);
+ const auto context = PreparedContextAt(weak.get(), session);
auto &entry = context->cacheRead(item);
if (entry.requestId) {
} else if (const auto user = item->history()->peer->asUser()) {
@@ -325,13 +325,13 @@ struct State {
not_null item,
const ReactionId &reaction,
not_null context) {
- auto weak = QPointer(context.get());
+ auto weak = base::make_weak(context);
const auto session = &item->history()->session();
return [=](auto consumer) {
if (!weak) {
return rpl::lifetime();
}
- const auto context = PreparedContextAt(weak.data(), session);
+ const auto context = PreparedContextAt(weak.get(), session);
auto &entry = context->cacheReacted(item, reaction);
if (!entry.requestId) {
using Flag = MTPmessages_GetMessageReactionsList::Flag;
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 2536944edc..2ee82a0f11 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h"
#include "data/data_forum.h"
#include "data/data_saved_messages.h"
+#include "data/data_saved_music.h"
#include "data/data_saved_sublist.h"
#include "data/data_search_controller.h"
#include "data/data_session.h"
@@ -2497,7 +2498,17 @@ void ApiWrap::refreshFileReference(
v::match(origin.data, [&](Data::FileOriginMessage data) {
if (const auto item = _session->data().message(data)) {
const auto media = item->media();
- const auto storyId = media ? media->storyId() : FullStoryId();
+ const auto mediaStory = media ? media->storyId() : FullStoryId();
+ const auto storyId = mediaStory
+ ? mediaStory
+ : FullStoryId{
+ (IsStoryMsgId(item->id)
+ ? item->history()->peer->id
+ : PeerId()),
+ (IsStoryMsgId(item->id)
+ ? StoryIdFromMsgId(item->id)
+ : StoryId())
+ };
if (storyId) {
request(MTPstories_GetStoriesByID(
_session->data().peer(storyId.peer)->input,
@@ -2508,6 +2519,17 @@ void ApiWrap::refreshFileReference(
request(MTPmessages_GetScheduledMessages(
item->history()->peer->input,
MTP_vector(1, MTP_int(realId))));
+ } else if (item->isSavedMusicItem()) {
+ const auto user = item->history()->peer->asUser();
+ const auto media = item->media();
+ const auto document = media ? media->document() : nullptr;
+ if (user && document) {
+ request(MTPusers_GetSavedMusicByID(
+ user->inputUser,
+ MTP_vector(1, document->mtpInput())));
+ } else {
+ fail();
+ }
} else if (item->isBusinessShortcut()) {
const auto &shortcuts = _session->data().shortcutMessages();
const auto realId = shortcuts.lookupId(item);
@@ -3379,8 +3401,8 @@ void ApiWrap::finishForwarding(const SendAction &action) {
return;
}
- forwardMessages(std::move(toForward), action);
history->setForwardDraft(topicRootId, monoforumPeerId, {});
+ forwardMessages(std::move(toForward), action);
}
_session->data().sendHistoryChangeNotifications();
@@ -3417,6 +3439,22 @@ void ApiWrap::forwardMessages(
auto &histories = _session->data().histories();
+ for (auto i = begin(draft.items); i != end(draft.items);) {
+ const auto item = *i;
+ if (item->isSavedMusicItem()) {
+ SendExistingDocument(MessageToSend(action), item->media()->document());
+ i = draft.items.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ if (draft.items.empty()) {
+ if (successCallback) {
+ successCallback();
+ }
+ return;
+ }
+
struct SharedCallback {
int requestsLeft = 0;
FnMut callback;
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 5eabc79096..0a2a728eee 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -28,7 +28,7 @@ namespace Data {
struct UpdatedFileReferences;
class WallPaper;
struct ResolvedForwardDraft;
-enum class DefaultNotify;
+enum class DefaultNotify : uint8_t;
enum class StickersType : uchar;
class Forum;
class ForumTopic;
diff --git a/Telegram/SourceFiles/boxes/about_box.cpp b/Telegram/SourceFiles/boxes/about_box.cpp
index 4fdf2c7c8e..c3d2fef1af 100644
--- a/Telegram/SourceFiles/boxes/about_box.cpp
+++ b/Telegram/SourceFiles/boxes/about_box.cpp
@@ -13,12 +13,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/update_checker.h"
#include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
+#include "ui/painter.h"
+#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
+#include "styles/style_channel_earn.h"
+#include "styles/style_chat.h"
+#include "styles/style_dialogs.h"
+#include "styles/style_menu_icons.h"
+#include "styles/style_premium.h"
+#include "styles/style_settings.h"
#include
#include
@@ -108,3 +116,151 @@ QString currentVersionText() {
#endif
return result;
}
+
+void ArchiveHintBox(
+ not_null box,
+ bool unarchiveOnNewMessage,
+ Fn onUnarchive) {
+ box->setNoContentMargin(true);
+
+ const auto content = box->verticalLayout().get();
+
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ {
+ const auto &icon = st::dialogsArchiveUserpic;
+ const auto rect = Rect(icon.size() * 2);
+ auto owned = object_ptr(content);
+ owned->resize(rect.size());
+ owned->setNaturalWidth(rect.width());
+ const auto widget = box->addRow(std::move(owned), style::al_top);
+ widget->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = Painter(widget);
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st::activeButtonBg);
+ p.drawEllipse(rect);
+ icon.paintInCenter(p, rect);
+ }, widget->lifetime());
+ }
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ box->addRow(
+ object_ptr(
+ content,
+ tr::lng_archive_hint_title(),
+ st::boxTitle),
+ style::al_top);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ {
+ const auto label = box->addRow(
+ object_ptr(
+ content,
+ (unarchiveOnNewMessage
+ ? tr::lng_archive_hint_about_unmuted
+ : tr::lng_archive_hint_about)(
+ lt_link,
+ tr::lng_archive_hint_about_link(
+ lt_emoji,
+ rpl::single(
+ Ui::Text::IconEmoji(&st::textMoreIconEmoji)),
+ Ui::Text::RichLangValue
+ ) | rpl::map([](TextWithEntities text) {
+ return Ui::Text::Link(std::move(text), 1);
+ }),
+ Ui::Text::RichLangValue),
+ st::channelEarnHistoryRecipientLabel));
+ label->resizeToWidth(box->width()
+ - rect::m::sum::h(st::boxRowPadding));
+ label->setLink(
+ 1,
+ std::make_shared([=](ClickContext context) {
+ if (context.button == Qt::LeftButton) {
+ onUnarchive();
+ }
+ }));
+ }
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ {
+ const auto padding = QMargins(
+ st::settingsButton.padding.left(),
+ st::boxRowPadding.top(),
+ st::boxRowPadding.right(),
+ st::boxRowPadding.bottom());
+ const auto addEntry = [&](
+ rpl::producer title,
+ rpl::producer about,
+ const style::icon &icon) {
+ const auto top = content->add(
+ object_ptr(
+ content,
+ std::move(title),
+ st::channelEarnSemiboldLabel),
+ padding);
+ Ui::AddSkip(content, st::channelEarnHistoryThreeSkip);
+ content->add(
+ object_ptr(
+ content,
+ std::move(about),
+ st::channelEarnHistoryRecipientLabel),
+ padding);
+ const auto left = Ui::CreateChild(
+ box->verticalLayout().get());
+ left->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = Painter(left);
+ icon.paint(p, 0, 0, left->width());
+ }, left->lifetime());
+ left->resize(icon.size());
+ top->geometryValue(
+ ) | rpl::start_with_next([=](const QRect &g) {
+ left->moveToLeft(
+ (g.left() - left->width()) / 2,
+ g.top() + st::channelEarnHistoryThreeSkip);
+ }, left->lifetime());
+ };
+ addEntry(
+ tr::lng_archive_hint_section_1(),
+ tr::lng_archive_hint_section_1_info(),
+ st::menuIconArchive);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ addEntry(
+ tr::lng_archive_hint_section_2(),
+ tr::lng_archive_hint_section_2_info(),
+ st::menuIconStealth);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ addEntry(
+ tr::lng_archive_hint_section_3(),
+ tr::lng_archive_hint_section_3_info(),
+ st::menuIconStoriesSavedSection);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ }
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ Ui::AddSkip(content);
+ {
+ const auto &st = st::premiumPreviewDoubledLimitsBox;
+ box->setStyle(st);
+ auto button = object_ptr(
+ box,
+ tr::lng_archive_hint_button(),
+ st::defaultActiveButton);
+ button->setTextTransform(
+ Ui::RoundButton::TextTransform::NoTransform);
+ button->resizeToWidth(box->width()
+ - st.buttonPadding.left()
+ - st.buttonPadding.left());
+ button->setClickedCallback([=] { box->closeBox(); });
+ box->addButton(std::move(button));
+ }
+}
+
diff --git a/Telegram/SourceFiles/boxes/about_box.h b/Telegram/SourceFiles/boxes/about_box.h
index 8bdb04cdd4..429c25cb43 100644
--- a/Telegram/SourceFiles/boxes/about_box.h
+++ b/Telegram/SourceFiles/boxes/about_box.h
@@ -14,5 +14,9 @@ class SessionController;
}
void AboutBox(not_null box, Window::SessionController* controller);
+void ArchiveHintBox(
+ not_null box,
+ bool unarchiveOnNewMessage,
+ Fn onUnarchive);
QString currentVersionText();
diff --git a/Telegram/SourceFiles/boxes/about_sponsored_box.cpp b/Telegram/SourceFiles/boxes/about_sponsored_box.cpp
index 8c932df6af..e4e3515c04 100644
--- a/Telegram/SourceFiles/boxes/about_sponsored_box.cpp
+++ b/Telegram/SourceFiles/boxes/about_sponsored_box.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/file_utilities.h"
#include "lang/lang_keys.h"
#include "ui/layers/generic_box.h"
+#include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
@@ -18,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
namespace {
-constexpr auto kUrl = "https://promote.telegram.org"_cs;
+constexpr auto kUrl = "https://ads.telegram.org"_cs;
} // namespace
@@ -54,8 +55,16 @@ void AboutSponsoredBox(not_null box) {
};
const auto &stLabel = st::aboutLabel;
- const auto info1 = box->addRow(object_ptr(box, stLabel));
- info1->setText(tr::lng_sponsored_info_description1(tr::now));
+ auto text1 = tr::lng_sponsored_info_description1_linked(
+ lt_link,
+ rpl::combine(
+ tr::lng_sponsored_info_description1_link(),
+ tr::lng_sponsored_info_description1_url()
+ ) | rpl::map([](const QString &text, const QString &url) {
+ return Ui::Text::Link(text, url);
+ }),
+ Ui::Text::RichLangValue);
+ box->addRow(object_ptr(box, std::move(text1), stLabel));
box->addSkip(st::sponsoredUrlButtonSkip);
addUrl();
diff --git a/Telegram/SourceFiles/boxes/abstract_box.h b/Telegram/SourceFiles/boxes/abstract_box.h
index d48e93108f..119875a293 100644
--- a/Telegram/SourceFiles/boxes/abstract_box.h
+++ b/Telegram/SourceFiles/boxes/abstract_box.h
@@ -37,11 +37,11 @@ void showBox(
} // namespace internal
template
-QPointer show(
+base::weak_qptr show(
object_ptr content,
Ui::LayerOptions options = Ui::LayerOption::CloseOther,
anim::type animated = anim::type::normal) {
- auto result = QPointer(content.data());
+ auto result = base::weak_qptr(content.data());
internal::showBox(std::move(content), options, animated);
return result;
}
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp
index 792eff79b4..6e9bc23955 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.cpp
+++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp
@@ -194,16 +194,16 @@ void ShowAddParticipantsError(
&& channel->canAddAdmins()) {
const auto makeAdmin = [=](Fn close) {
const auto user = forbidden.users.front();
- const auto weak = std::make_shared>();
+ const auto weak = std::make_shared>();
const auto done = [=](auto&&...) {
- if (const auto strong = weak->data()) {
+ if (const auto strong = weak->get()) {
strong->uiShow()->showToast(
tr::lng_box_done(tr::now));
strong->closeBox();
}
};
const auto fail = [=] {
- if (const auto strong = weak->data()) {
+ if (const auto strong = weak->get()) {
strong->closeBox();
}
};
@@ -446,7 +446,7 @@ void AddContactBox::save() {
firstName = lastName;
lastName = QString();
}
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
const auto session = _session;
_sentName = firstName;
_contactId = base::RandomValue();
@@ -716,7 +716,7 @@ TimeId GroupInfoBox::ttlPeriod() const {
}
void GroupInfoBox::createGroup(
- QPointer selectUsersBox,
+ base::weak_qptr selectUsersBox,
const QString &title,
const std::vector> &users) {
if (_creationRequestId) {
@@ -750,8 +750,8 @@ void GroupInfoBox::createGroup(
_creationRequestId = 0;
const auto controller = _navigation->parentController();
if (type == u"NO_CHAT_TITLE"_q) {
- const auto weak = Ui::MakeWeak(this);
- if (const auto strong = selectUsersBox.data()) {
+ const auto weak = base::make_weak(this);
+ if (const auto strong = selectUsersBox.get()) {
strong->closeBox();
}
if (weak) {
@@ -792,10 +792,10 @@ void GroupInfoBox::submit() {
} else if (_canAddBot) {
createGroup(nullptr, title, { not_null(_canAddBot) });
} else {
- auto initBox = [title, weak = Ui::MakeWeak(this)](
+ auto initBox = [title, weak = base::make_weak(this)](
not_null box) {
auto create = [box, title, weak] {
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->createGroup(
box.get(),
title,
diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h
index 9bc7de6520..4032653564 100644
--- a/Telegram/SourceFiles/boxes/add_contact_box.h
+++ b/Telegram/SourceFiles/boxes/add_contact_box.h
@@ -129,7 +129,7 @@ protected:
private:
void createChannel(const QString &title, const QString &description);
void createGroup(
- QPointer selectUsersBox,
+ base::weak_qptr selectUsersBox,
const QString &title,
const std::vector> &users);
void submitName();
diff --git a/Telegram/SourceFiles/boxes/background_box.cpp b/Telegram/SourceFiles/boxes/background_box.cpp
index 59729b7fed..8723572da2 100644
--- a/Telegram/SourceFiles/boxes/background_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_box.cpp
@@ -345,7 +345,7 @@ void BackgroundBox::resetForPeer() {
api->applyUpdates(result);
}).send();
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
_forPeer->setWallPaper({});
if (weak) {
_controller->finishChatThemeEdit(_forPeer);
@@ -358,7 +358,7 @@ bool BackgroundBox::forChannel() const {
void BackgroundBox::removePaper(const Data::WallPaper &paper) {
const auto session = &_controller->session();
- const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) {
+ const auto remove = [=, weak = base::make_weak(this)](Fn &&close) {
close();
if (weak) {
weak->_inner->removePaper(paper);
@@ -451,12 +451,12 @@ auto BackgroundBox::Inner::resolveResetCustomPaper() const
return {};
}
const auto nonCustom = Window::Theme::Background()->paper();
- const auto themeEmoji = _forPeer->themeEmoji();
- if (forChannel() || themeEmoji.isEmpty()) {
+ const auto themeToken = _forPeer->themeToken();
+ if (forChannel() || themeToken.isEmpty()) {
return nonCustom;
}
const auto &themes = _forPeer->owner().cloudThemes();
- const auto theme = themes.themeForEmoji(themeEmoji);
+ const auto theme = themes.themeForToken(themeToken);
if (!theme) {
return nonCustom;
}
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp
index 0faa677720..24d4fd1835 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp
@@ -161,7 +161,7 @@ constexpr auto kMaxWallPaperSlugLength = 255;
return paper;
}
const auto &themes = session->data().cloudThemes();
- if (const auto theme = themes.themeForEmoji(paper.emojiId())) {
+ if (const auto theme = themes.themeForToken(paper.emojiId())) {
using Type = Data::CloudThemeType;
const auto type = dark ? Type::Dark : Type::Light;
const auto i = theme->settings.find(type);
@@ -241,12 +241,12 @@ BackgroundPreviewBox::BackgroundPreviewBox(
}, lifetime());
const auto prepare = [=](bool dark, auto pointer) {
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
crl::async([=] {
auto result = std::make_unique();
Window::Theme::PreparePaletteCallback(dark, {})(*result);
crl::on_main([=, result = std::move(result)]() mutable {
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->*pointer = std::move(result);
strong->paletteReady();
}
@@ -685,7 +685,7 @@ void BackgroundPreviewBox::checkLevelForChannel() {
const auto show = _controller->uiShow();
_forPeerLevelCheck = true;
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
CheckBoostLevel(show, _forPeer, [=](int level) {
if (!weak) {
return std::optional();
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index c159627e71..4d3512711b 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -758,7 +758,7 @@ backgroundCheckbox: Checkbox(defaultCheckbox) {
textFg: msgServiceFg;
textFgActive: msgServiceFg;
- width: -50px;
+ width: -10px;
margin: margins(0px, 0px, 0px, 0px);
textPosition: point(0px, 6px);
@@ -1116,8 +1116,16 @@ moderateBoxExpandInnerSkip: 2px;
moderateBoxExpandFont: font(11px);
moderateBoxExpandToggleSize: 4px;
moderateBoxExpandToggleFourStrokes: 3px;
-moderateBoxExpandIcon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }};
-moderateBoxExpandIconDown: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }};
+moderateBoxExpandIcon: IconEmoji{
+ icon: icon{{ "info/edit/expand_arrow_small-flip_vertical", windowActiveTextFg }};
+ padding: margins(-2px, -1px, 0px, 0px);
+ useIconColor: true;
+}
+moderateBoxExpandIconDown: IconEmoji{
+ icon: icon{{ "info/edit/expand_arrow_small", windowActiveTextFg }};
+ padding: margins(-2px, -1px, 0px, 0px);
+ useIconColor: true;
+}
moderateBoxDividerLabel: FlatLabel(boxDividerLabel) {
palette: TextPalette(defaultTextPalette) {
selectLinkFg: windowActiveTextFg;
diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp
index b2bc215cd3..9f824b8486 100644
--- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp
+++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h"
+#include "ui/rect.h"
#include "ui/wrap/slide_wrap.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@@ -119,16 +120,12 @@ void DeleteMessagesBox::prepare() {
Ui::Text::RichLangValue);
deleteStyle = &st::attentionBoxButton;
} else if (_wipeHistoryJustClear) {
- const auto isChannel = peer->isBroadcast();
- const auto isPublicGroup = peer->isMegagroup()
- && peer->asChannel()->isPublic();
- if (isChannel || isPublicGroup) {
- canDelete = false;
- }
- details.text = isChannel
- ? tr::lng_no_clear_history_channel(tr::now)
- : isPublicGroup
- ? tr::lng_no_clear_history_group(tr::now)
+ _revokeJustClearForChannel = true;
+ details.text = (peer->isChannel() && !peer->isMegagroup())
+ ? tr::lng_sure_delete_channel_history(
+ tr::now,
+ lt_channel,
+ peer->name())
: peer->isSelf()
? tr::lng_sure_delete_saved_messages(tr::now)
: peer->isUser()
@@ -164,7 +161,8 @@ void DeleteMessagesBox::prepare() {
}
deleteStyle = &st::attentionBoxButton;
}
- if (auto revoke = revokeText(peer)) {
+ if (_revokeJustClearForChannel) {
+ } else if (auto revoke = revokeText(peer)) {
_revoke.create(
this,
revoke->checkbox,
@@ -234,12 +232,14 @@ void DeleteMessagesBox::prepare() {
search->searchMessages({ .from = _moderateFrom });
}
} else {
- details.text = (_ids.size() == 1)
+ details.text = hasSavedMusicMessages()
+ ? tr::lng_selected_remove_saved_music(tr::now)
+ : (_ids.size() == 1)
? tr::lng_selected_delete_sure_this(tr::now)
: tr::lng_selected_delete_sure(tr::now, lt_count, _ids.size());
if (const auto peer = checkFromSinglePeer()) {
auto count = int(_ids.size());
- if (hasScheduledMessages()) {
+ if (hasScheduledMessages() || hasSavedMusicMessages()) {
} else if (auto revoke = revokeText(peer)) {
const auto &settings = Core::App().settings();
const auto revokeByDefault
@@ -293,6 +293,7 @@ void DeleteMessagesBox::prepare() {
}
}
_text.create(this, rpl::single(std::move(details)), st::boxLabel);
+ _text->resizeToWidth(st::boxWidth - rect::m::sum::h(st::boxPadding));
if (_wipeHistoryJustClear && _wipeHistoryPeer) {
const auto validator = TTLMenu::TTLValidator(
@@ -322,28 +323,36 @@ void DeleteMessagesBox::prepare() {
addButton(tr::lng_about_done(), [=] { closeBox(); });
}
- auto fullHeight = st::boxPadding.top()
- + _text->height()
- + st::boxPadding.bottom();
- if (_moderateFrom) {
- fullHeight += st::boxMediumSkip;
- if (_banUser) {
- fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
+ const auto &padding = st::boxPadding;
+ rpl::combine(
+ widthValue(),
+ _text->naturalWidthValue()
+ ) | rpl::start_with_next([=](int full, int) {
+ _text->resizeToNaturalWidth(full - padding.left() - padding.right());
+
+ auto fullHeight = st::boxPadding.top()
+ + _text->height()
+ + st::boxPadding.bottom();
+ if (_moderateFrom) {
+ fullHeight += st::boxMediumSkip;
+ if (_banUser) {
+ fullHeight += _banUser->heightNoMargins() + st::boxLittleSkip;
+ }
+ fullHeight += _reportSpam->heightNoMargins();
+ if (_deleteAll) {
+ fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
+ }
+ } else if (_revoke) {
+ fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
}
- fullHeight += _reportSpam->heightNoMargins();
- if (_deleteAll) {
- fullHeight += st::boxLittleSkip + _deleteAll->heightNoMargins();
+ if (_autoDeleteSettings) {
+ fullHeight += st::boxMediumSkip
+ + _autoDeleteSettings->height()
+ + st::boxLittleSkip;
}
- } else if (_revoke) {
- fullHeight += st::boxMediumSkip + _revoke->heightNoMargins();
- }
- if (_autoDeleteSettings) {
- fullHeight += st::boxMediumSkip
- + _autoDeleteSettings->height()
- + st::boxLittleSkip;
- }
- setDimensions(st::boxWidth, fullHeight);
- _fullHeight = fullHeight;
+ setDimensions(st::boxWidth, fullHeight);
+ _fullHeight = fullHeight;
+ }, lifetime());
}
bool DeleteMessagesBox::hasScheduledMessages() const {
@@ -357,6 +366,17 @@ bool DeleteMessagesBox::hasScheduledMessages() const {
return false;
}
+bool DeleteMessagesBox::hasSavedMusicMessages() const {
+ for (const auto &fullId : _ids) {
+ if (const auto item = _session->data().message(fullId)) {
+ if (item->isSavedMusicItem()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
PeerData *DeleteMessagesBox::checkFromSinglePeer() const {
auto result = (PeerData*)nullptr;
for (const auto &fullId : _ids) {
@@ -530,10 +550,10 @@ void DeleteMessagesBox::deleteAndClear() {
? PaidPostType::None
: paidPostType();
if (warnPaidType != PaidPostType::None) {
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
const auto callback = [=](Fn close) {
close();
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->_confirmedDeletePaidSuggestedPosts = true;
strong->deleteAndClear();
}
@@ -562,16 +582,21 @@ void DeleteMessagesBox::deleteAndClear() {
!_revoke->checked());
Core::App().saveSettingsDelayed();
}
- const auto revoke = _revoke ? _revoke->checked() : _revokeForBot;
+ const auto revoke = _revoke
+ ? _revoke->checked()
+ : (_revokeForBot || _revokeJustClearForChannel);
const auto session = _session;
const auto invokeCallbackAndClose = [&] {
// deleteMessages can initiate closing of the current section,
// which will cause this box to be destroyed.
- const auto weak = Ui::MakeWeak(this);
+ const auto weak = base::make_weak(this);
+ if (hasSavedMusicMessages()) {
+ uiShow()->showToast(tr::lng_saved_music_removed(tr::now));
+ }
if (const auto callback = _deleteConfirmedCallback) {
callback();
}
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->closeBox();
}
};
diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.h b/Telegram/SourceFiles/boxes/delete_messages_box.h
index 3762212b0d..635a1fd362 100644
--- a/Telegram/SourceFiles/boxes/delete_messages_box.h
+++ b/Telegram/SourceFiles/boxes/delete_messages_box.h
@@ -58,6 +58,7 @@ private:
void deleteAndClear();
[[nodiscard]] PeerData *checkFromSinglePeer() const;
[[nodiscard]] bool hasScheduledMessages() const;
+ [[nodiscard]] bool hasSavedMusicMessages() const;
[[nodiscard]] std::optional revokeText(
not_null peer) const;
[[nodiscard]] PaidPostType paidPostType() const;
@@ -75,6 +76,7 @@ private:
bool _moderateDeleteAll = false;
bool _revokeForBot = false;
+ bool _revokeJustClearForChannel = false;
object_ptr _text = { nullptr };
object_ptr _revoke = { nullptr };
diff --git a/Telegram/SourceFiles/boxes/dictionaries_manager.cpp b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp
index 72cfd29c0d..8881449fcb 100644
--- a/Telegram/SourceFiles/boxes/dictionaries_manager.cpp
+++ b/Telegram/SourceFiles/boxes/dictionaries_manager.cpp
@@ -282,7 +282,7 @@ auto AddButtonWithLoader(
) | rpl::start_with_next([=](bool toggled) {
const auto &state = buttonState->current();
if (toggled && (v::is(state) || v::is(state))) {
- const auto weak = Ui::MakeWeak(button);
+ const auto weak = base::make_weak(button);
setLocalLoader(base::make_unique_q(
QCoreApplication::instance(),
session,
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index cb27997d66..3e48724b7f 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -1075,7 +1075,7 @@ void EditCaptionBox::save() {
}
void EditCaptionBox::closeAfterSave() {
- const auto weak = MakeWeak(this);
+ const auto weak = base::make_weak(this);
if (_saved) {
_saved();
}
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
index 86816ba15b..411f4b1e36 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
@@ -8,37 +8,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/edit_privacy_box.h"
#include "api/api_global_privacy.h"
+#include "apiwrap.h"
#include "boxes/filters/edit_filter_chats_list.h"
-#include "ui/effects/premium_graphics.h"
-#include "ui/layers/generic_box.h"
-#include "ui/widgets/checkbox.h"
-#include "ui/widgets/continuous_sliders.h"
-#include "ui/widgets/shadow.h"
-#include "ui/text/format_values.h"
-#include "ui/text/text_utilities.h"
-#include "ui/toast/toast.h"
-#include "ui/wrap/slide_wrap.h"
-#include "ui/painter.h"
-#include "ui/vertical_list.h"
+#include "boxes/peers/edit_peer_invite_link.h"
+#include "data/data_channel.h"
+#include "data/data_chat.h"
+#include "data/data_peer_values.h"
+#include "data/data_user.h"
#include "history/history.h"
-#include "boxes/peer_list_controllers.h"
+#include "lang/lang_keys.h"
+#include "main/main_app_config.h"
+#include "main/main_session.h"
#include "settings/settings_premium.h"
#include "settings/settings_privacy_controllers.h"
#include "settings/settings_privacy_security.h"
-#include "calls/calls_instance.h"
-#include "lang/lang_keys.h"
-#include "apiwrap.h"
-#include "main/main_app_config.h"
-#include "main/main_session.h"
-#include "data/data_user.h"
-#include "data/data_chat.h"
-#include "data/data_channel.h"
-#include "data/data_peer_values.h"
+#include "ui/boxes/peer_qr_box.h"
+#include "ui/controls/invite_link_buttons.h"
+#include "ui/controls/invite_link_label.h"
+#include "ui/effects/premium_graphics.h"
+#include "ui/layers/generic_box.h"
+#include "ui/painter.h"
+#include "ui/text/format_values.h"
+#include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
+#include "ui/vertical_list.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/checkbox.h"
+#include "ui/widgets/continuous_sliders.h"
+#include "ui/widgets/popup_menu.h"
+#include "ui/widgets/shadow.h"
+#include "ui/wrap/slide_wrap.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
-#include "styles/style_settings.h"
+#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
+#include "styles/style_settings.h"
#include "styles/style_window.h"
namespace {
@@ -1293,7 +1298,7 @@ void EditDirectMessagesPriceBox(
object_ptr>(
box,
object_ptr(box)),
- {});
+ style::margins());
wrap->toggle(savedValue.has_value(), anim::type::instant);
wrap->toggleOn(toggle->toggledChanges());
@@ -1312,10 +1317,62 @@ void EditDirectMessagesPriceBox(
*result = stars;
}, box->lifetime());
+ if (const auto username = channel->username(); !username.isEmpty()) {
+ Ui::AddSkip(inner);
+ Ui::AddSubsectionTitle(
+ inner,
+ tr::lng_manage_monoforum_link_subtitle());
+
+ constexpr auto kDirectParam = "?direct"_cs;
+ const auto link = channel->session().createInternalLinkFull(username)
+ + kDirectParam.utf8();
+ const auto copyLink = [=] {
+ TextUtilities::SetClipboardText(TextForMimeData::Simple(link));
+ box->uiShow()->showToast(tr::lng_group_invite_copied(tr::now));
+ };
+ const auto shareLink = [=] {
+ box->uiShow()->showBox(ShareInviteLinkBox(channel, link));
+ };
+ const auto createMenu = [=] {
+ auto result = base::make_unique_q(
+ inner,
+ st::popupMenuWithIcons);
+ result->addAction(
+ tr::lng_group_invite_context_qr(tr::now),
+ [=] {
+ box->uiShow()->showBox(Box([=](
+ not_null qrBox) {
+ Ui::FillPeerQrBox(qrBox, channel, link, nullptr);
+ }));
+ },
+ &st::menuIconQrCode);
+ return result;
+ };
+
+ auto linkText = Ui::Text::StripUrlProtocol(link);
+ const auto label = inner->lifetime().make_state(
+ inner,
+ rpl::single(std::move(linkText)),
+ createMenu);
+ inner->add(
+ label->take(),
+ st::inviteLinkFieldPadding);
+
+ label->clicks() | rpl::start_with_next(copyLink, label->lifetime());
+
+ Ui::AddSkip(inner);
+
+ AddCopyShareLinkButtons(inner, copyLink, shareLink);
+ Ui::AddSkip(inner);
+ Ui::AddSkip(inner);
+
+ Ui::AddDivider(inner);
+ }
+
box->addButton(tr::lng_settings_save(), [=] {
- const auto weak = Ui::MakeWeak(box);
+ const auto weak = base::make_weak(box);
callback(toggle->toggled() ? *result : std::optional());
- if (const auto strong = weak.data()) {
+ if (const auto strong = weak.get()) {
strong->closeBox();
}
});
diff --git a/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp b/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp
index 65591aaf9a..2f1add532b 100644
--- a/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp
@@ -143,6 +143,11 @@ private:
int id,
TextWithEntities text,
anim::type animated);
+ void insertTask(
+ int beforeIndex,
+ int id,
+ TextWithEntities text,
+ anim::type animated);
void initTaskField(not_null task, TextWithEntities text);
void checkLastTask();
void validateState();
@@ -150,6 +155,9 @@ private:
void destroy(std::unique_ptr task);
void removeDestroyed(not_null field);
int findField(not_null field) const;
+ void handlePaste(
+ not_null field,
+ const QStringList &list);
not_null _box;
not_null _container;
@@ -187,6 +195,27 @@ void InitField(
options);
}
+[[nodiscard]] QStringList ParsePastedList(const QString &text) {
+ auto list = QStringView(text).split('\n');
+ for (auto i = list.begin(); i != list.end();) {
+ auto text = i->trimmed();
+ if (text.isEmpty() && (i + 1 != list.end())) {
+ i = list.erase(i);
+ } else {
+ *i++ = text;
+ }
+ }
+ if (list.size() < 2) {
+ return {};
+ }
+ auto result = QStringList();
+ result.reserve(list.size());
+ for (const auto &view : list) {
+ result.push_back(view.toString());
+ }
+ return result;
+}
+
not_null CreateWarningLabel(
not_null parent,
not_null field,
@@ -263,7 +292,7 @@ Tasks::Task::Task(
session->user()->isPremium()
? st::createPollOptionFieldPremium
: st::createPollOptionField,
- Ui::InputField::Mode::NoNewlines,
+ Ui::InputField::Mode::MultiLine,
tr::lng_todo_create_list_add()))
, _limit(session->appConfig().todoListItemTextLimit()) {
InitField(outer, _field, session);
@@ -629,24 +658,35 @@ void Tasks::addTask(
int id,
TextWithEntities text,
anim::type animated) {
+ insertTask(_list.size(), id, std::move(text), animated);
+}
+
+void Tasks::insertTask(
+ int beforeIndex,
+ int id,
+ TextWithEntities text,
+ anim::type animated) {
if (full()) {
return;
}
+ Assert(beforeIndex >= 0 && beforeIndex <= _list.size());
if (_list.size() > 1) {
(*(_list.end() - 2))->removePlaceholder();
(*(_list.end() - 2))->toggleRemoveAlways(true);
}
const auto locked = id && _existingLocked;
- _list.push_back(std::make_unique(
- _box,
- _container,
- &_controller->session(),
- id,
- _position + _list.size() + _destroyed.size(),
- locked));
- const auto field = _list.back()->field();
+ const auto i = _list.insert(
+ begin(_list) + beforeIndex,
+ std::make_unique(
+ _box,
+ _container,
+ &_controller->session(),
+ id,
+ _position + beforeIndex + _destroyed.size(),
+ locked));
+ const auto field = i->get()->field();
if (!locked) {
- initTaskField(_list.back().get(), std::move(text));
+ initTaskField(i->get(), std::move(text));
} else {
InitMessageFieldHandlers(
_controller,
@@ -659,7 +699,7 @@ void Tasks::addTask(
});
}
field->finishAnimating();
- _list.back()->show(animated);
+ i->get()->show(animated);
fixShadows();
}
@@ -706,6 +746,14 @@ void Tasks::initTaskField(not_null task, TextWithEntities text) {
}, field->lifetime());
field->changes(
) | rpl::start_with_next([=] {
+ auto list = ParsePastedList(field->getLastText());
+ if (!list.empty()) {
+ field->setText(list.front());
+ field->forceProcessContentsChanges();
+
+ list.pop_front();
+ handlePaste(field, list);
+ }
Ui::PostponeCall(crl::guard(field, [=] {
validateState();
}));
@@ -793,6 +841,27 @@ int Tasks::findField(not_null field) const {
return result;
}
+void Tasks::handlePaste(
+ not_null field,
+ const QStringList &list) {
+ const auto index = findField(field);
+ for (auto i = 0, count = int(list.size()); i != count; ++i) {
+ insertTask(
+ index + 1 + i,
+ 0, // id
+ TextWithEntities{ list[i] },
+ anim::type::instant);
+ }
+ const auto last = std::min(
+ int(index + list.size()),
+ int(_list.size()) - 1);
+ const auto add = _list[last]->field();
+ crl::on_main(add, [=] {
+ add->setCursorPosition(add->getLastText().size());
+ add->setFocus();
+ });
+}
+
void Tasks::checkLastTask() {
removeEmptyTail();
addEmptyTask();
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
index be4531aec5..7584c9f137 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_box.cpp
@@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/filter_icons.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
+#include "ui/rect.h"
#include "ui/power_saving.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
@@ -593,7 +594,7 @@ void EditFilterBox(
) | rpl::start_with_next([=](const QRect &r) {
const auto h = st::normalFont->height;
preview->setGeometry(
- colors->x(),
+ rect::right(colors) - st::settingsFilterTagPreviewSkip,
r.y() + (r.height() - h) / 2 + st::lineWidth,
colors->width(),
h);
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
index 90dd083bd1..d420be8351 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
@@ -538,25 +538,24 @@ void LinkController::addHeader(not_null container) {
const auto isStatic = _filterTitle.isStatic;
verticalLayout->add(
- object_ptr>(
+ object_ptr(
verticalLayout,
- object_ptr(
- verticalLayout,
- (_data.url.isEmpty()
- ? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
- : tr::lng_filters_link_share_about(
- lt_folder,
- rpl::single(Ui::Text::Wrapped(
- _filterTitle.text,
- EntityType::Bold)),
- Ui::Text::WithEntities)),
- st::settingsFilterDividerLabel,
- st::defaultPopupMenu,
- Core::TextContext({
- .session = &_window->session(),
- .customEmojiLoopLimit = isStatic ? -1 : 0,
- }))),
- st::filterLinkDividerLabelPadding);
+ (_data.url.isEmpty()
+ ? tr::lng_filters_link_no_about(Ui::Text::WithEntities)
+ : tr::lng_filters_link_share_about(
+ lt_folder,
+ rpl::single(Ui::Text::Wrapped(
+ _filterTitle.text,
+ EntityType::Bold)),
+ Ui::Text::WithEntities)),
+ st::settingsFilterDividerLabel,
+ st::defaultPopupMenu,
+ Core::TextContext({
+ .session = &_window->session(),
+ .customEmojiLoopLimit = isStatic ? -1 : 0,
+ })),
+ st::filterLinkDividerLabelPadding,
+ style::al_top)->setTryMakeSimilarLines(true);
verticalLayout->geometryValue(
) | rpl::start_with_next([=](const QRect &r) {
@@ -582,7 +581,7 @@ void LinkController::addLinkBlock(not_null container) {
using namespace Settings;
const auto link = _data.url;
- const auto weak = Ui::MakeWeak(container);
+ const auto weak = base::make_weak(container);
const auto copyLink = crl::guard(weak, [=] {
CopyInviteLink(delegate()->peerListUiShow(), link);
});
diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
index 8df343cec4..da7aef097f 100644
--- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
@@ -24,7 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
-#include "ui/widgets/label_with_custom_emoji.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h"
@@ -53,9 +52,8 @@ void GiftCreditsBox(
Ui::AddSkip(content);
const auto &stUser = st::premiumGiftsUserpicButton;
const auto userpicWrap = content->add(
- object_ptr>(
- content,
- object_ptr(content, peer, stUser)));
+ object_ptr(content, peer, stUser),
+ style::al_top);
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -78,19 +76,17 @@ void GiftCreditsBox(
u"internal:stars_examples"_q);
});
content->add(
- object_ptr>(
+ object_ptr(
content,
- Ui::CreateLabelWithCustomEmoji(
- content,
- tr::lng_credits_box_history_entry_gift_out_about(
- lt_user,
- rpl::single(TextWithEntities{ peer->shortName() }),
- lt_link,
- std::move(link),
- Ui::Text::RichLangValue),
- Core::TextContext({ .session = &peer->session() }),
- st::creditsBoxAbout)),
- st::boxRowPadding);
+ tr::lng_credits_box_history_entry_gift_out_about(
+ lt_user,
+ rpl::single(TextWithEntities{ peer->shortName() }),
+ lt_link,
+ std::move(link),
+ Ui::Text::RichLangValue),
+ st::creditsBoxAbout),
+ st::boxRowPadding,
+ style::al_top);
}
Ui::AddSkip(content);
Ui::AddSkip(box->verticalLayout());
@@ -101,6 +97,7 @@ void GiftCreditsBox(
peer,
CreditsAmount(),
[=] { gifted(); box->uiShow()->hideLayer(); },
+ box->showFinishes(),
tr::lng_credits_summary_options_subtitle(),
{});
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 425e816af4..9d674b9059 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_premium.h"
#include "api/api_premium_option.h"
#include "apiwrap.h"
+#include "base/event_filter.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
@@ -31,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget.
+#include "info/channel_statistics/earn/earn_icons.h"
#include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_values.h"
#include "lang/lang_keys.h"
@@ -54,6 +56,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
+#include "ui/text/custom_emoji_helper.h"
+#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/checkbox.h"
@@ -75,9 +79,81 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
-constexpr auto kRarityTooltipDuration = 3 * crl::time(1000);
+constexpr auto kTooltipDuration = 3 * crl::time(1000);
+constexpr auto kPriceTooltipDuration = 6 * crl::time(1000);
constexpr auto kHorizontalBar = QChar(0x2015);
+struct InfoTooltipData {
+ not_null parent;
+ Ui::ImportantTooltip *raw = nullptr;
+};
+
+void ShowInfoTooltip(
+ std::shared_ptr data,
+ not_null target,
+ rpl::producer text,
+ int duration) {
+ if (data->raw) {
+ data->raw->toggleAnimated(false);
+ }
+ const auto parent = data->parent;
+ const auto tooltip = Ui::CreateChild(
+ parent,
+ Ui::MakeNiceTooltipLabel(
+ parent,
+ std::move(text),
+ st::boxWideWidth,
+ st::defaultImportantTooltipLabel),
+ st::defaultImportantTooltip);
+ tooltip->toggleFast(false);
+
+ base::install_event_filter(tooltip, qApp, [=](not_null e) {
+ if (e->type() == QEvent::MouseButtonPress) {
+ tooltip->toggleAnimated(false);
+ }
+ return base::EventFilterResult::Continue;
+ });
+
+ const auto update = [=] {
+ const auto geometry = Ui::MapFrom(parent, target, target->rect());
+ const auto countPosition = [=](QSize size) {
+ const auto left = geometry.x()
+ + (geometry.width() - size.width()) / 2;
+ const auto right = parent->width()
+ - st::normalFont->spacew;
+ return QPoint(
+ std::max(std::min(left, right - size.width()), 0),
+ geometry.y() - size.height() - st::normalFont->descent);
+ };
+ tooltip->pointAt(geometry, RectPart::Top, countPosition);
+ };
+ parent->widthValue(
+ ) | rpl::start_with_next(update, tooltip->lifetime());
+
+ update();
+ tooltip->toggleAnimated(true);
+
+ data->raw = tooltip;
+ tooltip->shownValue() | rpl::filter(
+ !rpl::mappers::_1
+ ) | rpl::start_with_next([=] {
+ crl::on_main(tooltip, [=] {
+ if (tooltip->isHidden()) {
+ if (data->raw == tooltip) {
+ data->raw = nullptr;
+ }
+ delete tooltip;
+ }
+ });
+ }, tooltip->lifetime());
+
+ base::timer_once(
+ duration
+ ) | rpl::start_with_next([=] {
+ tooltip->toggleAnimated(false);
+ }, tooltip->lifetime());
+}
+
[[nodiscard]] QString CreateMessageLink(
not_null session,
PeerId peerId,
@@ -110,6 +186,33 @@ constexpr auto kHorizontalBar = QChar(0x2015);
};
}
+[[nodiscard]] TextWithEntities FormatValuePrice(
+ int64 price,
+ QString currency,
+ bool approximately = false) {
+ auto result = TextWithEntities();
+ if (approximately) {
+ result.append('~');
+ }
+ return result.append(Ui::FillAmountAndCurrency(price, currency));
+}
+
+[[nodiscard]] TextWithEntities FormatValueDate(TimeId date) {
+ const auto parsed = base::unixtime::parse(date).date();
+ const auto day = parsed.day();
+ const auto month = parsed.month();
+ const auto year = parsed.year();
+ return { tr::lng_month_day_year(
+ tr::now,
+ lt_month,
+ Lang::MonthDay(month)(tr::now),
+ lt_day,
+ QString::number(day),
+ lt_year,
+ QString::number(year))
+ };
+}
+
[[nodiscard]] object_ptr MakeLinkCopyIcon(
not_null parent) {
auto result = object_ptr(parent);
@@ -156,6 +259,61 @@ constexpr auto kHorizontalBar = QChar(0x2015);
: st::giveawayGiftCodeValueMultiline));
}
+[[nodiscard]] object_ptr MakeValueWithSmallButton(
+ not_null table,
+ not_null value,
+ rpl::producer buttonText,
+ Fn button)> handler = nullptr,
+ int topSkip = 0) {
+ class MarginedWidget final : public Ui::RpWidget {
+ public:
+ using RpWidget::RpWidget;
+ QMargins getMargins() const override {
+ return { 0, 0, 0, st::giveawayGiftCodePeerMargin.bottom() };
+ }
+ };
+ auto result = object_ptr(table);
+ const auto raw = result.data();
+
+ value->setParent(raw);
+ value->show();
+
+ const auto button = Ui::CreateChild(
+ raw,
+ std::move(buttonText),
+ table->st().smallButton);
+ button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+ if (handler) {
+ button->setClickedCallback([button, handler = std::move(handler)] {
+ handler(button);
+ });
+ } else {
+ button->setAttribute(Qt::WA_TransparentForMouseEvents);
+ }
+ rpl::combine(
+ raw->widthValue(),
+ button->widthValue(),
+ value->naturalWidthValue()
+ ) | rpl::start_with_next([=](int width, int buttonWidth, int) {
+ const auto buttonSkip = st::normalFont->spacew + buttonWidth;
+ value->resizeToNaturalWidth(width - buttonSkip);
+ value->moveToLeft(0, 0, width);
+ button->moveToLeft(
+ rect::right(value) + st::normalFont->spacew,
+ (topSkip
+ + (table->st().defaultValue.style.font->ascent
+ - table->st().smallButton.style.font->ascent)),
+ width);
+ }, value->lifetime());
+
+ value->heightValue() | rpl::start_with_next([=](int height) {
+ const auto bottom = st::giveawayGiftCodePeerMargin.bottom();
+ raw->resize(raw->width(), height + bottom);
+ }, raw->lifetime());
+
+ return result;
+}
+
[[nodiscard]] object_ptr MakePeerTableValue(
not_null table,
std::shared_ptr show,
@@ -174,38 +332,18 @@ constexpr auto kHorizontalBar = QChar(0x2015);
raw,
(button && handler) ? peer->shortName() : peer->name(),
table->st().defaultValue);
- const auto send = (button && handler)
- ? Ui::CreateChild(
- raw,
- std::move(button),
- table->st().smallButton)
- : nullptr;
- if (send) {
- send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
- send->setClickedCallback(std::move(handler));
- }
- rpl::combine(
- raw->widthValue(),
- send ? send->widthValue() : rpl::single(0)
- ) | rpl::start_with_next([=](int width, int sendWidth) {
+
+ raw->widthValue() | rpl::start_with_next([=](int width) {
const auto position = st::giveawayGiftCodeNamePosition;
- const auto sendSkip = sendWidth
- ? (st::normalFont->spacew + sendWidth)
- : 0;
- label->resizeToNaturalWidth(width - position.x() - sendSkip);
+ label->resizeToNaturalWidth(width - position.x());
label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width);
- if (send) {
- send->moveToLeft(
- position.x() + label->width() + st::normalFont->spacew,
- (position.y()
- + table->st().defaultValue.style.font->ascent
- - table->st().smallButton.style.font->ascent),
- width);
- }
}, label->lifetime());
+ label->naturalWidthValue() | rpl::start_with_next([=](int width) {
+ raw->setNaturalWidth(st::giveawayGiftCodeNamePosition.x() + width);
+ }, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c);
@@ -214,7 +352,15 @@ constexpr auto kHorizontalBar = QChar(0x2015);
show->showBox(PrepareShortInfoBox(peer, show));
});
- return result;
+ if (!button || !handler) {
+ return result;
+ }
+ return MakeValueWithSmallButton(
+ table,
+ result.release(),
+ std::move(button),
+ [=](not_null button) { handler(); },
+ st::giveawayGiftCodeNamePosition.y());
}
[[nodiscard]] object_ptr MakePeerWithStatusValue(
@@ -222,24 +368,21 @@ constexpr auto kHorizontalBar = QChar(0x2015);
std::shared_ptr show,
PeerId id,
Fn, EmojiStatusId)> pushStatusId) {
- auto result = object_ptr(table);
+ auto result = object_ptr(table);
const auto raw = result.data();
- const auto &st = st::giveawayGiftCodeUserpic;
- raw->resize(raw->width(), st.photoSize);
+ const auto peerLabel = MakePeerTableValue(table, show, id).release();
+ peerLabel->setParent(raw);
+ peerLabel->show();
- const auto peer = show->session().data().peer(id);
- const auto userpic = Ui::CreateChild(raw, peer, st);
- const auto label = Ui::CreateChild(
- raw,
- peer->name(),
- table->st().defaultValue);
+ raw->resize(raw->width(), peerLabel->height());
using namespace Info::Profile;
struct State {
rpl::variable content;
};
- const auto state = label->lifetime().make_state();
+ const auto peer = show->session().data().peer(id);
+ const auto state = peerLabel->lifetime().make_state();
state->content = EmojiStatusIdValue(
peer
) | rpl::map([=](EmojiStatusId emojiStatusId) {
@@ -252,7 +395,7 @@ constexpr auto kHorizontalBar = QChar(0x2015);
.emojiStatusId = emojiStatusId,
};
});
- const auto badge = label->lifetime().make_state(
+ const auto badge = peerLabel->lifetime().make_state(
raw,
st::infoPeerBadge,
&peer->session(),
@@ -270,32 +413,19 @@ constexpr auto kHorizontalBar = QChar(0x2015);
raw->widthValue(),
rpl::single(rpl::empty) | rpl::then(badge->updated())
) | rpl::start_with_next([=](int width, const auto &) {
- const auto position = st::giveawayGiftCodeNamePosition;
const auto badgeWidget = badge->widget();
const auto badgeSkip = badgeWidget
? (st::normalFont->spacew + badgeWidget->width())
: 0;
- label->resizeToNaturalWidth(width - position.x() - badgeSkip);
- label->moveToLeft(position.x(), position.y(), width);
- const auto top = (raw->height() - userpic->height()) / 2;
- userpic->moveToLeft(0, top, width);
+ peerLabel->resizeToNaturalWidth(width - badgeSkip);
+ peerLabel->moveToLeft(0, 0, width);
if (badgeWidget) {
badgeWidget->moveToLeft(
- position.x() + label->width() + st::normalFont->spacew,
- (position.y()
- + table->st().defaultValue.style.font->ascent
- - table->st().smallButton.style.font->ascent),
+ peerLabel->width() + st::normalFont->spacew,
+ st::giftBoxByStarsStarTop,
width);
}
- }, label->lifetime());
-
- userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
- label->setAttribute(Qt::WA_TransparentForMouseEvents);
- label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c);
-
- raw->setClickedCallback([=] {
- show->showBox(PrepareShortInfoBox(peer, show));
- });
+ }, raw->lifetime());
return result;
}
@@ -340,7 +470,7 @@ void AddTableRow(
not_null table,
rpl::producer label,
object_ptr value,
- style::margins valueMargins) {
+ style::margins valueMargins = st::giveawayGiftCodeValueMargin) {
table->addRow(
(label
? object_ptr(
@@ -353,55 +483,108 @@ void AddTableRow(
valueMargins);
}
+[[nodiscard]] object_ptr MakePriceWithChangePercentValue(
+ not_null table,
+ const std::shared_ptr &value) {
+ auto label = object_ptr(
+ table,
+ rpl::single(FormatValuePrice(value->lastSalePrice, value->currency)),
+ table->st().defaultValue);
+ label->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+ const auto initial = value->initialSalePrice;
+ if (!initial) {
+ return label;
+ }
+
+ const auto diff = (100 * (value->lastSalePrice - initial))
+ / float64(initial);
+ const auto use = (std::abs(diff) >= 10.)
+ ? base::SafeRound(diff)
+ : (int(base::SafeRound(diff * 100)) / 100.);
+ const auto prefix = (use > 0) ? u"+"_q : QString();
+ const auto percent = Lang::FormatExactCountDecimal(use) + '%';
+ auto text = rpl::single(prefix + percent);
+ return MakeValueWithSmallButton(table, label, std::move(text));
+}
+
+[[nodiscard]] object_ptr MakePriceValueWithTooltip(
+ not_null table,
+ std::shared_ptr data,
+ TextWithEntities price,
+ TextWithEntities tooltip) {
+ const auto label = Ui::CreateChild(
+ table,
+ rpl::single(price),
+ table->st().defaultValue);
+ label->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+ const auto handler = [=](not_null button) {
+ ShowInfoTooltip(
+ data,
+ button,
+ rpl::single(tooltip),
+ kPriceTooltipDuration);
+ };
+ auto text = rpl::single(u"?"_q);
+ return MakeValueWithSmallButton(table, label, std::move(text), handler);
+}
+
+[[nodiscard]] object_ptr MakeMinimumPriceValue(
+ not_null table,
+ std::shared_ptr tooltip,
+ const std::shared_ptr &unique) {
+ const auto &value = unique->value;
+ const auto text = FormatValuePrice(value->minimumPrice, value->currency);
+ return MakePriceValueWithTooltip(
+ table,
+ std::move(tooltip),
+ text,
+ tr::lng_gift_value_minimum_price_tooltip(
+ tr::now,
+ lt_amount,
+ Ui::Text::Bold(text.text),
+ lt_gift,
+ Ui::Text::Bold(unique->title),
+ Ui::Text::WithEntities));
+}
+
+[[nodiscard]] object_ptr MakeAveragePriceValue(
+ not_null table,
+ std::shared_ptr tooltip,
+ const std::shared_ptr &unique) {
+ const auto &value = unique->value;
+ const auto text = FormatValuePrice(value->averagePrice, value->currency);
+ return MakePriceValueWithTooltip(
+ table,
+ std::move(tooltip),
+ text,
+ tr::lng_gift_value_average_price_tooltip(
+ tr::now,
+ lt_amount,
+ Ui::Text::Bold(text.text),
+ lt_gift,
+ Ui::Text::Bold(unique->title),
+ Ui::Text::WithEntities));
+}
+
[[nodiscard]] object_ptr MakeAttributeValue(
not_null table,
const Data::UniqueGiftAttribute &attribute,
Fn, int)> showTooltip) {
- auto result = object_ptr(table);
- const auto raw = result.data();
-
const auto label = Ui::CreateChild(
- raw,
+ table,
attribute.name,
table->st().defaultValue);
- const auto permille = attribute.rarityPermille;
-
- const auto text = QString::number(permille / 10.) + '%';
- const auto rarity = Ui::CreateChild(
- raw,
- rpl::single(text),
- table->st().smallButton);
- rarity->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
-
- rpl::combine(
- raw->widthValue(),
- rarity->widthValue()
- ) | rpl::start_with_next([=](int width, int convertWidth) {
- const auto convertSkip = convertWidth
- ? (st::normalFont->spacew + convertWidth)
- : 0;
- label->resizeToNaturalWidth(width - convertSkip);
- label->moveToLeft(0, 0, width);
- rarity->moveToLeft(
- label->width() + st::normalFont->spacew,
- (table->st().defaultValue.style.font->ascent
- - table->st().smallButton.style.font->ascent),
- width);
- }, label->lifetime());
-
- label->heightValue() | rpl::start_with_next([=](int height) {
- raw->resize(
- raw->width(),
- height + st::giveawayGiftCodeValueMargin.bottom());
- }, raw->lifetime());
-
label->setAttribute(Qt::WA_TransparentForMouseEvents);
- rarity->setClickedCallback([=] {
- showTooltip(rarity, permille);
- });
+ const auto permille = attribute.rarityPermille;
+ auto text = rpl::single(QString::number(permille / 10.) + '%');
- return result;
+ const auto handler = [=](not_null button) {
+ showTooltip(button, permille);
+ };
+ return MakeValueWithSmallButton(table, label, std::move(text), handler);
}
[[nodiscard]] object_ptr MakeStarGiftStarsValue(
@@ -409,113 +592,88 @@ void AddTableRow(
std::shared_ptr show,
const Data::CreditsHistoryEntry &entry,
Fn convertToStars) {
- auto result = object_ptr(table);
- const auto raw = result.data();
-
- const auto star = Ui::CreateSingleStarWidget(
- raw,
- table->st().defaultValue.style.font->height);
- const auto label = Ui::CreateChild(
- raw,
- Lang::FormatCreditsAmountDecimal(entry.credits),
+ auto helper = Ui::Text::CustomEmojiHelper();
+ const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji(
+ )).append(' ').append(Lang::FormatCreditsAmountDecimal(entry.credits));
+ auto label = object_ptr(
+ table,
+ rpl::single(price),
table->st().defaultValue,
- st::defaultPopupMenu);
-
- const auto convert = convertToStars
- ? Ui::CreateChild(
- raw,
- tr::lng_gift_sell_small(
- lt_count_decimal,
- rpl::single(entry.starsConverted * 1.)),
- table->st().smallButton)
- : nullptr;
- if (convert) {
- using namespace Ui;
- convert->setTextTransform(RoundButton::TextTransform::NoTransform);
- convert->setClickedCallback(std::move(convertToStars));
- }
- rpl::combine(
- raw->widthValue(),
- convert ? convert->widthValue() : rpl::single(0)
- ) | rpl::start_with_next([=](int width, int convertWidth) {
- const auto convertSkip = convertWidth
- ? (st::normalFont->spacew + convertWidth)
- : 0;
- const auto labelLeft = rect::right(star) + st::normalFont->spacew;
- label->resizeToNaturalWidth(width - convertSkip - labelLeft);
- star->moveToLeft(0, 0, width);
- label->moveToLeft(labelLeft, 0, width);
- if (convert) {
- convert->moveToLeft(
- rect::right(label) + st::normalFont->spacew,
- (table->st().defaultValue.style.font->ascent
- - table->st().smallButton.style.font->ascent),
- width);
- }
- }, label->lifetime());
-
- label->heightValue() | rpl::start_with_next([=](int height) {
- raw->resize(
- raw->width(),
- height + st::giveawayGiftCodeValueMargin.bottom());
- }, raw->lifetime());
-
+ st::defaultPopupMenu,
+ helper.context());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
- return result;
+ if (!convertToStars) {
+ return label;
+ }
+ const auto handler = [=](not_null button) {
+ convertToStars();
+ };
+ auto text = tr::lng_gift_sell_small(
+ lt_count_decimal,
+ rpl::single(entry.starsConverted * 1.));
+ return MakeValueWithSmallButton(
+ table,
+ label.release(),
+ std::move(text),
+ handler);
}
-[[nodiscard]] object_ptr MakeNonUniqueStatusTableValue(
+[[nodiscard]] object_ptr MakeUniqueGiftValueValue(
not_null table,
- Fn startUpgrade) {
- auto result = object_ptr(table);
- const auto raw = result.data();
+ std::shared_ptr show,
+ const Data::CreditsHistoryEntry &entry,
+ Settings::CreditsEntryBoxStyleOverrides st) {
+ const auto unique = entry.uniqueGift;
+ const auto value = unique ? unique->value : nullptr;
+ const auto loading = std::make_shared(false);
const auto label = Ui::CreateChild(
- raw,
- tr::lng_gift_unique_status_non(),
+ table,
+ rpl::single(
+ FormatValuePrice(value->valuePrice, value->currency, true)),
table->st().defaultValue,
st::defaultPopupMenu);
-
- const auto upgrade = startUpgrade
- ? Ui::CreateChild(
- raw,
- tr::lng_gift_unique_status_upgrade(),
- table->st().smallButton)
- : (Ui::RoundButton*)(nullptr);
- if (upgrade) {
- using namespace Ui;
- upgrade->setTextTransform(RoundButton::TextTransform::NoTransform);
- upgrade->setClickedCallback(startUpgrade);
- }
-
- rpl::combine(
- raw->widthValue(),
- upgrade ? upgrade->widthValue() : rpl::single(0)
- ) | rpl::start_with_next([=](int width, int toggleWidth) {
- const auto toggleSkip = toggleWidth
- ? (st::normalFont->spacew + toggleWidth)
- : 0;
- label->resizeToNaturalWidth(width - toggleSkip);
- label->moveToLeft(0, 0, width);
- if (upgrade) {
- upgrade->moveToLeft(
- label->width() + st::normalFont->spacew,
- (table->st().defaultValue.style.font->ascent
- - table->st().smallButton.style.font->ascent),
- width);
- }
- }, label->lifetime());
-
- label->heightValue() | rpl::start_with_next([=](int height) {
- raw->resize(
- raw->width(),
- height + st::giveawayGiftCodeValueMargin.bottom());
- }, raw->lifetime());
-
label->setAttribute(Qt::WA_TransparentForMouseEvents);
- return result;
+ const auto handler = [=](not_null