Merge tag 'v5.8.2' into dev

This commit is contained in:
AlexeyZavar 2024-11-20 16:15:31 +03:00
commit dbc0cae68c
394 changed files with 17142 additions and 3328 deletions

2
.gitmodules vendored
View file

@ -3,7 +3,7 @@
url = https://github.com/telegramdesktop/libtgvoip url = https://github.com/telegramdesktop/libtgvoip
[submodule "Telegram/ThirdParty/GSL"] [submodule "Telegram/ThirdParty/GSL"]
path = 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"] [submodule "Telegram/ThirdParty/xxHash"]
path = Telegram/ThirdParty/xxHash path = Telegram/ThirdParty/xxHash
url = https://github.com/Cyan4973/xxHash.git url = https://github.com/Cyan4973/xxHash.git

View file

@ -192,6 +192,8 @@ PRIVATE
api/api_bot.h api/api_bot.h
api/api_chat_filters.cpp api/api_chat_filters.cpp
api/api_chat_filters.h 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.cpp
api/api_chat_invite.h api/api_chat_invite.h
api/api_chat_links.cpp api/api_chat_links.cpp
@ -716,6 +718,8 @@ PRIVATE
data/data_thread.h data/data_thread.h
data/data_types.cpp data/data_types.cpp
data/data_types.h data/data_types.h
data/data_unread_value.cpp
data/data_unread_value.h
data/data_user.cpp data/data_user.cpp
data/data_user.h data/data_user.h
data/data_user_photos.cpp data/data_user_photos.cpp
@ -1062,6 +1066,10 @@ PRIVATE
info/profile/info_profile_values.h info/profile/info_profile_values.h
info/profile/info_profile_widget.cpp info/profile/info_profile_widget.cpp
info/profile/info_profile_widget.h 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.cpp
info/saved/info_saved_sublists_widget.h info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp info/settings/info_settings_widget.cpp
@ -1075,6 +1083,7 @@ PRIVATE
info/statistics/info_statistics_list_controllers.h info/statistics/info_statistics_list_controllers.h
info/statistics/info_statistics_recent_message.cpp info/statistics/info_statistics_recent_message.cpp
info/statistics/info_statistics_recent_message.h info/statistics/info_statistics_recent_message.h
info/statistics/info_statistics_tag.h
info/statistics/info_statistics_widget.cpp info/statistics/info_statistics_widget.cpp
info/statistics/info_statistics_widget.h info/statistics/info_statistics_widget.h
info/stories/info_stories_inner_widget.cpp info/stories/info_stories_inner_widget.cpp
@ -1111,6 +1120,10 @@ PRIVATE
info/info_wrap_widget.h info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h 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.cpp
inline_bots/inline_bot_layout_internal.h inline_bots/inline_bot_layout_internal.h
inline_bots/inline_bot_layout_item.cpp inline_bots/inline_bot_layout_item.cpp
@ -1242,6 +1255,8 @@ PRIVATE
media/streaming/media_streaming_player.h media/streaming/media_streaming_player.h
media/streaming/media_streaming_reader.cpp media/streaming/media_streaming_reader.cpp
media/streaming/media_streaming_reader.h 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.cpp
media/streaming/media_streaming_utility.h media/streaming/media_streaming_utility.h
media/streaming/media_streaming_video_track.cpp media/streaming/media_streaming_video_track.cpp
@ -1581,6 +1596,8 @@ PRIVATE
ui/chat/choose_send_as.h ui/chat/choose_send_as.h
ui/chat/choose_theme_controller.cpp ui/chat/choose_theme_controller.cpp
ui/chat/choose_theme_controller.h 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.cpp
ui/controls/emoji_button_factory.h ui/controls/emoji_button_factory.h
ui/controls/location_picker.cpp ui/controls/location_picker.cpp
@ -1612,6 +1629,8 @@ PRIVATE
ui/widgets/expandable_peer_list.h ui/widgets/expandable_peer_list.h
ui/widgets/label_with_custom_emoji.cpp ui/widgets/label_with_custom_emoji.cpp
ui/widgets/label_with_custom_emoji.h 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.cpp
ui/countryinput.h ui/countryinput.h
ui/dynamic_thumbnails.cpp ui/dynamic_thumbnails.cpp
@ -1931,7 +1950,7 @@ endif()
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder}) 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 target_link_libraries(Telegram
PRIVATE PRIVATE
delayimp 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 base/platform/win/base_windows_safe_library.h
) )
target_include_directories(Updater PRIVATE ${lib_base_loc}) target_include_directories(Updater PRIVATE ${lib_base_loc})
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") if (MSVC)
target_link_libraries(Updater target_link_libraries(Updater
PRIVATE PRIVATE
delayimp delayimp

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -72,6 +72,9 @@ var IV = {
} }
}, },
frameKeyDown: function (e) { frameKeyDown: function (e) {
const key0 = (e.key === '0')
|| (e.code === 'Key0')
|| (e.keyCode === 48);
const keyW = (e.key === 'w') const keyW = (e.key === 'w')
|| (e.code === 'KeyW') || (e.code === 'KeyW')
|| (e.keyCode === 87); || (e.keyCode === 87);
@ -81,12 +84,12 @@ var IV = {
const keyM = (e.key === 'm') const keyM = (e.key === 'm')
|| (e.code === 'KeyM') || (e.code === 'KeyM')
|| (e.keyCode === 77); || (e.keyCode === 77);
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) { if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
e.preventDefault(); e.preventDefault();
IV.notify({ IV.notify({
event: 'keydown', event: 'keydown',
modifier: e.ctrlKey ? 'ctrl' : 'cmd', 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) { } else if (e.key === 'Escape' || e.keyCode === 27) {
e.preventDefault(); e.preventDefault();

View file

@ -499,8 +499,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_notify_global" = "Global settings"; "lng_settings_notify_global" = "Global settings";
"lng_settings_notify_title" = "Notifications for chats"; "lng_settings_notify_title" = "Notifications for chats";
"lng_settings_desktop_notify" = "Desktop notifications"; "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_use_windows" = "Use Windows notifications";
"lng_settings_skip_in_focus" = "Respect system Focus mode";
"lng_settings_use_native_notifications" = "Use native notifications"; "lng_settings_use_native_notifications" = "Use native notifications";
"lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_position" = "Location on the screen";
"lng_settings_notifications_count" = "Notifications count"; "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_messages_privacy" = "Messages";
"lng_settings_voices_privacy" = "Voice messages"; "lng_settings_voices_privacy" = "Voice messages";
"lng_settings_bio_privacy" = "Bio"; "lng_settings_bio_privacy" = "Bio";
"lng_settings_gifts_privacy" = "Gifts";
"lng_settings_birthday_privacy" = "Date of Birth"; "lng_settings_birthday_privacy" = "Date of Birth";
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages."; "lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
"lng_settings_privacy_premium_link" = "Telegram Premium"; "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_blocked_list_subtitle#other" = "{count} blocked users";
"lng_edit_privacy_everyone" = "Everybody"; "lng_edit_privacy_everyone" = "Everybody";
"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
"lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium"; "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_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users"; "lng_edit_privacy_premium" = "Premium users";
"lng_edit_privacy_miniapps" = "Mini Apps";
"lng_edit_privacy_exceptions" = "Add exceptions"; "lng_edit_privacy_exceptions" = "Add exceptions";
"lng_edit_privacy_user_types" = "User types"; "lng_edit_privacy_user_types" = "User types";
"lng_edit_privacy_users_and_groups" = "Users and groups"; "lng_edit_privacy_users_and_groups" = "Users and groups";
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers"; "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#one" = "{count} user";
"lng_edit_privacy_exceptions_count#other" = "{count} users"; "lng_edit_privacy_exceptions_count#other" = "{count} users";
"lng_edit_privacy_exceptions_premium_and" = "Premium & {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_exceptions_add" = "Add users";
"lng_edit_privacy_phone_number_title" = "Phone number privacy"; "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" = "You haven't entered your date of birth yet.\n{link}";
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >"; "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_title" = "Calls";
"lng_edit_privacy_calls_header" = "Who can call me"; "lng_edit_privacy_calls_header" = "Who can call me";
"lng_edit_privacy_calls_always_empty" = "Always allow"; "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" = "Open App";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}."; "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_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_info_add_as_contact" = "Add to contacts";
"lng_profile_shared_media" = "Shared media"; "lng_profile_shared_media" = "Shared media";
"lng_profile_suggest_photo" = "Suggest Profile Photo"; "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" = "Set Profile Photo";
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
"lng_profile_photo_reset" = "Reset to Original"; "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_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
"lng_profile_suggest_button" = "Suggest"; "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."; "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_link" = "Public Link";
"lng_manage_peer_bot_public_links" = "Public Links"; "lng_manage_peer_bot_public_links" = "Public Links";
"lng_manage_peer_bot_balance" = "Balance"; "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_intro" = "Edit Intro";
"lng_manage_peer_bot_edit_commands" = "Edit Commands"; "lng_manage_peer_bot_edit_commands" = "Edit Commands";
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings"; "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_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#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_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_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#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."; "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_channel_public_link_copied" = "Link copied to clipboard.";
"lng_context_about_private_link" = "This link will only work for members of this chat."; "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" = "Forwarded from {user}";
"lng_forwarded_story" = "Story 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_recommended_message_title" = "Recommended";
"lng_edited" = "edited"; "lng_edited" = "edited";
"lng_commented" = "commented"; "lng_commented" = "commented";
"lng_approximate" = "appx.";
"lng_edited_date" = "Edited: {date}"; "lng_edited_date" = "Edited: {date}";
"lng_sent_date" = "Sent: {date}"; "lng_sent_date" = "Sent: {date}";
"lng_approximate_about" = "Estimated date of video publishing.";
"lng_views_tooltip#one" = "Views: {count}"; "lng_views_tooltip#one" = "Views: {count}";
"lng_views_tooltip#other" = "Views: {count}"; "lng_views_tooltip#other" = "Views: {count}";
"lng_forwards_tooltip#one" = "Shares: {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_cancel" = "Cancel";
"lng_media_video" = "Video"; "lng_media_video" = "Video";
"lng_media_audio" = "Voice message"; "lng_media_audio" = "Voice message";
"lng_media_round" = "Video message";
"lng_media_auto_settings" = "Automatic media download"; "lng_media_auto_settings" = "Automatic media download";
"lng_media_auto_in_private" = "In private chats"; "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_tab_out" = "Outgoing";
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase"; "lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
"lng_credits_summary_balance" = "Balance"; "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_gift_button" = "Gift Stars to Friends";
"lng_credits_box_out_title" = "Confirm Your Purchase"; "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**?"; "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_giveaway_name" = "Received Prize";
"lng_credits_box_history_entry_gift_sent" = "Sent Gift"; "lng_credits_box_history_entry_gift_sent" = "Sent Gift";
"lng_credits_box_history_entry_gift_converted" = "Converted 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_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_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_about_link" = "See Examples {emoji}";
"lng_credits_box_history_entry_gift_examples" = "Examples"; "lng_credits_box_history_entry_gift_examples" = "Examples";
"lng_credits_box_history_entry_ads" = "Ads Platform"; "lng_credits_box_history_entry_ads" = "Ads Platform";
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up"; "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_via_premium_bot" = "Premium Bot";
"lng_credits_box_history_entry_id" = "Transaction ID"; "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_id_copied" = "Transaction ID copied to clipboard.";
"lng_credits_box_history_entry_success_date" = "Transaction date"; "lng_credits_box_history_entry_success_date" = "Transaction date";
"lng_credits_box_history_entry_success_url" = "Transaction link"; "lng_credits_box_history_entry_success_url" = "Transaction link";
"lng_credits_box_history_entry_media" = "Media"; "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" = "You can dispute this transaction {link}.";
"lng_credits_box_history_entry_about_link" = "here"; "lng_credits_box_history_entry_about_link" = "here";
"lng_credits_box_history_entry_reaction_name" = "Star Reaction"; "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_unclaimed" = "Incomplete Giveaway";
"lng_gift_link_reason_chosen" = "You were selected by the channel"; "lng_gift_link_reason_chosen" = "You were selected by the channel";
"lng_gift_link_label_date" = "Date"; "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" = "You can also {link} to a friend as a gift.";
"lng_gift_link_also_send_link" = "send this link"; "lng_gift_link_also_send_link" = "send this link";
"lng_gift_link_use" = "Use 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#one" = "Convert to {count} Star";
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars"; "lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
"lng_gift_convert_sure_title" = "Convert Gift to 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_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
"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#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_convert_sure" = "Convert";
"lng_gift_display_done" = "The gift is now shown on your profile page."; "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."; "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_title" = "Sold Out!";
"lng_gift_sold_out_text#one" = "All {count} gift was already sold."; "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_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_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "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" = "Release outside this field to cancel";
"lng_record_cancel_stories" = "Release outside 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" = "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" = "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_lock_discard" = "Discard";
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message."; "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_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_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_will_be_notified" = "Subscribers will be notified when you post.";
"lng_wont_be_notified" = "Subscribers will receive a silent notification."; "lng_wont_be_notified" = "Subscribers will receive a silent notification.";
"lng_willbe_history" = "Select a chat to start messaging"; "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" = "Send message now?";
"lng_scheduled_send_now_many#one" = "Send {count} message now?"; "lng_scheduled_send_now_many#one" = "Send {count} message now?";
"lng_scheduled_send_now_many#other" = "Send {count} messages 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#one" = "View {count} Reply";
"lng_replies_view#other" = "View {count} Replies"; "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_settings" = "Settings";
"lng_bot_open" = "Open Bot"; "lng_bot_open" = "Open Bot";
"lng_bot_terms" = "Terms of Use"; "lng_bot_terms" = "Terms of Use";
"lng_bot_privacy" = "Privacy Policy";
"lng_bot_reload_page" = "Reload Page"; "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" = "{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."; "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_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_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_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#one" = "{count} monthly user";
"lng_bot_status_users#other" = "{count} monthly users"; "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_make_paid" = "Make This Content Paid";
"lng_context_change_price" = "Change Price"; "lng_context_change_price" = "Change Price";
"lng_context_mention" = "Mention";
"lng_context_search_from" = "Search messages";
"lng_factcheck_title" = "Fact Check"; "lng_factcheck_title" = "Fact Check";
"lng_factcheck_placeholder" = "Add Facts or Context"; "lng_factcheck_placeholder" = "Add Facts or Context";
"lng_factcheck_whats_this" = "what's this?"; "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_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat"; "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_show_in_chat" = "Show in Chat";
"lng_reply_remove" = "Do Not Reply"; "lng_reply_remove" = "Do Not Reply";
"lng_reply_about_quote" = "You can select a specific part to quote."; "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_downloads" = "Downloads";
"lng_mediaview_playback_speed" = "Playback speed: {speed}"; "lng_mediaview_playback_speed" = "Playback speed: {speed}";
"lng_mediaview_rotate_video" = "Rotate video"; "lng_mediaview_rotate_video" = "Rotate video";
"lng_mediaview_quality_auto" = "Auto";
"lng_theme_preview_title" = "Theme Preview"; "lng_theme_preview_title" = "Theme Preview";
"lng_theme_preview_generating" = "Generating color 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_no_use" = "Unfortunately, you can't use payments with current system configuration.";
"lng_payments_webview_install_edge" = "Please install {link}."; "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_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_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
"lng_payments_receipt_label" = "Receipt"; "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_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_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_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_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."; "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_files" = "Files";
"lng_rights_chat_voice_messages" = "Voice messages"; "lng_rights_chat_voice_messages" = "Voice messages";
"lng_rights_chat_video_messages" = "Video messages"; "lng_rights_chat_video_messages" = "Video messages";
"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}."; "lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
"lng_rights_chat_banned_by" = "Banned by {user} at {date}."; "lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
"lng_rights_chat_banned_until_header" = "Restricted until"; "lng_rights_chat_banned_until_header" = "Restricted until";
"lng_rights_chat_banned_forever" = "Forever"; "lng_rights_chat_banned_forever" = "Forever";
"lng_rights_chat_banned_day#one" = "For {count} day"; "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_outdated_now" = "So Telegram Desktop can update to newer versions.";
"lng_filters_all" = "All chats"; "lng_filters_all" = "All chats";
"lng_filters_all_short" = "All";
"lng_filters_setup" = "Edit"; "lng_filters_setup" = "Edit";
"lng_filters_title" = "Folders"; "lng_filters_title" = "Folders";
"lng_filters_subtitle" = "My 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_add" = "{chat} added to {folder} folder";
"lng_filters_toast_remove" = "{chat} removed from {folder} folder"; "lng_filters_toast_remove" = "{chat} removed from {folder} folder";
"lng_filters_shareable_status" = "shareable 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_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"; "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_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_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_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_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_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_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#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_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_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_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"; "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_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" = "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_about_link" = "Learn more {emoji}";
"lng_channel_earn_overview_title" = "Rewards overview"; "lng_channel_earn_overview_title" = "Rewards overview";
"lng_channel_earn_available" = "Rewards available for collection"; "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#one" = "{emoji} {count} CPM";
"lng_channel_earn_cpm#other" = "{emoji} {count} CPM"; "lng_channel_earn_cpm#other" = "{emoji} {count} CPM";
"lng_channel_earn_learn_title" = "Earn From Your Channel"; "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_subtitle" = "Telegram Ads";
"lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel."; "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_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_split_about" = "You can receive 50% of the ad revenue as rewards in TON.";
"lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals"; "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" = "You cannot withdraw less than {link}.";
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star"; "lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars"; "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_add" = "Add";
"lng_contact_send_message" = "Message"; "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_window_title" = "Instant View";
"lng_iv_wrong_layout" = "Wrong layout?"; "lng_iv_wrong_layout" = "Wrong layout?";
"lng_iv_not_supported" = "This link appears to be invalid."; "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_title" = "Download speed limited";
"lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}."; "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_channels_recommended" = "Similar channels";
"lng_bot_apps_your" = "Apps you use"; "lng_bot_apps_your" = "Apps you use";
"lng_bot_apps_popular" = "Grossing apps"; "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_box_title" = "Choose font family";
"lng_font_default" = "Default"; "lng_font_default" = "Default";

View file

@ -28,6 +28,7 @@
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file> <file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file> <file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file> <file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file> <file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file> <file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View file

@ -7,6 +7,7 @@
<file alias="art/logo_256.png">../../art/logo_256.png</file> <file alias="art/logo_256.png">../../art/logo_256.png</file>
<file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file> <file alias="art/logo_256_no_margin.png">../../art/logo_256_no_margin.png</file>
<file alias="art/themeimage.jpg">../../art/themeimage.jpg</file> <file alias="art/themeimage.jpg">../../art/themeimage.jpg</file>
<file alias="art/round_placeholder.jpg">../../art/round_placeholder.jpg</file>
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file> <file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file> <file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file> <file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="5.6.3.0" /> Version="5.8.2.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,6,3,0 FILEVERSION 5,8,2,0
PRODUCTVERSION 5,6,3,0 PRODUCTVERSION 5,8,2,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.6.3.0" VALUE "FileVersion", "5.8.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.6.3.0" VALUE "ProductVersion", "5.8.2.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,6,3,0 FILEVERSION 5,8,2,0
PRODUCTVERSION 5,6,3,0 PRODUCTVERSION 5,8,2,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.6.3.0" VALUE "FileVersion", "5.8.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.6.3.0" VALUE "ProductVersion", "5.8.2.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText #include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
@ -152,6 +153,7 @@ void InitFilterLinkHeader(
.badge = (type == Ui::FilterLinkHeaderType::AddingChats .badge = (type == Ui::FilterLinkHeaderType::AddingChats
? std::move(count) ? std::move(count)
: rpl::single(0)), : rpl::single(0)),
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
}); });
const auto widget = header.widget; const auto widget = header.widget;
widget->resizeToWidth(st::boxWideWidth); widget->resizeToWidth(st::boxWideWidth);

View file

@ -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<Main::Session*> session,
FilterId filterId,
std::vector<not_null<PeerData*>> 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<MTPInputPeer>(ranges::views::all(
leave
) | ranges::views::transform([](not_null<PeerData*> peer) {
return MTPInputPeer(peer->input);
}) | ranges::to<QVector<MTPInputPeer>>())
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result);
}).send();
}
}
} // namespace
RemoveComplexChatFilter::RemoveComplexChatFilter() = default;
void RemoveComplexChatFilter::request(
QPointer<Ui::RpWidget> widget,
base::weak_ptr<Window::SessionController> 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<void()> 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<void()> &&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<MTPPeer> &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<not_null<PeerData*>> 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

View file

@ -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<Ui::RpWidget> widget,
base::weak_ptr<Window::SessionController> weak,
FilterId id);
private:
FilterId _removingId = 0;
mtpRequestId _removingRequestId = 0;
};
} // namespace Api

View file

