diff --git a/.gitmodules b/.gitmodules
index a2a91a3f1..23ed731c2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,7 +3,7 @@
url = https://github.com/telegramdesktop/libtgvoip
[submodule "Telegram/ThirdParty/GSL"]
path = Telegram/ThirdParty/GSL
- url = https://github.com/desktop-app/GSL.git
+ url = https://github.com/Microsoft/GSL.git
[submodule "Telegram/ThirdParty/xxHash"]
path = Telegram/ThirdParty/xxHash
url = https://github.com/Cyan4973/xxHash.git
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 680ac3d5a..82f1089f8 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -192,6 +192,8 @@ PRIVATE
api/api_bot.h
api/api_chat_filters.cpp
api/api_chat_filters.h
+ api/api_chat_filters_remove_manager.cpp
+ api/api_chat_filters_remove_manager.h
api/api_chat_invite.cpp
api/api_chat_invite.h
api/api_chat_links.cpp
@@ -716,6 +718,8 @@ PRIVATE
data/data_thread.h
data/data_types.cpp
data/data_types.h
+ data/data_unread_value.cpp
+ data/data_unread_value.h
data/data_user.cpp
data/data_user.h
data/data_user_photos.cpp
@@ -1062,6 +1066,10 @@ PRIVATE
info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp
info/profile/info_profile_widget.h
+ info/reactions_list/info_reactions_list_widget.cpp
+ 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_sublists_widget.cpp
info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp
@@ -1075,6 +1083,7 @@ PRIVATE
info/statistics/info_statistics_list_controllers.h
info/statistics/info_statistics_recent_message.cpp
info/statistics/info_statistics_recent_message.h
+ info/statistics/info_statistics_tag.h
info/statistics/info_statistics_widget.cpp
info/statistics/info_statistics_widget.h
info/stories/info_stories_inner_widget.cpp
@@ -1111,6 +1120,10 @@ PRIVATE
info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h
+ inline_bots/inline_bot_confirm_prepared.cpp
+ inline_bots/inline_bot_confirm_prepared.h
+ inline_bots/inline_bot_downloads.cpp
+ inline_bots/inline_bot_downloads.h
inline_bots/inline_bot_layout_internal.cpp
inline_bots/inline_bot_layout_internal.h
inline_bots/inline_bot_layout_item.cpp
@@ -1242,6 +1255,8 @@ PRIVATE
media/streaming/media_streaming_player.h
media/streaming/media_streaming_reader.cpp
media/streaming/media_streaming_reader.h
+ media/streaming/media_streaming_round_preview.cpp
+ media/streaming/media_streaming_round_preview.h
media/streaming/media_streaming_utility.cpp
media/streaming/media_streaming_utility.h
media/streaming/media_streaming_video_track.cpp
@@ -1581,6 +1596,8 @@ PRIVATE
ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h
+ ui/chat/sponsored_message_bar.cpp
+ ui/chat/sponsored_message_bar.h
ui/controls/emoji_button_factory.cpp
ui/controls/emoji_button_factory.h
ui/controls/location_picker.cpp
@@ -1612,6 +1629,8 @@ PRIVATE
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/countryinput.cpp
ui/countryinput.h
ui/dynamic_thumbnails.cpp
@@ -1931,7 +1950,7 @@ endif()
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
-if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+if (MSVC)
target_link_libraries(Telegram
PRIVATE
delayimp
@@ -2028,7 +2047,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
base/platform/win/base_windows_safe_library.h
)
target_include_directories(Updater PRIVATE ${lib_base_loc})
- if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ if (MSVC)
target_link_libraries(Updater
PRIVATE
delayimp
diff --git a/Telegram/Resources/animations/hello_status.tgs b/Telegram/Resources/animations/hello_status.tgs
new file mode 100644
index 000000000..b48182c2c
Binary files /dev/null and b/Telegram/Resources/animations/hello_status.tgs differ
diff --git a/Telegram/Resources/art/round_placeholder.jpg b/Telegram/Resources/art/round_placeholder.jpg
new file mode 100644
index 000000000..cbdc9aee3
Binary files /dev/null and b/Telegram/Resources/art/round_placeholder.jpg differ
diff --git a/Telegram/Resources/icons/chat/input_video.png b/Telegram/Resources/icons/chat/input_video.png
new file mode 100644
index 000000000..34a71853d
Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video.png differ
diff --git a/Telegram/Resources/icons/chat/input_video@2x.png b/Telegram/Resources/icons/chat/input_video@2x.png
new file mode 100644
index 000000000..0657613a8
Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video@2x.png differ
diff --git a/Telegram/Resources/icons/chat/input_video@3x.png b/Telegram/Resources/icons/chat/input_video@3x.png
new file mode 100644
index 000000000..9fb24d542
Binary files /dev/null and b/Telegram/Resources/icons/chat/input_video@3x.png differ
diff --git a/Telegram/Resources/icons/menu/edited_status.png b/Telegram/Resources/icons/menu/edited_status.png
new file mode 100644
index 000000000..70fb24466
Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status.png differ
diff --git a/Telegram/Resources/icons/menu/edited_status@2x.png b/Telegram/Resources/icons/menu/edited_status@2x.png
new file mode 100644
index 000000000..004167a14
Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status@2x.png differ
diff --git a/Telegram/Resources/icons/menu/edited_status@3x.png b/Telegram/Resources/icons/menu/edited_status@3x.png
new file mode 100644
index 000000000..1acdf8ea9
Binary files /dev/null and b/Telegram/Resources/icons/menu/edited_status@3x.png differ
diff --git a/Telegram/Resources/icons/player/player_settings.png b/Telegram/Resources/icons/player/player_settings.png
new file mode 100644
index 000000000..65a420de0
Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings.png differ
diff --git a/Telegram/Resources/icons/player/player_settings@2x.png b/Telegram/Resources/icons/player/player_settings@2x.png
new file mode 100644
index 000000000..af78b2666
Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings@2x.png differ
diff --git a/Telegram/Resources/icons/player/player_settings@3x.png b/Telegram/Resources/icons/player/player_settings@3x.png
new file mode 100644
index 000000000..da1ec8c72
Binary files /dev/null and b/Telegram/Resources/icons/player/player_settings@3x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/input_round_s.png b/Telegram/Resources/icons/voice_lock/input_round_s.png
new file mode 100644
index 000000000..549dd1ab1
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s.png differ
diff --git a/Telegram/Resources/icons/voice_lock/input_round_s@2x.png b/Telegram/Resources/icons/voice_lock/input_round_s@2x.png
new file mode 100644
index 000000000..4ab801a46
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s@2x.png differ
diff --git a/Telegram/Resources/icons/voice_lock/input_round_s@3x.png b/Telegram/Resources/icons/voice_lock/input_round_s@3x.png
new file mode 100644
index 000000000..878aa7e6a
Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_round_s@3x.png differ
diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js
index fda34772f..9bd67163a 100644
--- a/Telegram/Resources/iv_html/page.js
+++ b/Telegram/Resources/iv_html/page.js
@@ -72,6 +72,9 @@ var IV = {
}
},
frameKeyDown: function (e) {
+ const key0 = (e.key === '0')
+ || (e.code === 'Key0')
+ || (e.keyCode === 48);
const keyW = (e.key === 'w')
|| (e.code === 'KeyW')
|| (e.keyCode === 87);
@@ -81,12 +84,12 @@ var IV = {
const keyM = (e.key === 'm')
|| (e.code === 'KeyM')
|| (e.keyCode === 77);
- if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
+ if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
e.preventDefault();
IV.notify({
event: 'keydown',
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
- key: keyW ? 'w' : keyQ ? 'q' : 'm',
+ key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
});
} else if (e.key === 'Escape' || e.keyCode === 27) {
e.preventDefault();
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index e13e83477..69205e1f4 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -499,8 +499,9 @@ 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_native_title" = "Native notifications";
+"lng_settings_native_title" = "System integration";
"lng_settings_use_windows" = "Use Windows notifications";
+"lng_settings_skip_in_focus" = "Respect system Focus mode";
"lng_settings_use_native_notifications" = "Use native notifications";
"lng_settings_notifications_position" = "Location on the screen";
"lng_settings_notifications_count" = "Notifications count";
@@ -681,6 +682,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_messages_privacy" = "Messages";
"lng_settings_voices_privacy" = "Voice messages";
"lng_settings_bio_privacy" = "Bio";
+"lng_settings_gifts_privacy" = "Gifts";
"lng_settings_birthday_privacy" = "Date of Birth";
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
"lng_settings_privacy_premium_link" = "Telegram Premium";
@@ -1161,19 +1163,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_blocked_list_subtitle#other" = "{count} blocked users";
"lng_edit_privacy_everyone" = "Everybody";
+"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
"lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
+"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
"lng_edit_privacy_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users";
+"lng_edit_privacy_miniapps" = "Mini Apps";
"lng_edit_privacy_exceptions" = "Add exceptions";
"lng_edit_privacy_user_types" = "User types";
"lng_edit_privacy_users_and_groups" = "Users and groups";
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers";
+"lng_edit_privacy_miniapps_status" = "web mini apps that you use";
"lng_edit_privacy_exceptions_count#one" = "{count} user";
"lng_edit_privacy_exceptions_count#other" = "{count} users";
"lng_edit_privacy_exceptions_premium_and" = "Premium & {users}";
+"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}";
"lng_edit_privacy_exceptions_add" = "Add users";
"lng_edit_privacy_phone_number_title" = "Phone number privacy";
@@ -1227,6 +1234,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
+"lng_edit_privacy_gifts_title" = "Gifts";
+"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile";
+"lng_edit_privacy_gifts_always_empty" = "Always allow";
+"lng_edit_privacy_gifts_never_empty" = "Never allow";
+"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
+"lng_edit_privacy_gifts_always_title" = "Always allow";
+"lng_edit_privacy_gifts_never_title" = "Never allow";
+
"lng_edit_privacy_calls_title" = "Calls";
"lng_edit_privacy_calls_header" = "Who can call me";
"lng_edit_privacy_calls_always_empty" = "Always allow";
@@ -1460,11 +1475,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_open_app" = "Open App";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
+"lng_profile_bot_permissions_title" = "Allow access to";
+"lng_profile_bot_emoji_status_access" = "Emoji Status";
"lng_info_add_as_contact" = "Add to contacts";
"lng_profile_shared_media" = "Shared media";
"lng_profile_suggest_photo" = "Suggest Profile Photo";
+"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
"lng_profile_set_photo_for" = "Set Profile Photo";
+"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
"lng_profile_photo_reset" = "Reset to Original";
+"lng_profile_photo_from_clipboard" = "From clipboard";
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
"lng_profile_suggest_button" = "Suggest";
"lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves.";
@@ -1593,6 +1613,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_bot_public_link" = "Public Link";
"lng_manage_peer_bot_public_links" = "Public Links";
"lng_manage_peer_bot_balance" = "Balance";
+"lng_manage_peer_bot_balance_currency" = "Toncoin";
+"lng_manage_peer_bot_balance_credits" = "Stars";
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
@@ -1870,6 +1892,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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.";
+"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
+"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
"lng_action_gift_sent_subtitle" = "Gift for {user}";
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
@@ -2097,6 +2121,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_public_link_copied" = "Link copied to clipboard.";
"lng_context_about_private_link" = "This link will only work for members of this chat.";
+"lng_public_post_private_hint_ctrl" = "Use Ctrl+Click to copy a non-public link.";
+"lng_public_post_private_hint_cmd" = "Use Cmd+Click to copy a non-public link.";
"lng_forwarded" = "Forwarded from {user}";
"lng_forwarded_story" = "Story from {user}";
@@ -2115,8 +2141,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_recommended_message_title" = "Recommended";
"lng_edited" = "edited";
"lng_commented" = "commented";
+"lng_approximate" = "appx.";
"lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}";
+"lng_approximate_about" = "Estimated date of video publishing.";
"lng_views_tooltip#one" = "Views: {count}";
"lng_views_tooltip#other" = "Views: {count}";
"lng_forwards_tooltip#one" = "Shares: {count}";
@@ -2151,6 +2179,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_cancel" = "Cancel";
"lng_media_video" = "Video";
"lng_media_audio" = "Voice message";
+"lng_media_round" = "Video message";
"lng_media_auto_settings" = "Automatic media download";
"lng_media_auto_in_private" = "In private chats";
@@ -2404,6 +2433,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_summary_history_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance";
+"lng_credits_more_options" = "More Options";
+"lng_credits_balance_me" = "your balance";
+"lng_credits_buy_button" = "Buy More Stars";
"lng_credits_gift_button" = "Gift Stars to Friends";
"lng_credits_box_out_title" = "Confirm Your Purchase";
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
@@ -2442,18 +2474,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
+"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
+"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
"lng_credits_box_history_entry_gift_examples" = "Examples";
"lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
+"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";
+"lng_credits_box_history_entry_floodskip_row" = "Messages";
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
"lng_credits_box_history_entry_id" = "Transaction ID";
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_success_date" = "Transaction date";
"lng_credits_box_history_entry_success_url" = "Transaction link";
"lng_credits_box_history_entry_media" = "Media";
+"lng_credits_box_history_entry_message" = "Message";
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
"lng_credits_box_history_entry_about_link" = "here";
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
@@ -2988,6 +3027,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
"lng_gift_link_reason_chosen" = "You were selected by the channel";
"lng_gift_link_label_date" = "Date";
+"lng_gift_link_label_first_sale" = "First Sale";
+"lng_gift_link_label_last_sale" = "Last Sale";
+"lng_gift_link_label_value" = "Value";
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
"lng_gift_link_also_send_link" = "send this link";
"lng_gift_link_use" = "Use Link";
@@ -3044,8 +3086,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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";
-"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone.";
-"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone.";
+"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
+"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
+"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
+"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
+"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
"lng_gift_convert_sure" = "Convert";
"lng_gift_display_done" = "The gift is now shown on your profile page.";
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
@@ -3054,6 +3099,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sold_out_title" = "Sold Out!";
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
+"lng_gift_send_small" = "send a gift";
+"lng_gift_sell_small#one" = "sell for {count} Star";
+"lng_gift_sell_small#other" = "sell for {count} Stars";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
@@ -3243,11 +3291,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_record_cancel" = "Release outside this field to cancel";
"lng_record_cancel_stories" = "Release outside to cancel";
"lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?";
+"lng_record_lock_cancel_sure_round" = "Do you want to stop recording and discard your video message?";
"lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?";
+"lng_record_listen_cancel_sure_round" = "Do you want to discard your recorded video message?";
"lng_record_lock_discard" = "Discard";
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
+"lng_record_voice_tip" = "Hold to record audio. Click to switch to video.";
+"lng_record_video_tip" = "Hold to record video. Click to switch to audio.";
+"lng_record_audio_problem" = "Could not start audio recording. Please check your microphone.";
+"lng_record_video_problem" = "Could not start video recording. Please check your camera.";
"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
"lng_record_once_active_tooltip" = "The recipient will be able to listen only once.";
+"lng_record_once_active_video" = "The recipient will be able to watch only once.";
"lng_will_be_notified" = "Subscribers will be notified when you post.";
"lng_wont_be_notified" = "Subscribers will receive a silent notification.";
"lng_willbe_history" = "Select a chat to start messaging";
@@ -3276,6 +3331,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_scheduled_send_now" = "Send message now?";
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
+"lng_scheduled_video_tip_title" = "Improving video...";
+"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the best viewing experience.";
+"lng_scheduled_video_tip" = "Processing video may take a few minutes.";
+"lng_scheduled_video_published" = "Video Published.";
+"lng_scheduled_video_view" = "View";
"lng_replies_view#one" = "View {count} Reply";
"lng_replies_view#other" = "View {count} Replies";
@@ -3359,6 +3419,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_settings" = "Settings";
"lng_bot_open" = "Open Bot";
"lng_bot_terms" = "Terms of Use";
+"lng_bot_privacy" = "Privacy Policy";
"lng_bot_reload_page" = "Reload Page";
"lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachment menu so you can access it from any chat.";
"lng_bot_add_to_menu_done" = "Bot added to the menu.";
@@ -3374,6 +3435,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
+"lng_bot_emoji_status_confirm" = "Confirm";
+"lng_bot_emoji_status_title" = "Set Emoji Status";
+"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
+"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
+"lng_bot_emoji_status_access_allow" = "Allow";
+"lng_bot_share_prepared_title" = "Share Message";
+"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
+"lng_bot_share_prepared_button" = "Share With...";
+"lng_bot_download_file" = "Download File";
+"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
+"lng_bot_download_file_button" = "Download";
+"lng_bot_download_starting" = "Starting...";
+"lng_bot_download_failed" = "Failed. {retry}";
+"lng_bot_download_retry" = "Retry";
"lng_bot_status_users#one" = "{count} monthly user";
"lng_bot_status_users#other" = "{count} monthly users";
@@ -3585,6 +3660,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price";
+"lng_context_mention" = "Mention";
+"lng_context_search_from" = "Search messages";
+
"lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context";
"lng_factcheck_whats_this" = "what's this?";
@@ -3682,6 +3760,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
+"lng_reply_in_author" = "Message author";
+"lng_reply_in_chats_list" = "Your chats";
"lng_reply_show_in_chat" = "Show in Chat";
"lng_reply_remove" = "Do Not Reply";
"lng_reply_about_quote" = "You can select a specific part to quote.";
@@ -3869,6 +3949,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_mediaview_downloads" = "Downloads";
"lng_mediaview_playback_speed" = "Playback speed: {speed}";
"lng_mediaview_rotate_video" = "Rotate video";
+"lng_mediaview_quality_auto" = "Auto";
"lng_theme_preview_title" = "Theme Preview";
"lng_theme_preview_generating" = "Generating color theme preview...";
@@ -3950,7 +4031,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration.";
"lng_payments_webview_install_edge" = "Please install {link}.";
-"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkitgtk-6.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
+"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
+"lng_payments_webview_enable_opengl" = "Please enable OpenGL in application settings.";
+"lng_payments_webview_switch_x11" = "Unsupported display server. Please switch to X11.";
"lng_payments_webview_update_windows" = "Please update your system to Windows 8.1 or later.";
"lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
"lng_payments_receipt_label" = "Receipt";
@@ -4310,7 +4393,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'.";
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
-"lng_rights_about_by" = "This admin promoted by {user} at {date}.";
+"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
@@ -4389,8 +4472,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_chat_files" = "Files";
"lng_rights_chat_voice_messages" = "Voice messages";
"lng_rights_chat_video_messages" = "Video messages";
-"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}.";
-"lng_rights_chat_banned_by" = "Banned by {user} at {date}.";
+"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
+"lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
"lng_rights_chat_banned_until_header" = "Restricted until";
"lng_rights_chat_banned_forever" = "Forever";
"lng_rights_chat_banned_day#one" = "For {count} day";
@@ -4999,6 +5082,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
"lng_filters_all" = "All chats";
+"lng_filters_all_short" = "All";
"lng_filters_setup" = "Edit";
"lng_filters_title" = "Folders";
"lng_filters_subtitle" = "My folders";
@@ -5053,6 +5137,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_filters_toast_add" = "{chat} added to {folder} folder";
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
"lng_filters_shareable_status" = "shareable folder";
+"lng_filters_view_subtitle" = "Tabs view";
+"lng_filters_vertical" = "Tabs on the left";
+"lng_filters_horizontal" = "Tabs at the top";
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
"lng_filters_link" = "Share Folder";
@@ -5172,13 +5259,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_revenued_subtitle" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:";
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
+"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
+"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
+"lng_sponsored_revenued_info2_bot_description" = "50% of the revenue from Telegram Ads goes to the developer of the mini app where they are displayed.";
"lng_sponsored_revenued_info3_title" = "Can Be Removed";
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
+"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
+"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
+"lng_sponsored_top_bar_hide" = "remove";
"lng_telegram_features_url" = "https://t.me/TelegramTips";
@@ -5482,6 +5575,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_title" = "Monetization";
"lng_channel_earn_about" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}";
+"lng_channel_earn_about_bot" = "Telegram shares 50% of the revenue from ads displayed in your bot. {link}";
"lng_channel_earn_about_link" = "Learn more {emoji}";
"lng_channel_earn_overview_title" = "Rewards overview";
"lng_channel_earn_available" = "Rewards available for collection";
@@ -5514,8 +5608,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_cpm#one" = "{emoji} {count} CPM";
"lng_channel_earn_cpm#other" = "{emoji} {count} CPM";
"lng_channel_earn_learn_title" = "Earn From Your Channel";
+"lng_channel_earn_bot_learn_title" = "Earn From Your Bot";
"lng_channel_earn_learn_in_subtitle" = "Telegram Ads";
"lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel.";
+"lng_channel_earn_learn_bot_in_about" = "Telegram can display ads in your bot.";
"lng_channel_earn_learn_split_subtitle" = "50:50 revenue split";
"lng_channel_earn_learn_split_about" = "You can receive 50% of the ad revenue as rewards in TON.";
"lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals";
@@ -5552,6 +5648,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}.";
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
+"lng_bot_copy_text_tooltip" = "Copy to Clipboard: {text}";
"lng_contact_add" = "Add";
"lng_contact_send_message" = "Message";
@@ -5562,6 +5659,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_iv_window_title" = "Instant View";
"lng_iv_wrong_layout" = "Wrong layout?";
"lng_iv_not_supported" = "This link appears to be invalid.";
+"lng_iv_zoom_tooltip_ctrl" = "Hold Ctrl to zoom by 5%.\nHold Alt to zoom by 1%.";
+"lng_iv_zoom_tooltip_cmd" = "Hold Cmd to zoom by 5%.\nHold Alt to zoom by 1%.";
"lng_limit_download_title" = "Download speed limited";
"lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}.";
@@ -5599,6 +5698,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channels_recommended" = "Similar channels";
"lng_bot_apps_your" = "Apps you use";
"lng_bot_apps_popular" = "Grossing apps";
+"lng_bot_apps_which" = "Which apps are included here? {link}";
+"lng_bot_apps_which_link" = "Learn >";
+
+"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.";
+"lng_popular_apps_info_bot" = "@botfather";
+"lng_popular_apps_info_here" = "here";
+"lng_popular_apps_info_url" = "https://core.telegram.org/bots/webapps#launching-the-main-mini-app";
+"lng_popular_apps_info_confirm" = "Understood";
"lng_font_box_title" = "Choose font family";
"lng_font_default" = "Default";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index ac6ff1dbf..f60061f99 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -28,6 +28,7 @@
../../animations/collectible_phone.tgs
../../animations/search.tgs
../../animations/noresults.tgs
+ ../../animations/hello_status.tgs
../../animations/dice/dice_idle.tgs
../../animations/dice/dart_idle.tgs
diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc
index db6371e82..7d0463c4e 100644
--- a/Telegram/Resources/qrc/telegram/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram/telegram.qrc
@@ -7,6 +7,7 @@
../../art/logo_256.png
../../art/logo_256_no_margin.png
../../art/themeimage.jpg
+ ../../art/round_placeholder.jpg
../../day-blue.tdesktop-theme
../../night.tdesktop-theme
../../night-green.tdesktop-theme
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 798a9e220..8d93b797d 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="5.8.2.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 4404a4c05..4f0000cc7 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,6,3,0
- PRODUCTVERSION 5,6,3,0
+ FILEVERSION 5,8,2,0
+ PRODUCTVERSION 5,8,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "5.6.3.0"
+ VALUE "FileVersion", "5.8.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.6.3.0"
+ VALUE "ProductVersion", "5.8.2.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index fbe2ad09e..3cb7b8d82 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,6,3,0
- PRODUCTVERSION 5,6,3,0
+ FILEVERSION 5,8,2,0
+ PRODUCTVERSION 5,8,2,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.6.3.0"
+ VALUE "FileVersion", "5.8.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "5.6.3.0"
+ VALUE "ProductVersion", "5.8.2.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_chat_filters.cpp b/Telegram/SourceFiles/api/api_chat_filters.cpp
index ee6b97b58..2c8410b80 100644
--- a/Telegram/SourceFiles/api/api_chat_filters.cpp
+++ b/Telegram/SourceFiles/api/api_chat_filters.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h"
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
#include "core/application.h"
+#include "core/core_settings.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_chat_filters.h"
@@ -152,6 +153,7 @@ void InitFilterLinkHeader(
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
? std::move(count)
: rpl::single(0)),
+ .horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
});
const auto widget = header.widget;
widget->resizeToWidth(st::boxWideWidth);
diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
new file mode 100644
index 000000000..fe96accd6
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
@@ -0,0 +1,128 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "api/api_chat_filters_remove_manager.h"
+
+#include "api/api_chat_filters.h"
+#include "apiwrap.h"
+#include "data/data_chat_filters.h"
+#include "data/data_peer.h"
+#include "data/data_session.h"
+#include "lang/lang_keys.h"
+#include "main/main_session.h"
+#include "ui/boxes/confirm_box.h"
+#include "ui/ui_utility.h"
+#include "window/window_controller.h"
+#include "window/window_session_controller.h"
+#include "styles/style_layers.h"
+
+namespace Api {
+namespace {
+
+void RemoveChatFilter(
+ not_null session,
+ FilterId filterId,
+ std::vector> leave) {
+ const auto api = &session->api();
+ session->data().chatsFilters().apply(MTP_updateDialogFilter(
+ MTP_flags(MTPDupdateDialogFilter::Flag(0)),
+ MTP_int(filterId),
+ MTPDialogFilter()));
+ if (leave.empty()) {
+ api->request(MTPmessages_UpdateDialogFilter(
+ MTP_flags(MTPmessages_UpdateDialogFilter::Flag(0)),
+ MTP_int(filterId),
+ MTPDialogFilter()
+ )).send();
+ } else {
+ api->request(MTPchatlists_LeaveChatlist(
+ MTP_inputChatlistDialogFilter(MTP_int(filterId)),
+ MTP_vector(ranges::views::all(
+ leave
+ ) | ranges::views::transform([](not_null peer) {
+ return MTPInputPeer(peer->input);
+ }) | ranges::to>())
+ )).done([=](const MTPUpdates &result) {
+ api->applyUpdates(result);
+ }).send();
+ }
+}
+
+} // namespace
+
+RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
+
+void RemoveComplexChatFilter::request(
+ QPointer widget,
+ base::weak_ptr weak,
+ FilterId id) {
+ const auto session = &weak->session();
+ const auto &list = session->data().chatsFilters().list();
+ const auto i = ranges::find(list, id, &Data::ChatFilter::id);
+ const auto filter = (i != end(list)) ? *i : Data::ChatFilter();
+ const auto has = filter.hasMyLinks();
+ const auto confirm = [=](Fn action, bool onlyWhenHas = false) {
+ if (!has && onlyWhenHas) {
+ action();
+ return;
+ }
+ weak->window().show(Ui::MakeConfirmBox({
+ .text = (has
+ ? tr::lng_filters_delete_sure()
+ : tr::lng_filters_remove_sure()),
+ .confirmed = [=](Fn &&close) { close(); action(); },
+ .confirmText = (has
+ ? tr::lng_box_delete()
+ : tr::lng_filters_remove_yes()),
+ .confirmStyle = &st::attentionBoxButton,
+ }));
+ };
+ const auto simple = [=] {
+ confirm([=] { RemoveChatFilter(session, id, {}); });
+ };
+ const auto suggestRemoving = Api::ExtractSuggestRemoving(filter);
+ if (suggestRemoving.empty()) {
+ simple();
+ return;
+ } else if (_removingRequestId) {
+ if (_removingId == id) {
+ return;
+ }
+ session->api().request(_removingRequestId).cancel();
+ }
+ _removingId = id;
+ _removingRequestId = session->api().request(
+ MTPchatlists_GetLeaveChatlistSuggestions(
+ MTP_inputChatlistDialogFilter(
+ MTP_int(id)))
+ ).done(crl::guard(widget, [=, this](const MTPVector &result) {
+ _removingRequestId = 0;
+ const auto suggestRemovePeers = ranges::views::all(
+ result.v
+ ) | ranges::views::transform([=](const MTPPeer &peer) {
+ return session->data().peer(peerFromMTP(peer));
+ }) | ranges::to_vector;
+ const auto chosen = crl::guard(widget, [=](
+ std::vector> peers) {
+ RemoveChatFilter(session, id, std::move(peers));
+ });
+ confirm(crl::guard(widget, [=] {
+ Api::ProcessFilterRemove(
+ weak,
+ filter.title(),
+ filter.iconEmoji(),
+ suggestRemoving,
+ suggestRemovePeers,
+ chosen);
+ }), true);
+ })).fail(crl::guard(widget, [=, this] {
+ _removingRequestId = 0;
+ simple();
+ })).send();
+}
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
new file mode 100644
index 000000000..ce92b2df3
--- /dev/null
+++ b/Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
@@ -0,0 +1,35 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+namespace Ui {
+class RpWidget;
+} // namespace Ui
+
+namespace Api {
+
+class RemoveComplexChatFilter final {
+public:
+ RemoveComplexChatFilter();
+
+ void request(
+ QPointer widget,
+ base::weak_ptr weak,
+ FilterId id);
+
+private:
+ FilterId _removingId = 0;
+ mtpRequestId _removingRequestId = 0;
+
+};
+
+} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp
index be07e72ac..0a53c61dd 100644
--- a/Telegram/SourceFiles/api/api_chat_invite.cpp
+++ b/Telegram/SourceFiles/api/api_chat_invite.cpp
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
+#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/empty_userpic.h"
@@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
struct State final {
std::shared_ptr photoMedia;
std::unique_ptr photoEmpty;
+ QImage frame;
std::optional api;
Ui::RpWidget* saveButton = nullptr;
@@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
const auto userpic = userpicWrap->entity();
const auto photoSize = st::confirmInvitePhotoSize;
userpic->resize(Size(photoSize));
+ const auto creditsIconSize = photoSize / 3;
+ const auto creditsIconCallback =
+ Ui::PaintOutlinedColoredCreditsIconCallback(
+ creditsIconSize,
+ 1.5);
+ state->frame = QImage(
+ Size(photoSize * style::DevicePixelRatio()),
+ QImage::Format_ARGB32_Premultiplied);
+ state->frame.setDevicePixelRatio(style::DevicePixelRatio());
const auto options = Images::Option::RoundCircle;
userpic->paintRequest(
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
- auto p = QPainter(userpic);
- if (state->photoMedia) {
- if (const auto image = state->photoMedia->image(small)) {
- p.drawPixmap(
+ state->frame.fill(Qt::transparent);
+ {
+ auto p = QPainter(&state->frame);
+ if (state->photoMedia) {
+ if (const auto image = state->photoMedia->image(small)) {
+ p.drawPixmap(
+ 0,
+ 0,
+ image->pix(Size(photoSize), { .options = options }));
+ }
+ } else if (state->photoEmpty) {
+ state->photoEmpty->paintCircle(
+ p,
0,
0,
- image->pix(Size(photoSize), { .options = options }));
+ userpic->width(),
+ photoSize);
+ }
+ if (creditsIconCallback) {
+ p.translate(
+ photoSize - creditsIconSize,
+ photoSize - creditsIconSize);
+ creditsIconCallback(p);
}
- } else if (state->photoEmpty) {
- state->photoEmpty->paintCircle(
- p,
- 0,
- 0,
- userpic->width(),
- photoSize);
}
+ auto p = QPainter(userpic);
+ p.drawImage(0, 0, state->frame);
}, userpicWrap->lifetime());
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
if (photo) {
diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp
index 4ef4b09c8..80e995219 100644
--- a/Telegram/SourceFiles/api/api_credits.cpp
+++ b/Telegram/SourceFiles/api/api_credits.cpp
@@ -39,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size());
for (const auto &media : list->v) {
- media.match([&](const MTPDmessageMediaPhoto &photo) {
- if (const auto inner = photo.vphoto()) {
+ media.match([&](const MTPDmessageMediaPhoto &data) {
+ if (const auto inner = data.vphoto()) {
const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) {
extended.push_back(CreditsHistoryMedia{
@@ -49,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
});
}
}
- }, [&](const MTPDmessageMediaDocument &document) {
- if (const auto inner = document.vdocument()) {
- const auto document = owner->processDocument(*inner);
+ }, [&](const MTPDmessageMediaDocument &data) {
+ if (const auto inner = data.vdocument()) {
+ const auto document = owner->processDocument(
+ *inner,
+ data.valt_documents());
if (document->isAnimation()
|| document->isVideoFile()
|| document->isGifv()) {
@@ -71,7 +73,9 @@ constexpr auto kTransactionsLimit = 100;
return PeerId(0);
}).value;
const auto stargift = tl.data().vstargift();
+ const auto reaction = tl.data().is_reaction();
const auto incoming = (int64(tl.data().vstars().v) >= 0);
+ const auto saveActorId = (reaction || !extended.empty()) && incoming;
return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()),
@@ -81,12 +85,13 @@ constexpr auto kTransactionsLimit = 100;
.extended = std::move(extended),
.credits = tl.data().vstars().v,
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
- .barePeerId = barePeerId,
+ .barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = (stargift
? owner->processDocument(stargift->data().vsticker())->id
: 0),
+ .bareActorId = saveActorId ? barePeerId : uint64(0),
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
@@ -101,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
}, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads;
+ }, [](const MTPDstarsTransactionPeerAPI &) {
+ return Data::CreditsHistoryEntry::PeerType::API;
}),
.subscriptionUntil = tl.data().vsubscription_period()
? base::unixtime::parse(base::unixtime::now()
@@ -110,10 +117,12 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
- .convertStars = int(stargift
+ .starsConverted = int(stargift
? stargift->data().vconvert_stars().v
: 0),
+ .floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming,
+ .stargift = stargift.has_value(),
.reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(),
@@ -166,7 +175,8 @@ constexpr auto kTransactionsLimit = 100;
.balance = status.data().vbalance().v,
.subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(),
- .allLoaded = !status.data().vnext_offset().has_value(),
+ .allLoaded = !status.data().vnext_offset().has_value()
+ && !status.data().vsubscriptions_next_offset().has_value(),
.token = qs(status.data().vnext_offset().value_or_empty()),
.tokenSubscriptions = qs(
status.data().vsubscriptions_next_offset().value_or_empty()),
diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h
index d2e819e08..e178cb866 100644
--- a/Telegram/SourceFiles/api/api_credits.h
+++ b/Telegram/SourceFiles/api/api_credits.h
@@ -99,8 +99,8 @@ public:
[[nodiscard]] Data::CreditsEarnStatistics data() const;
private:
+ const bool _isUser = false;
Data::CreditsEarnStatistics _data;
- bool _isUser = false;
mtpRequestId _requestId = 0;
diff --git a/Telegram/SourceFiles/api/api_earn.cpp b/Telegram/SourceFiles/api/api_earn.cpp
index f05e5f618..c44690151 100644
--- a/Telegram/SourceFiles/api/api_earn.cpp
+++ b/Telegram/SourceFiles/api/api_earn.cpp
@@ -40,6 +40,7 @@ void HandleWithdrawalButton(
std::shared_ptr show) {
Expects(receiver.currencyReceiver
|| (receiver.creditsReceiver && receiver.creditsAmount));
+
struct State {
rpl::lifetime lifetime;
bool loading = false;
@@ -58,8 +59,7 @@ void HandleWithdrawalButton(
const auto processOut = [=] {
if (state->loading) {
return;
- }
- if (peer && !receiver.creditsAmount()) {
+ } else if (peer && !receiver.creditsAmount()) {
return;
}
state->loading = true;
@@ -89,12 +89,15 @@ void HandleWithdrawalButton(
}
};
const auto fail = [=](const MTP::Error &error) {
- show->showToast(error.type());
+ const auto message = error.type();
+ if (box && !box->handleCustomCheckError(message)) {
+ show->showToast(message);
+ }
};
if (channel) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
- channel->inputChannel,
+ channel->input,
result.result
)).done([=](const ChannelOutUrl &r) {
done(qs(r.data().vurl()));
@@ -134,7 +137,7 @@ void HandleWithdrawalButton(
if (channel) {
session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl(
- channel->inputChannel,
+ channel->input,
MTP_inputCheckPasswordEmpty()
)).fail(fail).send();
} else if (peer) {
diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp
index 84ce45783..d88a5ddce 100644
--- a/Telegram/SourceFiles/api/api_premium.cpp
+++ b/Telegram/SourceFiles/api/api_premium.cpp
@@ -772,10 +772,13 @@ std::optional FromTL(
return StarGift{
.id = uint64(data.vid().v),
.stars = int64(data.vstars().v),
- .convertStars = int64(data.vconvert_stars().v),
+ .starsConverted = int64(data.vconvert_stars().v),
.document = document,
.limitedLeft = remaining.value_or_empty(),
.limitedCount = total.value_or_empty(),
+ .firstSaleDate = data.vfirst_sale_date().value_or_empty(),
+ .lastSaleDate = data.vlast_sale_date().value_or_empty(),
+ .birthday = data.is_birthday(),
};
}
@@ -789,7 +792,7 @@ std::optional FromTL(
return {};
}
return UserStarGift{
- .gift = std::move(*parsed),
+ .info = std::move(*parsed),
.message = (data.vmessage()
? TextWithEntities{
.text = qs(data.vmessage()->data().vtext()),
@@ -798,7 +801,7 @@ std::optional FromTL(
data.vmessage()->data().ventities().v),
}
: TextWithEntities()),
- .convertStars = int64(data.vconvert_stars().value_or_empty()),
+ .starsConverted = int64(data.vconvert_stars().value_or_empty()),
.fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v)
: PeerId()),
diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h
index 805b68e03..23455ba18 100644
--- a/Telegram/SourceFiles/api/api_premium.h
+++ b/Telegram/SourceFiles/api/api_premium.h
@@ -76,16 +76,23 @@ struct GiftOptionData {
struct StarGift {
uint64 id = 0;
int64 stars = 0;
- int64 convertStars = 0;
+ int64 starsConverted = 0;
not_null document;
int limitedLeft = 0;
int limitedCount = 0;
+ TimeId firstSaleDate = 0;
+ TimeId lastSaleDate = 0;
+ bool birthday = false;
+
+ friend inline bool operator==(
+ const StarGift &,
+ const StarGift &) = default;
};
struct UserStarGift {
- StarGift gift;
+ StarGift info;
TextWithEntities message;
- int64 convertStars = 0;
+ int64 starsConverted = 0;
PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0;
diff --git a/Telegram/SourceFiles/api/api_report.cpp b/Telegram/SourceFiles/api/api_report.cpp
index 309e2d240..f3b0658bd 100644
--- a/Telegram/SourceFiles/api/api_report.cpp
+++ b/Telegram/SourceFiles/api/api_report.cpp
@@ -40,29 +40,20 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
} // namespace
-void SendReport(
+void SendPhotoReport(
std::shared_ptr show,
not_null peer,
Ui::ReportReason reason,
const QString &comment,
- std::variant> data) {
- auto done = [=] {
+ not_null photo) {
+ peer->session().api().request(MTPaccount_ReportProfilePhoto(
+ peer->input,
+ photo->mtpInput(),
+ ReasonToTL(reason),
+ MTP_string(comment)
+ )).done([=] {
show->showToast(tr::lng_report_thanks(tr::now));
- };
- v::match(data, [&](v::null_t) {
- peer->session().api().request(MTPaccount_ReportPeer(
- peer->input,
- ReasonToTL(reason),
- MTP_string(comment)
- )).done(std::move(done)).send();
- }, [&](not_null photo) {
- peer->session().api().request(MTPaccount_ReportProfilePhoto(
- peer->input,
- photo->mtpInput(),
- ReasonToTL(reason),
- MTP_string(comment)
- )).done(std::move(done)).send();
- });
+ }).send();
}
auto CreateReportMessagesOrStoriesCallback(
diff --git a/Telegram/SourceFiles/api/api_report.h b/Telegram/SourceFiles/api/api_report.h
index f0c0320f3..02d1c9bef 100644
--- a/Telegram/SourceFiles/api/api_report.h
+++ b/Telegram/SourceFiles/api/api_report.h
@@ -41,12 +41,12 @@ struct ReportResult final {
bool successful = false;
};
-void SendReport(
+void SendPhotoReport(
std::shared_ptr show,
not_null peer,
Ui::ReportReason reason,
const QString &comment,
- std::variant> data);
+ not_null photo);
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
std::shared_ptr show,
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index a3003bab4..d0b897a07 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -456,6 +456,7 @@ void SendConfirmedFile(
not_null session,
const std::shared_ptr &file) {
const auto isEditing = (file->type != SendMediaType::Audio)
+ && (file->type != SendMediaType::Round)
&& (file->to.replaceMediaOf != 0);
const auto newId = FullMsgId(
file->to.peer,
@@ -525,7 +526,8 @@ void SendConfirmedFile(
// Shortcut messages have no 'edited' badge.
flags |= MessageFlag::HideEdited;
}
- if (file->type == SendMediaType::Audio) {
+ if (file->type == SendMediaType::Audio
+ || file->type == SendMediaType::Round) {
if (!peer->isChannel() || peer->isMegagroup()) {
flags |= MessageFlag::MediaIsUnread;
}
@@ -551,29 +553,25 @@ void SendConfirmedFile(
MTPint());
} else if (file->type == SendMediaType::Audio) {
const auto ttlSeconds = file->to.options.ttlSeconds;
- const auto isVoice = [&] {
- return file->document.match([](const MTPDdocumentEmpty &d) {
- return false;
- }, [](const MTPDdocument &d) {
- return ranges::any_of(d.vattributes().v, [&](
- const MTPDocumentAttribute &attribute) {
- using Att = MTPDdocumentAttributeAudio;
- return attribute.match([](const Att &data) -> bool {
- return data.vflags().v & Att::Flag::f_voice;
- }, [](const auto &) {
- return false;
- });
- });
- });
- }();
using Flag = MTPDmessageMediaDocument::Flag;
return MTP_messageMediaDocument(
MTP_flags(Flag::f_document
- | (isVoice ? Flag::f_voice : Flag())
+ | Flag::f_voice
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
file->document,
MTPVector(), // alt_documents
MTP_int(ttlSeconds));
+ } else if (file->type == SendMediaType::Round) {
+ using Flag = MTPDmessageMediaDocument::Flag;
+ const auto ttlSeconds = file->to.options.ttlSeconds;
+ return MTP_messageMediaDocument(
+ MTP_flags(Flag::f_document
+ | Flag::f_round
+ | (ttlSeconds ? Flag::f_ttl_seconds : Flag())
+ | (file->spoiler ? Flag::f_spoiler : Flag())),
+ file->document,
+ MTPVector(), // alt_documents
+ MTP_int(ttlSeconds));
} else {
Unexpected("Type in sendFilesConfirmed.");
}
diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index c960aca33..daed8f1ee 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_stories.h"
#include "data/data_story.h"
+#include "data/data_user.h"
#include "history/history.h"
#include "main/main_session.h"
@@ -341,6 +342,10 @@ void PublicForwards::request(
.token = nextToken,
});
};
+ const auto processFail = [=] {
+ _requestId = 0;
+ done({});
+ };
constexpr auto kLimit = tl::make_int(100);
if (_fullId.messageId) {
@@ -349,14 +354,14 @@ void PublicForwards::request(
MTP_int(_fullId.messageId.msg),
MTP_string(token),
kLimit
- )).done(processResult).fail([=] { _requestId = 0; }).send();
+ )).done(processResult).fail(processFail).send();
} else if (_fullId.storyId) {
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel->input,
MTP_int(_fullId.storyId.story),
MTP_string(token),
kLimit
- )).done(processResult).fail([=] { _requestId = 0; }).send();
+ )).done(processResult).fail(processFail).send();
}
}
@@ -381,7 +386,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
}
void MessageStatistics::request(Fn done) {
- if (channel()->isMegagroup()) {
+ if (channel()->isMegagroup() && !_storyId) {
return;
}
const auto requestFirstPublicForwards = [=](
@@ -681,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
return _boostStatus;
}
-ChannelEarnStatistics::ChannelEarnStatistics(not_null channel)
-: StatisticsRequestSender(channel) {
+EarnStatistics::EarnStatistics(not_null peer)
+: StatisticsRequestSender(peer)
+, _isUser(peer->isUser()) {
}
-rpl::producer ChannelEarnStatistics::request() {
+rpl::producer EarnStatistics::request() {
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
makeRequest(MTPstats_GetBroadcastRevenueStats(
MTP_flags(0),
- channel()->inputChannel
+ (_isUser ? user()->input : channel()->input)
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
const auto &data = result.data();
const auto &balances = data.vbalances().data();
@@ -708,18 +714,22 @@ rpl::producer ChannelEarnStatistics::request() {
requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
_data.firstHistorySlice = std::move(slice);
- api().request(
- MTPchannels_GetFullChannel(channel()->inputChannel)
- ).done([=](const MTPmessages_ChatFull &result) {
- result.data().vfull_chat().match([&](
- const MTPDchannelFull &d) {
- _data.switchedOff = d.is_restricted_sponsored();
- }, [](const auto &) {
- });
+ if (!_isUser) {
+ api().request(
+ MTPchannels_GetFullChannel(channel()->inputChannel)
+ ).done([=](const MTPmessages_ChatFull &result) {
+ result.data().vfull_chat().match([&](
+ const MTPDchannelFull &d) {
+ _data.switchedOff = d.is_restricted_sponsored();
+ }, [](const auto &) {
+ });
+ consumer.put_done();
+ }).fail([=](const MTP::Error &error) {
+ consumer.put_error_copy(error.type());
+ }).send();
+ } else {
consumer.put_done();
- }).fail([=](const MTP::Error &error) {
- consumer.put_error_copy(error.type());
- }).send();
+ }
});
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
@@ -729,7 +739,7 @@ rpl::producer ChannelEarnStatistics::request() {
};
}
-void ChannelEarnStatistics::requestHistory(
+void EarnStatistics::requestHistory(
const Data::EarnHistorySlice::OffsetToken &token,
Fn done) {
if (_requestId) {
@@ -738,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
- channel()->inputChannel,
+ (_isUser ? user()->input : channel()->input),
MTP_int(token),
(!token) ? kTlFirstSlice : kTlLimit
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
@@ -799,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
}).send();
}
-Data::EarnStatistics ChannelEarnStatistics::data() const {
+Data::EarnStatistics EarnStatistics::data() const {
return _data;
}
diff --git a/Telegram/SourceFiles/api/api_statistics.h b/Telegram/SourceFiles/api/api_statistics.h
index 213ab9293..b419b8841 100644
--- a/Telegram/SourceFiles/api/api_statistics.h
+++ b/Telegram/SourceFiles/api/api_statistics.h
@@ -79,9 +79,9 @@ private:
};
-class ChannelEarnStatistics final : public StatisticsRequestSender {
+class EarnStatistics final : public StatisticsRequestSender {
public:
- explicit ChannelEarnStatistics(not_null channel);
+ explicit EarnStatistics(not_null peer);
[[nodiscard]] rpl::producer request();
void requestHistory(
@@ -94,6 +94,7 @@ public:
static constexpr auto kLimit = int(10);
private:
+ const bool _isUser = false;
Data::EarnStatistics _data;
mtpRequestId _requestId = 0;
diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp
index eea4f6f35..b4775f07b 100644
--- a/Telegram/SourceFiles/api/api_updates.cpp
+++ b/Telegram/SourceFiles/api/api_updates.cpp
@@ -320,6 +320,9 @@ void Updates::feedUpdateVector(
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
return;
}
+ if (policy == SkipUpdatePolicy::SkipNone) {
+ applyConvertToScheduledOnSend(updates);
+ }
for (const auto &entry : std::as_const(list)) {
const auto type = entry.type();
if ((policy == SkipUpdatePolicy::SkipMessageIds
@@ -333,6 +336,15 @@ void Updates::feedUpdateVector(
session().data().sendHistoryChangeNotifications();
}
+void Updates::checkForSentToScheduled(const MTPUpdates &updates) {
+ updates.match([&](const MTPDupdates &data) {
+ applyConvertToScheduledOnSend(data.vupdates(), true);
+ }, [&](const MTPDupdatesCombined &data) {
+ applyConvertToScheduledOnSend(data.vupdates(), true);
+ }, [](const auto &) {
+ });
+}
+
void Updates::feedMessageIds(const MTPVector &updates) {
for (const auto &update : updates.v) {
if (update.type() == mtpc_updateMessageID) {
@@ -436,6 +448,7 @@ void Updates::feedChannelDifference(
session().data().processChats(data.vchats());
_handlingChannelDifference = true;
+ applyConvertToScheduledOnSend(data.vother_updates());
feedMessageIds(data.vother_updates());
session().data().processMessages(
data.vnew_messages(),
@@ -600,6 +613,7 @@ void Updates::feedDifference(
Core::App().checkAutoLock();
session().data().processUsers(users);
session().data().processChats(chats);
+ applyConvertToScheduledOnSend(other);
feedMessageIds(other);
session().data().processMessages(msgs, NewMessageType::Unread);
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
@@ -885,6 +899,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
}
}
+void Updates::applyConvertToScheduledOnSend(
+ const MTPVector &other,
+ bool skipScheduledCheck) {
+ for (const auto &update : other.v) {
+ update.match([&](const MTPDupdateNewScheduledMessage &data) {
+ const auto &message = data.vmessage();
+ const auto id = IdFromMessage(message);
+ const auto scheduledMessages = &_session->scheduledMessages();
+ const auto scheduledId = scheduledMessages->localMessageId(id);
+ for (const auto &updateId : other.v) {
+ updateId.match([&](const MTPDupdateMessageID &dataId) {
+ if (dataId.vid().v == id) {
+ auto &owner = session().data();
+ if (skipScheduledCheck) {
+ const auto peerId = PeerFromMessage(message);
+ const auto history = owner.historyLoaded(peerId);
+ if (history) {
+ _session->data().sentToScheduled({
+ .history = history,
+ .scheduledId = scheduledId,
+ });
+ }
+ return;
+ }
+ const auto rand = dataId.vrandom_id().v;
+ const auto localId = owner.messageIdByRandomId(rand);
+ if (const auto local = owner.message(localId)) {
+ if (!local->isScheduled()) {
+ _session->data().sentToScheduled({
+ .history = local->history(),
+ .scheduledId = scheduledId,
+ });
+
+ // We've sent a non-scheduled message,
+ // but it was converted to a scheduled.
+ local->destroy();
+ }
+ }
+ }
+ }, [](const auto &) {});
+ }
+ }, [](const auto &) {});
+ }
+}
+
void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
updates.match([&](const MTPDupdates &data) {
session().data().processUsers(data.vusers());
diff --git a/Telegram/SourceFiles/api/api_updates.h b/Telegram/SourceFiles/api/api_updates.h
index 654e36b51..b1ecb870f 100644
--- a/Telegram/SourceFiles/api/api_updates.h
+++ b/Telegram/SourceFiles/api/api_updates.h
@@ -40,6 +40,8 @@ public:
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
void applyUpdateNoPtsCheck(const MTPUpdate &update);
+ void checkForSentToScheduled(const MTPUpdates &updates);
+
[[nodiscard]] int32 pts() const;
void updateOnline(crl::time lastNonIdleTime = 0);
@@ -131,6 +133,9 @@ private:
// Doesn't call sendHistoryChangeNotifications itself.
void feedUpdate(const MTPUpdate &update);
+ void applyConvertToScheduledOnSend(
+ const MTPVector &other,
+ bool skipScheduledCheck = false);
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
bool whenGetDiffChanged(
diff --git a/Telegram/SourceFiles/api/api_user_privacy.cpp b/Telegram/SourceFiles/api/api_user_privacy.cpp
index d0c17fa6b..78f863bed 100644
--- a/Telegram/SourceFiles/api/api_user_privacy.cpp
+++ b/Telegram/SourceFiles/api/api_user_privacy.cpp
@@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
if (rule.always.premiums && (rule.option != Option::Everyone)) {
result.push_back(MTP_inputPrivacyValueAllowPremium());
}
+ if (rule.always.miniapps && (rule.option != Option::Everyone)) {
+ result.push_back(MTP_inputPrivacyValueAllowBots());
+ }
}
if (!rule.ignoreNever) {
const auto users = collectInputUsers(rule.never);
@@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
MTP_inputPrivacyValueDisallowChatParticipants(
MTP_vector(chats)));
}
+ if (rule.never.miniapps && (rule.option != Option::Nobody)) {
+ result.push_back(MTP_inputPrivacyValueDisallowBots());
+ }
}
result.push_back([&] {
switch (rule.option) {
@@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
setOption(Option::CloseFriends);
}, [&](const MTPDprivacyValueAllowPremium &) {
result.always.premiums = true;
+ }, [&](const MTPDprivacyValueAllowBots &) {
+ result.always.miniapps = true;
+ }, [&](const MTPDprivacyValueDisallowBots &) {
+ result.never.miniapps = true;
}, [&](const MTPDprivacyValueAllowUsers &data) {
const auto &users = data.vusers().v;
always.reserve(always.size() + users.size());
@@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
+ case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
}
Unexpected("Key in Api::UserPrivacy::KetToTL.");
}
@@ -228,6 +239,8 @@ std::optional TLToKey(mtpTypeId type) {
case mtpc_inputPrivacyKeyAbout: return Key::About;
case mtpc_privacyKeyBirthday:
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
+ case mtpc_privacyKeyStarGiftsAutoSave:
+ case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
}
return std::nullopt;
}
diff --git a/Telegram/SourceFiles/api/api_user_privacy.h b/Telegram/SourceFiles/api/api_user_privacy.h
index 471f41f48..a1f66189f 100644
--- a/Telegram/SourceFiles/api/api_user_privacy.h
+++ b/Telegram/SourceFiles/api/api_user_privacy.h
@@ -31,6 +31,7 @@ public:
Voices,
About,
Birthday,
+ GiftsAutoSave,
};
enum class Option {
Everyone,
@@ -41,6 +42,7 @@ public:
struct Exceptions {
std::vector> peers;
bool premiums = false;
+ bool miniapps = false;
};
struct Rule {
Option option = Option::Everyone;
diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp
index 607e4453b..86bc8d49b 100644
--- a/Telegram/SourceFiles/api/api_who_reacted.cpp
+++ b/Telegram/SourceFiles/api/api_who_reacted.cpp
@@ -756,5 +756,19 @@ rpl::producer WhoReacted(
const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr);
}
+rpl::producer WhenEdited(
+ not_null author,
+ TimeId date) {
+ return rpl::single(Ui::WhoReadContent{
+ .participants = { Ui::WhoReadParticipant{
+ .name = author->name(),
+ .date = FormatReadDate(date, QDateTime::currentDateTime()),
+ .id = author->id.value,
+ } },
+ .type = Ui::WhoReadType::Edited,
+ .fullReadCount = 1,
+ });
+}
+
} // namespace Api
diff --git a/Telegram/SourceFiles/api/api_who_reacted.h b/Telegram/SourceFiles/api/api_who_reacted.h
index 9a9100535..0d1cf7234 100644
--- a/Telegram/SourceFiles/api/api_who_reacted.h
+++ b/Telegram/SourceFiles/api/api_who_reacted.h
@@ -61,5 +61,8 @@ struct WhoReadList {
const Data::ReactionId &reaction,
not_null context, // Cache results for this lifetime.
const style::WhoRead &st);
+[[nodiscard]] rpl::producer WhenEdited(
+ not_null author,
+ TimeId date);
} // namespace Api
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index a91296d2d..6ffccae96 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -731,7 +731,8 @@ void ApiWrap::finalizeMessageDataRequest(
QString ApiWrap::exportDirectMessageLink(
not_null item,
- bool inRepliesContext) {
+ bool inRepliesContext,
+ bool forceNonPublicLink) {
Expects(item->history()->peer->isChannel());
const auto itemId = item->fullId();
@@ -754,7 +755,7 @@ QString ApiWrap::exportDirectMessageLink(
const auto sender = root
? root->discussionPostOriginalSender()
: nullptr;
- if (sender && sender->hasUsername()) {
+ if (sender && sender->hasUsername() && !forceNonPublicLink) {
// Comment to a public channel.
const auto forwarded = root->Get();
linkItemId = forwarded->savedFromMsgId;
@@ -770,7 +771,7 @@ QString ApiWrap::exportDirectMessageLink(
}
}
}
- const auto base = linkChannel->hasUsername()
+ const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
? linkChannel->username()
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
const auto post = QString::number(linkItemId.bare);
@@ -784,6 +785,7 @@ QString ApiWrap::exportDirectMessageLink(
? (QString::number(linkThreadId.bare) + '/' + post)
: post);
if (linkChannel->hasUsername()
+ && !forceNonPublicLink
&& !linkChannel->isMegagroup()
&& !linkCommentId
&& !linkThreadId) {
@@ -797,6 +799,9 @@ QString ApiWrap::exportDirectMessageLink(
}
return session().createInternalLinkFull(query);
};
+ if (forceNonPublicLink) {
+ return fallback();
+ }
const auto i = _unlikelyMessageLinks.find(itemId);
const auto current = (i != end(_unlikelyMessageLinks))
? i->second
@@ -3373,6 +3378,7 @@ void ApiWrap::forwardMessages(
}
const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds;
+ const auto scheduled = action.options.scheduled;
histories.sendRequest(history, requestType, [=](Fn finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
@@ -3385,6 +3391,9 @@ void ApiWrap::forwardMessages(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
)).done([=](const MTPUpdates &result) {
+ if (!scheduled) {
+ this->updates().checkForSentToScheduled(result);
+ }
applyUpdates(result);
if (shared && !--shared->requestsLeft) {
shared->callback();
@@ -3553,6 +3562,7 @@ void ApiWrap::sendVoiceMessage(
QByteArray result,
VoiceWaveform waveform,
crl::time duration,
+ bool video,
const SendAction &action) {
const auto caption = TextWithTags();
const auto to = FileLoadTaskOptions(action);
@@ -3561,6 +3571,7 @@ void ApiWrap::sendVoiceMessage(
result,
duration,
waveform,
+ video,
to,
caption));
}
@@ -4008,7 +4019,8 @@ void ApiWrap::sendInlineResult(
not_null bot,
not_null data,
const SendAction &action,
- std::optional localMessageId) {
+ std::optional localMessageId,
+ Fn done) {
sendAction(action);
const auto history = action.history;
@@ -4088,11 +4100,17 @@ void ApiWrap::sendInlineResult(
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
+ if (done) {
+ done(true);
+ }
}, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft(
topicRootId,
UnixtimeFromMsgId(response.outerMsgId));
+ if (done) {
+ done(false);
+ }
});
finishForwarding(action);
}
@@ -4304,6 +4322,7 @@ void ApiWrap::sendMultiPaidMedia(
auto &histories = history->owner().histories();
const auto peer = history->peer;
const auto itemId = item->fullId();
+ album->sent = true;
histories.sendPreparedMessage(
history,
replyTo,
@@ -4375,6 +4394,9 @@ void ApiWrap::sendAlbumWithCancelled(
}
void ApiWrap::sendAlbumIfReady(not_null album) {
+ if (album->sent) {
+ return;
+ }
const auto groupId = album->groupId;
if (album->items.empty()) {
_sendingAlbums.remove(groupId);
@@ -4399,6 +4421,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) {
return;
} else if (medias.size() < 2) {
const auto &single = medias.front().data();
+ album->sent = true;
sendMediaWithRandomId(
sample,
single.vmedia(),
@@ -4431,6 +4454,7 @@ void ApiWrap::sendAlbumIfReady(not_null album) {
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
+ album->sent = true;
histories.sendPreparedMessage(
history,
replyTo,
diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h
index 7259c410d..f25368293 100644
--- a/Telegram/SourceFiles/apiwrap.h
+++ b/Telegram/SourceFiles/apiwrap.h
@@ -164,7 +164,8 @@ public:
void requestMessageData(PeerData *peer, MsgId msgId, Fn done);
QString exportDirectMessageLink(
not_null item,
- bool inRepliesContext);
+ bool inRepliesContext,
+ bool forceNonPublicLink = false);
QString exportDirectStoryLink(not_null item);
void requestContacts();
@@ -317,6 +318,7 @@ public:
QByteArray result,
VoiceWaveform waveform,
crl::time duration,
+ bool video,
const SendAction &action);
void sendFiles(
Ui::PreparedList &&list,
@@ -359,7 +361,8 @@ public:
not_null bot,
not_null data,
const SendAction &action,
- std::optional localMessageId);
+ std::optional localMessageId,
+ Fn done = nullptr);
void sendMessageFail(
const MTP::Error &error,
not_null peer,
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index 54798434a..be1a42ae2 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
iconPosition: point(10px, -1px);
rippleAreaPosition: point(1px, 6px);
rippleAreaSize: 42px;
- ripple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
- }
+ ripple: defaultRippleAnimationBgOver;
}
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
@@ -416,9 +414,7 @@ calendarPrevious: IconButton {
rippleAreaPosition: point(2px, 2px);
rippleAreaSize: 44px;
- ripple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
- }
+ ripple: defaultRippleAnimationBgOver;
}
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
calendarNext: IconButton(calendarPrevious) {
@@ -616,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
proxyRowPadding: margins(22px, 8px, 8px, 8px);
proxyRowIconSkip: 32px;
proxyRowSkip: 2px;
-proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
-}
+proxyRowRipple: defaultRippleAnimationBgOver;
proxyRowTitleFg: windowFg;
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
linkFg: windowSubTextFg;
@@ -683,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 36px;
- ripple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
- }
+ ripple: defaultRippleAnimationBgOver;
}
themesMenuPosition: point(-2px, 25px);
@@ -738,9 +730,7 @@ createPollOptionRemove: CrossButton {
duration: 135;
loadingPeriod: 1000;
- ripple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
- }
+ ripple: defaultRippleAnimationBgOver;
}
createPollOptionRemovePosition: point(11px, 9px);
createPollOptionEmojiPositionSkip: 4px;
@@ -888,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
peerListSingleRow: PeerList(peerListBox) {
padding: margins(0px, 0px, 0px, 0px);
}
+peerListSmallSkips: PeerList(peerListBox) {
+ padding: margins(
+ 0px,
+ defaultVerticalListSkip,
+ 0px,
+ defaultVerticalListSkip);
+}
scheduleHeight: 95px;
scheduleDateTop: 38px;
@@ -951,9 +948,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
textTop: 7px;
style: defaultTextStyle;
- ripple: RippleAnimation(defaultRippleAnimation) {
- color: windowBgOver;
- }
+ ripple: defaultRippleAnimationBgOver;
}
requestPeerRestriction: FlatLabel(defaultFlatLabel) {
diff --git a/Telegram/SourceFiles/boxes/choose_filter_box.cpp b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
index 4e99a01ba..252403af8 100644
--- a/Telegram/SourceFiles/boxes/choose_filter_box.cpp
+++ b/Telegram/SourceFiles/boxes/choose_filter_box.cpp
@@ -96,6 +96,9 @@ void ChangeFilterById(
Ui::Text::WithEntities));
}
}).fail([=](const MTP::Error &error) {
+ LOG(("API Error: failed to %1 a dialog to a folder. %2")
+ .arg(add ? u"add"_q : u"remove"_q)
+ .arg(error.type()));
// Revert filter on fail.
history->owner().chatsFilters().set(was);
}).send();
diff --git a/Telegram/SourceFiles/boxes/edit_caption_box.cpp b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
index 96d8fa782..f6e48b221 100644
--- a/Telegram/SourceFiles/boxes/edit_caption_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_caption_box.cpp
@@ -238,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
Fn saved)
: _controller(controller)
, _historyItem(item)
-, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
+, _isAllowedEditMedia(item->allowsEditMedia())
, _albumType(ComputeAlbumType(item))
, _controls(base::make_unique_q(this))
, _scroll(base::make_unique_q(this, st::boxScroll))
@@ -253,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
, _initialText(std::move(text))
, _initialList(std::move(list))
, _saved(std::move(saved)) {
- Expects(item->media() != nullptr);
- Expects(item->media()->allowsEditCaption());
+ Expects(!_initialList.files.empty());
+ Expects(!item->media() || item->media()->allowsEditCaption());
_mediaEditManager.start(item, spoilered, invertCaption);
@@ -422,7 +422,8 @@ void EditCaptionBox::prepare() {
setInitialText();
if (!setPreparedList(std::move(_initialList))) {
- rebuildPreview();
+ crl::on_main(this, [=] { closeBox(); });
+ return;
}
setupEditEventHandler();
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
index c9a9055e1..94c4af054 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
@@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
+#include "styles/style_window.h"
namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
+constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
using Exceptions = Api::UserPrivacy::Exceptions;
+enum class SpecialRowType {
+ Premiums,
+ MiniApps,
+};
+
+[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
+ bool forceRound) {
+ return [=](QPainter &p, int x, int y, int outerWidth, int size) {
+ auto gradient = QLinearGradient(
+ QPointF(x, y),
+ QPointF(x + size, y + size));
+ gradient.setStops(Ui::Premium::ButtonGradientStops());
+
+ auto hq = PainterHighQualityEnabler(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(gradient);
+ if (forceRound) {
+ p.drawEllipse(x, y, size, size);
+ } else {
+ const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
+ p.drawRoundedRect(x, y, size, size, radius, radius);
+ }
+ st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
+ };
+}
+
+[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
+ bool forceRound) {
+ return [=](QPainter &p, int x, int y, int outerWidth, int size) {
+ const auto &color1 = st::historyPeer6UserpicBg;
+ const auto &color2 = st::historyPeer6UserpicBg2;
+
+ auto hq = PainterHighQualityEnabler(p);
+ auto gradient = QLinearGradient(x, y, x, y + size);
+ gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
+
+ p.setPen(Qt::NoPen);
+ p.setBrush(gradient);
+ if (forceRound) {
+ p.drawEllipse(x, y, size, size);
+ } else {
+ const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
+ p.drawRoundedRect(x, y, size, size, radius, radius);
+ }
+ st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
+ };
+}
+
void CreateRadiobuttonLock(
not_null widget,
const style::Checkbox &st) {
@@ -102,7 +152,7 @@ public:
not_null session,
rpl::producer title,
const Exceptions &selected,
- bool allowChoosePremiums);
+ std::optional allowChooseSpecial);
Main::Session &session() const override;
void rowClicked(not_null row) override;
@@ -110,18 +160,20 @@ public:
bool handleDeselectForeignRow(PeerListRowId itemId) override;
[[nodiscard]] bool premiumsSelected() const;
+ [[nodiscard]] bool miniAppsSelected() const;
protected:
void prepareViewHook() override;
std::unique_ptr createRow(not_null history) override;
private:
- [[nodiscard]] object_ptr preparePremiumsRowList();
+ [[nodiscard]] object_ptr prepareSpecialRowList(
+ SpecialRowType type);
const not_null _session;
rpl::producer _title;
Exceptions _selected;
- bool _allowChoosePremiums = false;
+ std::optional _allowChooseSpecial;
PeerListContentDelegate *_typesDelegate = nullptr;
Fn _deselectOption;
@@ -133,9 +185,9 @@ struct RowSelectionChange {
bool checked = false;
};
-class PremiumsRow final : public PeerListRow {
+class SpecialRow final : public PeerListRow {
public:
- PremiumsRow();
+ explicit SpecialRow(SpecialRowType type);
QString generateName() override;
QString generateShortName() override;
@@ -147,66 +199,61 @@ public:
class TypesController final : public PeerListController {
public:
- TypesController(not_null session, bool premiums);
+ TypesController(not_null session, SpecialRowType type);
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null row) override;
- [[nodiscard]] bool premiumsSelected() const;
- [[nodiscard]] rpl::producer premiumsChanges() const;
+ [[nodiscard]] bool specialSelected() const;
+ [[nodiscard]] rpl::producer specialChanges() const;
[[nodiscard]] auto rowSelectionChanges() const
-> rpl::producer;
private:
const not_null _session;
+ const SpecialRowType _type;
rpl::event_stream<> _selectionChanged;
rpl::event_stream _rowSelectionChanges;
};
-PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
- setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
+SpecialRow::SpecialRow(SpecialRowType type)
+: PeerListRow((type == SpecialRowType::Premiums)
+ ? kPremiumsRowId
+ : kMiniAppsRowId) {
+ setCustomStatus((id() == kPremiumsRowId)
+ ? tr::lng_edit_privacy_premium_status(tr::now)
+ : tr::lng_edit_privacy_miniapps_status(tr::now));
}
-QString PremiumsRow::generateName() {
- return tr::lng_edit_privacy_premium(tr::now);
+QString SpecialRow::generateName() {
+ return (id() == kPremiumsRowId)
+ ? tr::lng_edit_privacy_premium(tr::now)
+ : tr::lng_edit_privacy_miniapps(tr::now);
}
-QString PremiumsRow::generateShortName() {
+QString SpecialRow::generateShortName() {
return generateName();
}
-PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
+PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
bool forceRound) {
- return [=](QPainter &p, int x, int y, int outerWidth, int size) {
- auto gradient = QLinearGradient(
- QPointF(x, y),
- QPointF(x + size, y + size));
- gradient.setStops(Ui::Premium::ButtonGradientStops());
-
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- p.setBrush(gradient);
- if (forceRound) {
- p.drawEllipse(x, y, size, size);
- } else {
- const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
- p.drawRoundedRect(x, y, size, size, radius, radius);
- }
- st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
- };
+ return (id() == kPremiumsRowId)
+ ? GeneratePremiumsUserpicCallback(forceRound)
+ : GenerateMiniAppsUserpicCallback(forceRound);
}
-bool PremiumsRow::useForumLikeUserpic() const {
+bool SpecialRow::useForumLikeUserpic() const {
return true;
}
TypesController::TypesController(
not_null session,
- bool premiums)
-: _session(session) {
+ SpecialRowType type)
+: _session(session)
+, _type(type) {
}
Main::Session &TypesController::session() const {
@@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
}
void TypesController::prepare() {
- delegate()->peerListAppendRow(std::make_unique());
+ delegate()->peerListAppendRow(std::make_unique(_type));
delegate()->peerListRefreshRows();
}
-bool TypesController::premiumsSelected() const {
- const auto row = delegate()->peerListFindRow(kPremiumsRowId);
+bool TypesController::specialSelected() const {
+ const auto premiums = (_type == SpecialRowType::Premiums);
+ const auto row = delegate()->peerListFindRow(premiums
+ ? kPremiumsRowId
+ : kMiniAppsRowId);
Assert(row != nullptr);
return row->checked();
@@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null row) {
_rowSelectionChanges.fire({ row, checked });
}
-rpl::producer TypesController::premiumsChanges() const {
+rpl::producer TypesController::specialChanges() const {
return _rowSelectionChanges.events(
) | rpl::map([=] {
- return premiumsSelected();
+ return specialSelected();
});
}
@@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
not_null session,
rpl::producer title,
const Exceptions &selected,
- bool allowChoosePremiums)
+ std::optional allowChooseSpecial)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _selected(selected)
-, _allowChoosePremiums(allowChoosePremiums) {
+, _allowChooseSpecial(allowChooseSpecial) {
}
Main::Session &PrivacyExceptionsBoxController::session() const {
@@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
void PrivacyExceptionsBoxController::prepareViewHook() {
delegate()->peerListSetTitle(std::move(_title));
- if (_allowChoosePremiums || _selected.premiums) {
- delegate()->peerListSetAboveWidget(preparePremiumsRowList());
+ if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
+ delegate()->peerListSetAboveWidget(prepareSpecialRowList(
+ _allowChooseSpecial.value_or(_selected.premiums
+ ? SpecialRowType::Premiums
+ : SpecialRowType::MiniApps)));
}
delegate()->peerListAddSelectedPeers(_selected.peers);
}
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
- return (itemId == kPremiumsRowId);
+ return (itemId == kPremiumsRowId)
+ || (itemId == kMiniAppsRowId);
}
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
@@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
return false;
}
-auto PrivacyExceptionsBoxController::preparePremiumsRowList()
+auto PrivacyExceptionsBoxController::prepareSpecialRowList(
+ SpecialRowType type)
-> object_ptr {
auto result = object_ptr((QWidget*)nullptr);
const auto container = result.data();
@@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_typesDelegate = lifetime.make_state();
const auto controller = lifetime.make_state(
&session(),
- _selected.premiums);
+ type);
const auto content = result->add(object_ptr(
container,
controller));
_typesDelegate->setContent(content);
controller->setDelegate(_typesDelegate);
+ const auto selectType = [&](PeerListRowId id) {
+ const auto row = _typesDelegate->peerListFindRow(id);
+ if (row) {
+ content->changeCheckState(row, true, anim::type::instant);
+ this->delegate()->peerListSetForeignRowChecked(
+ row,
+ true,
+ anim::type::instant);
+ }
+ };
if (_selected.premiums) {
- const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
- Assert(row != nullptr);
-
- content->changeCheckState(row, true, anim::type::instant);
- this->delegate()->peerListSetForeignRowChecked(
- row,
- true,
- anim::type::instant);
+ selectType(kPremiumsRowId);
+ } else if (_selected.miniapps) {
+ selectType(kMiniAppsRowId);
}
container->add(CreatePeerListSectionSubtitle(
container,
tr::lng_edit_privacy_users_and_groups()));
- controller->premiumsChanges(
- ) | rpl::start_with_next([=](bool premiums) {
- _selected.premiums = premiums;
+ controller->specialChanges(
+ ) | rpl::start_with_next([=](bool chosen) {
+ if (type == SpecialRowType::Premiums) {
+ _selected.premiums = chosen;
+ } else {
+ _selected.miniapps = chosen;
+ }
}, lifetime);
controller->rowSelectionChanges(
@@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
if (itemId == kPremiumsRowId) {
_selected.premiums = false;
+ } else if (itemId == kMiniAppsRowId) {
+ _selected.miniapps = false;
}
_typesDelegate->peerListSetRowChecked(row, false);
}
@@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
return result;
}
-[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
+bool PrivacyExceptionsBoxController::premiumsSelected() const {
return _selected.premiums;
}
+bool PrivacyExceptionsBoxController::miniAppsSelected() const {
+ return _selected.miniapps;
+}
+
void PrivacyExceptionsBoxController::rowClicked(not_null row) {
const auto peer = row->peer();
@@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
_value.always.premiums = true;
}
+ if (_controller->allowMiniAppsToggle(Exception::Always)
+ && _value.option == Option::Everyone) {
+ // If we switch from Everyone to Contacts or Nobody suggest MiniApps.
+ _value.always.miniapps = true;
+ }
}
void EditPrivacyBox::prepare() {
@@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
&_window->session(),
_controller->exceptionBoxTitle(exception),
exceptions(exception),
- _controller->allowPremiumsToggle(exception));
+ (_controller->allowPremiumsToggle(exception)
+ ? SpecialRowType::Premiums
+ : _controller->allowMiniAppsToggle(exception)
+ ? SpecialRowType::MiniApps
+ : std::optional()));
auto initBox = [=, controller = controller.get()](
not_null box) {
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
- exceptions(exception).peers = box->collectSelectedRows();
- exceptions(exception).premiums = controller->premiumsSelected();
+ auto &setTo = exceptions(exception);
+ setTo.peers = box->collectSelectedRows();
+ setTo.premiums = controller->premiumsSelected();
+ setTo.miniapps = controller->miniAppsSelected();
const auto type = [&] {
switch (exception) {
case Exception::Always: return Exception::Never;
@@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
}
Unexpected("Invalid exception value.");
}();
- auto &removeFrom = exceptions(type).peers;
+ auto &removeFrom = exceptions(type);
for (const auto peer : exceptions(exception).peers) {
- removeFrom.erase(
- ranges::remove(removeFrom, peer),
- end(removeFrom));
+ removeFrom.peers.erase(
+ ranges::remove(removeFrom.peers, peer),
+ end(removeFrom.peers));
+ }
+ if (setTo.premiums) {
+ removeFrom.premiums = false;
+ }
+ if (setTo.miniapps) {
+ removeFrom.miniapps = false;
}
done();
box->closeBox();
@@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
lt_count,
count)
: tr::lng_edit_privacy_exceptions_add(tr::now);
- return !value.premiums
- ? users
- : !count
- ? tr::lng_edit_privacy_premium(tr::now)
- : tr::lng_edit_privacy_exceptions_premium_and(
- tr::now,
- lt_users,
- users);
+ return value.premiums
+ ? (!count
+ ? tr::lng_edit_privacy_premium(tr::now)
+ : tr::lng_edit_privacy_exceptions_premium_and(
+ tr::now,
+ lt_users,
+ users))
+ : value.miniapps
+ ? (!count
+ ? tr::lng_edit_privacy_miniapps(tr::now)
+ : tr::lng_edit_privacy_exceptions_miniapps_and(
+ tr::now,
+ lt_users,
+ users))
+ : users;
});
_controller->handleExceptionsChange(
exception,
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h
index 3aae28403..bd87e90f2 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.h
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h
@@ -61,6 +61,10 @@ public:
Exception exception) const {
return false;
}
+ [[nodiscard]] virtual bool allowMiniAppsToggle(
+ Exception exception) const {
+ return false;
+ }
virtual void handleExceptionsChange(
Exception exception,
rpl::producer value) {
diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
index f0ecd05f6..896ecea51 100644
--- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp
@@ -123,7 +123,9 @@ void GiftCreditsBox(
box->verticalLayout(),
peer,
0,
- [=] { gifted(); box->uiShow()->hideLayer(); });
+ [=] { gifted(); box->uiShow()->hideLayer(); },
+ tr::lng_credits_summary_options_subtitle(),
+ {});
box->setPinnedToBottomContent(
object_ptr(box));
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index fd640e496..a3d385c72 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
+#include "boxes/star_gift_box.h" // ShowStarGiftBox.
#include "data/data_boosts.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@@ -123,7 +124,8 @@ namespace {
[[nodiscard]] object_ptr MakePeerTableValue(
not_null parent,
not_null controller,
- PeerId id) {
+ PeerId id,
+ bool withSendGiftButton = false) {
auto result = object_ptr(parent);
const auto raw = result.data();
@@ -134,15 +136,40 @@ namespace {
const auto userpic = Ui::CreateChild(raw, peer, st);
const auto label = Ui::CreateChild(
raw,
- peer->name(),
+ withSendGiftButton ? peer->shortName() : peer->name(),
st::giveawayGiftCodeValue);
- raw->widthValue(
- ) | rpl::start_with_next([=](int width) {
+ const auto send = withSendGiftButton
+ ? Ui::CreateChild(
+ raw,
+ tr::lng_gift_send_small(),
+ st::starGiftSmallButton)
+ : nullptr;
+ if (send) {
+ send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
+ send->setClickedCallback([=] {
+ Ui::ShowStarGiftBox(controller->parentController(), peer);
+ });
+ }
+ rpl::combine(
+ raw->widthValue(),
+ send ? send->widthValue() : rpl::single(0)
+ ) | rpl::start_with_next([=](int width, int sendWidth) {
const auto position = st::giveawayGiftCodeNamePosition;
- label->resizeToNaturalWidth(width - position.x());
+ const auto sendSkip = sendWidth
+ ? (st::normalFont->spacew + sendWidth)
+ : 0;
+ label->resizeToNaturalWidth(width - position.x() - sendSkip);
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()
+ + st::giveawayGiftCodeValue.style.font->ascent
+ - st::starGiftSmallButton.style.font->ascent),
+ width);
+ }
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
@@ -210,14 +237,82 @@ void AddTableRow(
valueMargins);
}
+object_ptr MakeStarGiftStarsValue(
+ not_null parent,
+ not_null controller,
+ const Data::CreditsHistoryEntry &entry,
+ Fn convertToStars) {
+ auto result = object_ptr(parent);
+ const auto raw = result.data();
+
+ const auto session = &controller->session();
+ const auto makeContext = [session](Fn update) {
+ return Core::MarkedTextContext{
+ .session = session,
+ .customEmojiRepaint = std::move(update),
+ };
+ };
+ auto star = session->data().customEmojiManager().creditsEmoji();
+ const auto label = Ui::CreateChild(
+ raw,
+ rpl::single(
+ star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
+ st::giveawayGiftCodeValue,
+ st::defaultPopupMenu,
+ std::move(makeContext));
+
+ const auto convert = convertToStars
+ ? Ui::CreateChild(
+ raw,
+ tr::lng_gift_sell_small(
+ lt_count_decimal,
+ rpl::single(entry.starsConverted * 1.)),
+ st::starGiftSmallButton)
+ : nullptr;
+ if (convert) {
+ convert->setTextTransform(Ui::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;
+ label->resizeToNaturalWidth(width - convertSkip);
+ label->moveToLeft(0, 0, width);
+ if (convert) {
+ convert->moveToLeft(
+ label->width() + st::normalFont->spacew,
+ (st::giveawayGiftCodeValue.style.font->ascent
+ - st::starGiftSmallButton.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;
+}
+
not_null AddTableRow(
not_null table,
rpl::producer label,
- rpl::producer value) {
+ rpl::producer value,
+ const Fn)> &makeContext = nullptr) {
auto widget = object_ptr(
table,
std::move(value),
- st::giveawayGiftCodeValue);
+ st::giveawayGiftCodeValue,
+ st::defaultPopupMenu,
+ std::move(makeContext));
const auto result = widget.data();
AddTableRow(
table,
@@ -939,26 +1034,57 @@ void ResolveGiveawayInfo(
void AddStarGiftTable(
not_null controller,
not_null container,
- const Data::CreditsHistoryEntry &entry) {
+ const Data::CreditsHistoryEntry &entry,
+ Fn convertToStars) {
auto table = container->add(
object_ptr(
container,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
+ const auto session = &controller->session();
if (peerId) {
+ const auto user = session->data().peer(peerId)->asUser();
+ const auto withSendButton = entry.in && user && !user->isBot();
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
- controller,
- peerId);
- } else {
+ MakePeerTableValue(table, controller, peerId, withSendButton),
+ st::giveawayGiftCodePeerMargin);
+ } else if (!entry.soldOutInfo) {
AddTableRow(
table,
tr::lng_credits_box_history_entry_peer_in(),
MakeHiddenPeerTableValue(table, controller),
st::giveawayGiftCodePeerMargin);
}
+ if (!entry.firstSaleDate.isNull()) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_first_sale(),
+ rpl::single(Ui::Text::WithEntities(
+ langDateTime(entry.firstSaleDate))));
+ }
+ if (!entry.lastSaleDate.isNull()) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_last_sale(),
+ rpl::single(Ui::Text::WithEntities(
+ langDateTime(entry.lastSaleDate))));
+ }
+ {
+ const auto margin = st::giveawayGiftCodeValueMargin
+ - QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_value(),
+ MakeStarGiftStarsValue(
+ table,
+ controller,
+ entry,
+ std::move(convertToStars)),
+ margin);
+ }
if (!entry.date.isNull()) {
AddTableRow(
table,
@@ -967,14 +1093,14 @@ void AddStarGiftTable(
}
if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{
- QString::number(entry.limitedCount)
+ Lang::FormatCountDecimal(entry.limitedCount)
});
AddTableRow(
table,
tr::lng_gift_availability(),
((entry.limitedLeft > 0)
? tr::lng_gift_availability_left(
- lt_count,
+ lt_count_decimal,
rpl::single(entry.limitedLeft * 1.),
lt_amount,
std::move(amount),
@@ -985,7 +1111,6 @@ void AddStarGiftTable(
Ui::Text::WithEntities)));
}
if (!entry.description.empty()) {
- const auto session = &controller->session();
const auto makeContext = [=](Fn update) {
return Core::MarkedTextContext{
.session = session,
@@ -1020,12 +1145,17 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId);
+ const auto actorId = PeerId(entry.bareActorId);
const auto session = &controller->session();
- if (peerId) {
+ if (actorId || peerId) {
auto text = entry.in
? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer();
- AddTableRow(table, std::move(text), controller, peerId);
+ AddTableRow(
+ table,
+ std::move(text),
+ controller,
+ actorId ? actorId : peerId);
}
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto peer = session->data().peer(peerId);
@@ -1044,7 +1174,9 @@ void AddCreditsHistoryEntryTable(
});
AddTableRow(
table,
- tr::lng_credits_box_history_entry_media(),
+ (entry.reaction
+ ? tr::lng_credits_box_history_entry_message
+ : tr::lng_credits_box_history_entry_media)(),
std::move(label),
st::giveawayGiftCodeValueMargin);
}
@@ -1116,12 +1248,24 @@ void AddCreditsHistoryEntryTable(
}
}
if (!entry.id.isEmpty()) {
- constexpr auto kOneLineCount = 18;
- const auto oneLine = entry.id.length() <= kOneLineCount;
+ constexpr auto kOneLineCount = 22;
+ const auto oneLine = entry.id.size() <= kOneLineCount;
+ auto multiLine = QString();
+ if (!oneLine) {
+ for (auto i = 0; i < entry.id.size(); ++i) {
+ multiLine.append(entry.id[i]);
+ if ((i + 1) % kOneLineCount == 0) {
+ multiLine.append('\n');
+ }
+ }
+ }
auto label = object_ptr(
table,
rpl::single(
- Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
+ Ui::Text::Wrapped(
+ { oneLine ? entry.id : std::move(multiLine) },
+ EntityType::Code,
+ {})),
oneLine
? st::giveawayGiftCodeValue
: st::giveawayGiftCodeValueMultiline);
@@ -1138,6 +1282,14 @@ void AddCreditsHistoryEntryTable(
std::move(label),
st::giveawayGiftCodeValueMargin);
}
+ if (entry.floodSkip) {
+ AddTableRow(
+ table,
+ tr::lng_credits_box_history_entry_floodskip_row(),
+ rpl::single(
+ Ui::Text::WithEntities(
+ Lang::FormatCountDecimal(entry.floodSkip))));
+ }
if (!entry.date.isNull()) {
AddTableRow(
table,
@@ -1178,6 +1330,16 @@ void AddSubscriptionEntryTable(
controller,
peerId);
if (!s.until.isNull()) {
+ if (s.subscription.period > 0) {
+ const auto subscribed = s.until.addSecs(-s.subscription.period);
+ if (subscribed.isValid()) {
+ AddTableRow(
+ table,
+ tr::lng_group_invite_joined_row_date(),
+ rpl::single(
+ Ui::Text::WithEntities(langDateTime(subscribed))));
+ }
+ }
AddTableRow(
table,
s.expired
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index 31ca5fcf2..3a2c20498 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -57,7 +57,8 @@ void ResolveGiveawayInfo(
void AddStarGiftTable(
not_null controller,
not_null container,
- const Data::CreditsHistoryEntry &entry);
+ const Data::CreditsHistoryEntry &entry,
+ Fn convertToStars);
void AddCreditsHistoryEntryTable(
not_null controller,
not_null container,
diff --git a/Telegram/SourceFiles/boxes/local_storage_box.cpp b/Telegram/SourceFiles/boxes/local_storage_box.cpp
index fcdbd8d6d..647d6e0e9 100644
--- a/Telegram/SourceFiles/boxes/local_storage_box.cpp
+++ b/Telegram/SourceFiles/boxes/local_storage_box.cpp
@@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
}
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
- if (!_progress || true) {
+#if 0 // not used
+ if (!_progress) {
return;
}
auto p = QPainter(this);
@@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
st::proxyCheckingPosition.y() + bottom
},
width());
+#endif
}
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index b0f3c0a50..5a440ced5 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
content()->selectSkipPage(height(), 1);
} else if (e->key() == Qt::Key_PageUp) {
content()->selectSkipPage(height(), -1);
- } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
+ } else if (e->key() == Qt::Key_Escape
+ && _select
+ && !_select->entity()->getQuery().isEmpty()) {
_select->entity()->clearQuery();
} else {
BoxContent::keyPressEvent(e);
@@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0);
- content()->searchQueryChanged(query);
+ const auto isEmpty = content()->searchQueryChanged(query);
+ if (_specialTabsMode.enabled) {
+ const auto was = _specialTabsMode.searchIsActive;
+ _specialTabsMode.searchIsActive = !isEmpty;
+ if (was != _specialTabsMode.searchIsActive) {
+ if (_specialTabsMode.searchIsActive) {
+ _specialTabsMode.topSkip = _addedTopScrollSkip;
+ setAddedTopScrollSkip(0);
+ } else {
+ setAddedTopScrollSkip(_specialTabsMode.topSkip);
+ }
+ }
+ }
}
void PeerListBox::resizeEvent(QResizeEvent *e) {
@@ -543,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
return result;
}
+rpl::producer PeerListBox::multiSelectHeightValue() const {
+ return _select ? _select->heightValue() : rpl::single(0);
+}
+
+void PeerListBox::setSpecialTabMode(bool value) {
+ content()->setIgnoreHiddenRowsOnSearch(value);
+ if (value) {
+ _specialTabsMode.enabled = true;
+ } else {
+ _specialTabsMode = {};
+ }
+}
+
PeerListRow::PeerListRow(not_null peer)
: PeerListRow(peer, peer->id.value) {
}
@@ -1385,10 +1412,12 @@ int PeerListContent::labelHeight() const {
void PeerListContent::refreshRows() {
if (!_hiddenRows.empty()) {
- _filterResults.clear();
- for (const auto &row : _rows) {
- if (!row->hidden()) {
- _filterResults.push_back(row.get());
+ if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
+ _filterResults.clear();
+ for (const auto &row : _rows) {
+ if (!row->hidden()) {
+ _filterResults.push_back(row.get());
+ }
}
}
}
@@ -1944,6 +1973,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
}
}
+ if (_controller->overrideKeyboardNavigation(
+ direction,
+ _selected.index.value,
+ newSelectedIndex)) {
+ return { _selected.index.value, _selected.index.value };
+ }
+
_selected.index.value = newSelectedIndex;
_selected.element = 0;
if (newSelectedIndex >= 0) {
@@ -2043,13 +2079,16 @@ void PeerListContent::checkScrollForPreload() {
}
}
-void PeerListContent::searchQueryChanged(QString query) {
+PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
const auto normalizedQuery = searchWordsList.join(' ');
+ if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
+ _filterResults.clear();
+ }
if (_normalizedSearchQuery != normalizedQuery) {
setSearchQuery(query, normalizedQuery);
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
- Assert(_hiddenRows.empty());
+ Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
auto minimalList = (const std::vector>*)nullptr;
for (const auto &searchWord : searchWordsList) {
@@ -2097,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
}
refreshRows();
}
+ return _normalizedSearchQuery.isEmpty();
}
std::unique_ptr PeerListContent::saveState() const {
@@ -2185,6 +2225,10 @@ void PeerListContent::dragLeft() {
clearSelection();
}
+void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
+ _ignoreHiddenRowsOnSearch = value;
+}
+
void PeerListContent::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index 4c4374b84..bd71470a7 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -357,6 +357,8 @@ public:
virtual int peerListPartitionRows(Fn border) = 0;
virtual std::shared_ptr peerListUiShow() = 0;
+ virtual void peerListSelectSkip(int direction) = 0;
+
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
@@ -573,6 +575,13 @@ public:
Unexpected("PeerListController::customRowRippleMaskGenerator.");
}
+ virtual bool overrideKeyboardNavigation(
+ int direction,
+ int fromIndex,
+ int toIndex) {
+ return false;
+ }
+
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
@@ -643,12 +652,15 @@ public:
[[nodiscard]] bool hasPressed() const;
void clearSelection();
- void searchQueryChanged(QString query);
+ using IsEmpty = bool;
+ IsEmpty searchQueryChanged(QString query);
bool submitted();
PeerListRowId updateFromParentDrag(QPoint globalPosition);
void dragLeft();
+ void setIgnoreHiddenRowsOnSearch(bool value);
+
// Interface for the controller.
void appendRow(std::unique_ptr row);
void appendSearchRow(std::unique_ptr row);
@@ -870,6 +882,7 @@ private:
int _aboveHeight = 0;
int _belowHeight = 0;
bool _hideEmpty = false;
+ bool _ignoreHiddenRowsOnSearch = false;
object_ptr _aboveWidget = { nullptr };
object_ptr _aboveSearchWidget = { nullptr };
object_ptr _belowWidget = { nullptr };
@@ -1016,6 +1029,10 @@ public:
bool highlightRow,
Fn)> destroyed = nullptr) override;
+ void peerListSelectSkip(int direction) override {
+ _content->selectSkip(direction);
+ }
+
void peerListPressLeftToContextMenu(bool shown) override {
_content->pressLeftToContextMenu(shown);
}
@@ -1089,6 +1106,9 @@ public:
[[nodiscard]] std::vector collectSelectedIds();
[[nodiscard]] std::vector> collectSelectedRows();
+ [[nodiscard]] rpl::producer multiSelectHeightValue() const;
+
+ void setSpecialTabMode(bool value);
void peerListSetTitle(rpl::producer title) override {
setTitle(std::move(title));
@@ -1155,4 +1175,11 @@ private:
bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0;
+ struct SpecialTabsMode final {
+ bool enabled = false;
+ bool searchIsActive = false;
+ int topSkip = 0;
+ };
+ SpecialTabsMode _specialTabsMode;
+
};
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index 53d56080a..05b8d6d20 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -1065,6 +1065,11 @@ std::unique_ptr ChooseTopicBoxController::createSearchRow(
return nullptr;
}
+std::unique_ptr ChooseTopicBoxController::MakeRow(
+ not_null topic) {
+ return std::make_unique(topic);
+}
+
auto ChooseTopicBoxController::createRow(not_null topic)
-> std::unique_ptr {
const auto skip = _filter && !_filter(topic);
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.h b/Telegram/SourceFiles/boxes/peer_list_controllers.h
index 20479c2b4..07f71534a 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.h
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.h
@@ -335,6 +335,9 @@ public:
void loadMoreRows() override;
std::unique_ptr createSearchRow(PeerListRowId id) override;
+ [[nodiscard]] static std::unique_ptr MakeRow(
+ not_null topic);
+
private:
class Row final : public PeerListRow {
public:
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
index 345d9880e..bdd3bdc24 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
@@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "base/unixtime.h"
#include "ui/effects/outline_segments.h"
+#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
+#include "ui/text/text_utilities.h"
#include "info/profile/info_profile_values.h"
#include "window/window_session_controller.h"
#include "history/history.h"
+#include "styles/style_chat.h"
#include "styles/style_menu_icons.h"
namespace {
@@ -1645,6 +1648,51 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu(
auto result = base::make_unique_q(
parent,
st::popupMenuWithIcons);
+ const auto addToEnd = gsl::finally([&] {
+ const auto addInfoAction = [&](
+ not_null by,
+ tr::phrase phrase,
+ TimeId since) {
+ auto text = phrase(
+ tr::now,
+ lt_user,
+ Ui::Text::Bold(by->name()),
+ lt_date,
+ Ui::Text::Bold(
+ langDateTimeFull(base::unixtime::parse(since))),
+ Ui::Text::WithEntities);
+ auto button = base::make_unique_q(
+ result->menu(),
+ result->st().menu,
+ st::historyHasCustomEmoji,
+ st::historyHasCustomEmojiPosition,
+ std::move(text));
+ if (const auto n = _navigation) {
+ button->setClickedCallback([=] {
+ n->parentController()->show(PrepareShortInfoBox(by, n));
+ });
+ }
+ result->addSeparator();
+ result->addAction(std::move(button));
+ };
+
+ if (const auto by = _additional.restrictedBy(participant)) {
+ if (const auto since = _additional.restrictedSince(participant)) {
+ addInfoAction(
+ by,
+ _additional.isKicked(participant)
+ ? tr::lng_rights_chat_banned_by
+ : tr::lng_rights_chat_restricted_by,
+ since);
+ }
+ } else if (user) {
+ if (const auto by = _additional.adminPromotedBy(user)) {
+ if (const auto since = _additional.adminPromotedSince(user)) {
+ addInfoAction(by, tr::lng_rights_about_by, since);
+ }
+ }
+ }
+ });
if (_navigation) {
result->addAction(
(participant->isUser()
@@ -1652,39 +1700,14 @@ base::unique_qptr ParticipantsBoxController::rowContextMenu(
: participant->isBroadcast()
? tr::lng_context_view_channel
: tr::lng_context_view_group)(tr::now),
- crl::guard(this, [=] {
- _navigation->showPeerInfo(participant); }),
+ crl::guard(this, [=, this] {
+ _navigation->parentController()->show(
+ PrepareShortInfoBox(participant, _navigation));
+ }),
(participant->isUser()
? &st::menuIconProfile
: &st::menuIconInfo));
}
- if (const auto by = _additional.restrictedBy(participant)) {
- result->addAction(
- (_role == Role::Kicked
- ? tr::lng_channel_banned_status_removed_by
- : tr::lng_channel_banned_status_restricted_by)(
- tr::now,
- lt_user,
- by->name()),
- crl::guard(this, [=] {
- _navigation->parentController()->show(
- PrepareShortInfoBox(by, _navigation));
- }),
- &st::menuIconAdmin);
- } else if (user) {
- if (const auto by = _additional.adminPromotedBy(user)) {
- result->addAction(
- tr::lng_channel_admin_status_promoted_by(
- tr::now,
- lt_user,
- by->name()),
- crl::guard(this, [=] {
- _navigation->parentController()->show(
- PrepareShortInfoBox(by, _navigation));
- }),
- &st::menuIconAdmin);
- }
- }
if (_role == Role::Kicked) {
if (_peer->isMegagroup()
&& _additional.canRestrictParticipant(participant)) {
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index 4b11e3745..76fd9856b 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_peer_photo.h"
+#include "api/api_statistics.h"
#include "api/api_user_names.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
@@ -46,6 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
+#include "info/channel_statistics/earn/earn_format.h"
+#include "info/channel_statistics/earn/earn_icons.h"
+#include "info/channel_statistics/earn/info_channel_earn_widget.h"
#include "info/profile/info_profile_values.h"
#include "info/info_memento.h"
#include "lang/lang_keys.h"
@@ -352,7 +356,8 @@ private:
void fillPendingRequestsButton();
void fillBotUsernamesButton();
- void fillBotBalanceButton();
+ void fillBotCurrencyButton();
+ void fillBotCreditsButton();
void fillBotEditIntroButton();
void fillBotEditCommandsButton();
void fillBotEditSettingsButton();
@@ -1174,7 +1179,8 @@ void Controller::fillManageSection() {
::AddSkip(container, 0);
fillBotUsernamesButton();
- fillBotBalanceButton();
+ fillBotCurrencyButton();
+ fillBotCreditsButton();
fillBotEditIntroButton();
fillBotEditCommandsButton();
fillBotEditSettingsButton();
@@ -1583,7 +1589,72 @@ void Controller::fillBotUsernamesButton() {
{ &st::menuIconLinks });
}
-void Controller::fillBotBalanceButton() {
+void Controller::fillBotCurrencyButton() {
+ Expects(_isBot);
+
+ struct State final {
+ rpl::variable balance;
+ };
+
+ auto &lifetime = _controls.buttonsLayout->lifetime();
+ const auto state = lifetime.make_state();
+ const auto format = [=](uint64 balance) {
+ return Info::ChannelEarn::MajorPart(balance)
+ + Info::ChannelEarn::MinorPart(balance);
+ };
+ const auto was = _peer->session().credits().balanceCurrency(
+ _peer->id);
+ if (was) {
+ state->balance = format(was);
+ }
+
+ const auto wrap = _controls.buttonsLayout->add(
+ object_ptr>(
+ _controls.buttonsLayout,
+ EditPeerInfoBox::CreateButton(
+ _controls.buttonsLayout,
+ tr::lng_manage_peer_bot_balance_currency(),
+ state->balance.value(),
+ [controller = _navigation->parentController(), peer = _peer] {
+ controller->showSection(Info::ChannelEarn::Make(peer));
+ },
+ st::manageGroupButton,
+ {})));
+ wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
+
+ const auto button = wrap->entity();
+ {
+ const auto currencyLoad
+ = button->lifetime().make_state(_peer);
+ currencyLoad->request(
+ ) | rpl::start_with_error_done([=](const QString &error) {
+ }, [=] {
+ const auto balance = currencyLoad->data().currentBalance;
+ if (balance) {
+ wrap->toggle(true, anim::type::normal);
+ }
+ state->balance = format(balance);
+ }, button->lifetime());
+ }
+ {
+ const auto icon = Ui::CreateChild(button);
+ icon->resize(st::menuIconLinks.size());
+ const auto image = Ui::Earn::MenuIconCurrency(icon->size());
+ icon->paintRequest() | rpl::start_with_next([=] {
+ auto p = QPainter(icon);
+ p.drawImage(0, 0, image);
+ }, icon->lifetime());
+
+ button->sizeValue(
+ ) | rpl::start_with_next([=](const QSize &size) {
+ icon->moveToLeft(
+ button->st().iconLeft,
+ (size.height() - icon->height()) / 2);
+ }, icon->lifetime());
+ }
+}
+
+void Controller::fillBotCreditsButton() {
Expects(_isBot);
struct State final {
@@ -1593,7 +1664,7 @@ void Controller::fillBotBalanceButton() {
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state();
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
- state->balance = QString::number(balance);
+ state->balance = Lang::FormatCountDecimal(balance);
}
const auto wrap = _controls.buttonsLayout->add(
@@ -1601,7 +1672,7 @@ void Controller::fillBotBalanceButton() {
_controls.buttonsLayout,
EditPeerInfoBox::CreateButton(
_controls.buttonsLayout,
- tr::lng_manage_peer_bot_balance(),
+ tr::lng_manage_peer_bot_balance_credits(),
state->balance.value(),
[controller = _navigation->parentController(), peer = _peer] {
controller->showSection(Info::BotEarn::Make(peer));
@@ -1618,46 +1689,22 @@ void Controller::fillBotBalanceButton() {
if (data.balance) {
wrap->toggle(true, anim::type::normal);
}
- state->balance = QString::number(data.balance);
+ state->balance = Lang::FormatCountDecimal(data.balance);
});
}
{
- constexpr auto kSizeShift = 3;
- constexpr auto kStrokeWidth = 5;
-
const auto icon = Ui::CreateChild(button);
- icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
-
- auto colorized = [&] {
- auto f = QFile(Ui::Premium::Svg());
- if (!f.open(QIODevice::ReadOnly)) {
- return QString();
- }
- return QString::fromUtf8(
- f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
- }();
- colorized.replace(
- u"stroke=\"none\""_q,
- u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
- colorized.replace(
- u"stroke-width=\"1\""_q,
- u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
- const auto svg = icon->lifetime().make_state(
- colorized.toUtf8());
- svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
-
- const auto starSize = Size(icon->height());
-
- icon->paintRequest(
- ) | rpl::start_with_next([=] {
+ const auto image = Ui::Earn::MenuIconCredits();
+ icon->resize(image.size() / style::DevicePixelRatio());
+ icon->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(icon);
- svg->render(&p, Rect(starSize));
+ p.drawImage(0, 0, image);
}, icon->lifetime());
button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) {
icon->moveToLeft(
- button->st().iconLeft + kSizeShift / 2.,
+ button->st().iconLeft,
(size.height() - icon->height()) / 2);
}, icon->lifetime());
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
index 8e1a11030..c8b4f2d48 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
@@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "boxes/gift_premium_box.h"
#include "boxes/peer_list_box.h"
+#include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h"
#include "core/application.h"
#include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/credits.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
+#include "data/data_forum_topic.h"
#include "data/data_histories.h"
#include "data/data_peer.h"
#include "data/data_session.h"
@@ -51,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_credits.h"
+#include "styles/style_dialogs.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
#include "styles/style_layers.h" // st::boxDividerLabel.
@@ -264,8 +267,9 @@ private:
class SingleRowController final : public PeerListController {
public:
SingleRowController(
- not_null peer,
- rpl::producer status);
+ not_null thread,
+ rpl::producer status,
+ Fn clicked);
void prepare() override;
void loadMoreRows() override;
@@ -273,8 +277,10 @@ public:
Main::Session &session() const override;
private:
- const not_null _peer;
+ const not_null _session;
+ const base::weak_ptr _thread;
rpl::producer _status;
+ Fn _clicked;
rpl::lifetime _lifetime;
};
@@ -956,12 +962,11 @@ void Controller::rowClicked(not_null row) {
Ui::AddSkip(content);
Ui::AddSkip(content);
- const auto &stUser = st::boostReplaceUserpic;
+ const auto photoSize = st::boostReplaceUserpic.photoSize;
const auto session = &row->peer()->session();
content->add(object_ptr>(
content,
- object_ptr(content, channel, stUser))
- )->setAttribute(Qt::WA_TransparentForMouseEvents);
+ Settings::SubscriptionUserpic(content, channel, photoSize)));
Ui::AddSkip(content);
Ui::AddSkip(content);
@@ -1145,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
}
SingleRowController::SingleRowController(
- not_null peer,
- rpl::producer status)
-: _peer(peer)
-, _status(std::move(status)) {
+ not_null thread,
+ rpl::producer status,
+ Fn clicked)
+: _session(&thread->session())
+, _thread(thread)
+, _status(std::move(status))
+, _clicked(std::move(clicked)) {
}
void SingleRowController::prepare() {
- auto row = std::make_unique(_peer);
-
+ const auto strong = _thread.get();
+ if (!strong) {
+ return;
+ }
+ const auto topic = strong->asTopic();
+ auto row = topic
+ ? ChooseTopicBoxController::MakeRow(topic)
+ : std::make_unique(strong->peer());
const auto raw = row.get();
- std::move(
- _status
- ) | rpl::start_with_next([=](const QString &status) {
- raw->setCustomStatus(status);
- delegate()->peerListUpdateRow(raw);
- }, _lifetime);
-
+ if (_status) {
+ std::move(
+ _status
+ ) | rpl::start_with_next([=](const QString &status) {
+ raw->setCustomStatus(status);
+ delegate()->peerListUpdateRow(raw);
+ }, _lifetime);
+ }
delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows();
+
+ if (topic) {
+ topic->destroyed() | rpl::start_with_next([=] {
+ while (delegate()->peerListFullRowsCount()) {
+ delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
+ }
+ delegate()->peerListRefreshRows();
+ }, _lifetime);
+ }
}
void SingleRowController::loadMoreRows() {
}
void SingleRowController::rowClicked(not_null row) {
- ShowPeerInfoSync(row->peer());
+ if (const auto onstack = _clicked) {
+ onstack();
+ } else {
+ ShowPeerInfoSync(row->peer());
+ }
}
Main::Session &SingleRowController::session() const {
- return _peer->session();
+ return *_session;
}
} // namespace
@@ -1187,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
void AddSinglePeerRow(
not_null container,
not_null peer,
- rpl::producer status) {
+ rpl::producer status,
+ Fn clicked) {
+ AddSinglePeerRow(
+ container,
+ peer->owner().history(peer),
+ std::move(status),
+ std::move(clicked));
+}
+
+void AddSinglePeerRow(
+ not_null container,
+ not_null thread,
+ rpl::producer status,
+ Fn clicked) {
const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple
>();
const auto controller = container->lifetime().make_state<
SingleRowController
- >(peer, std::move(status));
- controller->setStyleOverrides(&st::peerListSingleRow);
+ >(thread, std::move(status), std::move(clicked));
+ controller->setStyleOverrides(thread->asTopic()
+ ? &st::chooseTopicList
+ : &st::peerListSingleRow);
const auto content = container->add(object_ptr(
container,
controller));
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h
index e59ffdd5e..45abe3a46 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.h
@@ -16,6 +16,10 @@ namespace Api {
struct InviteLink;
} // namespace Api
+namespace Data {
+class Thread;
+} // namespace Data
+
namespace Main {
class Session;
} // namespace Main
@@ -31,7 +35,14 @@ class BoxContent;
void AddSinglePeerRow(
not_null container,
not_null peer,
- rpl::producer status);
+ rpl::producer status,
+ Fn clicked = nullptr);
+
+void AddSinglePeerRow(
+ not_null container,
+ not_null thread,
+ rpl::producer status,
+ Fn clicked = nullptr);
void AddPermanentLinkBlock(
std::shared_ptr show,
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp
index db23233aa..4663d4332 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_requests_box.cpp
@@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/edit_peer_requests_box.h"
-#include "ui/effects/ripple_animation.h"
+#include "api/api_invite_links.h"
+#include "apiwrap.h"
+#include "base/unixtime.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
-#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
-#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
-#include "data/data_peer.h"
-#include "data/data_user.h"
-#include "data/data_chat.h"
+#include "boxes/peers/edit_peer_requests_box.h"
#include "data/data_channel.h"
+#include "data/data_chat.h"
+#include "data/data_peer.h"
#include "data/data_session.h"
-#include "base/unixtime.h"
+#include "data/data_user.h"
+#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
+#include "info/info_controller.h"
+#include "info/info_memento.h"
+#include "info/requests_list/info_requests_list_widget.h"
+#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mtproto/sender.h"
+#include "ui/effects/ripple_animation.h"
+#include "ui/painter.h"
#include "ui/round_rect.h"
#include "ui/text/text_utilities.h"
-#include "ui/painter.h"
-#include "lang/lang_keys.h"
#include "window/window_session_controller.h"
-#include "apiwrap.h"
-#include "api/api_invite_links.h"
#include "styles/style_boxes.h"
namespace {
@@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
void RequestsBoxController::Start(
not_null navigation,
not_null peer) {
- auto controller = std::make_unique(
- navigation,
- peer->migrateToOrMe());
- const auto initBox = [=](not_null box) {
- box->addButton(tr::lng_close(), [=] { box->closeBox(); });
- };
- navigation->parentController()->show(
- Box(std::move(controller), initBox));
+ navigation->showSection(
+ std::make_shared(
+ peer->migrateToOrMe(),
+ Info::Section::Type::RequestsList));
}
Main::Session &RequestsBoxController::session() const {
@@ -289,6 +288,58 @@ std::unique_ptr RequestsBoxController::createSearchRow(
return nullptr;
}
+std::unique_ptr RequestsBoxController::createRestoredRow(
+ not_null peer) {
+ if (const auto user = peer->asUser()) {
+ return createRow(user, _dates[user]);
+ }
+ return nullptr;
+}
+
+auto RequestsBoxController::saveState() const
+-> std::unique_ptr {
+ auto result = PeerListController::saveState();
+
+ auto my = std::make_unique();
+ my->dates = _dates;
+ my->offsetDate = _offsetDate;
+ my->offsetUser = _offsetUser;
+ my->allLoaded = _allLoaded;
+ my->wasLoading = (_loadRequestId != 0);
+ if (const auto search = searchController()) {
+ my->searchState = search->saveState();
+ }
+ result->controllerState = std::move(my);
+ return result;
+}
+
+void RequestsBoxController::restoreState(
+ std::unique_ptr state) {
+ auto typeErasedState = state
+ ? state->controllerState.get()
+ : nullptr;
+ if (const auto my = dynamic_cast(typeErasedState)) {
+ if (const auto requestId = base::take(_loadRequestId)) {
+ _api.request(requestId).cancel();
+ }
+ _dates = std::move(my->dates);
+ _offsetDate = my->offsetDate;
+ _offsetUser = my->offsetUser;
+ _allLoaded = my->allLoaded;
+ if (const auto search = searchController()) {
+ search->restoreState(std::move(my->searchState));
+ }
+ if (my->wasLoading) {
+ loadMoreRows();
+ }
+ PeerListController::restoreState(std::move(state));
+ if (delegate()->peerListFullRowsCount() || _allLoaded) {
+ refreshDescription();
+ delegate()->peerListRefreshRows();
+ }
+ }
+}
+
void RequestsBoxController::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(_peer->isBroadcast()
@@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
}
void RequestsBoxController::rowClicked(not_null row) {
- _navigation->parentController()->show(PrepareShortInfoBox(
- row->peer(),
- _navigation));
+ _navigation->showPeerInfo(row->peer());
}
void RequestsBoxController::rowElementClicked(
@@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
not_null user,
TimeId date) {
if (!delegate()->peerListFindRow(user->id.value)) {
+ _dates.emplace(user, date);
if (auto row = createRow(user, date)) {
delegate()->peerListAppendRow(std::move(row));
setDescriptionText(QString());
@@ -503,6 +553,7 @@ std::unique_ptr RequestsBoxController::createRow(
const auto search = static_cast(
searchController());
date = search->dateForUser(user);
+ _dates.emplace(user, date);
}
return std::make_unique(_helper.get(), user, date);
}
@@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null user) {
return {};
}
+auto RequestsBoxSearchController::saveState() const
+-> std::unique_ptr