@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_stars_colored.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
struct State final { struct State final {
std::shared_ptr<Data::PhotoMedia> photoMedia; std::shared_ptr<Data::PhotoMedia> photoMedia;
std::unique_ptr<Ui::EmptyUserpic> photoEmpty; std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
QImage frame;
std::optional<MTP::Sender> api; std::optional<MTP::Sender> api;
Ui::RpWidget* saveButton = nullptr; Ui::RpWidget* saveButton = nullptr;
@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
const auto userpic = userpicWrap->entity(); const auto userpic = userpicWrap->entity();
const auto photoSize = st::confirmInvitePhotoSize; const auto photoSize = st::confirmInvitePhotoSize;
userpic->resize(Size(photoSize)); 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; const auto options = Images::Option::RoundCircle;
userpic->paintRequest( userpic->paintRequest(
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] { ) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
auto p = QPainter(userpic); state->frame.fill(Qt::transparent);
if (state->photoMedia) { {
if (const auto image = state->photoMedia->image(small)) { auto p = QPainter(&state->frame);
p.drawPixmap( 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,
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->lifetime());
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents); userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
if (photo) { if (photo) {

View file

@ -39,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
if (const auto list = tl.data().vextended_media()) { if (const auto list = tl.data().vextended_media()) {
extended.reserve(list->v.size()); extended.reserve(list->v.size());
for (const auto &media : list->v) { for (const auto &media : list->v) {
media.match([&](const MTPDmessageMediaPhoto &photo) { media.match([&](const MTPDmessageMediaPhoto &data) {
if (const auto inner = photo.vphoto()) { if (const auto inner = data.vphoto()) {
const auto photo = owner->processPhoto(*inner); const auto photo = owner->processPhoto(*inner);
if (!photo->isNull()) { if (!photo->isNull()) {
extended.push_back(CreditsHistoryMedia{ extended.push_back(CreditsHistoryMedia{
@ -49,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
}); });
} }
} }
}, [&](const MTPDmessageMediaDocument &document) { }, [&](const MTPDmessageMediaDocument &data) {
if (const auto inner = document.vdocument()) { if (const auto inner = data.vdocument()) {
const auto document = owner->processDocument(*inner); const auto document = owner->processDocument(
*inner,
data.valt_documents());
if (document->isAnimation() if (document->isAnimation()
|| document->isVideoFile() || document->isVideoFile()
|| document->isGifv()) { || document->isGifv()) {
@ -71,7 +73,9 @@ constexpr auto kTransactionsLimit = 100;
return PeerId(0); return PeerId(0);
}).value; }).value;
const auto stargift = tl.data().vstargift(); const auto stargift = tl.data().vstargift();
const auto reaction = tl.data().is_reaction();
const auto incoming = (int64(tl.data().vstars().v) >= 0); const auto incoming = (int64(tl.data().vstars().v) >= 0);
const auto saveActorId = (reaction || !extended.empty()) && incoming;
return Data::CreditsHistoryEntry{ return Data::CreditsHistoryEntry{
.id = qs(tl.data().vid()), .id = qs(tl.data().vid()),
.title = qs(tl.data().vtitle().value_or_empty()), .title = qs(tl.data().vtitle().value_or_empty()),
@ -81,12 +85,13 @@ constexpr auto kTransactionsLimit = 100;
.extended = std::move(extended), .extended = std::move(extended),
.credits = tl.data().vstars().v, .credits = tl.data().vstars().v,
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()), .bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
.barePeerId = barePeerId, .barePeerId = saveActorId ? peer->id.value : barePeerId,
.bareGiveawayMsgId = uint64( .bareGiveawayMsgId = uint64(
tl.data().vgiveaway_post_id().value_or_empty()), tl.data().vgiveaway_post_id().value_or_empty()),
.bareGiftStickerId = (stargift .bareGiftStickerId = (stargift
? owner->processDocument(stargift->data().vsticker())->id ? owner->processDocument(stargift->data().vsticker())->id
: 0), : 0),
.bareActorId = saveActorId ? barePeerId : uint64(0),
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) { .peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
return Data::CreditsHistoryEntry::PeerType::Peer; return Data::CreditsHistoryEntry::PeerType::Peer;
}, [](const MTPDstarsTransactionPeerPlayMarket &) { }, [](const MTPDstarsTransactionPeerPlayMarket &) {
@ -101,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
return Data::CreditsHistoryEntry::PeerType::PremiumBot; return Data::CreditsHistoryEntry::PeerType::PremiumBot;
}, [](const MTPDstarsTransactionPeerAds &) { }, [](const MTPDstarsTransactionPeerAds &) {
return Data::CreditsHistoryEntry::PeerType::Ads; return Data::CreditsHistoryEntry::PeerType::Ads;
}, [](const MTPDstarsTransactionPeerAPI &) {
return Data::CreditsHistoryEntry::PeerType::API;
}), }),
.subscriptionUntil = tl.data().vsubscription_period() .subscriptionUntil = tl.data().vsubscription_period()
? base::unixtime::parse(base::unixtime::now() ? base::unixtime::parse(base::unixtime::now()
@ -110,10 +117,12 @@ constexpr auto kTransactionsLimit = 100;
? base::unixtime::parse(tl.data().vtransaction_date()->v) ? base::unixtime::parse(tl.data().vtransaction_date()->v)
: QDateTime(), : QDateTime(),
.successLink = qs(tl.data().vtransaction_url().value_or_empty()), .successLink = qs(tl.data().vtransaction_url().value_or_empty()),
.convertStars = int(stargift .starsConverted = int(stargift
? stargift->data().vconvert_stars().v ? stargift->data().vconvert_stars().v
: 0), : 0),
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
.converted = stargift && incoming, .converted = stargift && incoming,
.stargift = stargift.has_value(),
.reaction = tl.data().is_reaction(), .reaction = tl.data().is_reaction(),
.refunded = tl.data().is_refund(), .refunded = tl.data().is_refund(),
.pending = tl.data().is_pending(), .pending = tl.data().is_pending(),
@ -166,7 +175,8 @@ constexpr auto kTransactionsLimit = 100;
.balance = status.data().vbalance().v, .balance = status.data().vbalance().v,
.subscriptionsMissingBalance .subscriptionsMissingBalance
= status.data().vsubscriptions_missing_balance().value_or_empty(), = 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()), .token = qs(status.data().vnext_offset().value_or_empty()),
.tokenSubscriptions = qs( .tokenSubscriptions = qs(
status.data().vsubscriptions_next_offset().value_or_empty()), status.data().vsubscriptions_next_offset().value_or_empty()),

View file

@ -99,8 +99,8 @@ public:
[[nodiscard]] Data::CreditsEarnStatistics data() const; [[nodiscard]] Data::CreditsEarnStatistics data() const;
private: private:
const bool _isUser = false;
Data::CreditsEarnStatistics _data; Data::CreditsEarnStatistics _data;
bool _isUser = false;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;

View file

@ -40,6 +40,7 @@ void HandleWithdrawalButton(
std::shared_ptr<Ui::Show> show) { std::shared_ptr<Ui::Show> show) {
Expects(receiver.currencyReceiver Expects(receiver.currencyReceiver
|| (receiver.creditsReceiver && receiver.creditsAmount)); || (receiver.creditsReceiver && receiver.creditsAmount));
struct State { struct State {
rpl::lifetime lifetime; rpl::lifetime lifetime;
bool loading = false; bool loading = false;
@ -58,8 +59,7 @@ void HandleWithdrawalButton(
const auto processOut = [=] { const auto processOut = [=] {
if (state->loading) { if (state->loading) {
return; return;
} } else if (peer && !receiver.creditsAmount()) {
if (peer && !receiver.creditsAmount()) {
return; return;
} }
state->loading = true; state->loading = true;
@ -89,12 +89,15 @@ void HandleWithdrawalButton(
} }
}; };
const auto fail = [=](const MTP::Error &error) { 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) { if (channel) {
session->api().request( session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl( MTPstats_GetBroadcastRevenueWithdrawalUrl(
channel->inputChannel, channel->input,
result.result result.result
)).done([=](const ChannelOutUrl &r) { )).done([=](const ChannelOutUrl &r) {
done(qs(r.data().vurl())); done(qs(r.data().vurl()));
@ -134,7 +137,7 @@ void HandleWithdrawalButton(
if (channel) { if (channel) {
session->api().request( session->api().request(
MTPstats_GetBroadcastRevenueWithdrawalUrl( MTPstats_GetBroadcastRevenueWithdrawalUrl(
channel->inputChannel, channel->input,
MTP_inputCheckPasswordEmpty() MTP_inputCheckPasswordEmpty()
)).fail(fail).send(); )).fail(fail).send();
} else if (peer) { } else if (peer) {

View file

@ -772,10 +772,13 @@ std::optional<StarGift> FromTL(
return StarGift{ return StarGift{
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.stars = int64(data.vstars().v), .stars = int64(data.vstars().v),
.convertStars = int64(data.vconvert_stars().v), .starsConverted = int64(data.vconvert_stars().v),
.document = document, .document = document,
.limitedLeft = remaining.value_or_empty(), .limitedLeft = remaining.value_or_empty(),
.limitedCount = total.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<UserStarGift> FromTL(
return {}; return {};
} }
return UserStarGift{ return UserStarGift{
.gift = std::move(*parsed), .info = std::move(*parsed),
.message = (data.vmessage() .message = (data.vmessage()
? TextWithEntities{ ? TextWithEntities{
.text = qs(data.vmessage()->data().vtext()), .text = qs(data.vmessage()->data().vtext()),
@ -798,7 +801,7 @@ std::optional<UserStarGift> FromTL(
data.vmessage()->data().ventities().v), data.vmessage()->data().ventities().v),
} }
: TextWithEntities()), : TextWithEntities()),
.convertStars = int64(data.vconvert_stars().value_or_empty()), .starsConverted = int64(data.vconvert_stars().value_or_empty()),
.fromId = (data.vfrom_id() .fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v) ? peerFromUser(data.vfrom_id()->v)
: PeerId()), : PeerId()),

View file

@ -76,16 +76,23 @@ struct GiftOptionData {
struct StarGift { struct StarGift {
uint64 id = 0; uint64 id = 0;
int64 stars = 0; int64 stars = 0;
int64 convertStars = 0; int64 starsConverted = 0;
not_null<DocumentData*> document; not_null<DocumentData*> document;
int limitedLeft = 0; int limitedLeft = 0;
int limitedCount = 0; int limitedCount = 0;
TimeId firstSaleDate = 0;
TimeId lastSaleDate = 0;
bool birthday = false;
friend inline bool operator==(
const StarGift &,
const StarGift &) = default;
}; };
struct UserStarGift { struct UserStarGift {
StarGift gift; StarGift info;
TextWithEntities message; TextWithEntities message;
int64 convertStars = 0; int64 starsConverted = 0;
PeerId fromId = 0; PeerId fromId = 0;
MsgId messageId = 0; MsgId messageId = 0;
TimeId date = 0; TimeId date = 0;

View file

@ -40,29 +40,20 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
} // namespace } // namespace
void SendReport( void SendPhotoReport(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::ReportReason reason, Ui::ReportReason reason,
const QString &comment, const QString &comment,
std::variant<v::null_t, not_null<PhotoData*>> data) { not_null<PhotoData*> photo) {
auto done = [=] { peer->session().api().request(MTPaccount_ReportProfilePhoto(
peer->input,
photo->mtpInput(),
ReasonToTL(reason),
MTP_string(comment)
)).done([=] {
show->showToast(tr::lng_report_thanks(tr::now)); show->showToast(tr::lng_report_thanks(tr::now));
}; }).send();
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<PhotoData*> photo) {
peer->session().api().request(MTPaccount_ReportProfilePhoto(
peer->input,
photo->mtpInput(),
ReasonToTL(reason),
MTP_string(comment)
)).done(std::move(done)).send();
});
} }
auto CreateReportMessagesOrStoriesCallback( auto CreateReportMessagesOrStoriesCallback(

View file

@ -41,12 +41,12 @@ struct ReportResult final {
bool successful = false; bool successful = false;
}; };
void SendReport( void SendPhotoReport(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Ui::ReportReason reason, Ui::ReportReason reason,
const QString &comment, const QString &comment,
std::variant<v::null_t, not_null<PhotoData*>> data); not_null<PhotoData*> photo);
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback( [[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,

View file

@ -456,6 +456,7 @@ void SendConfirmedFile(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const std::shared_ptr<FilePrepareResult> &file) { const std::shared_ptr<FilePrepareResult> &file) {
const auto isEditing = (file->type != SendMediaType::Audio) const auto isEditing = (file->type != SendMediaType::Audio)
&& (file->type != SendMediaType::Round)
&& (file->to.replaceMediaOf != 0); && (file->to.replaceMediaOf != 0);
const auto newId = FullMsgId( const auto newId = FullMsgId(
file->to.peer, file->to.peer,
@ -525,7 +526,8 @@ void SendConfirmedFile(
// Shortcut messages have no 'edited' badge. // Shortcut messages have no 'edited' badge.
flags |= MessageFlag::HideEdited; flags |= MessageFlag::HideEdited;
} }
if (file->type == SendMediaType::Audio) { if (file->type == SendMediaType::Audio
|| file->type == SendMediaType::Round) {
if (!peer->isChannel() || peer->isMegagroup()) { if (!peer->isChannel() || peer->isMegagroup()) {
flags |= MessageFlag::MediaIsUnread; flags |= MessageFlag::MediaIsUnread;
} }
@ -551,29 +553,25 @@ void SendConfirmedFile(
MTPint()); MTPint());
} else if (file->type == SendMediaType::Audio) { } else if (file->type == SendMediaType::Audio) {
const auto ttlSeconds = file->to.options.ttlSeconds; 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; using Flag = MTPDmessageMediaDocument::Flag;
return MTP_messageMediaDocument( return MTP_messageMediaDocument(
MTP_flags(Flag::f_document MTP_flags(Flag::f_document
| (isVoice ? Flag::f_voice : Flag()) | Flag::f_voice
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())), | (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
file->document, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTP_int(ttlSeconds)); 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<MTPDocument>(), // alt_documents
MTP_int(ttlSeconds));
} else { } else {
Unexpected("Type in sendFilesConfirmed."); Unexpected("Type in sendFilesConfirmed.");
} }

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_story.h" #include "data/data_story.h"
#include "data/data_user.h"
#include "history/history.h" #include "history/history.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -341,6 +342,10 @@ void PublicForwards::request(
.token = nextToken, .token = nextToken,
}); });
}; };
const auto processFail = [=] {
_requestId = 0;
done({});
};
constexpr auto kLimit = tl::make_int(100); constexpr auto kLimit = tl::make_int(100);
if (_fullId.messageId) { if (_fullId.messageId) {
@ -349,14 +354,14 @@ void PublicForwards::request(
MTP_int(_fullId.messageId.msg), MTP_int(_fullId.messageId.msg),
MTP_string(token), MTP_string(token),
kLimit kLimit
)).done(processResult).fail([=] { _requestId = 0; }).send(); )).done(processResult).fail(processFail).send();
} else if (_fullId.storyId) { } else if (_fullId.storyId) {
_requestId = makeRequest(MTPstats_GetStoryPublicForwards( _requestId = makeRequest(MTPstats_GetStoryPublicForwards(
channel->input, channel->input,
MTP_int(_fullId.storyId.story), MTP_int(_fullId.storyId.story),
MTP_string(token), MTP_string(token),
kLimit 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<void(Data::MessageStatistics)> done) { void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
if (channel()->isMegagroup()) { if (channel()->isMegagroup() && !_storyId) {
return; return;
} }
const auto requestFirstPublicForwards = [=]( const auto requestFirstPublicForwards = [=](
@ -681,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
return _boostStatus; return _boostStatus;
} }
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel) EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
: StatisticsRequestSender(channel) { : StatisticsRequestSender(peer)
, _isUser(peer->isUser()) {
} }
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() { rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
return [=](auto consumer) { return [=](auto consumer) {
auto lifetime = rpl::lifetime(); auto lifetime = rpl::lifetime();
makeRequest(MTPstats_GetBroadcastRevenueStats( makeRequest(MTPstats_GetBroadcastRevenueStats(
MTP_flags(0), MTP_flags(0),
channel()->inputChannel (_isUser ? user()->input : channel()->input)
)).done([=](const MTPstats_BroadcastRevenueStats &result) { )).done([=](const MTPstats_BroadcastRevenueStats &result) {
const auto &data = result.data(); const auto &data = result.data();
const auto &balances = data.vbalances().data(); const auto &balances = data.vbalances().data();
@ -708,18 +714,22 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
requestHistory({}, [=](Data::EarnHistorySlice &&slice) { requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
_data.firstHistorySlice = std::move(slice); _data.firstHistorySlice = std::move(slice);
api().request( if (!_isUser) {
MTPchannels_GetFullChannel(channel()->inputChannel) api().request(
).done([=](const MTPmessages_ChatFull &result) { MTPchannels_GetFullChannel(channel()->inputChannel)
result.data().vfull_chat().match([&]( ).done([=](const MTPmessages_ChatFull &result) {
const MTPDchannelFull &d) { result.data().vfull_chat().match([&](
_data.switchedOff = d.is_restricted_sponsored(); const MTPDchannelFull &d) {
}, [](const auto &) { _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(); consumer.put_done();
}).fail([=](const MTP::Error &error) { }
consumer.put_error_copy(error.type());
}).send();
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type()); consumer.put_error_copy(error.type());
@ -729,7 +739,7 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
}; };
} }
void ChannelEarnStatistics::requestHistory( void EarnStatistics::requestHistory(
const Data::EarnHistorySlice::OffsetToken &token, const Data::EarnHistorySlice::OffsetToken &token,
Fn<void(Data::EarnHistorySlice)> done) { Fn<void(Data::EarnHistorySlice)> done) {
if (_requestId) { if (_requestId) {
@ -738,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice); constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
constexpr auto kTlLimit = tl::make_int(kLimit); constexpr auto kTlLimit = tl::make_int(kLimit);
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions( _requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
channel()->inputChannel, (_isUser ? user()->input : channel()->input),
MTP_int(token), MTP_int(token),
(!token) ? kTlFirstSlice : kTlLimit (!token) ? kTlFirstSlice : kTlLimit
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) { )).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
@ -799,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
}).send(); }).send();
} }
Data::EarnStatistics ChannelEarnStatistics::data() const { Data::EarnStatistics EarnStatistics::data() const {
return _data; return _data;
} }

View file

@ -79,9 +79,9 @@ private:
}; };
class ChannelEarnStatistics final : public StatisticsRequestSender { class EarnStatistics final : public StatisticsRequestSender {
public: public:
explicit ChannelEarnStatistics(not_null<ChannelData*> channel); explicit EarnStatistics(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(); [[nodiscard]] rpl::producer<rpl::no_value, QString> request();
void requestHistory( void requestHistory(
@ -94,6 +94,7 @@ public:
static constexpr auto kLimit = int(10); static constexpr auto kLimit = int(10);
private: private:
const bool _isUser = false;
Data::EarnStatistics _data; Data::EarnStatistics _data;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;

View file

@ -320,6 +320,9 @@ void Updates::feedUpdateVector(
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) { } else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
return; return;
} }
if (policy == SkipUpdatePolicy::SkipNone) {
applyConvertToScheduledOnSend(updates);
}
for (const auto &entry : std::as_const(list)) { for (const auto &entry : std::as_const(list)) {
const auto type = entry.type(); const auto type = entry.type();
if ((policy == SkipUpdatePolicy::SkipMessageIds if ((policy == SkipUpdatePolicy::SkipMessageIds
@ -333,6 +336,15 @@ void Updates::feedUpdateVector(
session().data().sendHistoryChangeNotifications(); 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<MTPUpdate> &updates) { void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
for (const auto &update : updates.v) { for (const auto &update : updates.v) {
if (update.type() == mtpc_updateMessageID) { if (update.type() == mtpc_updateMessageID) {
@ -436,6 +448,7 @@ void Updates::feedChannelDifference(
session().data().processChats(data.vchats()); session().data().processChats(data.vchats());
_handlingChannelDifference = true; _handlingChannelDifference = true;
applyConvertToScheduledOnSend(data.vother_updates());
feedMessageIds(data.vother_updates()); feedMessageIds(data.vother_updates());
session().data().processMessages( session().data().processMessages(
data.vnew_messages(), data.vnew_messages(),
@ -600,6 +613,7 @@ void Updates::feedDifference(
Core::App().checkAutoLock(); Core::App().checkAutoLock();
session().data().processUsers(users); session().data().processUsers(users);
session().data().processChats(chats); session().data().processChats(chats);
applyConvertToScheduledOnSend(other);
feedMessageIds(other); feedMessageIds(other);
session().data().processMessages(msgs, NewMessageType::Unread); session().data().processMessages(msgs, NewMessageType::Unread);
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds); feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
@ -885,6 +899,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
} }
} }
void Updates::applyConvertToScheduledOnSend(
const MTPVector<MTPUpdate> &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) { void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
updates.match([&](const MTPDupdates &data) { updates.match([&](const MTPDupdates &data) {
session().data().processUsers(data.vusers()); session().data().processUsers(data.vusers());

View file

@ -40,6 +40,8 @@ public:
void applyUpdatesNoPtsCheck(const MTPUpdates &updates); void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
void applyUpdateNoPtsCheck(const MTPUpdate &update); void applyUpdateNoPtsCheck(const MTPUpdate &update);
void checkForSentToScheduled(const MTPUpdates &updates);
[[nodiscard]] int32 pts() const; [[nodiscard]] int32 pts() const;
void updateOnline(crl::time lastNonIdleTime = 0); void updateOnline(crl::time lastNonIdleTime = 0);
@ -131,6 +133,9 @@ private:
// Doesn't call sendHistoryChangeNotifications itself. // Doesn't call sendHistoryChangeNotifications itself.
void feedUpdate(const MTPUpdate &update); void feedUpdate(const MTPUpdate &update);
void applyConvertToScheduledOnSend(
const MTPVector<MTPUpdate> &other,
bool skipScheduledCheck = false);
void applyGroupCallParticipantUpdates(const MTPUpdates &updates); void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
bool whenGetDiffChanged( bool whenGetDiffChanged(

View file

@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
if (rule.always.premiums && (rule.option != Option::Everyone)) { if (rule.always.premiums && (rule.option != Option::Everyone)) {
result.push_back(MTP_inputPrivacyValueAllowPremium()); result.push_back(MTP_inputPrivacyValueAllowPremium());
} }
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
result.push_back(MTP_inputPrivacyValueAllowBots());
}
} }
if (!rule.ignoreNever) { if (!rule.ignoreNever) {
const auto users = collectInputUsers(rule.never); const auto users = collectInputUsers(rule.never);
@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
MTP_inputPrivacyValueDisallowChatParticipants( MTP_inputPrivacyValueDisallowChatParticipants(
MTP_vector<MTPlong>(chats))); MTP_vector<MTPlong>(chats)));
} }
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
result.push_back(MTP_inputPrivacyValueDisallowBots());
}
} }
result.push_back([&] { result.push_back([&] {
switch (rule.option) { switch (rule.option) {
@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
setOption(Option::CloseFriends); setOption(Option::CloseFriends);
}, [&](const MTPDprivacyValueAllowPremium &) { }, [&](const MTPDprivacyValueAllowPremium &) {
result.always.premiums = true; result.always.premiums = true;
}, [&](const MTPDprivacyValueAllowBots &) {
result.always.miniapps = true;
}, [&](const MTPDprivacyValueDisallowBots &) {
result.never.miniapps = true;
}, [&](const MTPDprivacyValueAllowUsers &data) { }, [&](const MTPDprivacyValueAllowUsers &data) {
const auto &users = data.vusers().v; const auto &users = data.vusers().v;
always.reserve(always.size() + users.size()); always.reserve(always.size() + users.size());
@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages(); case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
case Key::About: return MTP_inputPrivacyKeyAbout(); case Key::About: return MTP_inputPrivacyKeyAbout();
case Key::Birthday: return MTP_inputPrivacyKeyBirthday(); case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
} }
Unexpected("Key in Api::UserPrivacy::KetToTL."); Unexpected("Key in Api::UserPrivacy::KetToTL.");
} }
@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
case mtpc_inputPrivacyKeyAbout: return Key::About; case mtpc_inputPrivacyKeyAbout: return Key::About;
case mtpc_privacyKeyBirthday: case mtpc_privacyKeyBirthday:
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday; case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
case mtpc_privacyKeyStarGiftsAutoSave:
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
} }
return std::nullopt; return std::nullopt;
} }

View file

@ -31,6 +31,7 @@ public:
Voices, Voices,
About, About,
Birthday, Birthday,
GiftsAutoSave,
}; };
enum class Option { enum class Option {
Everyone, Everyone,
@ -41,6 +42,7 @@ public:
struct Exceptions { struct Exceptions {
std::vector<not_null<PeerData*>> peers; std::vector<not_null<PeerData*>> peers;
bool premiums = false; bool premiums = false;
bool miniapps = false;
}; };
struct Rule { struct Rule {
Option option = Option::Everyone; Option option = Option::Everyone;

View file

@ -756,5 +756,19 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
const style::WhoRead &st) { const style::WhoRead &st) {
return WhoReacted(item, reaction, context, st, nullptr); return WhoReacted(item, reaction, context, st, nullptr);
} }
rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> 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 } // namespace Api

View file

@ -61,5 +61,8 @@ struct WhoReadList {
const Data::ReactionId &reaction, const Data::ReactionId &reaction,
not_null<QWidget*> context, // Cache results for this lifetime. not_null<QWidget*> context, // Cache results for this lifetime.
const style::WhoRead &st); const style::WhoRead &st);
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
not_null<PeerData*> author,
TimeId date);
} // namespace Api } // namespace Api

View file

@ -731,7 +731,8 @@ void ApiWrap::finalizeMessageDataRequest(
QString ApiWrap::exportDirectMessageLink( QString ApiWrap::exportDirectMessageLink(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
bool inRepliesContext) { bool inRepliesContext,
bool forceNonPublicLink) {
Expects(item->history()->peer->isChannel()); Expects(item->history()->peer->isChannel());
const auto itemId = item->fullId(); const auto itemId = item->fullId();
@ -754,7 +755,7 @@ QString ApiWrap::exportDirectMessageLink(
const auto sender = root const auto sender = root
? root->discussionPostOriginalSender() ? root->discussionPostOriginalSender()
: nullptr; : nullptr;
if (sender && sender->hasUsername()) { if (sender && sender->hasUsername() && !forceNonPublicLink) {
// Comment to a public channel. // Comment to a public channel.
const auto forwarded = root->Get<HistoryMessageForwarded>(); const auto forwarded = root->Get<HistoryMessageForwarded>();
linkItemId = forwarded->savedFromMsgId; linkItemId = forwarded->savedFromMsgId;
@ -770,7 +771,7 @@ QString ApiWrap::exportDirectMessageLink(
} }
} }
} }
const auto base = linkChannel->hasUsername() const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
? linkChannel->username() ? linkChannel->username()
: "c/" + QString::number(peerToChannel(linkChannel->id).bare); : "c/" + QString::number(peerToChannel(linkChannel->id).bare);
const auto post = QString::number(linkItemId.bare); const auto post = QString::number(linkItemId.bare);
@ -784,6 +785,7 @@ QString ApiWrap::exportDirectMessageLink(
? (QString::number(linkThreadId.bare) + '/' + post) ? (QString::number(linkThreadId.bare) + '/' + post)
: post); : post);
if (linkChannel->hasUsername() if (linkChannel->hasUsername()
&& !forceNonPublicLink
&& !linkChannel->isMegagroup() && !linkChannel->isMegagroup()
&& !linkCommentId && !linkCommentId
&& !linkThreadId) { && !linkThreadId) {
@ -797,6 +799,9 @@ QString ApiWrap::exportDirectMessageLink(
} }
return session().createInternalLinkFull(query); return session().createInternalLinkFull(query);
}; };
if (forceNonPublicLink) {
return fallback();
}
const auto i = _unlikelyMessageLinks.find(itemId); const auto i = _unlikelyMessageLinks.find(itemId);
const auto current = (i != end(_unlikelyMessageLinks)) const auto current = (i != end(_unlikelyMessageLinks))
? i->second ? i->second
@ -3373,6 +3378,7 @@ void ApiWrap::forwardMessages(
} }
const auto requestType = Data::Histories::RequestType::Send; const auto requestType = Data::Histories::RequestType::Send;
const auto idsCopy = localIds; const auto idsCopy = localIds;
const auto scheduled = action.options.scheduled;
histories.sendRequest(history, requestType, [=](Fn<void()> finish) { histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
history->sendRequestId = request(MTPmessages_ForwardMessages( history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags), MTP_flags(sendFlags),
@ -3385,6 +3391,9 @@ void ApiWrap::forwardMessages(
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId) Data::ShortcutIdToMTP(_session, action.options.shortcutId)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (!scheduled) {
this->updates().checkForSentToScheduled(result);
}
applyUpdates(result); applyUpdates(result);
if (shared && !--shared->requestsLeft) { if (shared && !--shared->requestsLeft) {
shared->callback(); shared->callback();
@ -3553,6 +3562,7 @@ void ApiWrap::sendVoiceMessage(
QByteArray result, QByteArray result,
VoiceWaveform waveform, VoiceWaveform waveform,
crl::time duration, crl::time duration,
bool video,
const SendAction &action) { const SendAction &action) {
const auto caption = TextWithTags(); const auto caption = TextWithTags();
const auto to = FileLoadTaskOptions(action); const auto to = FileLoadTaskOptions(action);
@ -3561,6 +3571,7 @@ void ApiWrap::sendVoiceMessage(
result, result,
duration, duration,
waveform, waveform,
video,
to, to,
caption)); caption));
} }
@ -4008,7 +4019,8 @@ void ApiWrap::sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
not_null<InlineBots::Result*> data, not_null<InlineBots::Result*> data,
const SendAction &action, const SendAction &action,
std::optional<MsgId> localMessageId) { std::optional<MsgId> localMessageId,
Fn<void(bool)> done) {
sendAction(action); sendAction(action);
const auto history = action.history; const auto history = action.history;
@ -4088,11 +4100,17 @@ void ApiWrap::sendInlineResult(
history->finishSavingCloudDraft( history->finishSavingCloudDraft(
topicRootId, topicRootId,
UnixtimeFromMsgId(response.outerMsgId)); UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(true);
}
}, [=](const MTP::Error &error, const MTP::Response &response) { }, [=](const MTP::Error &error, const MTP::Response &response) {
sendMessageFail(error, peer, randomId, newId); sendMessageFail(error, peer, randomId, newId);
history->finishSavingCloudDraft( history->finishSavingCloudDraft(
topicRootId, topicRootId,
UnixtimeFromMsgId(response.outerMsgId)); UnixtimeFromMsgId(response.outerMsgId));
if (done) {
done(false);
}
}); });
finishForwarding(action); finishForwarding(action);
} }
@ -4304,6 +4322,7 @@ void ApiWrap::sendMultiPaidMedia(
auto &histories = history->owner().histories(); auto &histories = history->owner().histories();
const auto peer = history->peer; const auto peer = history->peer;
const auto itemId = item->fullId(); const auto itemId = item->fullId();
album->sent = true;
histories.sendPreparedMessage( histories.sendPreparedMessage(
history, history,
replyTo, replyTo,
@ -4375,6 +4394,9 @@ void ApiWrap::sendAlbumWithCancelled(
} }
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) { void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
if (album->sent) {
return;
}
const auto groupId = album->groupId; const auto groupId = album->groupId;
if (album->items.empty()) { if (album->items.empty()) {
_sendingAlbums.remove(groupId); _sendingAlbums.remove(groupId);
@ -4399,6 +4421,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
return; return;
} else if (medias.size() < 2) { } else if (medias.size() < 2) {
const auto &single = medias.front().data(); const auto &single = medias.front().data();
album->sent = true;
sendMediaWithRandomId( sendMediaWithRandomId(
sample, sample,
single.vmedia(), single.vmedia(),
@ -4431,6 +4454,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0)); | (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories(); auto &histories = history->owner().histories();
const auto peer = history->peer; const auto peer = history->peer;
album->sent = true;
histories.sendPreparedMessage( histories.sendPreparedMessage(
history, history,
replyTo, replyTo,

View file

@ -164,7 +164,8 @@ public:
void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done); void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done);
QString exportDirectMessageLink( QString exportDirectMessageLink(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
bool inRepliesContext); bool inRepliesContext,
bool forceNonPublicLink = false);
QString exportDirectStoryLink(not_null<Data::Story*> item); QString exportDirectStoryLink(not_null<Data::Story*> item);
void requestContacts(); void requestContacts();
@ -317,6 +318,7 @@ public:
QByteArray result, QByteArray result,
VoiceWaveform waveform, VoiceWaveform waveform,
crl::time duration, crl::time duration,
bool video,
const SendAction &action); const SendAction &action);
void sendFiles( void sendFiles(
Ui::PreparedList &&list, Ui::PreparedList &&list,
@ -359,7 +361,8 @@ public:
not_null<UserData*> bot, not_null<UserData*> bot,
not_null<InlineBots::Result*> data, not_null<InlineBots::Result*> data,
const SendAction &action, const SendAction &action,
std::optional<MsgId> localMessageId); std::optional<MsgId> localMessageId,
Fn<void(bool)> done = nullptr);
void sendMessageFail( void sendMessageFail(
const MTP::Error &error, const MTP::Error &error,
not_null<PeerData*> peer, not_null<PeerData*> peer,

View file

@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
iconPosition: point(10px, -1px); iconPosition: point(10px, -1px);
rippleAreaPosition: point(1px, 6px); rippleAreaPosition: point(1px, 6px);
rippleAreaSize: 42px; rippleAreaSize: 42px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }}; contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }}; contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
@ -416,9 +414,7 @@ calendarPrevious: IconButton {
rippleAreaPosition: point(2px, 2px); rippleAreaPosition: point(2px, 2px);
rippleAreaSize: 44px; rippleAreaSize: 44px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }}; calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
calendarNext: IconButton(calendarPrevious) { calendarNext: IconButton(calendarPrevious) {
@ -616,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
proxyRowPadding: margins(22px, 8px, 8px, 8px); proxyRowPadding: margins(22px, 8px, 8px, 8px);
proxyRowIconSkip: 32px; proxyRowIconSkip: 32px;
proxyRowSkip: 2px; proxyRowSkip: 2px;
proxyRowRipple: RippleAnimation(defaultRippleAnimation) { proxyRowRipple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
proxyRowTitleFg: windowFg; proxyRowTitleFg: windowFg;
proxyRowTitlePalette: TextPalette(defaultTextPalette) { proxyRowTitlePalette: TextPalette(defaultTextPalette) {
linkFg: windowSubTextFg; linkFg: windowSubTextFg;
@ -683,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
rippleAreaPosition: point(4px, 4px); rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 36px; rippleAreaSize: 36px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
themesMenuPosition: point(-2px, 25px); themesMenuPosition: point(-2px, 25px);
@ -738,9 +730,7 @@ createPollOptionRemove: CrossButton {
duration: 135; duration: 135;
loadingPeriod: 1000; loadingPeriod: 1000;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
createPollOptionRemovePosition: point(11px, 9px); createPollOptionRemovePosition: point(11px, 9px);
createPollOptionEmojiPositionSkip: 4px; createPollOptionEmojiPositionSkip: 4px;
@ -888,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
peerListSingleRow: PeerList(peerListBox) { peerListSingleRow: PeerList(peerListBox) {
padding: margins(0px, 0px, 0px, 0px); padding: margins(0px, 0px, 0px, 0px);
} }
peerListSmallSkips: PeerList(peerListBox) {
padding: margins(
0px,
defaultVerticalListSkip,
0px,
defaultVerticalListSkip);
}
scheduleHeight: 95px; scheduleHeight: 95px;
scheduleDateTop: 38px; scheduleDateTop: 38px;
@ -951,9 +948,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
textTop: 7px; textTop: 7px;
style: defaultTextStyle; style: defaultTextStyle;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
requestPeerRestriction: FlatLabel(defaultFlatLabel) { requestPeerRestriction: FlatLabel(defaultFlatLabel) {

View file

@ -96,6 +96,9 @@ void ChangeFilterById(
Ui::Text::WithEntities)); Ui::Text::WithEntities));
} }
}).fail([=](const MTP::Error &error) { }).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. // Revert filter on fail.
history->owner().chatsFilters().set(was); history->owner().chatsFilters().set(was);
}).send(); }).send();

View file

@ -238,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
Fn<void()> saved) Fn<void()> saved)
: _controller(controller) : _controller(controller)
, _historyItem(item) , _historyItem(item)
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia()) , _isAllowedEditMedia(item->allowsEditMedia())
, _albumType(ComputeAlbumType(item)) , _albumType(ComputeAlbumType(item))
, _controls(base::make_unique_q<Ui::VerticalLayout>(this)) , _controls(base::make_unique_q<Ui::VerticalLayout>(this))
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll)) , _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
@ -253,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
, _initialText(std::move(text)) , _initialText(std::move(text))
, _initialList(std::move(list)) , _initialList(std::move(list))
, _saved(std::move(saved)) { , _saved(std::move(saved)) {
Expects(item->media() != nullptr); Expects(!_initialList.files.empty());
Expects(item->media()->allowsEditCaption()); Expects(!item->media() || item->media()->allowsEditCaption());
_mediaEditManager.start(item, spoilered, invertCaption); _mediaEditManager.start(item, spoilered, invertCaption);
@ -422,7 +422,8 @@ void EditCaptionBox::prepare() {
setInitialText(); setInitialText();
if (!setPreparedList(std::move(_initialList))) { if (!setPreparedList(std::move(_initialList))) {
rebuildPreview(); crl::on_main(this, [=] { closeBox(); });
return;
} }
setupEditEventHandler(); setupEditEventHandler();
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events()); SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());

View file

@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include "styles/style_window.h"
namespace { namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value; constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
using Exceptions = Api::UserPrivacy::Exceptions; 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( void CreateRadiobuttonLock(
not_null<Ui::RpWidget*> widget, not_null<Ui::RpWidget*> widget,
const style::Checkbox &st) { const style::Checkbox &st) {
@ -102,7 +152,7 @@ public:
not_null<Main::Session*> session, not_null<Main::Session*> session,
rpl::producer<QString> title, rpl::producer<QString> title,
const Exceptions &selected, const Exceptions &selected,
bool allowChoosePremiums); std::optional<SpecialRowType> allowChooseSpecial);
Main::Session &session() const override; Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
@ -110,18 +160,20 @@ public:
bool handleDeselectForeignRow(PeerListRowId itemId) override; bool handleDeselectForeignRow(PeerListRowId itemId) override;
[[nodiscard]] bool premiumsSelected() const; [[nodiscard]] bool premiumsSelected() const;
[[nodiscard]] bool miniAppsSelected() const;
protected: protected:
void prepareViewHook() override; void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override; std::unique_ptr<Row> createRow(not_null<History*> history) override;
private: private:
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList(); [[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
SpecialRowType type);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
rpl::producer<QString> _title; rpl::producer<QString> _title;
Exceptions _selected; Exceptions _selected;
bool _allowChoosePremiums = false; std::optional<SpecialRowType> _allowChooseSpecial;
PeerListContentDelegate *_typesDelegate = nullptr; PeerListContentDelegate *_typesDelegate = nullptr;
Fn<void(PeerListRowId)> _deselectOption; Fn<void(PeerListRowId)> _deselectOption;
@ -133,9 +185,9 @@ struct RowSelectionChange {
bool checked = false; bool checked = false;
}; };
class PremiumsRow final : public PeerListRow { class SpecialRow final : public PeerListRow {
public: public:
PremiumsRow(); explicit SpecialRow(SpecialRowType type);
QString generateName() override; QString generateName() override;
QString generateShortName() override; QString generateShortName() override;
@ -147,66 +199,61 @@ public:
class TypesController final : public PeerListController { class TypesController final : public PeerListController {
public: public:
TypesController(not_null<Main::Session*> session, bool premiums); TypesController(not_null<Main::Session*> session, SpecialRowType type);
Main::Session &session() const override; Main::Session &session() const override;
void prepare() override; void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
[[nodiscard]] bool premiumsSelected() const; [[nodiscard]] bool specialSelected() const;
[[nodiscard]] rpl::producer<bool> premiumsChanges() const; [[nodiscard]] rpl::producer<bool> specialChanges() const;
[[nodiscard]] auto rowSelectionChanges() const [[nodiscard]] auto rowSelectionChanges() const
-> rpl::producer<RowSelectionChange>; -> rpl::producer<RowSelectionChange>;
private: private:
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
const SpecialRowType _type;
rpl::event_stream<> _selectionChanged; rpl::event_stream<> _selectionChanged;
rpl::event_stream<RowSelectionChange> _rowSelectionChanges; rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
}; };
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) { SpecialRow::SpecialRow(SpecialRowType type)
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now)); : 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() { QString SpecialRow::generateName() {
return tr::lng_edit_privacy_premium(tr::now); 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(); return generateName();
} }
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback( PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
bool forceRound) { bool forceRound) {
return [=](QPainter &p, int x, int y, int outerWidth, int size) { return (id() == kPremiumsRowId)
auto gradient = QLinearGradient( ? GeneratePremiumsUserpicCallback(forceRound)
QPointF(x, y), : GenerateMiniAppsUserpicCallback(forceRound);
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));
};
} }
bool PremiumsRow::useForumLikeUserpic() const { bool SpecialRow::useForumLikeUserpic() const {
return true; return true;
} }
TypesController::TypesController( TypesController::TypesController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
bool premiums) SpecialRowType type)
: _session(session) { : _session(session)
, _type(type) {
} }
Main::Session &TypesController::session() const { Main::Session &TypesController::session() const {
@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
} }
void TypesController::prepare() { void TypesController::prepare() {
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>()); delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
} }
bool TypesController::premiumsSelected() const { bool TypesController::specialSelected() const {
const auto row = delegate()->peerListFindRow(kPremiumsRowId); const auto premiums = (_type == SpecialRowType::Premiums);
const auto row = delegate()->peerListFindRow(premiums
? kPremiumsRowId
: kMiniAppsRowId);
Assert(row != nullptr); Assert(row != nullptr);
return row->checked(); return row->checked();
@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
_rowSelectionChanges.fire({ row, checked }); _rowSelectionChanges.fire({ row, checked });
} }
rpl::producer<bool> TypesController::premiumsChanges() const { rpl::producer<bool> TypesController::specialChanges() const {
return _rowSelectionChanges.events( return _rowSelectionChanges.events(
) | rpl::map([=] { ) | rpl::map([=] {
return premiumsSelected(); return specialSelected();
}); });
} }
@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
rpl::producer<QString> title, rpl::producer<QString> title,
const Exceptions &selected, const Exceptions &selected,
bool allowChoosePremiums) std::optional<SpecialRowType> allowChooseSpecial)
: ChatsListBoxController(session) : ChatsListBoxController(session)
, _session(session) , _session(session)
, _title(std::move(title)) , _title(std::move(title))
, _selected(selected) , _selected(selected)
, _allowChoosePremiums(allowChoosePremiums) { , _allowChooseSpecial(allowChooseSpecial) {
} }
Main::Session &PrivacyExceptionsBoxController::session() const { Main::Session &PrivacyExceptionsBoxController::session() const {
@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
void PrivacyExceptionsBoxController::prepareViewHook() { void PrivacyExceptionsBoxController::prepareViewHook() {
delegate()->peerListSetTitle(std::move(_title)); delegate()->peerListSetTitle(std::move(_title));
if (_allowChoosePremiums || _selected.premiums) { if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
delegate()->peerListSetAboveWidget(preparePremiumsRowList()); delegate()->peerListSetAboveWidget(prepareSpecialRowList(
_allowChooseSpecial.value_or(_selected.premiums
? SpecialRowType::Premiums
: SpecialRowType::MiniApps)));
} }
delegate()->peerListAddSelectedPeers(_selected.peers); delegate()->peerListAddSelectedPeers(_selected.peers);
} }
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) { bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
return (itemId == kPremiumsRowId); return (itemId == kPremiumsRowId)
|| (itemId == kMiniAppsRowId);
} }
bool PrivacyExceptionsBoxController::handleDeselectForeignRow( bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
return false; return false;
} }
auto PrivacyExceptionsBoxController::preparePremiumsRowList() auto PrivacyExceptionsBoxController::prepareSpecialRowList(
SpecialRowType type)
-> object_ptr<Ui::RpWidget> { -> object_ptr<Ui::RpWidget> {
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr); auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data(); const auto container = result.data();
@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>(); _typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
const auto controller = lifetime.make_state<TypesController>( const auto controller = lifetime.make_state<TypesController>(
&session(), &session(),
_selected.premiums); type);
const auto content = result->add(object_ptr<PeerListContent>( const auto content = result->add(object_ptr<PeerListContent>(
container, container,
controller)); controller));
_typesDelegate->setContent(content); _typesDelegate->setContent(content);
controller->setDelegate(_typesDelegate); 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) { if (_selected.premiums) {
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId); selectType(kPremiumsRowId);
Assert(row != nullptr); } else if (_selected.miniapps) {
selectType(kMiniAppsRowId);
content->changeCheckState(row, true, anim::type::instant);
this->delegate()->peerListSetForeignRowChecked(
row,
true,
anim::type::instant);
} }
container->add(CreatePeerListSectionSubtitle( container->add(CreatePeerListSectionSubtitle(
container, container,
tr::lng_edit_privacy_users_and_groups())); tr::lng_edit_privacy_users_and_groups()));
controller->premiumsChanges( controller->specialChanges(
) | rpl::start_with_next([=](bool premiums) { ) | rpl::start_with_next([=](bool chosen) {
_selected.premiums = premiums; if (type == SpecialRowType::Premiums) {
_selected.premiums = chosen;
} else {
_selected.miniapps = chosen;
}
}, lifetime); }, lifetime);
controller->rowSelectionChanges( controller->rowSelectionChanges(
@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
if (const auto row = _typesDelegate->peerListFindRow(itemId)) { if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
if (itemId == kPremiumsRowId) { if (itemId == kPremiumsRowId) {
_selected.premiums = false; _selected.premiums = false;
} else if (itemId == kMiniAppsRowId) {
_selected.miniapps = false;
} }
_typesDelegate->peerListSetRowChecked(row, false); _typesDelegate->peerListSetRowChecked(row, false);
} }
@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
return result; return result;
} }
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const { bool PrivacyExceptionsBoxController::premiumsSelected() const {
return _selected.premiums; return _selected.premiums;
} }
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
return _selected.miniapps;
}
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) { void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer(); const auto peer = row->peer();
@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
// If we switch from Everyone to Contacts or Nobody suggest Premiums. // If we switch from Everyone to Contacts or Nobody suggest Premiums.
_value.always.premiums = true; _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() { void EditPrivacyBox::prepare() {
@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
&_window->session(), &_window->session(),
_controller->exceptionBoxTitle(exception), _controller->exceptionBoxTitle(exception),
exceptions(exception), exceptions(exception),
_controller->allowPremiumsToggle(exception)); (_controller->allowPremiumsToggle(exception)
? SpecialRowType::Premiums
: _controller->allowMiniAppsToggle(exception)
? SpecialRowType::MiniApps
: std::optional<SpecialRowType>()));
auto initBox = [=, controller = controller.get()]( auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) { not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] { box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
exceptions(exception).peers = box->collectSelectedRows(); auto &setTo = exceptions(exception);
exceptions(exception).premiums = controller->premiumsSelected(); setTo.peers = box->collectSelectedRows();
setTo.premiums = controller->premiumsSelected();
setTo.miniapps = controller->miniAppsSelected();
const auto type = [&] { const auto type = [&] {
switch (exception) { switch (exception) {
case Exception::Always: return Exception::Never; case Exception::Always: return Exception::Never;
@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
} }
Unexpected("Invalid exception value."); Unexpected("Invalid exception value.");
}(); }();
auto &removeFrom = exceptions(type).peers; auto &removeFrom = exceptions(type);
for (const auto peer : exceptions(exception).peers) { for (const auto peer : exceptions(exception).peers) {
removeFrom.erase( removeFrom.peers.erase(
ranges::remove(removeFrom, peer), ranges::remove(removeFrom.peers, peer),
end(removeFrom)); end(removeFrom.peers));
}
if (setTo.premiums) {
removeFrom.premiums = false;
}
if (setTo.miniapps) {
removeFrom.miniapps = false;
} }
done(); done();
box->closeBox(); box->closeBox();
@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
lt_count, lt_count,
count) count)
: tr::lng_edit_privacy_exceptions_add(tr::now); : tr::lng_edit_privacy_exceptions_add(tr::now);
return !value.premiums return value.premiums
? users ? (!count
: !count ? tr::lng_edit_privacy_premium(tr::now)
? tr::lng_edit_privacy_premium(tr::now) : tr::lng_edit_privacy_exceptions_premium_and(
: tr::lng_edit_privacy_exceptions_premium_and( tr::now,
tr::now, lt_users,
lt_users, 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( _controller->handleExceptionsChange(
exception, exception,

View file

@ -61,6 +61,10 @@ public:
Exception exception) const { Exception exception) const {
return false; return false;
} }
[[nodiscard]] virtual bool allowMiniAppsToggle(
Exception exception) const {
return false;
}
virtual void handleExceptionsChange( virtual void handleExceptionsChange(
Exception exception, Exception exception,
rpl::producer<int> value) { rpl::producer<int> value) {

View file

@ -123,7 +123,9 @@ void GiftCreditsBox(
box->verticalLayout(), box->verticalLayout(),
peer, peer,
0, 0,
[=] { gifted(); box->uiShow()->hideLayer(); }); [=] { gifted(); box->uiShow()->hideLayer(); },
tr::lng_credits_summary_options_subtitle(),
{});
box->setPinnedToBottomContent( box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box)); object_ptr<Ui::VerticalLayout>(box));

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/prepare_short_info_box.h" #include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peers/replace_boost_box.h" // BoostsForGift. #include "boxes/peers/replace_boost_box.h" // BoostsForGift.
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox. #include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
#include "data/data_boosts.h" #include "data/data_boosts.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -123,7 +124,8 @@ namespace {
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue( [[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
PeerId id) { PeerId id,
bool withSendGiftButton = false) {
auto result = object_ptr<Ui::AbstractButton>(parent); auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data(); const auto raw = result.data();
@ -134,15 +136,40 @@ namespace {
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st); const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
const auto label = Ui::CreateChild<Ui::FlatLabel>( const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw, raw,
peer->name(), withSendGiftButton ? peer->shortName() : peer->name(),
st::giveawayGiftCodeValue); st::giveawayGiftCodeValue);
raw->widthValue( const auto send = withSendGiftButton
) | rpl::start_with_next([=](int width) { ? Ui::CreateChild<Ui::RoundButton>(
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; 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); label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2; const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width); 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()); }, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents); userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
@ -210,14 +237,82 @@ void AddTableRow(
valueMargins); valueMargins);
} }
object_ptr<Ui::RpWidget> MakeStarGiftStarsValue(
not_null<QWidget*> parent,
not_null<Window::SessionNavigation*> controller,
const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars) {
auto result = object_ptr<Ui::RpWidget>(parent);
const auto raw = result.data();
const auto session = &controller->session();
const auto makeContext = [session](Fn<void()> update) {
return Core::MarkedTextContext{
.session = session,
.customEmojiRepaint = std::move(update),
};
};
auto star = session->data().customEmojiManager().creditsEmoji();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
rpl::single(
star.append(' ' + Lang::FormatCountDecimal(entry.credits))),
st::giveawayGiftCodeValue,
st::defaultPopupMenu,
std::move(makeContext));
const auto convert = convertToStars
? Ui::CreateChild<Ui::RoundButton>(
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<Ui::FlatLabel*> AddTableRow( not_null<Ui::FlatLabel*> AddTableRow(
not_null<Ui::TableLayout*> table, not_null<Ui::TableLayout*> table,
rpl::producer<QString> label, rpl::producer<QString> label,
rpl::producer<TextWithEntities> value) { rpl::producer<TextWithEntities> value,
const Fn<std::any(Fn<void()>)> &makeContext = nullptr) {
auto widget = object_ptr<Ui::FlatLabel>( auto widget = object_ptr<Ui::FlatLabel>(
table, table,
std::move(value), std::move(value),
st::giveawayGiftCodeValue); st::giveawayGiftCodeValue,
st::defaultPopupMenu,
std::move(makeContext));
const auto result = widget.data(); const auto result = widget.data();
AddTableRow( AddTableRow(
table, table,
@ -939,26 +1034,57 @@ void ResolveGiveawayInfo(
void AddStarGiftTable( void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry) { const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars) {
auto table = container->add( auto table = container->add(
object_ptr<Ui::TableLayout>( object_ptr<Ui::TableLayout>(
container, container,
st::giveawayGiftCodeTable), st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin); st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId); const auto peerId = PeerId(entry.barePeerId);
const auto session = &controller->session();
if (peerId) { if (peerId) {
const auto user = session->data().peer(peerId)->asUser();
const auto withSendButton = entry.in && user && !user->isBot();
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_box_history_entry_peer_in(), tr::lng_credits_box_history_entry_peer_in(),
controller, MakePeerTableValue(table, controller, peerId, withSendButton),
peerId); st::giveawayGiftCodePeerMargin);
} else { } else if (!entry.soldOutInfo) {
AddTableRow( AddTableRow(
table, table,
tr::lng_credits_box_history_entry_peer_in(), tr::lng_credits_box_history_entry_peer_in(),
MakeHiddenPeerTableValue(table, controller), MakeHiddenPeerTableValue(table, controller),
st::giveawayGiftCodePeerMargin); 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()) { if (!entry.date.isNull()) {
AddTableRow( AddTableRow(
table, table,
@ -967,14 +1093,14 @@ void AddStarGiftTable(
} }
if (entry.limitedCount > 0) { if (entry.limitedCount > 0) {
auto amount = rpl::single(TextWithEntities{ auto amount = rpl::single(TextWithEntities{
QString::number(entry.limitedCount) Lang::FormatCountDecimal(entry.limitedCount)
}); });
AddTableRow( AddTableRow(
table, table,
tr::lng_gift_availability(), tr::lng_gift_availability(),
((entry.limitedLeft > 0) ((entry.limitedLeft > 0)
? tr::lng_gift_availability_left( ? tr::lng_gift_availability_left(
lt_count, lt_count_decimal,
rpl::single(entry.limitedLeft * 1.), rpl::single(entry.limitedLeft * 1.),
lt_amount, lt_amount,
std::move(amount), std::move(amount),
@ -985,7 +1111,6 @@ void AddStarGiftTable(
Ui::Text::WithEntities))); Ui::Text::WithEntities)));
} }
if (!entry.description.empty()) { if (!entry.description.empty()) {
const auto session = &controller->session();
const auto makeContext = [=](Fn<void()> update) { const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{ return Core::MarkedTextContext{
.session = session, .session = session,
@ -1020,12 +1145,17 @@ void AddCreditsHistoryEntryTable(
st::giveawayGiftCodeTable), st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin); st::giveawayGiftCodeTableMargin);
const auto peerId = PeerId(entry.barePeerId); const auto peerId = PeerId(entry.barePeerId);
const auto actorId = PeerId(entry.bareActorId);
const auto session = &controller->session(); const auto session = &controller->session();
if (peerId) { if (actorId || peerId) {
auto text = entry.in auto text = entry.in
? tr::lng_credits_box_history_entry_peer_in() ? tr::lng_credits_box_history_entry_peer_in()
: tr::lng_credits_box_history_entry_peer(); : 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)) { if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
const auto peer = session->data().peer(peerId); const auto peer = session->data().peer(peerId);
@ -1044,7 +1174,9 @@ void AddCreditsHistoryEntryTable(
}); });
AddTableRow( AddTableRow(
table, 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), std::move(label),
st::giveawayGiftCodeValueMargin); st::giveawayGiftCodeValueMargin);
} }
@ -1116,12 +1248,24 @@ void AddCreditsHistoryEntryTable(
} }
} }
if (!entry.id.isEmpty()) { if (!entry.id.isEmpty()) {
constexpr auto kOneLineCount = 18; constexpr auto kOneLineCount = 22;
const auto oneLine = entry.id.length() <= kOneLineCount; 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<Ui::FlatLabel>( auto label = object_ptr<Ui::FlatLabel>(
table, table,
rpl::single( rpl::single(
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})), Ui::Text::Wrapped(
{ oneLine ? entry.id : std::move(multiLine) },
EntityType::Code,
{})),
oneLine oneLine
? st::giveawayGiftCodeValue ? st::giveawayGiftCodeValue
: st::giveawayGiftCodeValueMultiline); : st::giveawayGiftCodeValueMultiline);
@ -1138,6 +1282,14 @@ void AddCreditsHistoryEntryTable(
std::move(label), std::move(label),
st::giveawayGiftCodeValueMargin); 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()) { if (!entry.date.isNull()) {
AddTableRow( AddTableRow(
table, table,
@ -1178,6 +1330,16 @@ void AddSubscriptionEntryTable(
controller, controller,
peerId); peerId);
if (!s.until.isNull()) { 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( AddTableRow(
table, table,
s.expired s.expired

View file

@ -57,7 +57,8 @@ void ResolveGiveawayInfo(
void AddStarGiftTable( void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
const Data::CreditsHistoryEntry &entry); const Data::CreditsHistoryEntry &entry,
Fn<void()> convertToStars);
void AddCreditsHistoryEntryTable( void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,

View file

@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
} }
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) { void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
if (!_progress || true) { #if 0 // not used
if (!_progress) {
return; return;
} }
auto p = QPainter(this); auto p = QPainter(this);
@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
st::proxyCheckingPosition.y() + bottom st::proxyCheckingPosition.y() + bottom
}, },
width()); width());
#endif
} }
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const { QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {

View file

@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
content()->selectSkipPage(height(), 1); content()->selectSkipPage(height(), 1);
} else if (e->key() == Qt::Key_PageUp) { } else if (e->key() == Qt::Key_PageUp) {
content()->selectSkipPage(height(), -1); 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(); _select->entity()->clearQuery();
} else { } else {
BoxContent::keyPressEvent(e); BoxContent::keyPressEvent(e);
@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
void PeerListBox::searchQueryChanged(const QString &query) { void PeerListBox::searchQueryChanged(const QString &query) {
scrollToY(0); 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) { void PeerListBox::resizeEvent(QResizeEvent *e) {
@ -543,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
return result; return result;
} }
rpl::producer<int> 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<PeerData*> peer) PeerListRow::PeerListRow(not_null<PeerData*> peer)
: PeerListRow(peer, peer->id.value) { : PeerListRow(peer, peer->id.value) {
} }
@ -1385,10 +1412,12 @@ int PeerListContent::labelHeight() const {
void PeerListContent::refreshRows() { void PeerListContent::refreshRows() {
if (!_hiddenRows.empty()) { if (!_hiddenRows.empty()) {
_filterResults.clear(); if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
for (const auto &row : _rows) { _filterResults.clear();
if (!row->hidden()) { for (const auto &row : _rows) {
_filterResults.push_back(row.get()); 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.index.value = newSelectedIndex;
_selected.element = 0; _selected.element = 0;
if (newSelectedIndex >= 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 searchWordsList = TextUtilities::PrepareSearchWords(query);
const auto normalizedQuery = searchWordsList.join(' '); const auto normalizedQuery = searchWordsList.join(' ');
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
_filterResults.clear();
}
if (_normalizedSearchQuery != normalizedQuery) { if (_normalizedSearchQuery != normalizedQuery) {
setSearchQuery(query, normalizedQuery); setSearchQuery(query, normalizedQuery);
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) { if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
Assert(_hiddenRows.empty()); Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr; auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
for (const auto &searchWord : searchWordsList) { for (const auto &searchWord : searchWordsList) {
@ -2097,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
} }
refreshRows(); refreshRows();
} }
return _normalizedSearchQuery.isEmpty();
} }
std::unique_ptr<PeerListState> PeerListContent::saveState() const { std::unique_ptr<PeerListState> PeerListContent::saveState() const {
@ -2185,6 +2225,10 @@ void PeerListContent::dragLeft() {
clearSelection(); clearSelection();
} }
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
_ignoreHiddenRowsOnSearch = value;
}
void PeerListContent::visibleTopBottomUpdated( void PeerListContent::visibleTopBottomUpdated(
int visibleTop, int visibleTop,
int visibleBottom) { int visibleBottom) {

View file

@ -357,6 +357,8 @@ public:
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0; virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0; virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
virtual void peerListSelectSkip(int direction) = 0;
virtual void peerListPressLeftToContextMenu(bool shown) = 0; virtual void peerListPressLeftToContextMenu(bool shown) = 0;
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0; virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
@ -573,6 +575,13 @@ public:
Unexpected("PeerListController::customRowRippleMaskGenerator."); Unexpected("PeerListController::customRowRippleMaskGenerator.");
} }
virtual bool overrideKeyboardNavigation(
int direction,
int fromIndex,
int toIndex) {
return false;
}
[[nodiscard]] rpl::lifetime &lifetime() { [[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime; return _lifetime;
} }
@ -643,12 +652,15 @@ public:
[[nodiscard]] bool hasPressed() const; [[nodiscard]] bool hasPressed() const;
void clearSelection(); void clearSelection();
void searchQueryChanged(QString query); using IsEmpty = bool;
IsEmpty searchQueryChanged(QString query);
bool submitted(); bool submitted();
PeerListRowId updateFromParentDrag(QPoint globalPosition); PeerListRowId updateFromParentDrag(QPoint globalPosition);
void dragLeft(); void dragLeft();
void setIgnoreHiddenRowsOnSearch(bool value);
// Interface for the controller. // Interface for the controller.
void appendRow(std::unique_ptr<PeerListRow> row); void appendRow(std::unique_ptr<PeerListRow> row);
void appendSearchRow(std::unique_ptr<PeerListRow> row); void appendSearchRow(std::unique_ptr<PeerListRow> row);
@ -870,6 +882,7 @@ private:
int _aboveHeight = 0; int _aboveHeight = 0;
int _belowHeight = 0; int _belowHeight = 0;
bool _hideEmpty = false; bool _hideEmpty = false;
bool _ignoreHiddenRowsOnSearch = false;
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr }; object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr }; object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
object_ptr<Ui::RpWidget> _belowWidget = { nullptr }; object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
@ -1016,6 +1029,10 @@ public:
bool highlightRow, bool highlightRow,
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override; Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
void peerListSelectSkip(int direction) override {
_content->selectSkip(direction);
}
void peerListPressLeftToContextMenu(bool shown) override { void peerListPressLeftToContextMenu(bool shown) override {
_content->pressLeftToContextMenu(shown); _content->pressLeftToContextMenu(shown);
} }
@ -1089,6 +1106,9 @@ public:
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds(); [[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows(); [[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
void setSpecialTabMode(bool value);
void peerListSetTitle(rpl::producer<QString> title) override { void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title)); setTitle(std::move(title));
@ -1155,4 +1175,11 @@ private:
bool _scrollBottomFixed = false; bool _scrollBottomFixed = false;
int _addedTopScrollSkip = 0; int _addedTopScrollSkip = 0;
struct SpecialTabsMode final {
bool enabled = false;
bool searchIsActive = false;
int topSkip = 0;
};
SpecialTabsMode _specialTabsMode;
}; };

View file

@ -1065,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
return nullptr; return nullptr;
} }
std::unique_ptr<PeerListRow> ChooseTopicBoxController::MakeRow(
not_null<Data::ForumTopic*> topic) {
return std::make_unique<Row>(topic);
}
auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic) auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
-> std::unique_ptr<Row> { -> std::unique_ptr<Row> {
const auto skip = _filter && !_filter(topic); const auto skip = _filter && !_filter(topic);

View file

@ -335,6 +335,9 @@ public:
void loadMoreRows() override; void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override; std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
not_null<Data::ForumTopic*> topic);
private: private:
class Row final : public PeerListRow { class Row final : public PeerListRow {
public: public:

View file

@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h" #include "data/data_changes.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "ui/effects/outline_segments.h" #include "ui/effects/outline_segments.h"
#include "ui/widgets/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/text/text_utilities.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "history/history.h" #include "history/history.h"
#include "styles/style_chat.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
namespace { namespace {
@ -1645,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
auto result = base::make_unique_q<Ui::PopupMenu>( auto result = base::make_unique_q<Ui::PopupMenu>(
parent, parent,
st::popupMenuWithIcons); st::popupMenuWithIcons);
const auto addToEnd = gsl::finally([&] {
const auto addInfoAction = [&](
not_null<PeerData*> by,
tr::phrase<lngtag_user, lngtag_date> 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<Ui::Menu::MultilineAction>(
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) { if (_navigation) {
result->addAction( result->addAction(
(participant->isUser() (participant->isUser()
@ -1652,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
: participant->isBroadcast() : participant->isBroadcast()
? tr::lng_context_view_channel ? tr::lng_context_view_channel
: tr::lng_context_view_group)(tr::now), : tr::lng_context_view_group)(tr::now),
crl::guard(this, [=] { crl::guard(this, [=, this] {
_navigation->showPeerInfo(participant); }), _navigation->parentController()->show(
PrepareShortInfoBox(participant, _navigation));
}),
(participant->isUser() (participant->isUser()
? &st::menuIconProfile ? &st::menuIconProfile
: &st::menuIconInfo)); : &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 (_role == Role::Kicked) {
if (_peer->isMegagroup() if (_peer->isMegagroup()
&& _additional.canRestrictParticipant(participant)) { && _additional.canRestrictParticipant(participant)) {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "api/api_peer_photo.h" #include "api/api_peer_photo.h"
#include "api/api_statistics.h"
#include "api/api_user_names.h" #include "api/api_user_names.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/boxes/confirm_box.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 "history/admin_log/history_admin_log_section.h"
#include "info/bot/earn/info_bot_earn_widget.h" #include "info/bot/earn/info_bot_earn_widget.h"
#include "info/channel_statistics/boosts/info_boosts_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/profile/info_profile_values.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -352,7 +356,8 @@ private:
void fillPendingRequestsButton(); void fillPendingRequestsButton();
void fillBotUsernamesButton(); void fillBotUsernamesButton();
void fillBotBalanceButton(); void fillBotCurrencyButton();
void fillBotCreditsButton();
void fillBotEditIntroButton(); void fillBotEditIntroButton();
void fillBotEditCommandsButton(); void fillBotEditCommandsButton();
void fillBotEditSettingsButton(); void fillBotEditSettingsButton();
@ -1174,7 +1179,8 @@ void Controller::fillManageSection() {
::AddSkip(container, 0); ::AddSkip(container, 0);
fillBotUsernamesButton(); fillBotUsernamesButton();
fillBotBalanceButton(); fillBotCurrencyButton();
fillBotCreditsButton();
fillBotEditIntroButton(); fillBotEditIntroButton();
fillBotEditCommandsButton(); fillBotEditCommandsButton();
fillBotEditSettingsButton(); fillBotEditSettingsButton();
@ -1583,7 +1589,72 @@ void Controller::fillBotUsernamesButton() {
{ &st::menuIconLinks }); { &st::menuIconLinks });
} }
void Controller::fillBotBalanceButton() { void Controller::fillBotCurrencyButton() {
Expects(_isBot);
struct State final {
rpl::variable<QString> balance;
};
auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<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<Ui::SlideWrap<Ui::SettingsButton>>(
_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<Api::EarnStatistics>(_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<Ui::RpWidget>(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); Expects(_isBot);
struct State final { struct State final {
@ -1593,7 +1664,7 @@ void Controller::fillBotBalanceButton() {
auto &lifetime = _controls.buttonsLayout->lifetime(); auto &lifetime = _controls.buttonsLayout->lifetime();
const auto state = lifetime.make_state<State>(); const auto state = lifetime.make_state<State>();
if (const auto balance = _peer->session().credits().balance(_peer->id)) { 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( const auto wrap = _controls.buttonsLayout->add(
@ -1601,7 +1672,7 @@ void Controller::fillBotBalanceButton() {
_controls.buttonsLayout, _controls.buttonsLayout,
EditPeerInfoBox::CreateButton( EditPeerInfoBox::CreateButton(
_controls.buttonsLayout, _controls.buttonsLayout,
tr::lng_manage_peer_bot_balance(), tr::lng_manage_peer_bot_balance_credits(),
state->balance.value(), state->balance.value(),
[controller = _navigation->parentController(), peer = _peer] { [controller = _navigation->parentController(), peer = _peer] {
controller->showSection(Info::BotEarn::Make(peer)); controller->showSection(Info::BotEarn::Make(peer));
@ -1618,46 +1689,22 @@ void Controller::fillBotBalanceButton() {
if (data.balance) { if (data.balance) {
wrap->toggle(true, anim::type::normal); 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<Ui::RpWidget>(button); const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
icon->resize(Size(st::menuIconLinks.width() - kSizeShift)); const auto image = Ui::Earn::MenuIconCredits();
icon->resize(image.size() / style::DevicePixelRatio());
auto colorized = [&] { icon->paintRequest() | rpl::start_with_next([=] {
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<QSvgRenderer>(
colorized.toUtf8());
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
const auto starSize = Size(icon->height());
icon->paintRequest(
) | rpl::start_with_next([=] {
auto p = QPainter(icon); auto p = QPainter(icon);
svg->render(&p, Rect(starSize)); p.drawImage(0, 0, image);
}, icon->lifetime()); }, icon->lifetime());
button->sizeValue( button->sizeValue(
) | rpl::start_with_next([=](const QSize &size) { ) | rpl::start_with_next([=](const QSize &size) {
icon->moveToLeft( icon->moveToLeft(
button->st().iconLeft + kSizeShift / 2., button->st().iconLeft,
(size.height() - icon->height()) / 2); (size.height() - icon->height()) / 2);
}, icon->lifetime()); }, icon->lifetime());
} }

View file

@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/gift_premium_box.h" #include "boxes/gift_premium_box.h"
#include "boxes/peer_list_box.h" #include "boxes/peer_list_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "core/application.h" #include "core/application.h"
#include "core/ui_integration.h" // Core::MarkedTextContext. #include "core/ui_integration.h" // Core::MarkedTextContext.
#include "data/components/credits.h" #include "data/components/credits.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.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 "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_credits.h" #include "styles/style_credits.h"
#include "styles/style_dialogs.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_layers.h" // st::boxDividerLabel. #include "styles/style_layers.h" // st::boxDividerLabel.
@ -264,8 +267,9 @@ private:
class SingleRowController final : public PeerListController { class SingleRowController final : public PeerListController {
public: public:
SingleRowController( SingleRowController(
not_null<PeerData*> peer, not_null<Data::Thread*> thread,
rpl::producer<QString> status); rpl::producer<QString> status,
Fn<void()> clicked);
void prepare() override; void prepare() override;
void loadMoreRows() override; void loadMoreRows() override;
@ -273,8 +277,10 @@ public:
Main::Session &session() const override; Main::Session &session() const override;
private: private:
const not_null<PeerData*> _peer; const not_null<Main::Session*> _session;
const base::weak_ptr<Data::Thread> _thread;
rpl::producer<QString> _status; rpl::producer<QString> _status;
Fn<void()> _clicked;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };
@ -956,12 +962,11 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::AddSkip(content); Ui::AddSkip(content);
Ui::AddSkip(content); Ui::AddSkip(content);
const auto &stUser = st::boostReplaceUserpic; const auto photoSize = st::boostReplaceUserpic.photoSize;
const auto session = &row->peer()->session(); const auto session = &row->peer()->session();
content->add(object_ptr<Ui::CenterWrap<>>( content->add(object_ptr<Ui::CenterWrap<>>(
content, content,
object_ptr<Ui::UserpicButton>(content, channel, stUser)) Settings::SubscriptionUserpic(content, channel, photoSize)));
)->setAttribute(Qt::WA_TransparentForMouseEvents);
Ui::AddSkip(content); Ui::AddSkip(content);
Ui::AddSkip(content); Ui::AddSkip(content);
@ -1145,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
} }
SingleRowController::SingleRowController( SingleRowController::SingleRowController(
not_null<PeerData*> peer, not_null<Data::Thread*> thread,
rpl::producer<QString> status) rpl::producer<QString> status,
: _peer(peer) Fn<void()> clicked)
, _status(std::move(status)) { : _session(&thread->session())
, _thread(thread)
, _status(std::move(status))
, _clicked(std::move(clicked)) {
} }
void SingleRowController::prepare() { void SingleRowController::prepare() {
auto row = std::make_unique<PeerListRow>(_peer); const auto strong = _thread.get();
if (!strong) {
return;
}
const auto topic = strong->asTopic();
auto row = topic
? ChooseTopicBoxController::MakeRow(topic)
: std::make_unique<PeerListRow>(strong->peer());
const auto raw = row.get(); const auto raw = row.get();
std::move( if (_status) {
_status std::move(
) | rpl::start_with_next([=](const QString &status) { _status
raw->setCustomStatus(status); ) | rpl::start_with_next([=](const QString &status) {
delegate()->peerListUpdateRow(raw); raw->setCustomStatus(status);
}, _lifetime); delegate()->peerListUpdateRow(raw);
}, _lifetime);
}
delegate()->peerListAppendRow(std::move(row)); delegate()->peerListAppendRow(std::move(row));
delegate()->peerListRefreshRows(); 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::loadMoreRows() {
} }
void SingleRowController::rowClicked(not_null<PeerListRow*> row) { void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
ShowPeerInfoSync(row->peer()); if (const auto onstack = _clicked) {
onstack();
} else {
ShowPeerInfoSync(row->peer());
}
} }
Main::Session &SingleRowController::session() const { Main::Session &SingleRowController::session() const {
return _peer->session(); return *_session;
} }
} // namespace } // namespace
@ -1187,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
void AddSinglePeerRow( void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<QString> status) { rpl::producer<QString> status,
Fn<void()> clicked) {
AddSinglePeerRow(
container,
peer->owner().history(peer),
std::move(status),
std::move(clicked));
}
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked) {
const auto delegate = container->lifetime().make_state< const auto delegate = container->lifetime().make_state<
PeerListContentDelegateSimple PeerListContentDelegateSimple
>(); >();
const auto controller = container->lifetime().make_state< const auto controller = container->lifetime().make_state<
SingleRowController SingleRowController
>(peer, std::move(status)); >(thread, std::move(status), std::move(clicked));
controller->setStyleOverrides(&st::peerListSingleRow); controller->setStyleOverrides(thread->asTopic()
? &st::chooseTopicList
: &st::peerListSingleRow);
const auto content = container->add(object_ptr<PeerListContent>( const auto content = container->add(object_ptr<PeerListContent>(
container, container,
controller)); controller));

View file

@ -16,6 +16,10 @@ namespace Api {
struct InviteLink; struct InviteLink;
} // namespace Api } // namespace Api
namespace Data {
class Thread;
} // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -31,7 +35,14 @@ class BoxContent;
void AddSinglePeerRow( void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer, not_null<PeerData*> peer,
rpl::producer<QString> status); rpl::producer<QString> status,
Fn<void()> clicked = nullptr);
void AddSinglePeerRow(
not_null<Ui::VerticalLayout*> container,
not_null<Data::Thread*> thread,
rpl::producer<QString> status,
Fn<void()> clicked = nullptr);
void AddPermanentLinkBlock( void AddPermanentLinkBlock(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,

View file

@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/peers/edit_peer_requests_box.h" #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/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus #include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox #include "boxes/peers/edit_peer_requests_box.h"
#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 "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_peer.h"
#include "data/data_session.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 "main/main_session.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/round_rect.h" #include "ui/round_rect.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "lang/lang_keys.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
namespace { namespace {
@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
void RequestsBoxController::Start( void RequestsBoxController::Start(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
auto controller = std::make_unique<RequestsBoxController>( navigation->showSection(
navigation, std::make_shared<Info::Memento>(
peer->migrateToOrMe()); peer->migrateToOrMe(),
const auto initBox = [=](not_null<PeerListBox*> box) { Info::Section::Type::RequestsList));
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
};
navigation->parentController()->show(
Box<PeerListBox>(std::move(controller), initBox));
} }
Main::Session &RequestsBoxController::session() const { Main::Session &RequestsBoxController::session() const {
@ -289,6 +288,58 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
return nullptr; return nullptr;
} }
std::unique_ptr<PeerListRow> RequestsBoxController::createRestoredRow(
not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return createRow(user, _dates[user]);
}
return nullptr;
}
auto RequestsBoxController::saveState() const
-> std::unique_ptr<PeerListState> {
auto result = PeerListController::saveState();
auto my = std::make_unique<SavedState>();
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<PeerListState> state) {
auto typeErasedState = state
? state->controllerState.get()
: nullptr;
if (const auto my = dynamic_cast<SavedState*>(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() { void RequestsBoxController::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(_peer->isBroadcast() delegate()->peerListSetTitle(_peer->isBroadcast()
@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
} }
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) { void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
_navigation->parentController()->show(PrepareShortInfoBox( _navigation->showPeerInfo(row->peer());
row->peer(),
_navigation));
} }
void RequestsBoxController::rowElementClicked( void RequestsBoxController::rowElementClicked(
@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
not_null<UserData*> user, not_null<UserData*> user,
TimeId date) { TimeId date) {
if (!delegate()->peerListFindRow(user->id.value)) { if (!delegate()->peerListFindRow(user->id.value)) {
_dates.emplace(user, date);
if (auto row = createRow(user, date)) { if (auto row = createRow(user, date)) {
delegate()->peerListAppendRow(std::move(row)); delegate()->peerListAppendRow(std::move(row));
setDescriptionText(QString()); setDescriptionText(QString());
@ -503,6 +553,7 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
const auto search = static_cast<RequestsBoxSearchController*>( const auto search = static_cast<RequestsBoxSearchController*>(
searchController()); searchController());
date = search->dateForUser(user); date = search->dateForUser(user);
_dates.emplace(user, date);
} }
return std::make_unique<Row>(_helper.get(), user, date); return std::make_unique<Row>(_helper.get(), user, date);
} }
@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
return {}; return {};
} }
auto RequestsBoxSearchController::saveState() const
-> std::unique_ptr<SavedStateBase> {
auto result = std::make_unique<SavedState>();
result->query = _query;
result->offsetDate = _offsetDate;
result->offsetUser = _offsetUser;
result->allLoaded = _allLoaded;
result->wasLoading = (_requestId != 0);
return result;
}
void RequestsBoxSearchController::restoreState(
std::unique_ptr<SavedStateBase> state) {
if (auto my = dynamic_cast<SavedState*>(state.get())) {
if (auto requestId = base::take(_requestId)) {
_api.request(requestId).cancel();
}
_cache.clear();
_queries.clear();
_allLoaded = my->allLoaded;
_offsetDate = my->offsetDate;
_offsetUser = my->offsetUser;
_query = my->query;
if (my->wasLoading) {
searchOnServer();
}
}
}
bool RequestsBoxSearchController::searchInCache() { bool RequestsBoxSearchController::searchInCache() {
const auto i = _cache.find(_query); const auto i = _cache.find(_query);
if (i != _cache.cend()) { if (i != _cache.cend()) {

View file

@ -35,15 +35,32 @@ public:
Main::Session &session() const override; Main::Session &session() const override;
void prepare() override; void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void rowElementClicked(not_null<PeerListRow*> row, int element) override; void rowElementClicked(
not_null<PeerListRow*> row,
int element) override;
void loadMoreRows() override; void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow( std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override; not_null<PeerData*> peer) override;
std::unique_ptr<PeerListRow> createRestoredRow(
not_null<PeerData*> peer) override;
std::unique_ptr<PeerListState> saveState() const override;
void restoreState(std::unique_ptr<PeerListState> state) override;
private: private:
class RowHelper; class RowHelper;
struct SavedState : SavedStateBase {
using SearchStateBase = PeerListSearchController::SavedStateBase;
std::unique_ptr<SearchStateBase> searchState;
base::flat_map<not_null<UserData*>, TimeId> dates;
TimeId offsetDate = 0;
UserData *offsetUser = nullptr;
bool allLoaded = false;
bool wasLoading = false;
};
static std::unique_ptr<PeerListSearchController> CreateSearchController( static std::unique_ptr<PeerListSearchController> CreateSearchController(
not_null<PeerData*> peer); not_null<PeerData*> peer);
@ -63,6 +80,8 @@ private:
not_null<PeerData*> _peer; not_null<PeerData*> _peer;
MTP::Sender _api; MTP::Sender _api;
base::flat_map<not_null<UserData*>, TimeId> _dates;
TimeId _offsetDate = 0; TimeId _offsetDate = 0;
UserData *_offsetUser = nullptr; UserData *_offsetUser = nullptr;
mtpRequestId _loadRequestId = 0; mtpRequestId _loadRequestId = 0;
@ -82,7 +101,17 @@ public:
void removeFromCache(not_null<UserData*> user); void removeFromCache(not_null<UserData*> user);
[[nodiscard]] TimeId dateForUser(not_null<UserData*> user); [[nodiscard]] TimeId dateForUser(not_null<UserData*> user);
std::unique_ptr<SavedStateBase> saveState() const override;
void restoreState(std::unique_ptr<SavedStateBase> state) override;
private: private:
struct SavedState : SavedStateBase {
QString query;
TimeId offsetDate = 0;
UserData *offsetUser = nullptr;
bool allLoaded = false;
bool wasLoading = false;
};
struct Item { struct Item {
not_null<UserData*> user; not_null<UserData*> user;
TimeId date = 0; TimeId date = 0;

View file

@ -534,15 +534,16 @@ void PeerShortInfoCover::handleStreamingUpdate(
v::match(update.data, [&](Information &update) { v::match(update.data, [&](Information &update) {
streamingReady(std::move(update)); streamingReady(std::move(update));
}, [&](const PreloadedVideo &update) { }, [](PreloadedVideo) {
}, [&](const UpdateVideo &update) { }, [&](UpdateVideo update) {
_videoPosition = update.position; _videoPosition = update.position;
_widget->update(); _widget->update();
}, [&](const PreloadedAudio &update) { }, [](PreloadedAudio) {
}, [&](const UpdateAudio &update) { }, [](UpdateAudio) {
}, [&](const WaitingForData &update) { }, [](WaitingForData) {
}, [&](MutedByOther) { }, [](SpeedEstimate) {
}, [&](Finished) { }, [](MutedByOther) {
}, [](Finished) {
}); });
} }
@ -774,6 +775,10 @@ void PeerShortInfoBox::prepareRows() {
result->setContextCopyText(contextCopyText); result->setContextCopyText(contextCopyText);
return result; return result;
}; };
addInfoOneLine(
tr::lng_settings_channel_label(),
channelValue(),
tr::lng_context_copy_link(tr::now));
addInfoOneLine( addInfoOneLine(
tr::lng_info_link_label(), tr::lng_info_link_label(),
linkValue(), linkValue(),
@ -835,6 +840,13 @@ rpl::producer<QString> PeerShortInfoBox::nameValue() const {
}) | rpl::distinct_until_changed(); }) | rpl::distinct_until_changed();
} }
rpl::producer<TextWithEntities> PeerShortInfoBox::channelValue() const {
return _fields.value(
) | rpl::map([](const PeerShortInfoFields &fields) {
return Ui::Text::Link(fields.channelName, fields.channelLink);
}) | rpl::distinct_until_changed();
}
rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const { rpl::producer<TextWithEntities> PeerShortInfoBox::linkValue() const {
return _fields.value( return _fields.value(
) | rpl::map([](const PeerShortInfoFields &fields) { ) | rpl::map([](const PeerShortInfoFields &fields) {

View file

@ -37,6 +37,8 @@ enum class PeerShortInfoType {
struct PeerShortInfoFields { struct PeerShortInfoFields {
QString name; QString name;
QString channelName;
QString channelLink;
QString phone; QString phone;
QString link; QString link;
TextWithEntities about; TextWithEntities about;
@ -169,6 +171,7 @@ private:
int fillRoundedTopHeight(); int fillRoundedTopHeight();
[[nodiscard]] rpl::producer<QString> nameValue() const; [[nodiscard]] rpl::producer<QString> nameValue() const;
[[nodiscard]] rpl::producer<TextWithEntities> channelValue() const;
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const; [[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
[[nodiscard]] rpl::producer<QString> phoneValue() const; [[nodiscard]] rpl::producer<QString> phoneValue() const;
[[nodiscard]] rpl::producer<QString> usernameValue() const; [[nodiscard]] rpl::producer<QString> usernameValue() const;

View file

@ -202,6 +202,7 @@ void ProcessFullPhoto(
return peer->session().changes().peerFlagsValue( return peer->session().changes().peerFlagsValue(
peer, peer,
(UpdateFlag::Name (UpdateFlag::Name
| UpdateFlag::PersonalChannel
| UpdateFlag::PhoneNumber | UpdateFlag::PhoneNumber
| UpdateFlag::Username | UpdateFlag::Username
| UpdateFlag::About | UpdateFlag::About
@ -209,8 +210,20 @@ void ProcessFullPhoto(
) | rpl::map([=] { ) | rpl::map([=] {
const auto user = peer->asUser(); const auto user = peer->asUser();
const auto username = peer->username(); const auto username = peer->username();
const auto channelId = user ? user->personalChannelId() : 0;
const auto channel = channelId
? user->owner().channel(channelId).get()
: nullptr;
const auto channelUsername = channel
? channel->username()
: QString();
const auto hasChannel = !channelUsername.isEmpty();
return PeerShortInfoFields{ return PeerShortInfoFields{
.name = peer->name(), .name = peer->name(),
.channelName = hasChannel ? channel->name() : QString(),
.channelLink = (hasChannel
? channel->session().createInternalLinkFull(channelUsername)
: QString()),
.phone = user ? Ui::FormatPhone(user->phone()) : QString(), .phone = user ? Ui::FormatPhone(user->phone()) : QString(),
.link = ((user || username.isEmpty()) .link = ((user || username.isEmpty())
? QString() ? QString()

View file

@ -77,7 +77,7 @@ bool operator==(const Descriptor &a, const Descriptor &b) {
struct Preload { struct Preload {
Descriptor descriptor; Descriptor descriptor;
std::shared_ptr<Data::DocumentMedia> media; std::shared_ptr<Data::DocumentMedia> media;
std::weak_ptr<ChatHelpers::Show> show; std::weak_ptr<Main::SessionShow> show;
}; };
[[nodiscard]] std::vector<Preload> &Preloads() { [[nodiscard]] std::vector<Preload> &Preloads() {

View file

@ -27,13 +27,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
[[nodiscard]] object_ptr<Ui::BoxContent> Report( [[nodiscard]] object_ptr<Ui::BoxContent> ReportPhoto(
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::variant<v::null_t, not_null<PhotoData*>> data, not_null<PhotoData*> photo,
const style::ReportBox *stOverride) { const style::ReportBox *stOverride) {
const auto source = v::match(data, [](const MessageIdsList &ids) { const auto source = [&] {
return Ui::ReportSource::Message;
}, [&](not_null<PhotoData*> photo) {
return peer->isUser() return peer->isUser()
? (photo->hasVideo() ? (photo->hasVideo()
? Ui::ReportSource::ProfileVideo ? Ui::ReportSource::ProfileVideo
@ -45,19 +43,14 @@ namespace {
: (photo->hasVideo() : (photo->hasVideo()
? Ui::ReportSource::ChannelVideo ? Ui::ReportSource::ChannelVideo
: Ui::ReportSource::ChannelPhoto); : Ui::ReportSource::ChannelPhoto);
}, [&](StoryId id) { }();
return Ui::ReportSource::Story;
}, [](v::null_t) {
Unexpected("Bad source report.");
return Ui::ReportSource::Bot;
});
const auto st = stOverride ? stOverride : &st::defaultReportBox; const auto st = stOverride ? stOverride : &st::defaultReportBox;
return Box([=](not_null<Ui::GenericBox*> box) { return Box([=](not_null<Ui::GenericBox*> box) {
const auto show = box->uiShow(); const auto show = box->uiShow();
Ui::ReportReasonBox(box, *st, source, [=](Ui::ReportReason reason) { Ui::ReportReasonBox(box, *st, source, [=](Ui::ReportReason reason) {
show->showBox(Box([=](not_null<Ui::GenericBox*> box) { show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
Ui::ReportDetailsBox(box, *st, [=](const QString &text) { Ui::ReportDetailsBox(box, *st, [=](const QString &text) {
Api::SendReport(show, peer, reason, text, data); Api::SendPhotoReport(show, peer, reason, text, photo);
show->hideLayer(); show->hideLayer();
}); });
})); }));
@ -70,7 +63,7 @@ namespace {
object_ptr<Ui::BoxContent> ReportProfilePhotoBox( object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<PhotoData*> photo) { not_null<PhotoData*> photo) {
return Report(peer, photo, nullptr); return ReportPhoto(peer, photo, nullptr);
} }
void ShowReportMessageBox( void ShowReportMessageBox(
@ -86,7 +79,6 @@ void ShowReportMessageBox(
auto performRequest = [=]( auto performRequest = [=](
const auto &repeatRequest, const auto &repeatRequest,
Data::ReportInput reportInput) -> void { Data::ReportInput reportInput) -> void {
constexpr auto kToastDuration = crl::time(4000);
report(reportInput, [=](const Api::ReportResult &result) { report(reportInput, [=](const Api::ReportResult &result) {
if (!result.error.isEmpty()) { if (!result.error.isEmpty()) {
if (result.error == u"MESSAGE_ID_REQUIRED"_q) { if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
@ -206,6 +198,7 @@ void ShowReportMessageBox(
} }
})); }));
} else if (result.successful) { } else if (result.successful) {
constexpr auto kToastDuration = crl::time(4000);
show->showToast( show->showToast(
tr::lng_report_thanks(tr::now), tr::lng_report_thanks(tr::now),
kToastDuration); kToastDuration);

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "ui/widgets/chat_filters_tabs_strip.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/multi_select.h" #include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
@ -39,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/share_message_phrase_factory.h" #include "chat_helpers/share_message_phrase_factory.h"
#include "data/business/data_shortcut_messages.h" #include "data/business/data_shortcut_messages.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat_filters.h"
#include "data/data_game.h" #include "data/data_game.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -86,11 +88,14 @@ public:
void activateSkipColumn(int direction); void activateSkipColumn(int direction);
void activateSkipPage(int pageHeight, int direction); void activateSkipPage(int pageHeight, int direction);
void updateFilter(QString filter = QString()); void updateFilter(QString filter = QString());
[[nodiscard]] bool isFilterEmpty() const;
void selectActive(); void selectActive();
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const; rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
rpl::producer<> searchRequests() const; rpl::producer<> searchRequests() const;
void applyChatFilter(FilterId id);
protected: protected:
void visibleTopBottomUpdated( void visibleTopBottomUpdated(
int visibleTop, int visibleTop,
@ -171,7 +176,9 @@ private:
int _upon = -1; int _upon = -1;
int _visibleTop = 0; int _visibleTop = 0;
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed; std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
not_null<Dialogs::IndexedList*> _chatsIndexed;
QString _filter; QString _filter;
std::vector<not_null<Dialogs::Row*>> _filtered; std::vector<not_null<Dialogs::Row*>> _filtered;
@ -287,6 +294,10 @@ void ShareBox::prepare() {
_select->setQueryChangedCallback([=](const QString &query) { _select->setQueryChangedCallback([=](const QString &query) {
applyFilterUpdate(query); applyFilterUpdate(query);
if (_chatsFilters) {
updateScrollSkips();
scrollToY(0);
}
}); });
_select->setItemRemovedCallback([=](uint64 itemId) { _select->setItemRemovedCallback([=](uint64 itemId) {
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) { if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
@ -342,10 +353,32 @@ void ShareBox::prepare() {
{ .suggestCustomEmoji = true }); { .suggestCustomEmoji = true });
_select->raise(); _select->raise();
{
const auto chatsFilters = AddChatFiltersTabsStrip(
this,
_descriptor.session,
[this](FilterId id) {
_inner->applyChatFilter(id);
scrollToY(0);
});
chatsFilters->lower();
chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
updateScrollSkips();
scrollToY(0);
}, lifetime());
_select->heightValue() | rpl::start_with_next([=](int h) {
chatsFilters->moveToLeft(0, h);
}, chatsFilters->lifetime());
_chatsFilters = chatsFilters;
}
} }
int ShareBox::getTopScrollSkip() const { int ShareBox::getTopScrollSkip() const {
return _select->isHidden() ? 0 : _select->height(); return (_select->isHidden() ? 0 : _select->height())
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
? _chatsFilters->height()
: 0);
} }
int ShareBox::getBottomScrollSkip() const { int ShareBox::getBottomScrollSkip() const {
@ -676,9 +709,10 @@ ShareBox::Inner::Inner(
, _descriptor(descriptor) , _descriptor(descriptor)
, _show(std::move(show)) , _show(std::move(show))
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList) , _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
, _chatsIndexed( , _defaultChatsIndexed(
std::make_unique<Dialogs::IndexedList>( std::make_unique<Dialogs::IndexedList>(
Dialogs::SortMode::Add)) { Dialogs::SortMode::Add))
, _chatsIndexed(_defaultChatsIndexed.get()) {
_rowsTop = st::shareRowsTop; _rowsTop = st::shareRowsTop;
_rowHeight = st::shareRowHeight; _rowHeight = st::shareRowHeight;
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
@ -696,7 +730,7 @@ ShareBox::Inner::Inner(
const auto self = _descriptor.session->user(); const auto self = _descriptor.session->user();
const auto selfHistory = self->owner().history(self); const auto selfHistory = self->owner().history(self);
if (_descriptor.filterCallback(selfHistory)) { if (_descriptor.filterCallback(selfHistory)) {
_chatsIndexed->addToEnd(selfHistory); _defaultChatsIndexed->addToEnd(selfHistory);
} }
const auto addList = [&](not_null<Dialogs::IndexedList*> list) { const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
for (const auto &row : list->all()) { for (const auto &row : list->all()) {
@ -704,7 +738,7 @@ ShareBox::Inner::Inner(
if (!history->peer->isSelf() if (!history->peer->isSelf()
&& (history->asForum() && (history->asForum()
|| _descriptor.filterCallback(history))) { || _descriptor.filterCallback(history))) {
_chatsIndexed->addToEnd(history); _defaultChatsIndexed->addToEnd(history);
} }
} }
} }
@ -727,7 +761,7 @@ ShareBox::Inner::Inner(
_descriptor.session->changes().realtimeNameUpdates( _descriptor.session->changes().realtimeNameUpdates(
) | rpl::start_with_next([=](const Data::NameUpdate &update) { ) | rpl::start_with_next([=](const Data::NameUpdate &update) {
_chatsIndexed->peerNameChanged( _defaultChatsIndexed->peerNameChanged(
update.peer, update.peer,
update.oldFirstLetters); update.oldFirstLetters);
}, lifetime()); }, lifetime());
@ -1336,6 +1370,10 @@ void ShareBox::Inner::updateFilter(QString filter) {
} }
} }
bool ShareBox::Inner::isFilterEmpty() const {
return _filter.isEmpty();
}
rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const { rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
return _scrollToRequests.events(); return _scrollToRequests.events();
} }
@ -1344,6 +1382,30 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
return _searchRequests.events(); return _searchRequests.events();
} }
void ShareBox::Inner::applyChatFilter(FilterId id) {
if (!id) {
_chatsIndexed = _defaultChatsIndexed.get();
} else {
_customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
Dialogs::SortMode::Add);
_chatsIndexed = _customChatsIndexed.get();
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
for (const auto &row : list->all()) {
if (const auto history = row->history()) {
if (history->asForum()
|| _descriptor.filterCallback(history)) {
_customChatsIndexed->addToEnd(history);
}
}
}
};
const auto &data = _descriptor.session->data();
addList(data.chatsFilters().chatsList(id)->indexed());
}
update();
}
void ShareBox::Inner::peopleReceived( void ShareBox::Inner::peopleReceived(
const QString &query, const QString &query,
const QVector<MTPPeer> &my, const QVector<MTPPeer> &my,

View file

@ -174,6 +174,8 @@ private:
bool _peopleFull = false; bool _peopleFull = false;
mtpRequestId _peopleRequest = 0; mtpRequestId _peopleRequest = 0;
RpWidget *_chatsFilters = nullptr;
using PeopleCache = QMap<QString, MTPcontacts_Found>; using PeopleCache = QMap<QString, MTPcontacts_Found>;
PeopleCache _peopleCache; PeopleCache _peopleCache;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/random.h" #include "base/random.h"
#include "base/unixtime.h"
#include "api/api_premium.h" #include "api/api_premium.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/send_credits_box.h" #include "boxes/send_credits_box.h"
@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/data_credits.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h" #include "payments/payments_checkout_process.h"
#include "payments/payments_non_panel_process.h" #include "payments/payments_non_panel_process.h"
#include "settings/settings_credits.h" #include "settings/settings_credits.h"
#include "settings/settings_credits_graphics.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
@ -136,6 +139,23 @@ private:
}; };
[[nodiscard]] bool SortForBirthday(not_null<PeerData*> peer) {
const auto user = peer->asUser();
if (!user) {
return false;
}
const auto birthday = user->birthday();
if (!birthday) {
return false;
}
const auto is = [&](const QDate &date) {
return (date.day() == birthday.day())
&& (date.month() == birthday.month());
};
const auto now = QDate::currentDate();
return is(now) || is(now.addDays(1)) || is(now.addDays(-1));
}
PreviewDelegate::PreviewDelegate( PreviewDelegate::PreviewDelegate(
not_null<QWidget*> parent, not_null<QWidget*> parent,
not_null<Ui::ChatStyle*> st, not_null<Ui::ChatStyle*> st,
@ -213,7 +233,7 @@ auto GenerateGiftMedia(
return tr::lng_action_gift_got_stars_text( return tr::lng_action_gift_got_stars_text(
tr::now, tr::now,
lt_count, lt_count,
gift.convertStars, gift.info.starsConverted,
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
}); });
auto description = data.text.empty() auto description = data.text.empty()
@ -280,7 +300,7 @@ void ShowSentToast(
return tr::lng_gift_sent_about( return tr::lng_gift_sent_about(
tr::now, tr::now,
lt_count, lt_count,
gift.stars, gift.info.stars,
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
}); });
const auto strong = window->showToast({ const auto strong = window->showToast({
@ -338,7 +358,10 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
const auto cost = v::match(descriptor, [&](GiftTypePremium data) { const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
return FillAmountAndCurrency(data.cost, data.currency, true); return FillAmountAndCurrency(data.cost, data.currency, true);
}, [&](GiftTypeStars data) { }, [&](GiftTypeStars data) {
return tr::lng_gift_stars_title(tr::now, lt_count, data.stars); return tr::lng_gift_stars_title(
tr::now,
lt_count,
data.info.stars);
}); });
const auto text = tr::lng_action_gift_received( const auto text = tr::lng_action_gift_received(
tr::now, tr::now,
@ -508,14 +531,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
const auto &gifts = api->starGifts(); const auto &gifts = api->starGifts();
list.reserve(gifts.size()); list.reserve(gifts.size());
for (auto &gift : gifts) { for (auto &gift : gifts) {
list.push_back({ list.push_back({ .info = gift });
.id = gift.id,
.stars = gift.stars,
.convertStars = gift.convertStars,
.document = gift.document,
.limitedCount = gift.limitedCount,
.limitedLeft = gift.limitedLeft,
});
} }
auto &map = Map[session]; auto &map = Map[session];
if (map.last != list) { if (map.last != list) {
@ -587,7 +603,8 @@ struct GiftPriceTabs {
auto sameKey = 0; auto sameKey = 0;
for (const auto &gift : gifts) { for (const auto &gift : gifts) {
if (same) { if (same) {
const auto key = gift.stars * (gift.limitedCount ? -1 : 1); const auto key = gift.info.stars
* (gift.info.limitedCount ? -1 : 1);
if (!sameKey) { if (!sameKey) {
sameKey = key; sameKey = key;
} else if (sameKey != key) { } else if (sameKey != key) {
@ -595,12 +612,12 @@ struct GiftPriceTabs {
} }
} }
if (gift.limitedCount if (gift.info.limitedCount
&& (result.size() < 2 || result[1] != kPriceTabLimited)) { && (result.size() < 2 || result[1] != kPriceTabLimited)) {
result.insert(begin(result) + 1, kPriceTabLimited); result.insert(begin(result) + 1, kPriceTabLimited);
} }
if (!ranges::contains(result, gift.stars)) { if (!ranges::contains(result, gift.info.stars)) {
result.push_back(gift.stars); result.push_back(gift.info.stars);
} }
} }
if (same) { if (same) {
@ -838,16 +855,38 @@ void SendGift(
const auto processNonPanelPaymentFormFactory const auto processNonPanelPaymentFormFactory
= Payments::ProcessNonPanelPaymentFormFactory(window, done); = Payments::ProcessNonPanelPaymentFormFactory(window, done);
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
.giftId = gift.id, .giftId = gift.info.id,
.randomId = details.randomId, .randomId = details.randomId,
.message = details.text, .message = details.text,
.user = peer->asUser(), .user = peer->asUser(),
.limitedCount = gift.limitedCount, .limitedCount = gift.info.limitedCount,
.anonymous = details.anonymous, .anonymous = details.anonymous,
}, done, processNonPanelPaymentFormFactory); }, done, processNonPanelPaymentFormFactory);
}); });
} }
void SoldOutBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window,
const GiftTypeStars &gift) {
Settings::ReceiptCreditsBox(
box,
window,
Data::CreditsHistoryEntry{
.firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate),
.lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate),
.credits = uint64(gift.info.stars),
.bareGiftStickerId = gift.info.document->id,
.peerType = Data::CreditsHistoryEntry::PeerType::Peer,
.limitedCount = gift.info.limitedCount,
.limitedLeft = gift.info.limitedLeft,
.soldOutInfo = true,
.gift = true,
},
Data::SubscriptionEntry());
}
void SendGiftBox( void SendGiftBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
@ -873,7 +912,7 @@ void SendGiftBox(
}; };
}, [&](const GiftTypeStars &data) { }, [&](const GiftTypeStars &data) {
return Ui::CreditsEmojiSmall(session).append( return Ui::CreditsEmojiSmall(session).append(
Lang::FormatCountDecimal(std::abs(data.stars))); Lang::FormatCountDecimal(std::abs(data.info.stars)));
}); });
}()); }());
@ -1046,10 +1085,23 @@ void SendGiftBox(
const auto padding = st::giftBoxPadding; const auto padding = st::giftBoxPadding;
const auto available = width - padding.left() - padding.right(); const auto available = width - padding.left() - padding.right();
const auto perRow = available / single.width(); const auto perRow = available / single.width();
const auto count = int(gifts.list.size());
auto order = ranges::views::ints
| ranges::views::take(count)
| ranges::to_vector;
if (SortForBirthday(peer)) {
ranges::stable_partition(order, [&](int i) {
const auto &gift = gifts.list[i];
const auto stars = std::get_if<GiftTypeStars>(&gift);
return stars && stars->info.birthday;
});
}
auto x = padding.left(); auto x = padding.left();
auto y = padding.top(); auto y = padding.top();
state->buttons.resize(gifts.list.size()); state->buttons.resize(count);
for (auto &button : state->buttons) { for (auto &button : state->buttons) {
if (!button) { if (!button) {
button = std::make_unique<GiftButton>(raw, &state->delegate); button = std::make_unique<GiftButton>(raw, &state->delegate);
@ -1057,9 +1109,9 @@ void SendGiftBox(
} }
} }
const auto api = gifts.api; const auto api = gifts.api;
for (auto i = 0, count = int(gifts.list.size()); i != count; ++i) { for (auto i = 0; i != count; ++i) {
const auto button = state->buttons[i].get(); const auto button = state->buttons[i].get();
const auto &descriptor = gifts.list[i]; const auto &descriptor = gifts.list[order[i]];
button->setDescriptor(descriptor); button->setDescriptor(descriptor);
const auto last = !((i + 1) % perRow); const auto last = !((i + 1) % perRow);
@ -1076,27 +1128,22 @@ void SendGiftBox(
button->setClickedCallback([=] { button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor); const auto star = std::get_if<GiftTypeStars>(&descriptor);
if (star && star->limitedCount && !star->limitedLeft) { if (star
window->showToast({ && star->info.limitedCount
.title = tr::lng_gift_sold_out_title(tr::now), && !star->info.limitedLeft) {
.text = tr::lng_gift_sold_out_text( window->show(Box(SoldOutBox, window, *star));
tr::now,
lt_count_decimal,
star->limitedCount,
Ui::Text::RichLangValue),
});
} else { } else {
window->show( window->show(
Box(SendGiftBox, window, peer, api, descriptor)); Box(SendGiftBox, window, peer, api, descriptor));
} }
}); });
} }
if (gifts.list.size() % perRow) { if (count % perRow) {
y += padding.bottom() + single.height(); y += padding.bottom() + single.height();
} else { } else {
y += padding.bottom() - st::giftBoxGiftSkip.y(); y += padding.bottom() - st::giftBoxGiftSkip.y();
} }
raw->resize(raw->width(), gifts.list.empty() ? 0 : y); raw->resize(raw->width(), count ? y : 0);
}, raw->lifetime()); }, raw->lifetime());
return result; return result;
@ -1187,8 +1234,8 @@ void AddBlock(
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) { ) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) { gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
return (price == kPriceTabLimited) return (price == kPriceTabLimited)
? (!gift.limitedCount) ? (!gift.info.limitedCount)
: (price && gift.stars != price); : (price && gift.info.stars != price);
}), end(gifts)); }), end(gifts));
return GiftsDescriptor{ return GiftsDescriptor{
gifts | ranges::to<std::vector<GiftDescriptor>>(), gifts | ranges::to<std::vector<GiftDescriptor>>(),

View file

@ -409,9 +409,7 @@ callRatingStar: IconButton {
icon: icon {{ "calls/call_rating", windowSubTextFg }}; icon: icon {{ "calls/call_rating", windowSubTextFg }};
iconPosition: point(-1px, -1px); iconPosition: point(-1px, -1px);
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
rippleAreaPosition: point(0px, 0px); rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 36px; rippleAreaSize: 36px;
} }
@ -1410,9 +1408,7 @@ groupCallRtmpShowButton: IconButton(defaultIconButton) {
rippleAreaPosition: point(0px, 0px); rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 32px; rippleAreaSize: 32px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
groupCallSettingsRtmpShowButton: IconButton(groupCallRtmpShowButton) { groupCallSettingsRtmpShowButton: IconButton(groupCallRtmpShowButton) {
ripple: groupCallRipple; ripple: groupCallRipple;

View file

@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "calls/group/calls_group_common.h" #include "calls/group/calls_group_common.h"
#include "data/data_peer.h" #include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -169,7 +171,12 @@ void StartRtmpProcess::finish(JoinInfo info) {
void StartRtmpProcess::createBox() { void StartRtmpProcess::createBox() {
auto done = [=] { auto done = [=] {
const auto peer = _request->peer; const auto peer = _request->peer;
finish({ .peer = peer, .joinAs = peer, .rtmp = true }); const auto joinAs = (peer->isChat() && peer->asChat()->amCreator())
? peer
: (peer->isChannel() && peer->asChannel()->amCreator())
? peer
: peer->session().user();
finish({ .peer = peer, .joinAs = joinAs, .rtmp = true });
}; };
auto revoke = [=] { auto revoke = [=] {
const auto guard = base::make_weak(&_request->guard); const auto guard = base::make_weak(&_request->guard);

View file

@ -73,8 +73,8 @@ private:
SourceButton _widget; SourceButton _widget;
FlatLabel _label; FlatLabel _label;
RoundRect _selectedRect; Ui::RoundRect _selectedRect;
RoundRect _activeRect; Ui::RoundRect _activeRect;
tgcalls::DesktopCaptureSource _source; tgcalls::DesktopCaptureSource _source;
std::unique_ptr<Preview> _preview; std::unique_ptr<Preview> _preview;
rpl::event_stream<> _activations; rpl::event_stream<> _activations;

View file

@ -150,6 +150,8 @@ SendButton {
inner: IconButton; inner: IconButton;
record: icon; record: icon;
recordOver: icon; recordOver: icon;
round: icon;
roundOver: icon;
sendDisabledFg: color; sendDisabledFg: color;
} }
@ -331,9 +333,7 @@ stickersRemove: IconButton(defaultIconButton) {
rippleAreaSize: 40px; rippleAreaSize: 40px;
rippleAreaPosition: point(0px, 0px); rippleAreaPosition: point(0px, 0px);
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
stickersUndoRemove: RoundButton(defaultLightButton) { stickersUndoRemove: RoundButton(defaultLightButton) {
width: -16px; width: -16px;
@ -494,9 +494,7 @@ hashtagClose: IconButton {
rippleAreaPosition: point(5px, 5px); rippleAreaPosition: point(5px, 5px);
rippleAreaSize: 20px; rippleAreaSize: 20px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
stickerPanWidthMin: 64px; stickerPanWidthMin: 64px;
@ -898,9 +896,7 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
iconPosition: point(-1px, -1px); iconPosition: point(-1px, -1px);
rippleAreaSize: 40px; rippleAreaSize: 40px;
rippleAreaPosition: point(4px, 9px); rippleAreaPosition: point(4px, 9px);
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
height: 58px; height: 58px;
width: 48px; width: 48px;
} }
@ -927,9 +923,7 @@ historyReplyCancel: IconButton {
rippleAreaPosition: point(4px, 4px); rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 40px; rippleAreaSize: 40px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
historyPinnedShowAll: IconButton(historyReplyCancel) { historyPinnedShowAll: IconButton(historyReplyCancel) {
icon: icon {{ "pinned_show_all", historyReplyCancelFg }}; icon: icon {{ "pinned_show_all", historyReplyCancelFg }};
@ -1058,9 +1052,7 @@ historyAttach: IconButton(defaultIconButton) {
rippleAreaPosition: point(2px, 3px); rippleAreaPosition: point(2px, 3px);
rippleAreaSize: 40px; rippleAreaSize: 40px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
historyMessagesTTL: IconButtonWithText { historyMessagesTTL: IconButtonWithText {
@ -1082,6 +1074,13 @@ historyReplaceMedia: IconButton(historyAttach) {
color: lightButtonBgOver; color: lightButtonBgOver;
} }
} }
historyAddMedia: IconButton(historyAttach) {
icon: icon {{ "chat/input_attach", windowBgActive }};
iconOver: icon {{ "chat/input_attach", windowBgActive }};
ripple: RippleAnimation(defaultRippleAnimation) {
color: lightButtonBgOver;
}
}
historyAttachEmojiActive: icon {{ "chat/input_smile_face", windowBgActive }}; historyAttachEmojiActive: icon {{ "chat/input_smile_face", windowBgActive }};
historyEmojiCircle: size(20px, 20px); historyEmojiCircle: size(20px, 20px);
@ -1169,6 +1168,10 @@ historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }}; historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }}; historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }}; historyRecordVoiceActive: icon {{ "chat/input_record_filled", historyRecordVoiceFgActiveIcon }};
historyRecordRound: icon {{ "chat/input_video", historyRecordVoiceFg }};
historyRecordRoundOver: icon {{ "chat/input_video", historyRecordVoiceFgOver }};
historyRecordRoundActive: icon {{ "chat/input_video", historyRecordVoiceFgActiveIcon }};
historyRecordRoundIconPosition: point(0px, 0px);
historyRecordSendIconPosition: point(2px, 0px); historyRecordSendIconPosition: point(2px, 0px);
historyRecordVoiceRippleBgActive: lightButtonBgOver; historyRecordVoiceRippleBgActive: lightButtonBgOver;
historyRecordSignalRadius: 5px; historyRecordSignalRadius: 5px;
@ -1214,6 +1217,7 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }}; historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
historyRecordLockRound: icon {{ "voice_lock/input_round_s", historyToDownFg }};
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px); historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
historyRecordDelete: IconButton(historyAttach) { historyRecordDelete: IconButton(historyAttach) {
@ -1274,6 +1278,8 @@ historySend: SendButton {
} }
record: historyRecordVoice; record: historyRecordVoice;
recordOver: historyRecordVoiceOver; recordOver: historyRecordVoiceOver;
round: historyRecordRound;
roundOver: historyRecordRoundOver;
sendDisabledFg: historyComposeIconFg; sendDisabledFg: historyComposeIconFg;
} }
@ -1287,9 +1293,7 @@ defaultComposeFilesMenu: IconButton(defaultIconButton) {
rippleAreaPosition: point(1px, 6px); rippleAreaPosition: point(1px, 6px);
rippleAreaSize: 42px; rippleAreaSize: 42px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
defaultComposeFilesField: InputField(defaultInputField) { defaultComposeFilesField: InputField(defaultInputField) {
textMargins: margins(1px, 26px, 31px, 4px); textMargins: margins(1px, 26px, 31px, 4px);
@ -1349,9 +1353,7 @@ moreChatsBarClose: IconButton(defaultIconButton) {
rippleAreaPosition: point(0px, 4px); rippleAreaPosition: point(0px, 4px);
rippleAreaSize: 40px; rippleAreaSize: 40px;
ripple: RippleAnimation(defaultRippleAnimation) { ripple: defaultRippleAnimationBgOver;
color: windowBgOver;
}
} }
reportReasonTopSkip: 8px; reportReasonTopSkip: 8px;
@ -1517,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
sendGifBox: Box(defaultBox) { sendGifBox: Box(defaultBox) {
shadowIgnoreBottomSkip: true; shadowIgnoreBottomSkip: true;
} }
processingVideoTipMaxWidth: 364px;
processingVideoTipShift: 8px;
processingVideoToast: Toast(defaultToast) {
minWidth: 32px;
maxWidth: 380px;
padding: margins(19px, 17px, 19px, 17px);
}
processingVideoPreviewSkip: 8px;
processingVideoView: RoundButton(defaultActiveButton) {
width: -24px;
height: 52px;
textTop: 17px;
textFg: mediaviewTextLinkFg;
textFgOver: mediaviewTextLinkFg;
textBg: transparent;
textBgOver: transparent;
ripple: emptyRippleAnimation;
}

View file

@ -822,7 +822,7 @@ void StickersListFooter::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) { if (e->button() != Qt::LeftButton) {
return; return;
} }
_iconsMousePos = e ? e->globalPos() : QCursor::pos(); _iconsMousePos = e->globalPos();
updateSelected(); updateSelected();
if (_selected == SpecialOver::Settings) { if (_selected == SpecialOver::Settings) {

View file

@ -192,9 +192,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
}; };
const auto session = thumb const auto session = thumb
? &thumb->owner()->session() ? &thumb->owner()->session()
: media : &media->owner()->session();
? &media->owner()->session()
: nullptr;
return LottieCachedFromContent( return LottieCachedFromContent(
method, method,
baseKey, baseKey,

View file

@ -35,6 +35,10 @@ base::options::toggle TabbedPanelShowOnClick({
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click"; const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
bool ShowPanelOnClick() {
return TabbedPanelShowOnClick.value();
}
TabbedPanel::TabbedPanel( TabbedPanel::TabbedPanel(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,

View file

@ -25,6 +25,7 @@ namespace ChatHelpers {
class TabbedSelector; class TabbedSelector;
extern const char kOptionTabbedPanelShowOnClick[]; extern const char kOptionTabbedPanelShowOnClick[];
[[nodiscard]] bool ShowPanelOnClick();
struct TabbedPanelDescriptor { struct TabbedPanelDescriptor {
Window::SessionController *regularWindow = nullptr; Window::SessionController *regularWindow = nullptr;

View file

@ -189,6 +189,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
const auto game = media ? media->game() : nullptr; const auto game = media ? media->game() : nullptr;
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) { if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
openLink(); openLink();
return;
} }
const auto bot = _bot; const auto bot = _bot;
const auto title = game->title; const auto title = game->title;

View file

@ -16,6 +16,7 @@ constexpr auto kDocumentLinkMediaProperty = 0x03;
constexpr auto kSendReactionEmojiProperty = 0x04; constexpr auto kSendReactionEmojiProperty = 0x04;
constexpr auto kReactionsCountEmojiProperty = 0x05; constexpr auto kReactionsCountEmojiProperty = 0x05;
constexpr auto kDocumentFilenameTooltipProperty = 0x06; constexpr auto kDocumentFilenameTooltipProperty = 0x06;
constexpr auto kPhoneNumberLinkProperty = 0x07;
namespace Ui { namespace Ui {
class Show; class Show;

View file

@ -27,6 +27,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Core { namespace Core {
namespace { namespace {
constexpr auto kInitialVideoQuality = 480; // Start with SD.
[[nodiscard]] WindowPosition Deserialize(const QByteArray &data) { [[nodiscard]] WindowPosition Deserialize(const QByteArray &data) {
QDataStream stream(data); QDataStream stream(data);
stream.setVersion(QDataStream::Qt_5_1); stream.setVersion(QDataStream::Qt_5_1);
@ -92,6 +94,21 @@ void LogPosition(const WindowPosition &position, const QString &name) {
return RecentEmojiDocument{ id, (test == '1') }; return RecentEmojiDocument{ id, (test == '1') };
} }
[[nodiscard]] quint32 SerializeVideoQuality(Media::VideoQuality quality) {
static_assert(sizeof(Media::VideoQuality) == sizeof(uint32));
auto result = uint32();
const auto data = static_cast<const void*>(&quality);
memcpy(&result, data, sizeof(quality));
return result;
}
[[nodiscard]] Media::VideoQuality DeserializeVideoQuality(quint32 value) {
auto result = Media::VideoQuality();
const auto data = static_cast<void*>(&result);
memcpy(data, &value, sizeof(result));
return (result.height <= 4320) ? result : Media::VideoQuality();
}
} // namespace } // namespace
[[nodiscard]] WindowPosition AdjustToScale( [[nodiscard]] WindowPosition AdjustToScale(
@ -128,7 +145,8 @@ Settings::Settings()
, _floatPlayerColumn(Window::Column::Second) , _floatPlayerColumn(Window::Column::Second)
, _floatPlayerCorner(RectPart::TopRight) , _floatPlayerCorner(RectPart::TopRight)
, _dialogsWithChatWidthRatio(DefaultDialogsWidthRatio()) , _dialogsWithChatWidthRatio(DefaultDialogsWidthRatio())
, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio()) { , _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio())
, _videoQuality({ .height = kInitialVideoQuality }) {
} }
Settings::~Settings() = default; Settings::~Settings() = default;
@ -226,7 +244,7 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(_customFontFamily) + Serialize::stringSize(_customFontFamily)
+ sizeof(qint32) * 3 + sizeof(qint32) * 3
+ Serialize::bytearraySize(_tonsiteStorageToken) + Serialize::bytearraySize(_tonsiteStorageToken)
+ sizeof(qint32) * 2; + sizeof(qint32) * 7;
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -295,7 +313,7 @@ QByteArray Settings::serialize() const {
<< qint32(_thirdSectionExtendedBy) << qint32(_thirdSectionExtendedBy)
<< qint32(_notifyFromAll ? 1 : 0) << qint32(_notifyFromAll ? 1 : 0)
<< qint32(_nativeWindowFrame.current() ? 1 : 0) << qint32(_nativeWindowFrame.current() ? 1 : 0)
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0) << qint32(0) // Legacy system dark mode
<< _cameraDeviceId.current() << _cameraDeviceId.current()
<< qint32(_ipRevealWarning ? 1 : 0) << qint32(_ipRevealWarning ? 1 : 0)
<< qint32(_groupCallPushToTalk ? 1 : 0) << qint32(_groupCallPushToTalk ? 1 : 0)
@ -382,7 +400,12 @@ QByteArray Settings::serialize() const {
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2) << qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
<< _tonsiteStorageToken << _tonsiteStorageToken
<< qint32(_includeMutedCounterFolders ? 1 : 0) << qint32(_includeMutedCounterFolders ? 1 : 0)
<< qint32(_ivZoom.current()); << qint32(_chatFiltersHorizontal.current() ? 1 : 0)
<< qint32(_skipToastsInFocus ? 1 : 0)
<< qint32(_recordVideoMessages ? 1 : 0)
<< SerializeVideoQuality(_videoQuality)
<< qint32(_ivZoom.current())
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0);
} }
Ensures(result.size() == size); Ensures(result.size() == size);
@ -509,6 +532,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2; qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
QByteArray tonsiteStorageToken = _tonsiteStorageToken; QByteArray tonsiteStorageToken = _tonsiteStorageToken;
qint32 ivZoom = _ivZoom.current(); qint32 ivZoom = _ivZoom.current();
qint32 skipToastsInFocus = _skipToastsInFocus ? 1 : 0;
qint32 recordVideoMessages = _recordVideoMessages ? 1 : 0;
quint32 videoQuality = SerializeVideoQuality(_videoQuality);
quint32 chatFiltersHorizontal = _chatFiltersHorizontal.current() ? 1 : 0;
stream >> themesAccentColors; stream >> themesAccentColors;
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -590,6 +617,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
stream >> nativeWindowFrame; stream >> nativeWindowFrame;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
// Read over this one below, if was in the file.
stream >> systemDarkModeEnabled; stream >> systemDarkModeEnabled;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -818,9 +846,24 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> includeMutedCounterFolders; stream >> includeMutedCounterFolders;
} }
if (!stream.atEnd()) {
stream >> chatFiltersHorizontal;
}
if (!stream.atEnd()) {
stream >> skipToastsInFocus;
}
if (!stream.atEnd()) {
stream >> recordVideoMessages;
}
if (!stream.atEnd()) {
stream >> videoQuality;
}
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> ivZoom; stream >> ivZoom;
} }
if (!stream.atEnd()) {
stream >> systemDarkModeEnabled;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -1034,6 +1077,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
: (weatherInCelsius == 1); : (weatherInCelsius == 1);
_tonsiteStorageToken = tonsiteStorageToken; _tonsiteStorageToken = tonsiteStorageToken;
_ivZoom = ivZoom; _ivZoom = ivZoom;
_skipToastsInFocus = (skipToastsInFocus == 1);
_recordVideoMessages = (recordVideoMessages == 1);
_videoQuality = DeserializeVideoQuality(videoQuality);
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
} }
QString Settings::getSoundPath(const QString &key) const { QString Settings::getSoundPath(const QString &key) const {
@ -1360,6 +1407,7 @@ void Settings::resetOnLastLogout() {
_flashBounceNotify = true; _flashBounceNotify = true;
_notifyView = NotifyView::ShowPreview; _notifyView = NotifyView::ShowPreview;
//_nativeNotifications = std::nullopt; //_nativeNotifications = std::nullopt;
//_skipToastsInFocus = false;
//_notificationsCount = 3; //_notificationsCount = 3;
//_notificationsCorner = ScreenCorner::BottomRight; //_notificationsCorner = ScreenCorner::BottomRight;
_includeMutedCounter = true; _includeMutedCounter = true;
@ -1417,11 +1465,13 @@ void Settings::resetOnLastLogout() {
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w _thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
_notifyFromAll = true; _notifyFromAll = true;
_tabbedReplacedWithInfo = false; // per-window _tabbedReplacedWithInfo = false; // per-window
_systemDarkModeEnabled = false;
_hiddenGroupCallTooltips = 0; _hiddenGroupCallTooltips = 0;
_storiesClickTooltipHidden = false; _storiesClickTooltipHidden = false;
_ttlVoiceClickTooltipHidden = false; _ttlVoiceClickTooltipHidden = false;
_ivZoom = 100; _ivZoom = 100;
_recordVideoMessages = false;
_videoQuality = {};
_chatFiltersHorizontal = false;
_recentEmojiPreload.clear(); _recentEmojiPreload.clear();
_recentEmoji.clear(); _recentEmoji.clear();
@ -1479,6 +1529,14 @@ void Settings::setNativeNotifications(bool value) {
: std::make_optional(value); : std::make_optional(value);
} }
bool Settings::skipToastsInFocus() const {
return _skipToastsInFocus;
}
void Settings::setSkipToastsInFocus(bool value) {
_skipToastsInFocus = value;
}
void Settings::setTranslateButtonEnabled(bool value) { void Settings::setTranslateButtonEnabled(bool value) {
_translateButtonEnabled = value; _translateButtonEnabled = value;
} }
@ -1557,6 +1615,7 @@ auto Settings::skipTranslationLanguagesValue() const
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) { void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
_rememberedDeleteMessageOnlyForYou = value; _rememberedDeleteMessageOnlyForYou = value;
} }
bool Settings::rememberedDeleteMessageOnlyForYou() const { bool Settings::rememberedDeleteMessageOnlyForYou() const {
return _rememberedDeleteMessageOnlyForYou; return _rememberedDeleteMessageOnlyForYou;
} }
@ -1564,13 +1623,40 @@ bool Settings::rememberedDeleteMessageOnlyForYou() const {
int Settings::ivZoom() const { int Settings::ivZoom() const {
return _ivZoom.current(); return _ivZoom.current();
} }
rpl::producer<int> Settings::ivZoomValue() const { rpl::producer<int> Settings::ivZoomValue() const {
return _ivZoom.value(); return _ivZoom.value();
} }
void Settings::setIvZoom(int value) { void Settings::setIvZoom(int value) {
#ifdef Q_OS_WIN
constexpr auto kMin = 25;
constexpr auto kMax = 500;
#else
constexpr auto kMin = 30; constexpr auto kMin = 30;
constexpr auto kMax = 200; constexpr auto kMax = 200;
#endif
_ivZoom = std::clamp(value, kMin, kMax); _ivZoom = std::clamp(value, kMin, kMax);
} }
Media::VideoQuality Settings::videoQuality() const {
return _videoQuality;
}
void Settings::setVideoQuality(Media::VideoQuality value) {
_videoQuality = value;
}
bool Settings::chatFiltersHorizontal() const {
return _chatFiltersHorizontal.current();
}
rpl::producer<bool> Settings::chatFiltersHorizontalChanges() const {
return _chatFiltersHorizontal.changes();
}
void Settings::setChatFiltersHorizontal(bool value) {
_chatFiltersHorizontal = value;
}
} // namespace Core } // namespace Core

View file

@ -226,6 +226,9 @@ public:
[[nodiscard]] bool nativeNotifications() const; [[nodiscard]] bool nativeNotifications() const;
void setNativeNotifications(bool value); void setNativeNotifications(bool value);
[[nodiscard]] bool skipToastsInFocus() const;
void setSkipToastsInFocus(bool value);
[[nodiscard]] int notificationsCount() const { [[nodiscard]] int notificationsCount() const {
return _notificationsCount; return _notificationsCount;
} }
@ -631,6 +634,13 @@ public:
return _floatPlayerCorner; return _floatPlayerCorner;
} }
[[nodiscard]] bool recordVideoMessages() const {
return _recordVideoMessages;
}
void setRecordVideoMessages(bool value) {
_recordVideoMessages = value;
}
void updateDialogsWidthRatio(float64 ratio, bool nochat); void updateDialogsWidthRatio(float64 ratio, bool nochat);
[[nodiscard]] float64 dialogsWidthRatio(bool nochat) const; [[nodiscard]] float64 dialogsWidthRatio(bool nochat) const;
@ -925,6 +935,13 @@ public:
[[nodiscard]] rpl::producer<int> ivZoomValue() const; [[nodiscard]] rpl::producer<int> ivZoomValue() const;
void setIvZoom(int value); void setIvZoom(int value);
[[nodiscard]] bool chatFiltersHorizontal() const;
[[nodiscard]] rpl::producer<bool> chatFiltersHorizontalChanges() const;
void setChatFiltersHorizontal(bool value);
[[nodiscard]] Media::VideoQuality videoQuality() const;
void setVideoQuality(Media::VideoQuality quality);
[[nodiscard]] static bool ThirdColumnByDefault(); [[nodiscard]] static bool ThirdColumnByDefault();
[[nodiscard]] static float64 DefaultDialogsWidthRatio(); [[nodiscard]] static float64 DefaultDialogsWidthRatio();
@ -964,6 +981,7 @@ private:
bool _flashBounceNotify = true; bool _flashBounceNotify = true;
NotifyView _notifyView = NotifyView::ShowPreview; NotifyView _notifyView = NotifyView::ShowPreview;
std::optional<bool> _nativeNotifications; std::optional<bool> _nativeNotifications;
bool _skipToastsInFocus = false;
int _notificationsCount = 3; int _notificationsCount = 3;
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight; ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
bool _includeMutedCounter = true; bool _includeMutedCounter = true;
@ -1024,7 +1042,7 @@ private:
bool _notifyFromAll = true; bool _notifyFromAll = true;
rpl::variable<bool> _nativeWindowFrame = false; rpl::variable<bool> _nativeWindowFrame = false;
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt; rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
rpl::variable<bool> _systemDarkModeEnabled = false; rpl::variable<bool> _systemDarkModeEnabled = true;
rpl::variable<WindowTitleContent> _windowTitleContent; rpl::variable<WindowTitleContent> _windowTitleContent;
WindowPosition _windowPosition; // per-window WindowPosition _windowPosition; // per-window
bool _disableOpenGL = false; bool _disableOpenGL = false;
@ -1061,6 +1079,8 @@ private:
std::optional<bool> _weatherInCelsius; std::optional<bool> _weatherInCelsius;
QByteArray _tonsiteStorageToken; QByteArray _tonsiteStorageToken;
rpl::variable<int> _ivZoom = 100; rpl::variable<int> _ivZoom = 100;
Media::VideoQuality _videoQuality;
rpl::variable<bool> _chatFiltersHorizontal = false;
bool _tabbedReplacedWithInfo = false; // per-window bool _tabbedReplacedWithInfo = false; // per-window
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
@ -1071,6 +1091,8 @@ private:
bool _rememberedFlashBounceNotifyFromTray = false; bool _rememberedFlashBounceNotifyFromTray = false;
bool _dialogsWidthSetToZeroWithoutChat = false; bool _dialogsWidthSetToZeroWithoutChat = false;
bool _recordVideoMessages = false;
QByteArray _photoEditorBrush; QByteArray _photoEditorBrush;
}; };

View file

@ -179,6 +179,34 @@ auto PersonalChannelController::chosen() const
return _chosen.events(); return _chosen.events();
} }
Window::SessionController *ApplyAccountIndex(
not_null<Window::SessionController*> controller,
int accountIndex) {
if (accountIndex <= 0) {
return nullptr;
}
const auto list = Core::App().domain().orderedAccounts();
if (accountIndex > int(list.size())) {
return nullptr;
}
const auto account = list[accountIndex - 1];
if (account == &controller->session().account()) {
return controller;
} else if (const auto window = Core::App().windowFor({ account })) {
if (&window->account() != account) {
Core::App().domain().maybeActivate(account);
if (&window->account() != account) {
return nullptr;
}
}
const auto session = window->sessionController();
if (session) {
return session;
}
}
return nullptr;
}
void SavePersonalChannel( void SavePersonalChannel(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
ChannelData *channel) { ChannelData *channel) {
@ -475,6 +503,20 @@ bool ResolveUsernameOrPhone(
const auto params = url_parse_params( const auto params = url_parse_params(
match->captured(1), match->captured(1),
qthelp::UrlParamNameTransform::ToLower); qthelp::UrlParamNameTransform::ToLower);
if (params.contains(u"acc"_q)) {
const auto switched = ApplyAccountIndex(
controller,
params.value(u"acc"_q).toInt());
if (switched) {
controller = switched;
} else {
controller->showToast(u"Could not activate account %1."_q.arg(
params.value(u"acc"_q)));
return false;
}
}
const auto domainParam = params.value(u"domain"_q); const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q); const auto appnameParam = params.value(u"appname"_q);
const auto myContext = context.value<ClickHandlerContext>(); const auto myContext = context.value<ClickHandlerContext>();
@ -580,6 +622,7 @@ bool ResolveUsernameOrPhone(
.startAutoSubmit = myContext.botStartAutoSubmit, .startAutoSubmit = myContext.botStartAutoSubmit,
.botAppName = (appname.isEmpty() ? postParam : appname), .botAppName = (appname.isEmpty() ? postParam : appname),
.botAppForceConfirmation = myContext.mayShowConfirmation, .botAppForceConfirmation = myContext.mayShowConfirmation,
.botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
.attachBotUsername = params.value(u"attach"_q), .attachBotUsername = params.value(u"attach"_q),
.attachBotToggleCommand = (params.contains(u"startattach"_q) .attachBotToggleCommand = (params.contains(u"startattach"_q)
? params.value(u"startattach"_q) ? params.value(u"startattach"_q)
@ -969,6 +1012,17 @@ bool ShowStarsExamples(
return true; return true;
} }
bool ShowPopularAppsAbout(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
controller->show(Dialogs::PopularAppsAboutBox(controller));
return true;
}
void ExportTestChatTheme( void ExportTestChatTheme(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) { not_null<const Data::CloudTheme*> theme) {
@ -1443,6 +1497,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^stars_examples$"_q, u"^stars_examples$"_q,
ShowStarsExamples, ShowStarsExamples,
}, },
{
u"^about_popular_apps$"_q,
ShowPopularAppsAbout,
},
}; };
return Result; return Result;
} }

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "core/phone_click_handler.h" #include "core/phone_click_handler.h"
#include "boxes/add_contact_box.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -48,6 +49,9 @@ public:
void handleKeyPress(not_null<QKeyEvent*> e) override; void handleKeyPress(not_null<QKeyEvent*> e) override;
[[nodiscard]] QString firstName() const;
[[nodiscard]] QString lastName() const;
protected: protected:
QPoint prepareRippleStartPosition() const override; QPoint prepareRippleStartPosition() const override;
QImage prepareRippleMask() const override; QImage prepareRippleMask() const override;
@ -130,6 +134,18 @@ ResolvePhoneAction::ResolvePhoneAction(
prepare(); prepare();
} }
QString ResolvePhoneAction::firstName() const {
const auto peer = _peer.current();
const auto user = peer ? peer->asUser() : nullptr;
return user ? user->firstName : QString();
}
QString ResolvePhoneAction::lastName() const {
const auto peer = _peer.current();
const auto user = peer ? peer->asUser() : nullptr;
return user ? user->lastName : QString();
}
void ResolvePhoneAction::paint(Painter &p) { void ResolvePhoneAction::paint(Painter &p) {
const auto selected = isSelected() && _peer.current(); const auto selected = isSelected() && _peer.current();
const auto height = contentHeight(); const auto height = contentHeight();
@ -275,6 +291,7 @@ PhoneClickHandler::PhoneClickHandler(
QString text) QString text)
: _session(session) : _session(session)
, _text(text) { , _text(text) {
setProperty(kPhoneNumberLinkProperty, _text);
} }
void PhoneClickHandler::onClick(ClickContext context) const { void PhoneClickHandler::onClick(ClickContext context) const {
@ -314,14 +331,29 @@ void PhoneClickHandler::onClick(ClickContext context) const {
TextForMimeData::Simple(phone.trimmed())); TextForMimeData::Simple(phone.trimmed()));
}, &st::menuIconCopy); }, &st::menuIconCopy);
auto resolvePhoneAction = base::make_unique_q<ResolvePhoneAction>(
menu,
menu->st().menu,
phone,
controller);
if (Trim(phone) != Trim(controller->session().user()->phone())) {
menu->addAction(
tr::lng_info_add_as_contact(tr::now),
[=, raw = Ui::MakeWeak(resolvePhoneAction.get())] {
controller->show(
Box<AddContactBox>(
&controller->session(),
raw ? raw->firstName() : QString(),
raw ? raw->lastName() : QString(),
Trim(phone)));
},
&st::menuIconInvite);
}
menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator); menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator);
menu->addAction( menu->addAction(std::move(resolvePhoneAction));
base::make_unique_q<ResolvePhoneAction>(
menu,
menu->st().menu,
phone,
controller));
menu->popup(pos); menu->popup(pos);
} }

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 5006003; constexpr auto AppVersion = 5008002;
constexpr auto AppVersionStr = "5.6.3"; constexpr auto AppVersionStr = "5.8.2";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -74,6 +74,11 @@ uint64 Credits::balance(PeerId peerId) const {
return (it != _cachedPeerBalances.end()) ? it->second : 0; return (it != _cachedPeerBalances.end()) ? it->second : 0;
} }
uint64 Credits::balanceCurrency(PeerId peerId) const {
const auto it = _cachedPeerCurrencyBalances.find(peerId);
return (it != _cachedPeerCurrencyBalances.end()) ? it->second : 0;
}
rpl::producer<uint64> Credits::balanceValue() const { rpl::producer<uint64> Credits::balanceValue() const {
return _nonLockedBalance.value(); return _nonLockedBalance.value();
} }
@ -128,4 +133,8 @@ void Credits::apply(PeerId peerId, uint64 balance) {
_cachedPeerBalances[peerId] = balance; _cachedPeerBalances[peerId] = balance;
} }
void Credits::applyCurrency(PeerId peerId, uint64 balance) {
_cachedPeerCurrencyBalances[peerId] = balance;
}
} // namespace Data } // namespace Data

View file

@ -35,6 +35,9 @@ public:
[[nodiscard]] rpl::producer<float64> rateValue( [[nodiscard]] rpl::producer<float64> rateValue(
not_null<PeerData*> ownedBotOrChannel); not_null<PeerData*> ownedBotOrChannel);
void applyCurrency(PeerId peerId, uint64 balance);
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
void lock(int count); void lock(int count);
void unlock(int count); void unlock(int count);
void withdrawLocked(int count); void withdrawLocked(int count);
@ -50,6 +53,7 @@ private:
std::unique_ptr<Api::CreditsStatus> _loader; std::unique_ptr<Api::CreditsStatus> _loader;
base::flat_map<PeerId, uint64> _cachedPeerBalances; base::flat_map<PeerId, uint64> _cachedPeerBalances;
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
uint64 _balance = 0; uint64 _balance = 0;
uint64 _locked = 0; uint64 _locked = 0;

View file

@ -112,6 +112,7 @@ void RecentPeers::applyLocal(QByteArray serialized) {
).arg(streamAppVersion)); ).arg(streamAppVersion));
_list.reserve(count); _list.reserve(count);
for (auto i = 0; i != int(count); ++i) { for (auto i = 0; i != int(count); ++i) {
const auto streamPosition = stream.underlying().device()->pos();
const auto peer = Serialize::readPeer( const auto peer = Serialize::readPeer(
_session, _session,
streamAppVersion, streamAppVersion,
@ -123,7 +124,8 @@ void RecentPeers::applyLocal(QByteArray serialized) {
DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2." DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2."
).arg(i + 1 ).arg(i + 1
).arg(count)); ).arg(count));
_list.clear(); DEBUG_LOG(("Failed bytes: %1.").arg(
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
return; return;
} }
} }

View file

@ -343,10 +343,20 @@ void ScheduledMessages::apply(
if (i == end(_data)) { if (i == end(_data)) {
return; return;
} }
for (const auto &id : update.vmessages().v) { const auto sent = update.vsent_messages();
const auto &ids = update.vmessages().v;
for (auto k = 0, count = int(ids.size()); k != count; ++k) {
const auto id = ids[k].v;
const auto &list = i->second; const auto &list = i->second;
const auto j = list.itemById.find(id.v); const auto j = list.itemById.find(id);
if (j != end(list.itemById)) { if (j != end(list.itemById)) {
if (sent && k < sent->v.size()) {
const auto &sentId = sent->v[k];
_session->data().sentFromScheduled({
.item = j->second,
.sentId = sentId.v,
});
}
j->second->destroy(); j->second->destroy();
i = _data.find(history); i = _data.find(history);
if (i == end(_data)) { if (i == end(_data)) {

View file

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/chat/sponsored_message_bar.h"
#include "ui/text/text_utilities.h" // Ui::Text::RichLangValue. #include "ui/text/text_utilities.h" // Ui::Text::RichLangValue.
// AyuGram includes // AyuGram includes
@ -77,6 +78,9 @@ void SponsoredMessages::clearOldRequests() {
SponsoredMessages::AppendResult SponsoredMessages::append( SponsoredMessages::AppendResult SponsoredMessages::append(
not_null<History*> history) { not_null<History*> history) {
if (isTopBarFor(history)) {
return SponsoredMessages::AppendResult::None;
}
const auto it = _data.find(history); const auto it = _data.find(history);
if (it == end(_data)) { if (it == end(_data)) {
return SponsoredMessages::AppendResult::None; return SponsoredMessages::AppendResult::None;
@ -198,9 +202,31 @@ void SponsoredMessages::inject(
} }
bool SponsoredMessages::canHaveFor(not_null<History*> history) const { bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
// AyuGram disableAds
auto settings = &AyuSettings::getInstance(); auto settings = &AyuSettings::getInstance();
return !settings->disableAds && history->peer->isChannel(); if (settings->disableAds) {
return false;
}
if (history->peer->isChannel()) {
return true;
} else if (const auto user = history->peer->asUser()) {
return user->isBot();
}
return false;
}
bool SponsoredMessages::isTopBarFor(not_null<History*> history) const {
auto settings = &AyuSettings::getInstance();
if (settings->disableAds) {
return false;
}
if (peerIsUser(history->peer->id)) {
if (const auto user = history->peer->asUser()) {
return user->isBot();
}
}
return false;
} }
void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) { void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
@ -224,10 +250,8 @@ void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
} }
} }
} }
const auto channel = history->peer->asChannel();
Assert(channel != nullptr);
request.requestId = _session->api().request( request.requestId = _session->api().request(
MTPchannels_GetSponsoredMessages(channel->inputChannel) MTPmessages_GetSponsoredMessages(history->peer->input)
).done([=](const MTPmessages_sponsoredMessages &result) { ).done([=](const MTPmessages_sponsoredMessages &result) {
parse(history, result); parse(history, result);
if (done) { if (done) {
@ -263,12 +287,62 @@ void SponsoredMessages::parse(
list.postsBetween = postsBetween->v; list.postsBetween = postsBetween->v;
list.state = State::InjectToMiddle; list.state = State::InjectToMiddle;
} else { } else {
list.state = State::AppendToEnd; list.state = history->peer->isChannel()
? State::AppendToEnd
: State::AppendToTopBar;
} }
}, [](const MTPDmessages_sponsoredMessagesEmpty &) { }, [](const MTPDmessages_sponsoredMessagesEmpty &) {
}); });
} }
FullMsgId SponsoredMessages::fillTopBar(
not_null<History*> history,
not_null<Ui::RpWidget*> widget) {
const auto it = _data.find(history);
if (it != end(_data)) {
auto &list = it->second;
if (!list.entries.empty()) {
const auto &entry = list.entries.front();
const auto fullId = entry.itemFullId;
Ui::FillSponsoredMessageBar(
widget,
_session,
fullId,
entry.sponsored.from,
entry.sponsored.textWithEntities);
return fullId;
}
}
return {};
}
rpl::producer<> SponsoredMessages::itemRemoved(const FullMsgId &fullId) {
if (IsServerMsgId(fullId.msg) || !fullId) {
return rpl::never<>();
}
const auto history = _session->data().history(fullId.peer);
const auto it = _data.find(history);
if (it == end(_data)) {
return rpl::never<>();
}
auto &list = it->second;
const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
return e.itemFullId == fullId;
});
if (entryIt == end(list.entries)) {
return rpl::never<>();
}
if (!entryIt->optionalDestructionNotifier) {
entryIt->optionalDestructionNotifier
= std::make_unique<rpl::lifetime>();
entryIt->optionalDestructionNotifier->add([this, fullId] {
_itemRemoved.fire_copy(fullId);
});
}
return _itemRemoved.events(
) | rpl::filter(rpl::mappers::_1 == fullId) | rpl::to_empty;
}
void SponsoredMessages::append( void SponsoredMessages::append(
not_null<History*> history, not_null<History*> history,
List &list, List &list,
@ -289,7 +363,9 @@ void SponsoredMessages::append(
}, [&](const MTPDmessageMediaDocument &media) { }, [&](const MTPDmessageMediaDocument &media) {
if (const auto tlDocument = media.vdocument()) { if (const auto tlDocument = media.vdocument()) {
tlDocument->match([&](const MTPDdocument &data) { tlDocument->match([&](const MTPDdocument &data) {
const auto d = history->owner().processDocument(data); const auto d = history->owner().processDocument(
data,
media.valt_documents());
if (d->isVideoFile() if (d->isVideoFile()
|| d->isSilentVideo() || d->isSilentVideo()
|| d->isAnimation() || d->isAnimation()
@ -412,7 +488,7 @@ void SponsoredMessages::clearItems(not_null<History*> history) {
const SponsoredMessages::Entry *SponsoredMessages::find( const SponsoredMessages::Entry *SponsoredMessages::find(
const FullMsgId &fullId) const { const FullMsgId &fullId) const {
if (!peerIsChannel(fullId.peer)) { if (!peerIsChannel(fullId.peer) && !peerIsUser(fullId.peer)) {
return nullptr; return nullptr;
} }
const auto history = _session->data().history(fullId.peer); const auto history = _session->data().history(fullId.peer);
@ -440,11 +516,11 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
if (request.requestId || TooEarlyForRequest(request.lastReceived)) { if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
return; return;
} }
const auto channel = entryPtr->item->history()->peer->asChannel();
Assert(channel != nullptr);
request.requestId = _session->api().request( request.requestId = _session->api().request(
MTPchannels_ViewSponsoredMessage( MTPmessages_ViewSponsoredMessage(
channel->inputChannel, entryPtr->item
? entryPtr->item->history()->peer->input
: _session->data().peer(fullId.peer)->input,
MTP_bytes(randomId)) MTP_bytes(randomId))
).done([=] { ).done([=] {
auto &request = _viewRequests[randomId]; auto &request = _viewRequests[randomId];
@ -495,14 +571,14 @@ void SponsoredMessages::clicked(
return; return;
} }
const auto randomId = entryPtr->sponsored.randomId; const auto randomId = entryPtr->sponsored.randomId;
const auto channel = entryPtr->item->history()->peer->asChannel(); using Flag = MTPmessages_ClickSponsoredMessage::Flag;
Assert(channel != nullptr); _session->api().request(MTPmessages_ClickSponsoredMessage(
using Flag = MTPchannels_ClickSponsoredMessage::Flag;
_session->api().request(MTPchannels_ClickSponsoredMessage(
MTP_flags(Flag(0) MTP_flags(Flag(0)
| (isMedia ? Flag::f_media : Flag(0)) | (isMedia ? Flag::f_media : Flag(0))
| (isFullscreen ? Flag::f_fullscreen : Flag(0))), | (isFullscreen ? Flag::f_fullscreen : Flag(0))),
channel->inputChannel, entryPtr->item
? entryPtr->item->history()->peer->input
: _session->data().peer(fullId.peer)->input,
MTP_bytes(randomId) MTP_bytes(randomId)
)).send(); )).send();
} }
@ -531,11 +607,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
return; return;
} }
const auto history = entry->item->history(); const auto history = _session->data().history(fullId.peer);
const auto channel = history->peer->asChannel();
if (!channel) {
return;
}
const auto erase = [=] { const auto erase = [=] {
const auto it = _data.find(history); const auto it = _data.find(history);
@ -554,8 +626,8 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
} }
state->requestId = _session->api().request( state->requestId = _session->api().request(
MTPchannels_ReportSponsoredMessage( MTPmessages_ReportSponsoredMessage(
channel->inputChannel, history->peer->input,
MTP_bytes(entry->sponsored.randomId), MTP_bytes(entry->sponsored.randomId),
MTP_bytes(optionId)) MTP_bytes(optionId))
).done([=]( ).done([=](

View file

@ -18,6 +18,10 @@ namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Ui {
class RpWidget;
} // namespace Ui
namespace Data { namespace Data {
class MediaPreload; class MediaPreload;
@ -76,6 +80,7 @@ public:
None, None,
AppendToEnd, AppendToEnd,
InjectToMiddle, InjectToMiddle,
AppendToTopBar,
}; };
struct Details { struct Details {
std::vector<TextWithEntities> info; std::vector<TextWithEntities> info;
@ -94,10 +99,15 @@ public:
~SponsoredMessages(); ~SponsoredMessages();
[[nodiscard]] bool canHaveFor(not_null<History*> history) const; [[nodiscard]] bool canHaveFor(not_null<History*> history) const;
[[nodiscard]] bool isTopBarFor(not_null<History*> history) const;
void request(not_null<History*> history, Fn<void()> done); void request(not_null<History*> history, Fn<void()> done);
void clearItems(not_null<History*> history); void clearItems(not_null<History*> history);
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const; [[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen); void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
[[nodiscard]] FullMsgId fillTopBar(
not_null<History*> history,
not_null<Ui::RpWidget*> widget);
[[nodiscard]] rpl::producer<> itemRemoved(const FullMsgId &);
[[nodiscard]] AppendResult append(not_null<History*> history); [[nodiscard]] AppendResult append(not_null<History*> history);
void inject( void inject(
@ -122,6 +132,7 @@ private:
FullMsgId itemFullId; FullMsgId itemFullId;
SponsoredMessage sponsored; SponsoredMessage sponsored;
std::unique_ptr<MediaPreload> preload; std::unique_ptr<MediaPreload> preload;
std::unique_ptr<rpl::lifetime> optionalDestructionNotifier;
}; };
struct List { struct List {
std::vector<Entry> entries; std::vector<Entry> entries;
@ -156,6 +167,8 @@ private:
base::flat_map<not_null<History*>, Request> _requests; base::flat_map<not_null<History*>, Request> _requests;
base::flat_map<RandomId, Request> _viewRequests; base::flat_map<RandomId, Request> _viewRequests;
rpl::event_stream<FullMsgId> _itemRemoved;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -300,6 +300,7 @@ void TopPeers::applyLocal(QByteArray serialized) {
_list.reserve(count); _list.reserve(count);
for (auto i = 0; i != int(count); ++i) { for (auto i = 0; i != int(count); ++i) {
auto rating = quint64(); auto rating = quint64();
const auto streamPosition = stream.underlying().device()->pos();
const auto peer = Serialize::readPeer( const auto peer = Serialize::readPeer(
_session, _session,
streamAppVersion, streamAppVersion,
@ -313,6 +314,8 @@ void TopPeers::applyLocal(QByteArray serialized) {
} else { } else {
DEBUG_LOG(("Suggestions: " DEBUG_LOG(("Suggestions: "
"Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count)); "Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count));
DEBUG_LOG(("Failed bytes: %1.").arg(
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
_list.clear(); _list.clear();
return; return;
} }

View file

@ -46,12 +46,15 @@ struct CreditsHistoryEntry final {
Unsupported, Unsupported,
PremiumBot, PremiumBot,
Ads, Ads,
API,
}; };
QString id; QString id;
QString title; QString title;
TextWithEntities description; TextWithEntities description;
QDateTime date; QDateTime date;
QDateTime firstSaleDate;
QDateTime lastSaleDate;
PhotoId photoId = 0; PhotoId photoId = 0;
std::vector<CreditsHistoryMedia> extended; std::vector<CreditsHistoryMedia> extended;
uint64 credits = 0; uint64 credits = 0;
@ -59,23 +62,27 @@ struct CreditsHistoryEntry final {
uint64 barePeerId = 0; uint64 barePeerId = 0;
uint64 bareGiveawayMsgId = 0; uint64 bareGiveawayMsgId = 0;
uint64 bareGiftStickerId = 0; uint64 bareGiftStickerId = 0;
uint64 bareActorId = 0;
PeerType peerType; PeerType peerType;
QDateTime subscriptionUntil; QDateTime subscriptionUntil;
QDateTime successDate; QDateTime successDate;
QString successLink; QString successLink;
int limitedCount = 0; int limitedCount = 0;
int limitedLeft = 0; int limitedLeft = 0;
int convertStars = 0; int starsConverted = 0;
bool converted = false; int floodSkip = 0;
bool anonymous = false; bool converted : 1 = false;
bool savedToProfile = false; bool anonymous : 1 = false;
bool fromGiftsList = false; bool stargift : 1 = false;
bool reaction = false; bool savedToProfile : 1 = false;
bool refunded = false; bool fromGiftsList : 1 = false;
bool pending = false; bool soldOutInfo : 1 = false;
bool failed = false; bool reaction : 1 = false;
bool in = false; bool refunded : 1 = false;
bool gift = false; bool pending : 1 = false;
bool failed : 1 = false;
bool in : 1 = false;
bool gift : 1 = false;
}; };
struct CreditsStatusSlice final { struct CreditsStatusSlice final {

View file

@ -332,6 +332,8 @@ void DocumentData::setattributes(
validateLottieSticker(); validateLottieSticker();
auto wasVideoData = isVideoFile() ? std::move(_additional) : nullptr;
_videoPreloadPrefix = 0; _videoPreloadPrefix = 0;
for (const auto &attribute : attributes) { for (const auto &attribute : attributes) {
attribute.match([&](const MTPDdocumentAttributeImageSize &data) { attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
@ -388,11 +390,21 @@ void DocumentData::setattributes(
: VideoDocument; : VideoDocument;
if (data.is_round_message()) { if (data.is_round_message()) {
_additional = std::make_unique<RoundData>(); _additional = std::make_unique<RoundData>();
} else if (const auto size = data.vpreload_prefix_size()) { } else {
if (size->v > 0 && size->v < kMaxAllowedPreloadPrefix) { if (const auto size = data.vpreload_prefix_size()) {
_videoPreloadPrefix = size->v; if (size->v > 0
&& size->v < kMaxAllowedPreloadPrefix) {
_videoPreloadPrefix = size->v;
}
} }
_additional = wasVideoData
? std::move(wasVideoData)
: std::make_unique<VideoData>();
video()->codec = qs(
data.vvideo_codec().value_or_empty());
} }
} else if (type == VideoDocument && wasVideoData) {
_additional = std::move(wasVideoData);
} else if (const auto info = sticker()) { } else if (const auto info = sticker()) {
info->type = StickerType::Webm; info->type = StickerType::Webm;
} }
@ -511,6 +523,108 @@ void DocumentData::setattributes(
} }
} }
void DocumentData::setVideoQualities(const QVector<MTPDocument> &list) {
auto qualities = std::vector<not_null<DocumentData*>>();
qualities.reserve(list.size());
for (const auto &document : list) {
qualities.push_back(owner().processDocument(document));
}
setVideoQualities(std::move(qualities));
}
void DocumentData::setVideoQualities(
std::vector<not_null<DocumentData*>> qualities) {
const auto data = video();
if (!data) {
return;
}
auto count = int(qualities.size());
if (qualities.empty()) {
return;
}
const auto good = [&](not_null<DocumentData*> document) {
return document->isVideoFile()
&& !document->dimensions.isEmpty()
&& !document->inappPlaybackFailed()
&& document->useStreamingLoader()
&& document->canBeStreamed(nullptr);
};
ranges::sort(
qualities,
ranges::greater(),
&DocumentData::resolveVideoQuality);
for (auto i = 0; i != count - 1;) {
const auto my = qualities[i];
const auto next = qualities[i + 1];
const auto myQuality = my->resolveVideoQuality();
const auto nextQuality = next->resolveVideoQuality();
const auto myGood = good(my);
const auto nextGood = good(next);
if (!myGood || !nextGood || myQuality == nextQuality) {
const auto removeMe = !myGood
|| (nextGood && (my->size > next->size));
const auto from = i + (removeMe ? 1 : 2);
for (auto j = from; j != count; ++j) {
qualities[j - 1] = qualities[j];
}
--count;
} else {
++i;
}
}
if (!qualities[count - 1]->resolveVideoQuality()) {
--count;
}
qualities.erase(qualities.begin() + count, qualities.end());
if (!qualities.empty()) {
if (const auto mine = resolveVideoQuality()) {
if (mine > qualities.front()->resolveVideoQuality()) {
qualities.insert(begin(qualities), this);
}
}
}
data->qualities = std::move(qualities);
}
int DocumentData::resolveVideoQuality() const {
const auto size = isVideoFile() ? dimensions : QSize();
return size.isEmpty() ? 0 : std::min(size.width(), size.height());
}
auto DocumentData::resolveQualities(HistoryItem *context) const
-> const std::vector<not_null<DocumentData*>> & {
static const auto empty = std::vector<not_null<DocumentData*>>();
const auto info = video();
const auto media = context ? context->media() : nullptr;
if (!info || !media || media->document() != this) {
return empty;
}
return media->hasQualitiesList() ? info->qualities : empty;
}
not_null<DocumentData*> DocumentData::chooseQuality(
HistoryItem *context,
Media::VideoQuality request) {
const auto &list = resolveQualities(context);
if (list.empty() || !request.height) {
return this;
}
const auto height = int(request.height);
auto closest = this;
auto closestAbs = std::abs(height - resolveVideoQuality());
auto closestSize = size;
for (const auto &quality : list) {
const auto abs = std::abs(height - quality->resolveVideoQuality());
if (abs < closestAbs
|| (abs == closestAbs && quality->size < closestSize)) {
closest = quality;
closestAbs = abs;
closestSize = quality->size;
}
}
return closest;
}
void DocumentData::validateLottieSticker() { void DocumentData::validateLottieSticker() {
if (type == FileDocument if (type == FileDocument
&& hasMimeType(u"application/x-tgsticker"_q)) { && hasMimeType(u"application/x-tgsticker"_q)) {
@ -631,6 +745,14 @@ bool DocumentData::emojiUsesTextColor() const {
return (_flags & Flag::UseTextColor); return (_flags & Flag::UseTextColor);
} }
void DocumentData::overrideEmojiUsesTextColor(bool value) {
if (value) {
_flags |= Flag::UseTextColor;
} else {
_flags &= ~Flag::UseTextColor;
}
}
bool DocumentData::hasThumbnail() const { bool DocumentData::hasThumbnail() const {
return _thumbnail.location.valid() return _thumbnail.location.valid()
&& !thumbnailFailed() && !thumbnailFailed()
@ -1384,6 +1506,16 @@ const RoundData *DocumentData::round() const {
return const_cast<DocumentData*>(this)->round(); return const_cast<DocumentData*>(this)->round();
} }
VideoData *DocumentData::video() {
return isVideoFile()
? static_cast<VideoData*>(_additional.get())
: nullptr;
}
const VideoData *DocumentData::video() const {
return const_cast<DocumentData*>(this)->video();
}
bool DocumentData::hasRemoteLocation() const { bool DocumentData::hasRemoteLocation() const {
return (_dc != 0 && _access != 0); return (_dc != 0 && _access != 0);
} }

View file

@ -31,11 +31,13 @@ struct Key;
} // namespace Storage } // namespace Storage
namespace Media { namespace Media {
namespace Streaming { struct VideoQuality;
class Loader;
} // namespace Streaming
} // namespace Media } // namespace Media
namespace Media::Streaming {
class Loader;
} // namespace Media::Streaming
namespace Data { namespace Data {
class Session; class Session;
class DocumentMedia; class DocumentMedia;
@ -92,6 +94,11 @@ struct VoiceData : public DocumentAdditionalData {
char wavemax = 0; char wavemax = 0;
}; };
struct VideoData : public DocumentAdditionalData {
QString codec;
std::vector<not_null<DocumentData*>> qualities;
};
using RoundData = VoiceData; using RoundData = VoiceData;
namespace Serialize { namespace Serialize {
@ -108,8 +115,16 @@ public:
void setattributes( void setattributes(
const QVector<MTPDocumentAttribute> &attributes); const QVector<MTPDocumentAttribute> &attributes);
void setVideoQualities(const QVector<MTPDocument> &list);
void automaticLoadSettingsChanged(); void automaticLoadSettingsChanged();
void setVideoQualities(std::vector<not_null<DocumentData*>> qualities);
[[nodiscard]] int resolveVideoQuality() const;
[[nodiscard]] auto resolveQualities(HistoryItem *context) const
-> const std::vector<not_null<DocumentData*>> &;
[[nodiscard]] not_null<DocumentData*> chooseQuality(
HistoryItem *context,
Media::VideoQuality request);
[[nodiscard]] bool loading() const; [[nodiscard]] bool loading() const;
[[nodiscard]] QString loadingFilePath() const; [[nodiscard]] QString loadingFilePath() const;
@ -161,6 +176,8 @@ public:
[[nodiscard]] const VoiceData *voice() const; [[nodiscard]] const VoiceData *voice() const;
[[nodiscard]] RoundData *round(); [[nodiscard]] RoundData *round();
[[nodiscard]] const RoundData *round() const; [[nodiscard]] const RoundData *round() const;
[[nodiscard]] VideoData *video();
[[nodiscard]] const VideoData *video() const;
void forceIsStreamedAnimation(); void forceIsStreamedAnimation();
[[nodiscard]] bool isVoiceMessage() const; [[nodiscard]] bool isVoiceMessage() const;
@ -189,6 +206,7 @@ public:
[[nodiscard]] bool isPremiumSticker() const; [[nodiscard]] bool isPremiumSticker() const;
[[nodiscard]] bool isPremiumEmoji() const; [[nodiscard]] bool isPremiumEmoji() const;
[[nodiscard]] bool emojiUsesTextColor() const; [[nodiscard]] bool emojiUsesTextColor() const;
void overrideEmojiUsesTextColor(bool value);
[[nodiscard]] bool hasThumbnail() const; [[nodiscard]] bool hasThumbnail() const;
[[nodiscard]] bool thumbnailLoading() const; [[nodiscard]] bool thumbnailLoading() const;

View file

@ -87,6 +87,7 @@ struct FileReferenceAccumulator {
push(data.vphoto()); push(data.vphoto());
}, [&](const MTPDmessageMediaDocument &data) { }, [&](const MTPDmessageMediaDocument &data) {
push(data.vdocument()); push(data.vdocument());
push(data.valt_documents());
}, [&](const MTPDmessageMediaWebPage &data) { }, [&](const MTPDmessageMediaWebPage &data) {
push(data.vwebpage()); push(data.vwebpage());
}, [&](const MTPDmessageMediaGame &data) { }, [&](const MTPDmessageMediaGame &data) {

View file

@ -112,7 +112,9 @@ private:
static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip; static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip;
[[nodiscard]] bool valid() const { [[nodiscard]] bool valid() const {
return !_available || (_value >= kSpecialValueSkip); constexpr auto kMaxSum = uint32(std::numeric_limits<TimeId>::max());
return (kMaxSum - _value > uint32(kLifeStartDate))
&& (!_available || (_value >= kSpecialValueSkip));
} }
LastseenStatus(uint32 value, bool available, bool hiddenByMe) LastseenStatus(uint32 value, bool available, bool hiddenByMe)

View file

@ -551,6 +551,10 @@ DocumentData *Media::document() const {
return nullptr; return nullptr;
} }
bool Media::hasQualitiesList() const {
return false;
}
PhotoData *Media::photo() const { PhotoData *Media::photo() const {
return nullptr; return nullptr;
} }
@ -964,12 +968,14 @@ MediaFile::MediaFile(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, bool skipPremiumEffect,
bool hasQualitiesList,
bool spoiler, bool spoiler,
crl::time ttlSeconds) crl::time ttlSeconds)
: Media(parent) : Media(parent)
, _document(document) , _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString()) , _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect) , _skipPremiumEffect(skipPremiumEffect)
, _hasQualitiesList(hasQualitiesList)
, _spoiler(spoiler) , _spoiler(spoiler)
, _ttlSeconds(ttlSeconds) { , _ttlSeconds(ttlSeconds) {
parent->history()->owner().registerDocumentItem(_document, parent); parent->history()->owner().registerDocumentItem(_document, parent);
@ -999,6 +1005,7 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
parent, parent,
_document, _document,
!_document->session().premium(), !_document->session().premium(),
_hasQualitiesList,
_spoiler, _spoiler,
_ttlSeconds); _ttlSeconds);
} }
@ -1007,6 +1014,10 @@ DocumentData *MediaFile::document() const {
return _document; return _document;
} }
bool MediaFile::hasQualitiesList() const {
return _hasQualitiesList;
}
bool MediaFile::uploading() const { bool MediaFile::uploading() const {
return _document->uploading(); return _document->uploading();
} }

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