Merge tag 'v5.8.2' into dev
2
.gitmodules
vendored
|
@ -3,7 +3,7 @@
|
|||
url = https://github.com/telegramdesktop/libtgvoip
|
||||
[submodule "Telegram/ThirdParty/GSL"]
|
||||
path = Telegram/ThirdParty/GSL
|
||||
url = https://github.com/desktop-app/GSL.git
|
||||
url = https://github.com/Microsoft/GSL.git
|
||||
[submodule "Telegram/ThirdParty/xxHash"]
|
||||
path = Telegram/ThirdParty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
|
|
|
@ -192,6 +192,8 @@ PRIVATE
|
|||
api/api_bot.h
|
||||
api/api_chat_filters.cpp
|
||||
api/api_chat_filters.h
|
||||
api/api_chat_filters_remove_manager.cpp
|
||||
api/api_chat_filters_remove_manager.h
|
||||
api/api_chat_invite.cpp
|
||||
api/api_chat_invite.h
|
||||
api/api_chat_links.cpp
|
||||
|
@ -716,6 +718,8 @@ PRIVATE
|
|||
data/data_thread.h
|
||||
data/data_types.cpp
|
||||
data/data_types.h
|
||||
data/data_unread_value.cpp
|
||||
data/data_unread_value.h
|
||||
data/data_user.cpp
|
||||
data/data_user.h
|
||||
data/data_user_photos.cpp
|
||||
|
@ -1062,6 +1066,10 @@ PRIVATE
|
|||
info/profile/info_profile_values.h
|
||||
info/profile/info_profile_widget.cpp
|
||||
info/profile/info_profile_widget.h
|
||||
info/reactions_list/info_reactions_list_widget.cpp
|
||||
info/reactions_list/info_reactions_list_widget.h
|
||||
info/requests_list/info_requests_list_widget.cpp
|
||||
info/requests_list/info_requests_list_widget.h
|
||||
info/saved/info_saved_sublists_widget.cpp
|
||||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
|
@ -1075,6 +1083,7 @@ PRIVATE
|
|||
info/statistics/info_statistics_list_controllers.h
|
||||
info/statistics/info_statistics_recent_message.cpp
|
||||
info/statistics/info_statistics_recent_message.h
|
||||
info/statistics/info_statistics_tag.h
|
||||
info/statistics/info_statistics_widget.cpp
|
||||
info/statistics/info_statistics_widget.h
|
||||
info/stories/info_stories_inner_widget.cpp
|
||||
|
@ -1111,6 +1120,10 @@ PRIVATE
|
|||
info/info_wrap_widget.h
|
||||
inline_bots/bot_attach_web_view.cpp
|
||||
inline_bots/bot_attach_web_view.h
|
||||
inline_bots/inline_bot_confirm_prepared.cpp
|
||||
inline_bots/inline_bot_confirm_prepared.h
|
||||
inline_bots/inline_bot_downloads.cpp
|
||||
inline_bots/inline_bot_downloads.h
|
||||
inline_bots/inline_bot_layout_internal.cpp
|
||||
inline_bots/inline_bot_layout_internal.h
|
||||
inline_bots/inline_bot_layout_item.cpp
|
||||
|
@ -1242,6 +1255,8 @@ PRIVATE
|
|||
media/streaming/media_streaming_player.h
|
||||
media/streaming/media_streaming_reader.cpp
|
||||
media/streaming/media_streaming_reader.h
|
||||
media/streaming/media_streaming_round_preview.cpp
|
||||
media/streaming/media_streaming_round_preview.h
|
||||
media/streaming/media_streaming_utility.cpp
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
|
@ -1581,6 +1596,8 @@ PRIVATE
|
|||
ui/chat/choose_send_as.h
|
||||
ui/chat/choose_theme_controller.cpp
|
||||
ui/chat/choose_theme_controller.h
|
||||
ui/chat/sponsored_message_bar.cpp
|
||||
ui/chat/sponsored_message_bar.h
|
||||
ui/controls/emoji_button_factory.cpp
|
||||
ui/controls/emoji_button_factory.h
|
||||
ui/controls/location_picker.cpp
|
||||
|
@ -1612,6 +1629,8 @@ PRIVATE
|
|||
ui/widgets/expandable_peer_list.h
|
||||
ui/widgets/label_with_custom_emoji.cpp
|
||||
ui/widgets/label_with_custom_emoji.h
|
||||
ui/widgets/chat_filters_tabs_strip.cpp
|
||||
ui/widgets/chat_filters_tabs_strip.h
|
||||
ui/countryinput.cpp
|
||||
ui/countryinput.h
|
||||
ui/dynamic_thumbnails.cpp
|
||||
|
@ -1931,7 +1950,7 @@ endif()
|
|||
|
||||
set_target_properties(Telegram PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_folder})
|
||||
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Telegram
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
@ -2028,7 +2047,7 @@ if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE AND NOT build_macstore AND NOT build_wins
|
|||
base/platform/win/base_windows_safe_library.h
|
||||
)
|
||||
target_include_directories(Updater PRIVATE ${lib_base_loc})
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
if (MSVC)
|
||||
target_link_libraries(Updater
|
||||
PRIVATE
|
||||
delayimp
|
||||
|
|
BIN
Telegram/Resources/animations/hello_status.tgs
Normal file
BIN
Telegram/Resources/art/round_placeholder.jpg
Normal file
After Width: | Height: | Size: 787 B |
BIN
Telegram/Resources/icons/chat/input_video.png
Normal file
After Width: | Height: | Size: 621 B |
BIN
Telegram/Resources/icons/chat/input_video@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/Resources/icons/chat/input_video@3x.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
Telegram/Resources/icons/menu/edited_status.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
Telegram/Resources/icons/menu/edited_status@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/edited_status@3x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Telegram/Resources/icons/player/player_settings.png
Normal file
After Width: | Height: | Size: 997 B |
BIN
Telegram/Resources/icons/player/player_settings@2x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Telegram/Resources/icons/player/player_settings@3x.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
|
@ -72,6 +72,9 @@ var IV = {
|
|||
}
|
||||
},
|
||||
frameKeyDown: function (e) {
|
||||
const key0 = (e.key === '0')
|
||||
|| (e.code === 'Key0')
|
||||
|| (e.keyCode === 48);
|
||||
const keyW = (e.key === 'w')
|
||||
|| (e.code === 'KeyW')
|
||||
|| (e.keyCode === 87);
|
||||
|
@ -81,12 +84,12 @@ var IV = {
|
|||
const keyM = (e.key === 'm')
|
||||
|| (e.code === 'KeyM')
|
||||
|| (e.keyCode === 77);
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM)) {
|
||||
if ((e.metaKey || e.ctrlKey) && (keyW || keyQ || keyM || key0)) {
|
||||
e.preventDefault();
|
||||
IV.notify({
|
||||
event: 'keydown',
|
||||
modifier: e.ctrlKey ? 'ctrl' : 'cmd',
|
||||
key: keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
key: key0 ? '0' : keyW ? 'w' : keyQ ? 'q' : 'm',
|
||||
});
|
||||
} else if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -499,8 +499,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_notify_global" = "Global settings";
|
||||
"lng_settings_notify_title" = "Notifications for chats";
|
||||
"lng_settings_desktop_notify" = "Desktop notifications";
|
||||
"lng_settings_native_title" = "Native notifications";
|
||||
"lng_settings_native_title" = "System integration";
|
||||
"lng_settings_use_windows" = "Use Windows notifications";
|
||||
"lng_settings_skip_in_focus" = "Respect system Focus mode";
|
||||
"lng_settings_use_native_notifications" = "Use native notifications";
|
||||
"lng_settings_notifications_position" = "Location on the screen";
|
||||
"lng_settings_notifications_count" = "Notifications count";
|
||||
|
@ -681,6 +682,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_messages_privacy" = "Messages";
|
||||
"lng_settings_voices_privacy" = "Voice messages";
|
||||
"lng_settings_bio_privacy" = "Bio";
|
||||
"lng_settings_gifts_privacy" = "Gifts";
|
||||
"lng_settings_birthday_privacy" = "Date of Birth";
|
||||
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
|
||||
"lng_settings_privacy_premium_link" = "Telegram Premium";
|
||||
|
@ -1161,19 +1163,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_blocked_list_subtitle#other" = "{count} blocked users";
|
||||
|
||||
"lng_edit_privacy_everyone" = "Everybody";
|
||||
"lng_edit_privacy_no_miniapps" = "Not Mini Apps";
|
||||
"lng_edit_privacy_contacts" = "My contacts";
|
||||
"lng_edit_privacy_close_friends" = "Close friends";
|
||||
"lng_edit_privacy_contacts_and_premium" = "Contacts & Premium";
|
||||
"lng_edit_privacy_contacts_and_miniapps" = "Contacts & Mini Apps";
|
||||
"lng_edit_privacy_nobody" = "Nobody";
|
||||
"lng_edit_privacy_premium" = "Premium users";
|
||||
"lng_edit_privacy_miniapps" = "Mini Apps";
|
||||
"lng_edit_privacy_exceptions" = "Add exceptions";
|
||||
"lng_edit_privacy_user_types" = "User types";
|
||||
"lng_edit_privacy_users_and_groups" = "Users and groups";
|
||||
"lng_edit_privacy_premium_status" = "all Telegram Premium subscribers";
|
||||
"lng_edit_privacy_miniapps_status" = "web mini apps that you use";
|
||||
|
||||
"lng_edit_privacy_exceptions_count#one" = "{count} user";
|
||||
"lng_edit_privacy_exceptions_count#other" = "{count} users";
|
||||
"lng_edit_privacy_exceptions_premium_and" = "Premium & {users}";
|
||||
"lng_edit_privacy_exceptions_miniapps_and" = "Mini Apps & {users}";
|
||||
"lng_edit_privacy_exceptions_add" = "Add users";
|
||||
|
||||
"lng_edit_privacy_phone_number_title" = "Phone number privacy";
|
||||
|
@ -1227,6 +1234,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_edit_privacy_birthday_yet" = "You haven't entered your date of birth yet.\n{link}";
|
||||
"lng_edit_privacy_birthday_yet_link" = "Add my birthday >";
|
||||
|
||||
"lng_edit_privacy_gifts_title" = "Gifts";
|
||||
"lng_edit_privacy_gifts_header" = "Who can display gifts on my profile";
|
||||
"lng_edit_privacy_gifts_always_empty" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_empty" = "Never allow";
|
||||
"lng_edit_privacy_gifts_exceptions" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile.";
|
||||
"lng_edit_privacy_gifts_always_title" = "Always allow";
|
||||
"lng_edit_privacy_gifts_never_title" = "Never allow";
|
||||
|
||||
"lng_edit_privacy_calls_title" = "Calls";
|
||||
"lng_edit_privacy_calls_header" = "Who can call me";
|
||||
"lng_edit_privacy_calls_always_empty" = "Always allow";
|
||||
|
@ -1460,11 +1475,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_open_app" = "Open App";
|
||||
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
|
||||
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
|
||||
"lng_profile_bot_permissions_title" = "Allow access to";
|
||||
"lng_profile_bot_emoji_status_access" = "Emoji Status";
|
||||
"lng_info_add_as_contact" = "Add to contacts";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_suggest_photo" = "Suggest Profile Photo";
|
||||
"lng_profile_suggest_photo_from_clipboard" = "Suggest From Clipboard";
|
||||
"lng_profile_set_photo_for" = "Set Profile Photo";
|
||||
"lng_profile_set_photo_for_from_clipboard" = "Set From Clipboard";
|
||||
"lng_profile_photo_reset" = "Reset to Original";
|
||||
"lng_profile_photo_from_clipboard" = "From clipboard";
|
||||
"lng_profile_suggest_sure" = "You can suggest {user} to set this photo for their Telegram profile.";
|
||||
"lng_profile_suggest_button" = "Suggest";
|
||||
"lng_profile_set_personal_sure" = "Only you will see this photo and it will replace any photo {user} sets for themselves.";
|
||||
|
@ -1593,6 +1613,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_manage_peer_bot_public_link" = "Public Link";
|
||||
"lng_manage_peer_bot_public_links" = "Public Links";
|
||||
"lng_manage_peer_bot_balance" = "Balance";
|
||||
"lng_manage_peer_bot_balance_currency" = "Toncoin";
|
||||
"lng_manage_peer_bot_balance_credits" = "Stars";
|
||||
"lng_manage_peer_bot_edit_intro" = "Edit Intro";
|
||||
"lng_manage_peer_bot_edit_commands" = "Edit Commands";
|
||||
"lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
|
||||
|
@ -1870,6 +1892,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
|
@ -2097,6 +2121,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_channel_public_link_copied" = "Link copied to clipboard.";
|
||||
"lng_context_about_private_link" = "This link will only work for members of this chat.";
|
||||
"lng_public_post_private_hint_ctrl" = "Use Ctrl+Click to copy a non-public link.";
|
||||
"lng_public_post_private_hint_cmd" = "Use Cmd+Click to copy a non-public link.";
|
||||
|
||||
"lng_forwarded" = "Forwarded from {user}";
|
||||
"lng_forwarded_story" = "Story from {user}";
|
||||
|
@ -2115,8 +2141,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_recommended_message_title" = "Recommended";
|
||||
"lng_edited" = "edited";
|
||||
"lng_commented" = "commented";
|
||||
"lng_approximate" = "appx.";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_sent_date" = "Sent: {date}";
|
||||
"lng_approximate_about" = "Estimated date of video publishing.";
|
||||
"lng_views_tooltip#one" = "Views: {count}";
|
||||
"lng_views_tooltip#other" = "Views: {count}";
|
||||
"lng_forwards_tooltip#one" = "Shares: {count}";
|
||||
|
@ -2151,6 +2179,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_media_cancel" = "Cancel";
|
||||
"lng_media_video" = "Video";
|
||||
"lng_media_audio" = "Voice message";
|
||||
"lng_media_round" = "Video message";
|
||||
|
||||
"lng_media_auto_settings" = "Automatic media download";
|
||||
"lng_media_auto_in_private" = "In private chats";
|
||||
|
@ -2404,6 +2433,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_summary_history_tab_out" = "Outgoing";
|
||||
"lng_credits_summary_history_entry_inner_in" = "In-App Purchase";
|
||||
"lng_credits_summary_balance" = "Balance";
|
||||
"lng_credits_more_options" = "More Options";
|
||||
"lng_credits_balance_me" = "your balance";
|
||||
"lng_credits_buy_button" = "Buy More Stars";
|
||||
"lng_credits_gift_button" = "Gift Stars to Friends";
|
||||
"lng_credits_box_out_title" = "Confirm Your Purchase";
|
||||
"lng_credits_box_out_sure#one" = "Do you want to buy **\"{text}\"** in **{bot}** for **{count} Star**?";
|
||||
|
@ -2442,18 +2474,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_unavailable" = "Unavailable";
|
||||
"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
"lng_credits_box_history_entry_gift_examples" = "Examples";
|
||||
"lng_credits_box_history_entry_ads" = "Ads Platform";
|
||||
"lng_credits_box_history_entry_premium_bot" = "Stars Top-Up";
|
||||
"lng_credits_box_history_entry_api" = "Paid Broadcast";
|
||||
"lng_credits_box_history_entry_floodskip_about#one" = "{count} Message";
|
||||
"lng_credits_box_history_entry_floodskip_about#other" = "{count} Messages";
|
||||
"lng_credits_box_history_entry_floodskip_row" = "Messages";
|
||||
"lng_credits_box_history_entry_via_premium_bot" = "Premium Bot";
|
||||
"lng_credits_box_history_entry_id" = "Transaction ID";
|
||||
"lng_credits_box_history_entry_id_copied" = "Transaction ID copied to clipboard.";
|
||||
"lng_credits_box_history_entry_success_date" = "Transaction date";
|
||||
"lng_credits_box_history_entry_success_url" = "Transaction link";
|
||||
"lng_credits_box_history_entry_media" = "Media";
|
||||
"lng_credits_box_history_entry_message" = "Message";
|
||||
"lng_credits_box_history_entry_about" = "You can dispute this transaction {link}.";
|
||||
"lng_credits_box_history_entry_about_link" = "here";
|
||||
"lng_credits_box_history_entry_reaction_name" = "Star Reaction";
|
||||
|
@ -2988,6 +3027,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_link_reason_unclaimed" = "Incomplete Giveaway";
|
||||
"lng_gift_link_reason_chosen" = "You were selected by the channel";
|
||||
"lng_gift_link_label_date" = "Date";
|
||||
"lng_gift_link_label_first_sale" = "First Sale";
|
||||
"lng_gift_link_label_last_sale" = "Last Sale";
|
||||
"lng_gift_link_label_value" = "Value";
|
||||
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
|
||||
"lng_gift_link_also_send_link" = "send this link";
|
||||
"lng_gift_link_use" = "Use Link";
|
||||
|
@ -3044,8 +3086,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
|
||||
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone.";
|
||||
"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone.";
|
||||
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
|
||||
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
|
||||
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
|
@ -3054,6 +3099,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_sold_out_title" = "Sold Out!";
|
||||
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
|
||||
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
|
||||
"lng_gift_send_small" = "send a gift";
|
||||
"lng_gift_sell_small#one" = "sell for {count} Star";
|
||||
"lng_gift_sell_small#other" = "sell for {count} Stars";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
|
@ -3243,11 +3291,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_record_cancel_stories" = "Release outside to cancel";
|
||||
"lng_record_lock_cancel_sure" = "Do you want to stop recording and discard your voice message?";
|
||||
"lng_record_lock_cancel_sure_round" = "Do you want to stop recording and discard your video message?";
|
||||
"lng_record_listen_cancel_sure" = "Do you want to discard your recorded voice message?";
|
||||
"lng_record_listen_cancel_sure_round" = "Do you want to discard your recorded video message?";
|
||||
"lng_record_lock_discard" = "Discard";
|
||||
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
|
||||
"lng_record_voice_tip" = "Hold to record audio. Click to switch to video.";
|
||||
"lng_record_video_tip" = "Hold to record video. Click to switch to audio.";
|
||||
"lng_record_audio_problem" = "Could not start audio recording. Please check your microphone.";
|
||||
"lng_record_video_problem" = "Could not start video recording. Please check your camera.";
|
||||
"lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
|
||||
"lng_record_once_active_tooltip" = "The recipient will be able to listen only once.";
|
||||
"lng_record_once_active_video" = "The recipient will be able to watch only once.";
|
||||
"lng_will_be_notified" = "Subscribers will be notified when you post.";
|
||||
"lng_wont_be_notified" = "Subscribers will receive a silent notification.";
|
||||
"lng_willbe_history" = "Select a chat to start messaging";
|
||||
|
@ -3276,6 +3331,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_scheduled_send_now" = "Send message now?";
|
||||
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
|
||||
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
|
||||
"lng_scheduled_video_tip_title" = "Improving video...";
|
||||
"lng_scheduled_video_tip_text" = "The video will be published after it's optimized for the best viewing experience.";
|
||||
"lng_scheduled_video_tip" = "Processing video may take a few minutes.";
|
||||
"lng_scheduled_video_published" = "Video Published.";
|
||||
"lng_scheduled_video_view" = "View";
|
||||
|
||||
"lng_replies_view#one" = "View {count} Reply";
|
||||
"lng_replies_view#other" = "View {count} Replies";
|
||||
|
@ -3359,6 +3419,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_bot_settings" = "Settings";
|
||||
"lng_bot_open" = "Open Bot";
|
||||
"lng_bot_terms" = "Terms of Use";
|
||||
"lng_bot_privacy" = "Privacy Policy";
|
||||
"lng_bot_reload_page" = "Reload Page";
|
||||
"lng_bot_add_to_menu" = "{bot} asks your permission to be added as an option to your attachment menu so you can access it from any chat.";
|
||||
"lng_bot_add_to_menu_done" = "Bot added to the menu.";
|
||||
|
@ -3374,6 +3435,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_bot_add_to_side_menu_done" = "Bot added to the main menu.";
|
||||
"lng_bot_no_scan_qr" = "QR Codes for bots are not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_no_share_story" = "Sharing to Stories is not supported on Desktop. Please use one of Telegram's mobile apps.";
|
||||
"lng_bot_emoji_status_confirm" = "Confirm";
|
||||
"lng_bot_emoji_status_title" = "Set Emoji Status";
|
||||
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
|
||||
"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
|
||||
"lng_bot_emoji_status_access_allow" = "Allow";
|
||||
"lng_bot_share_prepared_title" = "Share Message";
|
||||
"lng_bot_share_prepared_about" = "{bot} mini app suggests you to send this message to a chat you select.";
|
||||
"lng_bot_share_prepared_button" = "Share With...";
|
||||
"lng_bot_download_file" = "Download File";
|
||||
"lng_bot_download_file_sure" = "{bot} suggests you download the following file:";
|
||||
"lng_bot_download_file_button" = "Download";
|
||||
"lng_bot_download_starting" = "Starting...";
|
||||
"lng_bot_download_failed" = "Failed. {retry}";
|
||||
"lng_bot_download_retry" = "Retry";
|
||||
|
||||
"lng_bot_status_users#one" = "{count} monthly user";
|
||||
"lng_bot_status_users#other" = "{count} monthly users";
|
||||
|
@ -3585,6 +3660,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
"lng_context_change_price" = "Change Price";
|
||||
|
||||
"lng_context_mention" = "Mention";
|
||||
"lng_context_search_from" = "Search messages";
|
||||
|
||||
"lng_factcheck_title" = "Fact Check";
|
||||
"lng_factcheck_placeholder" = "Add Facts or Context";
|
||||
"lng_factcheck_whats_this" = "what's this?";
|
||||
|
@ -3682,6 +3760,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_reply_in_another_title" = "Reply in...";
|
||||
"lng_reply_in_another_chat" = "Reply in Another Chat";
|
||||
"lng_reply_in_author" = "Message author";
|
||||
"lng_reply_in_chats_list" = "Your chats";
|
||||
"lng_reply_show_in_chat" = "Show in Chat";
|
||||
"lng_reply_remove" = "Do Not Reply";
|
||||
"lng_reply_about_quote" = "You can select a specific part to quote.";
|
||||
|
@ -3869,6 +3949,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_mediaview_downloads" = "Downloads";
|
||||
"lng_mediaview_playback_speed" = "Playback speed: {speed}";
|
||||
"lng_mediaview_rotate_video" = "Rotate video";
|
||||
"lng_mediaview_quality_auto" = "Auto";
|
||||
|
||||
"lng_theme_preview_title" = "Theme Preview";
|
||||
"lng_theme_preview_generating" = "Generating color theme preview...";
|
||||
|
@ -3950,7 +4031,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration.";
|
||||
"lng_payments_webview_install_edge" = "Please install {link}.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkitgtk-6.0/webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_install_webkit" = "Please install WebKitGTK (webkit2gtk-4.1/webkit2gtk-4.0) using your package manager.";
|
||||
"lng_payments_webview_enable_opengl" = "Please enable OpenGL in application settings.";
|
||||
"lng_payments_webview_switch_x11" = "Unsupported display server. Please switch to X11.";
|
||||
"lng_payments_webview_update_windows" = "Please update your system to Windows 8.1 or later.";
|
||||
"lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost.";
|
||||
"lng_payments_receipt_label" = "Receipt";
|
||||
|
@ -4310,7 +4393,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_rights_edit_admin_rank_about" = "A title that members will see instead of '{title}'.";
|
||||
"lng_rights_about_add_admins_yes" = "This admin will be able to add new admins with equal or fewer rights.";
|
||||
"lng_rights_about_add_admins_no" = "This admin will not be able to add new admins.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} at {date}.";
|
||||
"lng_rights_about_by" = "This admin promoted by {user} on {date}.";
|
||||
|
||||
"lng_rights_about_admin_cant_edit" = "You can't edit the rights of this admin.";
|
||||
"lng_rights_about_restriction_cant_edit" = "You cannot change the restrictions for this user.";
|
||||
|
@ -4389,8 +4472,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_rights_chat_files" = "Files";
|
||||
"lng_rights_chat_voice_messages" = "Voice messages";
|
||||
"lng_rights_chat_video_messages" = "Video messages";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} at {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} at {date}.";
|
||||
"lng_rights_chat_restricted_by" = "Restricted by {user} on {date}.";
|
||||
"lng_rights_chat_banned_by" = "Banned by {user} on {date}.";
|
||||
"lng_rights_chat_banned_until_header" = "Restricted until";
|
||||
"lng_rights_chat_banned_forever" = "Forever";
|
||||
"lng_rights_chat_banned_day#one" = "For {count} day";
|
||||
|
@ -4999,6 +5082,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_outdated_now" = "So Telegram Desktop can update to newer versions.";
|
||||
|
||||
"lng_filters_all" = "All chats";
|
||||
"lng_filters_all_short" = "All";
|
||||
"lng_filters_setup" = "Edit";
|
||||
"lng_filters_title" = "Folders";
|
||||
"lng_filters_subtitle" = "My folders";
|
||||
|
@ -5053,6 +5137,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_filters_toast_add" = "{chat} added to {folder} folder";
|
||||
"lng_filters_toast_remove" = "{chat} removed from {folder} folder";
|
||||
"lng_filters_shareable_status" = "shareable folder";
|
||||
"lng_filters_view_subtitle" = "Tabs view";
|
||||
"lng_filters_vertical" = "Tabs on the left";
|
||||
"lng_filters_horizontal" = "Tabs at the top";
|
||||
|
||||
"lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder.";
|
||||
"lng_filters_link" = "Share Folder";
|
||||
|
@ -5172,13 +5259,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_sponsored_revenued_subtitle" = "Telegram Ads are very different from ads on other platforms. Ads such as this one:";
|
||||
"lng_sponsored_revenued_info1_title" = "Respect Your Privacy";
|
||||
"lng_sponsored_revenued_info1_description" = "Ads on Telegram do not use your personal information and are based on the channel in which you see them.";
|
||||
"lng_sponsored_revenued_info1_bot_description" = "Ads on Telegram do not use your personal information and are based on the mini app in which you see them.";
|
||||
"lng_sponsored_revenued_info2_title" = "Help the Channel Creator";
|
||||
"lng_sponsored_revenued_info2_bot_title" = "Help the Bot Developer";
|
||||
"lng_sponsored_revenued_info2_description" = "50% of the revenue from Telegram Ads goes to the owner of the channel where they are displayed.";
|
||||
"lng_sponsored_revenued_info2_bot_description" = "50% of the revenue from Telegram Ads goes to the developer of the mini app where they are displayed.";
|
||||
"lng_sponsored_revenued_info3_title" = "Can Be Removed";
|
||||
"lng_sponsored_revenued_info3_description#one" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_description#other" = "You can turn off ads by subscribing to {link}, and Level {count} channels can remove them for their subscribers.";
|
||||
"lng_sponsored_revenued_info3_bot_description" = "You can turn off ads in mini apps by subscribing to {link}.";
|
||||
"lng_sponsored_revenued_footer_title" = "Can I Launch an Ad?";
|
||||
"lng_sponsored_revenued_footer_description" = "Anyone can create an ad to display in this channel — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_revenued_footer_bot_description" = "Anyone can create an ad to display in this bot — with minimal budgets. Check out the **Telegram Ad Platform** for details. {link}";
|
||||
"lng_sponsored_top_bar_hide" = "remove";
|
||||
|
||||
"lng_telegram_features_url" = "https://t.me/TelegramTips";
|
||||
|
||||
|
@ -5482,6 +5575,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_channel_earn_title" = "Monetization";
|
||||
"lng_channel_earn_about" = "Telegram shares 50% of the revenue from ads displayed in your channel as rewards. {link}";
|
||||
"lng_channel_earn_about_bot" = "Telegram shares 50% of the revenue from ads displayed in your bot. {link}";
|
||||
"lng_channel_earn_about_link" = "Learn more {emoji}";
|
||||
"lng_channel_earn_overview_title" = "Rewards overview";
|
||||
"lng_channel_earn_available" = "Rewards available for collection";
|
||||
|
@ -5514,8 +5608,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_channel_earn_cpm#one" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_cpm#other" = "{emoji} {count} CPM";
|
||||
"lng_channel_earn_learn_title" = "Earn From Your Channel";
|
||||
"lng_channel_earn_bot_learn_title" = "Earn From Your Bot";
|
||||
"lng_channel_earn_learn_in_subtitle" = "Telegram Ads";
|
||||
"lng_channel_earn_learn_in_about" = "Telegram can display ads in your channel.";
|
||||
"lng_channel_earn_learn_bot_in_about" = "Telegram can display ads in your bot.";
|
||||
"lng_channel_earn_learn_split_subtitle" = "50:50 revenue split";
|
||||
"lng_channel_earn_learn_split_about" = "You can receive 50% of the ad revenue as rewards in TON.";
|
||||
"lng_channel_earn_learn_out_subtitle" = "Flexible withdrawals";
|
||||
|
@ -5552,6 +5648,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_bot_earn_credits_out_minimal" = "You cannot withdraw less than {link}.";
|
||||
"lng_bot_earn_credits_out_minimal_link#one" = "{count} star";
|
||||
"lng_bot_earn_credits_out_minimal_link#other" = "{count} stars";
|
||||
"lng_bot_copy_text_tooltip" = "Copy to Clipboard: {text}";
|
||||
|
||||
"lng_contact_add" = "Add";
|
||||
"lng_contact_send_message" = "Message";
|
||||
|
@ -5562,6 +5659,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_iv_window_title" = "Instant View";
|
||||
"lng_iv_wrong_layout" = "Wrong layout?";
|
||||
"lng_iv_not_supported" = "This link appears to be invalid.";
|
||||
"lng_iv_zoom_tooltip_ctrl" = "Hold Ctrl to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
"lng_iv_zoom_tooltip_cmd" = "Hold Cmd to zoom by 5%.\nHold Alt to zoom by 1%.";
|
||||
|
||||
"lng_limit_download_title" = "Download speed limited";
|
||||
"lng_limit_download_subscribe" = "Subscribe to {link} to increase download speed {increase}.";
|
||||
|
@ -5599,6 +5698,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_channels_recommended" = "Similar channels";
|
||||
"lng_bot_apps_your" = "Apps you use";
|
||||
"lng_bot_apps_popular" = "Grossing apps";
|
||||
"lng_bot_apps_which" = "Which apps are included here? {link}";
|
||||
"lng_bot_apps_which_link" = "Learn >";
|
||||
|
||||
"lng_popular_apps_info_title" = "Top Mini Apps";
|
||||
"lng_popular_apps_info_text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini apps in {bot} (as described {link}), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on the weekly average.";
|
||||
"lng_popular_apps_info_bot" = "@botfather";
|
||||
"lng_popular_apps_info_here" = "here";
|
||||
"lng_popular_apps_info_url" = "https://core.telegram.org/bots/webapps#launching-the-main-mini-app";
|
||||
"lng_popular_apps_info_confirm" = "Understood";
|
||||
|
||||
"lng_font_box_title" = "Choose font family";
|
||||
"lng_font_default" = "Default";
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||
<file alias="search.tgs">../../animations/search.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="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<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/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="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.6.3.0" />
|
||||
Version="5.8.2.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEVERSION 5,8,2,0
|
||||
PRODUCTVERSION 5,8,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "FileVersion", "5.8.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
VALUE "ProductVersion", "5.8.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/filters/edit_filter_links.h" // FilterChatStatusText
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
|
@ -152,6 +153,7 @@ void InitFilterLinkHeader(
|
|||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
.horizontalFilters = Core::App().settings().chatFiltersHorizontal(),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
|
|
128
Telegram/SourceFiles/api/api_chat_filters_remove_manager.cpp
Normal 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
|
35
Telegram/SourceFiles/api/api_chat_filters_remove_manager.h
Normal 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
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "settings/settings_credits_graphics.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/userpic_button.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
|
@ -129,6 +130,7 @@ void ConfirmSubscriptionBox(
|
|||
struct State final {
|
||||
std::shared_ptr<Data::PhotoMedia> photoMedia;
|
||||
std::unique_ptr<Ui::EmptyUserpic> photoEmpty;
|
||||
QImage frame;
|
||||
|
||||
std::optional<MTP::Sender> api;
|
||||
Ui::RpWidget* saveButton = nullptr;
|
||||
|
@ -146,25 +148,45 @@ void ConfirmSubscriptionBox(
|
|||
const auto userpic = userpicWrap->entity();
|
||||
const auto photoSize = st::confirmInvitePhotoSize;
|
||||
userpic->resize(Size(photoSize));
|
||||
const auto creditsIconSize = photoSize / 3;
|
||||
const auto creditsIconCallback =
|
||||
Ui::PaintOutlinedColoredCreditsIconCallback(
|
||||
creditsIconSize,
|
||||
1.5);
|
||||
state->frame = QImage(
|
||||
Size(photoSize * style::DevicePixelRatio()),
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
state->frame.setDevicePixelRatio(style::DevicePixelRatio());
|
||||
const auto options = Images::Option::RoundCircle;
|
||||
userpic->paintRequest(
|
||||
) | rpl::start_with_next([=, small = Data::PhotoSize::Small] {
|
||||
auto p = QPainter(userpic);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
state->frame.fill(Qt::transparent);
|
||||
{
|
||||
auto p = QPainter(&state->frame);
|
||||
if (state->photoMedia) {
|
||||
if (const auto image = state->photoMedia->image(small)) {
|
||||
p.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
image->pix(Size(photoSize), { .options = options }));
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
if (creditsIconCallback) {
|
||||
p.translate(
|
||||
photoSize - creditsIconSize,
|
||||
photoSize - creditsIconSize);
|
||||
creditsIconCallback(p);
|
||||
}
|
||||
} else if (state->photoEmpty) {
|
||||
state->photoEmpty->paintCircle(
|
||||
p,
|
||||
0,
|
||||
0,
|
||||
userpic->width(),
|
||||
photoSize);
|
||||
}
|
||||
auto p = QPainter(userpic);
|
||||
p.drawImage(0, 0, state->frame);
|
||||
}, userpicWrap->lifetime());
|
||||
userpicWrap->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
if (photo) {
|
||||
|
|
|
@ -39,8 +39,8 @@ constexpr auto kTransactionsLimit = 100;
|
|||
if (const auto list = tl.data().vextended_media()) {
|
||||
extended.reserve(list->v.size());
|
||||
for (const auto &media : list->v) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &photo) {
|
||||
if (const auto inner = photo.vphoto()) {
|
||||
media.match([&](const MTPDmessageMediaPhoto &data) {
|
||||
if (const auto inner = data.vphoto()) {
|
||||
const auto photo = owner->processPhoto(*inner);
|
||||
if (!photo->isNull()) {
|
||||
extended.push_back(CreditsHistoryMedia{
|
||||
|
@ -49,9 +49,11 @@ constexpr auto kTransactionsLimit = 100;
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [&](const MTPDmessageMediaDocument &document) {
|
||||
if (const auto inner = document.vdocument()) {
|
||||
const auto document = owner->processDocument(*inner);
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
if (const auto inner = data.vdocument()) {
|
||||
const auto document = owner->processDocument(
|
||||
*inner,
|
||||
data.valt_documents());
|
||||
if (document->isAnimation()
|
||||
|| document->isVideoFile()
|
||||
|| document->isGifv()) {
|
||||
|
@ -71,7 +73,9 @@ constexpr auto kTransactionsLimit = 100;
|
|||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto reaction = tl.data().is_reaction();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
const auto saveActorId = (reaction || !extended.empty()) && incoming;
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
|
@ -81,12 +85,13 @@ constexpr auto kTransactionsLimit = 100;
|
|||
.extended = std::move(extended),
|
||||
.credits = tl.data().vstars().v,
|
||||
.bareMsgId = uint64(tl.data().vmsg_id().value_or_empty()),
|
||||
.barePeerId = barePeerId,
|
||||
.barePeerId = saveActorId ? peer->id.value : barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.bareActorId = saveActorId ? barePeerId : uint64(0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
|
@ -101,6 +106,8 @@ constexpr auto kTransactionsLimit = 100;
|
|||
return Data::CreditsHistoryEntry::PeerType::PremiumBot;
|
||||
}, [](const MTPDstarsTransactionPeerAds &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Ads;
|
||||
}, [](const MTPDstarsTransactionPeerAPI &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::API;
|
||||
}),
|
||||
.subscriptionUntil = tl.data().vsubscription_period()
|
||||
? base::unixtime::parse(base::unixtime::now()
|
||||
|
@ -110,10 +117,12 @@ constexpr auto kTransactionsLimit = 100;
|
|||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
.starsConverted = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.floodSkip = int(tl.data().vfloodskip_number().value_or(0)),
|
||||
.converted = stargift && incoming,
|
||||
.stargift = stargift.has_value(),
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
|
@ -166,7 +175,8 @@ constexpr auto kTransactionsLimit = 100;
|
|||
.balance = status.data().vbalance().v,
|
||||
.subscriptionsMissingBalance
|
||||
= status.data().vsubscriptions_missing_balance().value_or_empty(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value(),
|
||||
.allLoaded = !status.data().vnext_offset().has_value()
|
||||
&& !status.data().vsubscriptions_next_offset().has_value(),
|
||||
.token = qs(status.data().vnext_offset().value_or_empty()),
|
||||
.tokenSubscriptions = qs(
|
||||
status.data().vsubscriptions_next_offset().value_or_empty()),
|
||||
|
|
|
@ -99,8 +99,8 @@ public:
|
|||
[[nodiscard]] Data::CreditsEarnStatistics data() const;
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::CreditsEarnStatistics _data;
|
||||
bool _isUser = false;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ void HandleWithdrawalButton(
|
|||
std::shared_ptr<Ui::Show> show) {
|
||||
Expects(receiver.currencyReceiver
|
||||
|| (receiver.creditsReceiver && receiver.creditsAmount));
|
||||
|
||||
struct State {
|
||||
rpl::lifetime lifetime;
|
||||
bool loading = false;
|
||||
|
@ -58,8 +59,7 @@ void HandleWithdrawalButton(
|
|||
const auto processOut = [=] {
|
||||
if (state->loading) {
|
||||
return;
|
||||
}
|
||||
if (peer && !receiver.creditsAmount()) {
|
||||
} else if (peer && !receiver.creditsAmount()) {
|
||||
return;
|
||||
}
|
||||
state->loading = true;
|
||||
|
@ -89,12 +89,15 @@ void HandleWithdrawalButton(
|
|||
}
|
||||
};
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
show->showToast(error.type());
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
};
|
||||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
result.result
|
||||
)).done([=](const ChannelOutUrl &r) {
|
||||
done(qs(r.data().vurl()));
|
||||
|
@ -134,7 +137,7 @@ void HandleWithdrawalButton(
|
|||
if (channel) {
|
||||
session->api().request(
|
||||
MTPstats_GetBroadcastRevenueWithdrawalUrl(
|
||||
channel->inputChannel,
|
||||
channel->input,
|
||||
MTP_inputCheckPasswordEmpty()
|
||||
)).fail(fail).send();
|
||||
} else if (peer) {
|
||||
|
|
|
@ -772,10 +772,13 @@ std::optional<StarGift> FromTL(
|
|||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.starsConverted = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
.firstSaleDate = data.vfirst_sale_date().value_or_empty(),
|
||||
.lastSaleDate = data.vlast_sale_date().value_or_empty(),
|
||||
.birthday = data.is_birthday(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -789,7 +792,7 @@ std::optional<UserStarGift> FromTL(
|
|||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.gift = std::move(*parsed),
|
||||
.info = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
|
@ -798,7 +801,7 @@ std::optional<UserStarGift> FromTL(
|
|||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.starsConverted = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
|
|
|
@ -76,16 +76,23 @@ struct GiftOptionData {
|
|||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
TimeId firstSaleDate = 0;
|
||||
TimeId lastSaleDate = 0;
|
||||
bool birthday = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
const StarGift &,
|
||||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift gift;
|
||||
StarGift info;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
int64 starsConverted = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
|
|
|
@ -40,29 +40,20 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
|||
|
||||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data) {
|
||||
auto done = [=] {
|
||||
not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done([=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
v::match(data, [&](v::null_t) {
|
||||
peer->session().api().request(MTPaccount_ReportPeer(
|
||||
peer->input,
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
photo->mtpInput(),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
|
|
|
@ -41,12 +41,12 @@ struct ReportResult final {
|
|||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendReport(
|
||||
void SendPhotoReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data);
|
||||
not_null<PhotoData*> photo);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
|
|
|
@ -456,6 +456,7 @@ void SendConfirmedFile(
|
|||
not_null<Main::Session*> session,
|
||||
const std::shared_ptr<FilePrepareResult> &file) {
|
||||
const auto isEditing = (file->type != SendMediaType::Audio)
|
||||
&& (file->type != SendMediaType::Round)
|
||||
&& (file->to.replaceMediaOf != 0);
|
||||
const auto newId = FullMsgId(
|
||||
file->to.peer,
|
||||
|
@ -525,7 +526,8 @@ void SendConfirmedFile(
|
|||
// Shortcut messages have no 'edited' badge.
|
||||
flags |= MessageFlag::HideEdited;
|
||||
}
|
||||
if (file->type == SendMediaType::Audio) {
|
||||
if (file->type == SendMediaType::Audio
|
||||
|| file->type == SendMediaType::Round) {
|
||||
if (!peer->isChannel() || peer->isMegagroup()) {
|
||||
flags |= MessageFlag::MediaIsUnread;
|
||||
}
|
||||
|
@ -551,29 +553,25 @@ void SendConfirmedFile(
|
|||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
const auto isVoice = [&] {
|
||||
return file->document.match([](const MTPDdocumentEmpty &d) {
|
||||
return false;
|
||||
}, [](const MTPDdocument &d) {
|
||||
return ranges::any_of(d.vattributes().v, [&](
|
||||
const MTPDocumentAttribute &attribute) {
|
||||
using Att = MTPDdocumentAttributeAudio;
|
||||
return attribute.match([](const Att &data) -> bool {
|
||||
return data.vflags().v & Att::Flag::f_voice;
|
||||
}, [](const auto &) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}();
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| Flag::f_voice
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
return MTP_messageMediaDocument(
|
||||
MTP_flags(Flag::f_document
|
||||
| Flag::f_round
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_story.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/history.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
|
@ -341,6 +342,10 @@ void PublicForwards::request(
|
|||
.token = nextToken,
|
||||
});
|
||||
};
|
||||
const auto processFail = [=] {
|
||||
_requestId = 0;
|
||||
done({});
|
||||
};
|
||||
|
||||
constexpr auto kLimit = tl::make_int(100);
|
||||
if (_fullId.messageId) {
|
||||
|
@ -349,14 +354,14 @@ void PublicForwards::request(
|
|||
MTP_int(_fullId.messageId.msg),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
} else if (_fullId.storyId) {
|
||||
_requestId = makeRequest(MTPstats_GetStoryPublicForwards(
|
||||
channel->input,
|
||||
MTP_int(_fullId.storyId.story),
|
||||
MTP_string(token),
|
||||
kLimit
|
||||
)).done(processResult).fail([=] { _requestId = 0; }).send();
|
||||
)).done(processResult).fail(processFail).send();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +386,7 @@ Data::PublicForwardsSlice MessageStatistics::firstSlice() const {
|
|||
}
|
||||
|
||||
void MessageStatistics::request(Fn<void(Data::MessageStatistics)> done) {
|
||||
if (channel()->isMegagroup()) {
|
||||
if (channel()->isMegagroup() && !_storyId) {
|
||||
return;
|
||||
}
|
||||
const auto requestFirstPublicForwards = [=](
|
||||
|
@ -681,17 +686,18 @@ Data::BoostStatus Boosts::boostStatus() const {
|
|||
return _boostStatus;
|
||||
}
|
||||
|
||||
ChannelEarnStatistics::ChannelEarnStatistics(not_null<ChannelData*> channel)
|
||||
: StatisticsRequestSender(channel) {
|
||||
EarnStatistics::EarnStatistics(not_null<PeerData*> peer)
|
||||
: StatisticsRequestSender(peer)
|
||||
, _isUser(peer->isUser()) {
|
||||
}
|
||||
|
||||
rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
||||
rpl::producer<rpl::no_value, QString> EarnStatistics::request() {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
makeRequest(MTPstats_GetBroadcastRevenueStats(
|
||||
MTP_flags(0),
|
||||
channel()->inputChannel
|
||||
(_isUser ? user()->input : channel()->input)
|
||||
)).done([=](const MTPstats_BroadcastRevenueStats &result) {
|
||||
const auto &data = result.data();
|
||||
const auto &balances = data.vbalances().data();
|
||||
|
@ -708,18 +714,22 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
|||
requestHistory({}, [=](Data::EarnHistorySlice &&slice) {
|
||||
_data.firstHistorySlice = std::move(slice);
|
||||
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
if (!_isUser) {
|
||||
api().request(
|
||||
MTPchannels_GetFullChannel(channel()->inputChannel)
|
||||
).done([=](const MTPmessages_ChatFull &result) {
|
||||
result.data().vfull_chat().match([&](
|
||||
const MTPDchannelFull &d) {
|
||||
_data.switchedOff = d.is_restricted_sponsored();
|
||||
}, [](const auto &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
} else {
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
}
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
|
@ -729,7 +739,7 @@ rpl::producer<rpl::no_value, QString> ChannelEarnStatistics::request() {
|
|||
};
|
||||
}
|
||||
|
||||
void ChannelEarnStatistics::requestHistory(
|
||||
void EarnStatistics::requestHistory(
|
||||
const Data::EarnHistorySlice::OffsetToken &token,
|
||||
Fn<void(Data::EarnHistorySlice)> done) {
|
||||
if (_requestId) {
|
||||
|
@ -738,7 +748,7 @@ void ChannelEarnStatistics::requestHistory(
|
|||
constexpr auto kTlFirstSlice = tl::make_int(kFirstSlice);
|
||||
constexpr auto kTlLimit = tl::make_int(kLimit);
|
||||
_requestId = api().request(MTPstats_GetBroadcastRevenueTransactions(
|
||||
channel()->inputChannel,
|
||||
(_isUser ? user()->input : channel()->input),
|
||||
MTP_int(token),
|
||||
(!token) ? kTlFirstSlice : kTlLimit
|
||||
)).done([=](const MTPstats_BroadcastRevenueTransactions &result) {
|
||||
|
@ -799,7 +809,7 @@ void ChannelEarnStatistics::requestHistory(
|
|||
}).send();
|
||||
}
|
||||
|
||||
Data::EarnStatistics ChannelEarnStatistics::data() const {
|
||||
Data::EarnStatistics EarnStatistics::data() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,9 +79,9 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class ChannelEarnStatistics final : public StatisticsRequestSender {
|
||||
class EarnStatistics final : public StatisticsRequestSender {
|
||||
public:
|
||||
explicit ChannelEarnStatistics(not_null<ChannelData*> channel);
|
||||
explicit EarnStatistics(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
void requestHistory(
|
||||
|
@ -94,6 +94,7 @@ public:
|
|||
static constexpr auto kLimit = int(10);
|
||||
|
||||
private:
|
||||
const bool _isUser = false;
|
||||
Data::EarnStatistics _data;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
|
|
@ -320,6 +320,9 @@ void Updates::feedUpdateVector(
|
|||
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
|
||||
return;
|
||||
}
|
||||
if (policy == SkipUpdatePolicy::SkipNone) {
|
||||
applyConvertToScheduledOnSend(updates);
|
||||
}
|
||||
for (const auto &entry : std::as_const(list)) {
|
||||
const auto type = entry.type();
|
||||
if ((policy == SkipUpdatePolicy::SkipMessageIds
|
||||
|
@ -333,6 +336,15 @@ void Updates::feedUpdateVector(
|
|||
session().data().sendHistoryChangeNotifications();
|
||||
}
|
||||
|
||||
void Updates::checkForSentToScheduled(const MTPUpdates &updates) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [&](const MTPDupdatesCombined &data) {
|
||||
applyConvertToScheduledOnSend(data.vupdates(), true);
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
void Updates::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
|
||||
for (const auto &update : updates.v) {
|
||||
if (update.type() == mtpc_updateMessageID) {
|
||||
|
@ -436,6 +448,7 @@ void Updates::feedChannelDifference(
|
|||
session().data().processChats(data.vchats());
|
||||
|
||||
_handlingChannelDifference = true;
|
||||
applyConvertToScheduledOnSend(data.vother_updates());
|
||||
feedMessageIds(data.vother_updates());
|
||||
session().data().processMessages(
|
||||
data.vnew_messages(),
|
||||
|
@ -600,6 +613,7 @@ void Updates::feedDifference(
|
|||
Core::App().checkAutoLock();
|
||||
session().data().processUsers(users);
|
||||
session().data().processChats(chats);
|
||||
applyConvertToScheduledOnSend(other);
|
||||
feedMessageIds(other);
|
||||
session().data().processMessages(msgs, NewMessageType::Unread);
|
||||
feedUpdateVector(other, SkipUpdatePolicy::SkipMessageIds);
|
||||
|
@ -885,6 +899,51 @@ void Updates::mtpUpdateReceived(const MTPUpdates &updates) {
|
|||
}
|
||||
}
|
||||
|
||||
void Updates::applyConvertToScheduledOnSend(
|
||||
const MTPVector<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) {
|
||||
updates.match([&](const MTPDupdates &data) {
|
||||
session().data().processUsers(data.vusers());
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
void applyUpdatesNoPtsCheck(const MTPUpdates &updates);
|
||||
void applyUpdateNoPtsCheck(const MTPUpdate &update);
|
||||
|
||||
void checkForSentToScheduled(const MTPUpdates &updates);
|
||||
|
||||
[[nodiscard]] int32 pts() const;
|
||||
|
||||
void updateOnline(crl::time lastNonIdleTime = 0);
|
||||
|
@ -131,6 +133,9 @@ private:
|
|||
// Doesn't call sendHistoryChangeNotifications itself.
|
||||
void feedUpdate(const MTPUpdate &update);
|
||||
|
||||
void applyConvertToScheduledOnSend(
|
||||
const MTPVector<MTPUpdate> &other,
|
||||
bool skipScheduledCheck = false);
|
||||
void applyGroupCallParticipantUpdates(const MTPUpdates &updates);
|
||||
|
||||
bool whenGetDiffChanged(
|
||||
|
|
|
@ -69,6 +69,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
|||
if (rule.always.premiums && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowPremium());
|
||||
}
|
||||
if (rule.always.miniapps && (rule.option != Option::Everyone)) {
|
||||
result.push_back(MTP_inputPrivacyValueAllowBots());
|
||||
}
|
||||
}
|
||||
if (!rule.ignoreNever) {
|
||||
const auto users = collectInputUsers(rule.never);
|
||||
|
@ -83,6 +86,9 @@ TLInputRules RulesToTL(const UserPrivacy::Rule &rule) {
|
|||
MTP_inputPrivacyValueDisallowChatParticipants(
|
||||
MTP_vector<MTPlong>(chats)));
|
||||
}
|
||||
if (rule.never.miniapps && (rule.option != Option::Nobody)) {
|
||||
result.push_back(MTP_inputPrivacyValueDisallowBots());
|
||||
}
|
||||
}
|
||||
result.push_back([&] {
|
||||
switch (rule.option) {
|
||||
|
@ -124,6 +130,10 @@ UserPrivacy::Rule TLToRules(const TLRules &rules, Data::Session &owner) {
|
|||
setOption(Option::CloseFriends);
|
||||
}, [&](const MTPDprivacyValueAllowPremium &) {
|
||||
result.always.premiums = true;
|
||||
}, [&](const MTPDprivacyValueAllowBots &) {
|
||||
result.always.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueDisallowBots &) {
|
||||
result.never.miniapps = true;
|
||||
}, [&](const MTPDprivacyValueAllowUsers &data) {
|
||||
const auto &users = data.vusers().v;
|
||||
always.reserve(always.size() + users.size());
|
||||
|
@ -199,6 +209,7 @@ MTPInputPrivacyKey KeyToTL(UserPrivacy::Key key) {
|
|||
case Key::Voices: return MTP_inputPrivacyKeyVoiceMessages();
|
||||
case Key::About: return MTP_inputPrivacyKeyAbout();
|
||||
case Key::Birthday: return MTP_inputPrivacyKeyBirthday();
|
||||
case Key::GiftsAutoSave: return MTP_inputPrivacyKeyStarGiftsAutoSave();
|
||||
}
|
||||
Unexpected("Key in Api::UserPrivacy::KetToTL.");
|
||||
}
|
||||
|
@ -228,6 +239,8 @@ std::optional<UserPrivacy::Key> TLToKey(mtpTypeId type) {
|
|||
case mtpc_inputPrivacyKeyAbout: return Key::About;
|
||||
case mtpc_privacyKeyBirthday:
|
||||
case mtpc_inputPrivacyKeyBirthday: return Key::Birthday;
|
||||
case mtpc_privacyKeyStarGiftsAutoSave:
|
||||
case mtpc_inputPrivacyKeyStarGiftsAutoSave: return Key::GiftsAutoSave;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
Voices,
|
||||
About,
|
||||
Birthday,
|
||||
GiftsAutoSave,
|
||||
};
|
||||
enum class Option {
|
||||
Everyone,
|
||||
|
@ -41,6 +42,7 @@ public:
|
|||
struct Exceptions {
|
||||
std::vector<not_null<PeerData*>> peers;
|
||||
bool premiums = false;
|
||||
bool miniapps = false;
|
||||
};
|
||||
struct Rule {
|
||||
Option option = Option::Everyone;
|
||||
|
|
|
@ -756,5 +756,19 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
|||
const style::WhoRead &st) {
|
||||
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
|
||||
|
|
|
@ -61,5 +61,8 @@ struct WhoReadList {
|
|||
const Data::ReactionId &reaction,
|
||||
not_null<QWidget*> context, // Cache results for this lifetime.
|
||||
const style::WhoRead &st);
|
||||
[[nodiscard]] rpl::producer<Ui::WhoReadContent> WhenEdited(
|
||||
not_null<PeerData*> author,
|
||||
TimeId date);
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -731,7 +731,8 @@ void ApiWrap::finalizeMessageDataRequest(
|
|||
|
||||
QString ApiWrap::exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext) {
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink) {
|
||||
Expects(item->history()->peer->isChannel());
|
||||
|
||||
const auto itemId = item->fullId();
|
||||
|
@ -754,7 +755,7 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
const auto sender = root
|
||||
? root->discussionPostOriginalSender()
|
||||
: nullptr;
|
||||
if (sender && sender->hasUsername()) {
|
||||
if (sender && sender->hasUsername() && !forceNonPublicLink) {
|
||||
// Comment to a public channel.
|
||||
const auto forwarded = root->Get<HistoryMessageForwarded>();
|
||||
linkItemId = forwarded->savedFromMsgId;
|
||||
|
@ -770,7 +771,7 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
}
|
||||
}
|
||||
}
|
||||
const auto base = linkChannel->hasUsername()
|
||||
const auto base = (linkChannel->hasUsername() && !forceNonPublicLink)
|
||||
? linkChannel->username()
|
||||
: "c/" + QString::number(peerToChannel(linkChannel->id).bare);
|
||||
const auto post = QString::number(linkItemId.bare);
|
||||
|
@ -784,6 +785,7 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
? (QString::number(linkThreadId.bare) + '/' + post)
|
||||
: post);
|
||||
if (linkChannel->hasUsername()
|
||||
&& !forceNonPublicLink
|
||||
&& !linkChannel->isMegagroup()
|
||||
&& !linkCommentId
|
||||
&& !linkThreadId) {
|
||||
|
@ -797,6 +799,9 @@ QString ApiWrap::exportDirectMessageLink(
|
|||
}
|
||||
return session().createInternalLinkFull(query);
|
||||
};
|
||||
if (forceNonPublicLink) {
|
||||
return fallback();
|
||||
}
|
||||
const auto i = _unlikelyMessageLinks.find(itemId);
|
||||
const auto current = (i != end(_unlikelyMessageLinks))
|
||||
? i->second
|
||||
|
@ -3373,6 +3378,7 @@ void ApiWrap::forwardMessages(
|
|||
}
|
||||
const auto requestType = Data::Histories::RequestType::Send;
|
||||
const auto idsCopy = localIds;
|
||||
const auto scheduled = action.options.scheduled;
|
||||
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
||||
history->sendRequestId = request(MTPmessages_ForwardMessages(
|
||||
MTP_flags(sendFlags),
|
||||
|
@ -3385,6 +3391,9 @@ void ApiWrap::forwardMessages(
|
|||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
}
|
||||
applyUpdates(result);
|
||||
if (shared && !--shared->requestsLeft) {
|
||||
shared->callback();
|
||||
|
@ -3553,6 +3562,7 @@ void ApiWrap::sendVoiceMessage(
|
|||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action) {
|
||||
const auto caption = TextWithTags();
|
||||
const auto to = FileLoadTaskOptions(action);
|
||||
|
@ -3561,6 +3571,7 @@ void ApiWrap::sendVoiceMessage(
|
|||
result,
|
||||
duration,
|
||||
waveform,
|
||||
video,
|
||||
to,
|
||||
caption));
|
||||
}
|
||||
|
@ -4008,7 +4019,8 @@ void ApiWrap::sendInlineResult(
|
|||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId) {
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done) {
|
||||
sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
|
@ -4088,11 +4100,17 @@ void ApiWrap::sendInlineResult(
|
|||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(true);
|
||||
}
|
||||
}, [=](const MTP::Error &error, const MTP::Response &response) {
|
||||
sendMessageFail(error, peer, randomId, newId);
|
||||
history->finishSavingCloudDraft(
|
||||
topicRootId,
|
||||
UnixtimeFromMsgId(response.outerMsgId));
|
||||
if (done) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
finishForwarding(action);
|
||||
}
|
||||
|
@ -4304,6 +4322,7 @@ void ApiWrap::sendMultiPaidMedia(
|
|||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
const auto itemId = item->fullId();
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
|
@ -4375,6 +4394,9 @@ void ApiWrap::sendAlbumWithCancelled(
|
|||
}
|
||||
|
||||
void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
||||
if (album->sent) {
|
||||
return;
|
||||
}
|
||||
const auto groupId = album->groupId;
|
||||
if (album->items.empty()) {
|
||||
_sendingAlbums.remove(groupId);
|
||||
|
@ -4399,6 +4421,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
|||
return;
|
||||
} else if (medias.size() < 2) {
|
||||
const auto &single = medias.front().data();
|
||||
album->sent = true;
|
||||
sendMediaWithRandomId(
|
||||
sample,
|
||||
single.vmedia(),
|
||||
|
@ -4431,6 +4454,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
|
|||
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
|
||||
auto &histories = history->owner().histories();
|
||||
const auto peer = history->peer;
|
||||
album->sent = true;
|
||||
histories.sendPreparedMessage(
|
||||
history,
|
||||
replyTo,
|
||||
|
|
|
@ -164,7 +164,8 @@ public:
|
|||
void requestMessageData(PeerData *peer, MsgId msgId, Fn<void()> done);
|
||||
QString exportDirectMessageLink(
|
||||
not_null<HistoryItem*> item,
|
||||
bool inRepliesContext);
|
||||
bool inRepliesContext,
|
||||
bool forceNonPublicLink = false);
|
||||
QString exportDirectStoryLink(not_null<Data::Story*> item);
|
||||
|
||||
void requestContacts();
|
||||
|
@ -317,6 +318,7 @@ public:
|
|||
QByteArray result,
|
||||
VoiceWaveform waveform,
|
||||
crl::time duration,
|
||||
bool video,
|
||||
const SendAction &action);
|
||||
void sendFiles(
|
||||
Ui::PreparedList &&list,
|
||||
|
@ -359,7 +361,8 @@ public:
|
|||
not_null<UserData*> bot,
|
||||
not_null<InlineBots::Result*> data,
|
||||
const SendAction &action,
|
||||
std::optional<MsgId> localMessageId);
|
||||
std::optional<MsgId> localMessageId,
|
||||
Fn<void(bool)> done = nullptr);
|
||||
void sendMessageFail(
|
||||
const MTP::Error &error,
|
||||
not_null<PeerData*> peer,
|
||||
|
|
|
@ -154,9 +154,7 @@ contactsSortButton: IconButton(defaultIconButton) {
|
|||
iconPosition: point(10px, -1px);
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
contactsSortOnlineIcon: icon{{ "contacts_online", boxTitleCloseFg }};
|
||||
contactsSortOnlineIconOver: icon{{ "contacts_online", boxTitleCloseFgOver }};
|
||||
|
@ -416,9 +414,7 @@ calendarPrevious: IconButton {
|
|||
|
||||
rippleAreaPosition: point(2px, 2px);
|
||||
rippleAreaSize: 44px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
calendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", menuIconFg }};
|
||||
calendarNext: IconButton(calendarPrevious) {
|
||||
|
@ -616,9 +612,7 @@ proxyTryIPv6Padding: margins(22px, 8px, 22px, 5px);
|
|||
proxyRowPadding: margins(22px, 8px, 8px, 8px);
|
||||
proxyRowIconSkip: 32px;
|
||||
proxyRowSkip: 2px;
|
||||
proxyRowRipple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
proxyRowRipple: defaultRippleAnimationBgOver;
|
||||
proxyRowTitleFg: windowFg;
|
||||
proxyRowTitlePalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: windowSubTextFg;
|
||||
|
@ -683,9 +677,7 @@ themesMenuToggle: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 36px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
themesMenuPosition: point(-2px, 25px);
|
||||
|
||||
|
@ -738,9 +730,7 @@ createPollOptionRemove: CrossButton {
|
|||
|
||||
duration: 135;
|
||||
loadingPeriod: 1000;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
createPollOptionRemovePosition: point(11px, 9px);
|
||||
createPollOptionEmojiPositionSkip: 4px;
|
||||
|
@ -888,6 +878,13 @@ peerListWithInviteViaLink: PeerList(peerListBox) {
|
|||
peerListSingleRow: PeerList(peerListBox) {
|
||||
padding: margins(0px, 0px, 0px, 0px);
|
||||
}
|
||||
peerListSmallSkips: PeerList(peerListBox) {
|
||||
padding: margins(
|
||||
0px,
|
||||
defaultVerticalListSkip,
|
||||
0px,
|
||||
defaultVerticalListSkip);
|
||||
}
|
||||
|
||||
scheduleHeight: 95px;
|
||||
scheduleDateTop: 38px;
|
||||
|
@ -951,9 +948,7 @@ sponsoredUrlButton: RoundButton(defaultActiveButton) {
|
|||
textTop: 7px;
|
||||
style: defaultTextStyle;
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
requestPeerRestriction: FlatLabel(defaultFlatLabel) {
|
||||
|
|
|
@ -96,6 +96,9 @@ void ChangeFilterById(
|
|||
Ui::Text::WithEntities));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
LOG(("API Error: failed to %1 a dialog to a folder. %2")
|
||||
.arg(add ? u"add"_q : u"remove"_q)
|
||||
.arg(error.type()));
|
||||
// Revert filter on fail.
|
||||
history->owner().chatsFilters().set(was);
|
||||
}).send();
|
||||
|
|
|
@ -238,7 +238,7 @@ EditCaptionBox::EditCaptionBox(
|
|||
Fn<void()> saved)
|
||||
: _controller(controller)
|
||||
, _historyItem(item)
|
||||
, _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
|
||||
, _isAllowedEditMedia(item->allowsEditMedia())
|
||||
, _albumType(ComputeAlbumType(item))
|
||||
, _controls(base::make_unique_q<Ui::VerticalLayout>(this))
|
||||
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
|
||||
|
@ -253,8 +253,8 @@ EditCaptionBox::EditCaptionBox(
|
|||
, _initialText(std::move(text))
|
||||
, _initialList(std::move(list))
|
||||
, _saved(std::move(saved)) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
Expects(!_initialList.files.empty());
|
||||
Expects(!item->media() || item->media()->allowsEditCaption());
|
||||
|
||||
_mediaEditManager.start(item, spoilered, invertCaption);
|
||||
|
||||
|
@ -422,7 +422,8 @@ void EditCaptionBox::prepare() {
|
|||
setInitialText();
|
||||
|
||||
if (!setPreparedList(std::move(_initialList))) {
|
||||
rebuildPreview();
|
||||
crl::on_main(this, [=] { closeBox(); });
|
||||
return;
|
||||
}
|
||||
setupEditEventHandler();
|
||||
SetupShadowsToScrollContent(this, _scroll, _contentHeight.events());
|
||||
|
|
|
@ -36,13 +36,63 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
|
||||
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
|
||||
|
||||
using Exceptions = Api::UserPrivacy::Exceptions;
|
||||
|
||||
enum class SpecialRowType {
|
||||
Premiums,
|
||||
MiniApps,
|
||||
};
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GeneratePremiumsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] PaintRoundImageCallback GenerateMiniAppsUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
const auto &color1 = st::historyPeer6UserpicBg;
|
||||
const auto &color2 = st::historyPeer6UserpicBg2;
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
auto gradient = QLinearGradient(x, y, x, y + size);
|
||||
gradient.setStops({ { 0., color1->c }, { 1., color2->c } });
|
||||
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::windowFilterTypeBots.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
}
|
||||
|
||||
void CreateRadiobuttonLock(
|
||||
not_null<Ui::RpWidget*> widget,
|
||||
const style::Checkbox &st) {
|
||||
|
@ -102,7 +152,7 @@ public:
|
|||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums);
|
||||
std::optional<SpecialRowType> allowChooseSpecial);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
@ -110,18 +160,20 @@ public:
|
|||
bool handleDeselectForeignRow(PeerListRowId itemId) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] bool miniAppsSelected() const;
|
||||
|
||||
protected:
|
||||
void prepareViewHook() override;
|
||||
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> preparePremiumsRowList();
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> prepareSpecialRowList(
|
||||
SpecialRowType type);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
rpl::producer<QString> _title;
|
||||
Exceptions _selected;
|
||||
bool _allowChoosePremiums = false;
|
||||
std::optional<SpecialRowType> _allowChooseSpecial;
|
||||
|
||||
PeerListContentDelegate *_typesDelegate = nullptr;
|
||||
Fn<void(PeerListRowId)> _deselectOption;
|
||||
|
@ -133,9 +185,9 @@ struct RowSelectionChange {
|
|||
bool checked = false;
|
||||
};
|
||||
|
||||
class PremiumsRow final : public PeerListRow {
|
||||
class SpecialRow final : public PeerListRow {
|
||||
public:
|
||||
PremiumsRow();
|
||||
explicit SpecialRow(SpecialRowType type);
|
||||
|
||||
QString generateName() override;
|
||||
QString generateShortName() override;
|
||||
|
@ -147,66 +199,61 @@ public:
|
|||
|
||||
class TypesController final : public PeerListController {
|
||||
public:
|
||||
TypesController(not_null<Main::Session*> session, bool premiums);
|
||||
TypesController(not_null<Main::Session*> session, SpecialRowType type);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
[[nodiscard]] bool premiumsSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> premiumsChanges() const;
|
||||
[[nodiscard]] bool specialSelected() const;
|
||||
[[nodiscard]] rpl::producer<bool> specialChanges() const;
|
||||
[[nodiscard]] auto rowSelectionChanges() const
|
||||
-> rpl::producer<RowSelectionChange>;
|
||||
|
||||
private:
|
||||
const not_null<Main::Session*> _session;
|
||||
const SpecialRowType _type;
|
||||
|
||||
rpl::event_stream<> _selectionChanged;
|
||||
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
|
||||
|
||||
};
|
||||
|
||||
PremiumsRow::PremiumsRow() : PeerListRow(kPremiumsRowId) {
|
||||
setCustomStatus(tr::lng_edit_privacy_premium_status(tr::now));
|
||||
SpecialRow::SpecialRow(SpecialRowType type)
|
||||
: PeerListRow((type == SpecialRowType::Premiums)
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId) {
|
||||
setCustomStatus((id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium_status(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps_status(tr::now));
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateName() {
|
||||
return tr::lng_edit_privacy_premium(tr::now);
|
||||
QString SpecialRow::generateName() {
|
||||
return (id() == kPremiumsRowId)
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_miniapps(tr::now);
|
||||
}
|
||||
|
||||
QString PremiumsRow::generateShortName() {
|
||||
QString SpecialRow::generateShortName() {
|
||||
return generateName();
|
||||
}
|
||||
|
||||
PaintRoundImageCallback PremiumsRow::generatePaintUserpicCallback(
|
||||
PaintRoundImageCallback SpecialRow::generatePaintUserpicCallback(
|
||||
bool forceRound) {
|
||||
return [=](QPainter &p, int x, int y, int outerWidth, int size) {
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(x, y),
|
||||
QPointF(x + size, y + size));
|
||||
gradient.setStops(Ui::Premium::ButtonGradientStops());
|
||||
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
if (forceRound) {
|
||||
p.drawEllipse(x, y, size, size);
|
||||
} else {
|
||||
const auto radius = size * Ui::ForumUserpicRadiusMultiplier();
|
||||
p.drawRoundedRect(x, y, size, size, radius, radius);
|
||||
}
|
||||
st::settingsPrivacyPremium.paintInCenter(p, QRect(x, y, size, size));
|
||||
};
|
||||
return (id() == kPremiumsRowId)
|
||||
? GeneratePremiumsUserpicCallback(forceRound)
|
||||
: GenerateMiniAppsUserpicCallback(forceRound);
|
||||
}
|
||||
|
||||
bool PremiumsRow::useForumLikeUserpic() const {
|
||||
bool SpecialRow::useForumLikeUserpic() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
TypesController::TypesController(
|
||||
not_null<Main::Session*> session,
|
||||
bool premiums)
|
||||
: _session(session) {
|
||||
SpecialRowType type)
|
||||
: _session(session)
|
||||
, _type(type) {
|
||||
}
|
||||
|
||||
Main::Session &TypesController::session() const {
|
||||
|
@ -214,12 +261,15 @@ Main::Session &TypesController::session() const {
|
|||
}
|
||||
|
||||
void TypesController::prepare() {
|
||||
delegate()->peerListAppendRow(std::make_unique<PremiumsRow>());
|
||||
delegate()->peerListAppendRow(std::make_unique<SpecialRow>(_type));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
|
||||
bool TypesController::premiumsSelected() const {
|
||||
const auto row = delegate()->peerListFindRow(kPremiumsRowId);
|
||||
bool TypesController::specialSelected() const {
|
||||
const auto premiums = (_type == SpecialRowType::Premiums);
|
||||
const auto row = delegate()->peerListFindRow(premiums
|
||||
? kPremiumsRowId
|
||||
: kMiniAppsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
return row->checked();
|
||||
|
@ -231,10 +281,10 @@ void TypesController::rowClicked(not_null<PeerListRow*> row) {
|
|||
_rowSelectionChanges.fire({ row, checked });
|
||||
}
|
||||
|
||||
rpl::producer<bool> TypesController::premiumsChanges() const {
|
||||
rpl::producer<bool> TypesController::specialChanges() const {
|
||||
return _rowSelectionChanges.events(
|
||||
) | rpl::map([=] {
|
||||
return premiumsSelected();
|
||||
return specialSelected();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -247,12 +297,12 @@ PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
|
|||
not_null<Main::Session*> session,
|
||||
rpl::producer<QString> title,
|
||||
const Exceptions &selected,
|
||||
bool allowChoosePremiums)
|
||||
std::optional<SpecialRowType> allowChooseSpecial)
|
||||
: ChatsListBoxController(session)
|
||||
, _session(session)
|
||||
, _title(std::move(title))
|
||||
, _selected(selected)
|
||||
, _allowChoosePremiums(allowChoosePremiums) {
|
||||
, _allowChooseSpecial(allowChooseSpecial) {
|
||||
}
|
||||
|
||||
Main::Session &PrivacyExceptionsBoxController::session() const {
|
||||
|
@ -261,14 +311,18 @@ Main::Session &PrivacyExceptionsBoxController::session() const {
|
|||
|
||||
void PrivacyExceptionsBoxController::prepareViewHook() {
|
||||
delegate()->peerListSetTitle(std::move(_title));
|
||||
if (_allowChoosePremiums || _selected.premiums) {
|
||||
delegate()->peerListSetAboveWidget(preparePremiumsRowList());
|
||||
if (_allowChooseSpecial || _selected.premiums || _selected.miniapps) {
|
||||
delegate()->peerListSetAboveWidget(prepareSpecialRowList(
|
||||
_allowChooseSpecial.value_or(_selected.premiums
|
||||
? SpecialRowType::Premiums
|
||||
: SpecialRowType::MiniApps)));
|
||||
}
|
||||
delegate()->peerListAddSelectedPeers(_selected.peers);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::isForeignRow(PeerListRowId itemId) {
|
||||
return (itemId == kPremiumsRowId);
|
||||
return (itemId == kPremiumsRowId)
|
||||
|| (itemId == kMiniAppsRowId);
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
||||
|
@ -280,7 +334,8 @@ bool PrivacyExceptionsBoxController::handleDeselectForeignRow(
|
|||
return false;
|
||||
}
|
||||
|
||||
auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
||||
auto PrivacyExceptionsBoxController::prepareSpecialRowList(
|
||||
SpecialRowType type)
|
||||
-> object_ptr<Ui::RpWidget> {
|
||||
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = result.data();
|
||||
|
@ -291,30 +346,39 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
|||
_typesDelegate = lifetime.make_state<PeerListContentDelegateSimple>();
|
||||
const auto controller = lifetime.make_state<TypesController>(
|
||||
&session(),
|
||||
_selected.premiums);
|
||||
type);
|
||||
const auto content = result->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
_typesDelegate->setContent(content);
|
||||
controller->setDelegate(_typesDelegate);
|
||||
|
||||
const auto selectType = [&](PeerListRowId id) {
|
||||
const auto row = _typesDelegate->peerListFindRow(id);
|
||||
if (row) {
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
}
|
||||
};
|
||||
if (_selected.premiums) {
|
||||
const auto row = _typesDelegate->peerListFindRow(kPremiumsRowId);
|
||||
Assert(row != nullptr);
|
||||
|
||||
content->changeCheckState(row, true, anim::type::instant);
|
||||
this->delegate()->peerListSetForeignRowChecked(
|
||||
row,
|
||||
true,
|
||||
anim::type::instant);
|
||||
selectType(kPremiumsRowId);
|
||||
} else if (_selected.miniapps) {
|
||||
selectType(kMiniAppsRowId);
|
||||
}
|
||||
container->add(CreatePeerListSectionSubtitle(
|
||||
container,
|
||||
tr::lng_edit_privacy_users_and_groups()));
|
||||
|
||||
controller->premiumsChanges(
|
||||
) | rpl::start_with_next([=](bool premiums) {
|
||||
_selected.premiums = premiums;
|
||||
controller->specialChanges(
|
||||
) | rpl::start_with_next([=](bool chosen) {
|
||||
if (type == SpecialRowType::Premiums) {
|
||||
_selected.premiums = chosen;
|
||||
} else {
|
||||
_selected.miniapps = chosen;
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
controller->rowSelectionChanges(
|
||||
|
@ -329,6 +393,8 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
|||
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
|
||||
if (itemId == kPremiumsRowId) {
|
||||
_selected.premiums = false;
|
||||
} else if (itemId == kMiniAppsRowId) {
|
||||
_selected.miniapps = false;
|
||||
}
|
||||
_typesDelegate->peerListSetRowChecked(row, false);
|
||||
}
|
||||
|
@ -337,10 +403,14 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
bool PrivacyExceptionsBoxController::premiumsSelected() const {
|
||||
return _selected.premiums;
|
||||
}
|
||||
|
||||
bool PrivacyExceptionsBoxController::miniAppsSelected() const {
|
||||
return _selected.miniapps;
|
||||
}
|
||||
|
||||
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
const auto peer = row->peer();
|
||||
|
||||
|
@ -412,6 +482,11 @@ EditPrivacyBox::EditPrivacyBox(
|
|||
// If we switch from Everyone to Contacts or Nobody suggest Premiums.
|
||||
_value.always.premiums = true;
|
||||
}
|
||||
if (_controller->allowMiniAppsToggle(Exception::Always)
|
||||
&& _value.option == Option::Everyone) {
|
||||
// If we switch from Everyone to Contacts or Nobody suggest MiniApps.
|
||||
_value.always.miniapps = true;
|
||||
}
|
||||
}
|
||||
|
||||
void EditPrivacyBox::prepare() {
|
||||
|
@ -427,12 +502,18 @@ void EditPrivacyBox::editExceptions(
|
|||
&_window->session(),
|
||||
_controller->exceptionBoxTitle(exception),
|
||||
exceptions(exception),
|
||||
_controller->allowPremiumsToggle(exception));
|
||||
(_controller->allowPremiumsToggle(exception)
|
||||
? SpecialRowType::Premiums
|
||||
: _controller->allowMiniAppsToggle(exception)
|
||||
? SpecialRowType::MiniApps
|
||||
: std::optional<SpecialRowType>()));
|
||||
auto initBox = [=, controller = controller.get()](
|
||||
not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
|
||||
exceptions(exception).peers = box->collectSelectedRows();
|
||||
exceptions(exception).premiums = controller->premiumsSelected();
|
||||
auto &setTo = exceptions(exception);
|
||||
setTo.peers = box->collectSelectedRows();
|
||||
setTo.premiums = controller->premiumsSelected();
|
||||
setTo.miniapps = controller->miniAppsSelected();
|
||||
const auto type = [&] {
|
||||
switch (exception) {
|
||||
case Exception::Always: return Exception::Never;
|
||||
|
@ -440,11 +521,17 @@ void EditPrivacyBox::editExceptions(
|
|||
}
|
||||
Unexpected("Invalid exception value.");
|
||||
}();
|
||||
auto &removeFrom = exceptions(type).peers;
|
||||
auto &removeFrom = exceptions(type);
|
||||
for (const auto peer : exceptions(exception).peers) {
|
||||
removeFrom.erase(
|
||||
ranges::remove(removeFrom, peer),
|
||||
end(removeFrom));
|
||||
removeFrom.peers.erase(
|
||||
ranges::remove(removeFrom.peers, peer),
|
||||
end(removeFrom.peers));
|
||||
}
|
||||
if (setTo.premiums) {
|
||||
removeFrom.premiums = false;
|
||||
}
|
||||
if (setTo.miniapps) {
|
||||
removeFrom.miniapps = false;
|
||||
}
|
||||
done();
|
||||
box->closeBox();
|
||||
|
@ -566,14 +653,21 @@ void EditPrivacyBox::setupContent() {
|
|||
lt_count,
|
||||
count)
|
||||
: tr::lng_edit_privacy_exceptions_add(tr::now);
|
||||
return !value.premiums
|
||||
? users
|
||||
: !count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users);
|
||||
return value.premiums
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_premium(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_premium_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: value.miniapps
|
||||
? (!count
|
||||
? tr::lng_edit_privacy_miniapps(tr::now)
|
||||
: tr::lng_edit_privacy_exceptions_miniapps_and(
|
||||
tr::now,
|
||||
lt_users,
|
||||
users))
|
||||
: users;
|
||||
});
|
||||
_controller->handleExceptionsChange(
|
||||
exception,
|
||||
|
|
|
@ -61,6 +61,10 @@ public:
|
|||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool allowMiniAppsToggle(
|
||||
Exception exception) const {
|
||||
return false;
|
||||
}
|
||||
virtual void handleExceptionsChange(
|
||||
Exception exception,
|
||||
rpl::producer<int> value) {
|
||||
|
|
|
@ -123,7 +123,9 @@ void GiftCreditsBox(
|
|||
box->verticalLayout(),
|
||||
peer,
|
||||
0,
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); });
|
||||
[=] { gifted(); box->uiShow()->hideLayer(); },
|
||||
tr::lng_credits_summary_options_subtitle(),
|
||||
{});
|
||||
|
||||
box->setPinnedToBottomContent(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/prepare_short_info_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h" // BoostsForGift.
|
||||
#include "boxes/premium_preview_box.h" // ShowPremiumPreviewBox.
|
||||
#include "boxes/star_gift_box.h" // ShowStarGiftBox.
|
||||
#include "data/data_boosts.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -123,7 +124,8 @@ namespace {
|
|||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
PeerId id,
|
||||
bool withSendGiftButton = false) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
|
@ -134,15 +136,40 @@ namespace {
|
|||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
peer->name(),
|
||||
withSendGiftButton ? peer->shortName() : peer->name(),
|
||||
st::giveawayGiftCodeValue);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto send = withSendGiftButton
|
||||
? 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;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
const auto sendSkip = sendWidth
|
||||
? (st::normalFont->spacew + sendWidth)
|
||||
: 0;
|
||||
label->resizeToNaturalWidth(width - position.x() - sendSkip);
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
if (send) {
|
||||
send->moveToLeft(
|
||||
position.x() + label->width() + st::normalFont->spacew,
|
||||
(position.y()
|
||||
+ st::giveawayGiftCodeValue.style.font->ascent
|
||||
- st::starGiftSmallButton.style.font->ascent),
|
||||
width);
|
||||
}
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
@ -210,14 +237,82 @@ void AddTableRow(
|
|||
valueMargins);
|
||||
}
|
||||
|
||||
object_ptr<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::TableLayout*> table,
|
||||
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>(
|
||||
table,
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeValue);
|
||||
st::giveawayGiftCodeValue,
|
||||
st::defaultPopupMenu,
|
||||
std::move(makeContext));
|
||||
const auto result = widget.data();
|
||||
AddTableRow(
|
||||
table,
|
||||
|
@ -939,26 +1034,57 @@ void ResolveGiveawayInfo(
|
|||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
const auto user = session->data().peer(peerId)->asUser();
|
||||
const auto withSendButton = entry.in && user && !user->isBot();
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
peerId);
|
||||
} else {
|
||||
MakePeerTableValue(table, controller, peerId, withSendButton),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
} else if (!entry.soldOutInfo) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
MakeHiddenPeerTableValue(table, controller),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
if (!entry.firstSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_first_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.firstSaleDate))));
|
||||
}
|
||||
if (!entry.lastSaleDate.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_last_sale(),
|
||||
rpl::single(Ui::Text::WithEntities(
|
||||
langDateTime(entry.lastSaleDate))));
|
||||
}
|
||||
{
|
||||
const auto margin = st::giveawayGiftCodeValueMargin
|
||||
- QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom());
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_value(),
|
||||
MakeStarGiftStarsValue(
|
||||
table,
|
||||
controller,
|
||||
entry,
|
||||
std::move(convertToStars)),
|
||||
margin);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
|
@ -967,14 +1093,14 @@ void AddStarGiftTable(
|
|||
}
|
||||
if (entry.limitedCount > 0) {
|
||||
auto amount = rpl::single(TextWithEntities{
|
||||
QString::number(entry.limitedCount)
|
||||
Lang::FormatCountDecimal(entry.limitedCount)
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
((entry.limitedLeft > 0)
|
||||
? tr::lng_gift_availability_left(
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
|
@ -985,7 +1111,6 @@ void AddStarGiftTable(
|
|||
Ui::Text::WithEntities)));
|
||||
}
|
||||
if (!entry.description.empty()) {
|
||||
const auto session = &controller->session();
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
|
@ -1020,12 +1145,17 @@ void AddCreditsHistoryEntryTable(
|
|||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
const auto actorId = PeerId(entry.bareActorId);
|
||||
const auto session = &controller->session();
|
||||
if (peerId) {
|
||||
if (actorId || peerId) {
|
||||
auto text = entry.in
|
||||
? tr::lng_credits_box_history_entry_peer_in()
|
||||
: tr::lng_credits_box_history_entry_peer();
|
||||
AddTableRow(table, std::move(text), controller, peerId);
|
||||
AddTableRow(
|
||||
table,
|
||||
std::move(text),
|
||||
controller,
|
||||
actorId ? actorId : peerId);
|
||||
}
|
||||
if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) {
|
||||
const auto peer = session->data().peer(peerId);
|
||||
|
@ -1044,7 +1174,9 @@ void AddCreditsHistoryEntryTable(
|
|||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_media(),
|
||||
(entry.reaction
|
||||
? tr::lng_credits_box_history_entry_message
|
||||
: tr::lng_credits_box_history_entry_media)(),
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
|
@ -1116,12 +1248,24 @@ void AddCreditsHistoryEntryTable(
|
|||
}
|
||||
}
|
||||
if (!entry.id.isEmpty()) {
|
||||
constexpr auto kOneLineCount = 18;
|
||||
const auto oneLine = entry.id.length() <= kOneLineCount;
|
||||
constexpr auto kOneLineCount = 22;
|
||||
const auto oneLine = entry.id.size() <= kOneLineCount;
|
||||
auto multiLine = QString();
|
||||
if (!oneLine) {
|
||||
for (auto i = 0; i < entry.id.size(); ++i) {
|
||||
multiLine.append(entry.id[i]);
|
||||
if ((i + 1) % kOneLineCount == 0) {
|
||||
multiLine.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(
|
||||
Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})),
|
||||
Ui::Text::Wrapped(
|
||||
{ oneLine ? entry.id : std::move(multiLine) },
|
||||
EntityType::Code,
|
||||
{})),
|
||||
oneLine
|
||||
? st::giveawayGiftCodeValue
|
||||
: st::giveawayGiftCodeValueMultiline);
|
||||
|
@ -1138,6 +1282,14 @@ void AddCreditsHistoryEntryTable(
|
|||
std::move(label),
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
if (entry.floodSkip) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_floodskip_row(),
|
||||
rpl::single(
|
||||
Ui::Text::WithEntities(
|
||||
Lang::FormatCountDecimal(entry.floodSkip))));
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
|
@ -1178,6 +1330,16 @@ void AddSubscriptionEntryTable(
|
|||
controller,
|
||||
peerId);
|
||||
if (!s.until.isNull()) {
|
||||
if (s.subscription.period > 0) {
|
||||
const auto subscribed = s.until.addSecs(-s.subscription.period);
|
||||
if (subscribed.isValid()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_group_invite_joined_row_date(),
|
||||
rpl::single(
|
||||
Ui::Text::WithEntities(langDateTime(subscribed))));
|
||||
}
|
||||
}
|
||||
AddTableRow(
|
||||
table,
|
||||
s.expired
|
||||
|
|
|
@ -57,7 +57,8 @@ void ResolveGiveawayInfo(
|
|||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void()> convertToStars);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
|
|
@ -240,7 +240,8 @@ int LocalStorageBox::Row::resizeGetHeight(int newWidth) {
|
|||
}
|
||||
|
||||
void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
||||
if (!_progress || true) {
|
||||
#if 0 // not used
|
||||
if (!_progress) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(this);
|
||||
|
@ -254,6 +255,7 @@ void LocalStorageBox::Row::paintEvent(QPaintEvent *e) {
|
|||
st::proxyCheckingPosition.y() + bottom
|
||||
},
|
||||
width());
|
||||
#endif
|
||||
}
|
||||
|
||||
QString LocalStorageBox::Row::titleText(const Database::TaggedSummary &data) const {
|
||||
|
|
|
@ -206,7 +206,9 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
|||
content()->selectSkipPage(height(), 1);
|
||||
} else if (e->key() == Qt::Key_PageUp) {
|
||||
content()->selectSkipPage(height(), -1);
|
||||
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
|
||||
} else if (e->key() == Qt::Key_Escape
|
||||
&& _select
|
||||
&& !_select->entity()->getQuery().isEmpty()) {
|
||||
_select->entity()->clearQuery();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
|
@ -215,7 +217,19 @@ void PeerListBox::keyPressEvent(QKeyEvent *e) {
|
|||
|
||||
void PeerListBox::searchQueryChanged(const QString &query) {
|
||||
scrollToY(0);
|
||||
content()->searchQueryChanged(query);
|
||||
const auto isEmpty = content()->searchQueryChanged(query);
|
||||
if (_specialTabsMode.enabled) {
|
||||
const auto was = _specialTabsMode.searchIsActive;
|
||||
_specialTabsMode.searchIsActive = !isEmpty;
|
||||
if (was != _specialTabsMode.searchIsActive) {
|
||||
if (_specialTabsMode.searchIsActive) {
|
||||
_specialTabsMode.topSkip = _addedTopScrollSkip;
|
||||
setAddedTopScrollSkip(0);
|
||||
} else {
|
||||
setAddedTopScrollSkip(_specialTabsMode.topSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerListBox::resizeEvent(QResizeEvent *e) {
|
||||
|
@ -543,6 +557,19 @@ auto PeerListBox::collectSelectedRows()
|
|||
return result;
|
||||
}
|
||||
|
||||
rpl::producer<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(peer, peer->id.value) {
|
||||
}
|
||||
|
@ -1385,10 +1412,12 @@ int PeerListContent::labelHeight() const {
|
|||
|
||||
void PeerListContent::refreshRows() {
|
||||
if (!_hiddenRows.empty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
if (!_ignoreHiddenRowsOnSearch || _normalizedSearchQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
for (const auto &row : _rows) {
|
||||
if (!row->hidden()) {
|
||||
_filterResults.push_back(row.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1944,6 +1973,13 @@ PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_controller->overrideKeyboardNavigation(
|
||||
direction,
|
||||
_selected.index.value,
|
||||
newSelectedIndex)) {
|
||||
return { _selected.index.value, _selected.index.value };
|
||||
}
|
||||
|
||||
_selected.index.value = newSelectedIndex;
|
||||
_selected.element = 0;
|
||||
if (newSelectedIndex >= 0) {
|
||||
|
@ -2043,13 +2079,16 @@ void PeerListContent::checkScrollForPreload() {
|
|||
}
|
||||
}
|
||||
|
||||
void PeerListContent::searchQueryChanged(QString query) {
|
||||
PeerListContent::IsEmpty PeerListContent::searchQueryChanged(QString query) {
|
||||
const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
|
||||
const auto normalizedQuery = searchWordsList.join(' ');
|
||||
if (_ignoreHiddenRowsOnSearch && !normalizedQuery.isEmpty()) {
|
||||
_filterResults.clear();
|
||||
}
|
||||
if (_normalizedSearchQuery != normalizedQuery) {
|
||||
setSearchQuery(query, normalizedQuery);
|
||||
if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
|
||||
Assert(_hiddenRows.empty());
|
||||
Assert(_hiddenRows.empty() || _ignoreHiddenRowsOnSearch);
|
||||
|
||||
auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
|
||||
for (const auto &searchWord : searchWordsList) {
|
||||
|
@ -2097,6 +2136,7 @@ void PeerListContent::searchQueryChanged(QString query) {
|
|||
}
|
||||
refreshRows();
|
||||
}
|
||||
return _normalizedSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListState> PeerListContent::saveState() const {
|
||||
|
@ -2185,6 +2225,10 @@ void PeerListContent::dragLeft() {
|
|||
clearSelection();
|
||||
}
|
||||
|
||||
void PeerListContent::setIgnoreHiddenRowsOnSearch(bool value) {
|
||||
_ignoreHiddenRowsOnSearch = value;
|
||||
}
|
||||
|
||||
void PeerListContent::visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
|
|
|
@ -357,6 +357,8 @@ public:
|
|||
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
|
||||
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
|
||||
|
||||
virtual void peerListSelectSkip(int direction) = 0;
|
||||
|
||||
virtual void peerListPressLeftToContextMenu(bool shown) = 0;
|
||||
virtual bool peerListTrackRowPressFromGlobal(QPoint globalPosition) = 0;
|
||||
|
||||
|
@ -573,6 +575,13 @@ public:
|
|||
Unexpected("PeerListController::customRowRippleMaskGenerator.");
|
||||
}
|
||||
|
||||
virtual bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
int fromIndex,
|
||||
int toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
@ -643,12 +652,15 @@ public:
|
|||
[[nodiscard]] bool hasPressed() const;
|
||||
void clearSelection();
|
||||
|
||||
void searchQueryChanged(QString query);
|
||||
using IsEmpty = bool;
|
||||
IsEmpty searchQueryChanged(QString query);
|
||||
bool submitted();
|
||||
|
||||
PeerListRowId updateFromParentDrag(QPoint globalPosition);
|
||||
void dragLeft();
|
||||
|
||||
void setIgnoreHiddenRowsOnSearch(bool value);
|
||||
|
||||
// Interface for the controller.
|
||||
void appendRow(std::unique_ptr<PeerListRow> row);
|
||||
void appendSearchRow(std::unique_ptr<PeerListRow> row);
|
||||
|
@ -870,6 +882,7 @@ private:
|
|||
int _aboveHeight = 0;
|
||||
int _belowHeight = 0;
|
||||
bool _hideEmpty = false;
|
||||
bool _ignoreHiddenRowsOnSearch = false;
|
||||
object_ptr<Ui::RpWidget> _aboveWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _aboveSearchWidget = { nullptr };
|
||||
object_ptr<Ui::RpWidget> _belowWidget = { nullptr };
|
||||
|
@ -1016,6 +1029,10 @@ public:
|
|||
bool highlightRow,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
|
||||
|
||||
void peerListSelectSkip(int direction) override {
|
||||
_content->selectSkip(direction);
|
||||
}
|
||||
|
||||
void peerListPressLeftToContextMenu(bool shown) override {
|
||||
_content->pressLeftToContextMenu(shown);
|
||||
}
|
||||
|
@ -1089,6 +1106,9 @@ public:
|
|||
|
||||
[[nodiscard]] std::vector<PeerListRowId> collectSelectedIds();
|
||||
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
|
||||
[[nodiscard]] rpl::producer<int> multiSelectHeightValue() const;
|
||||
|
||||
void setSpecialTabMode(bool value);
|
||||
|
||||
void peerListSetTitle(rpl::producer<QString> title) override {
|
||||
setTitle(std::move(title));
|
||||
|
@ -1155,4 +1175,11 @@ private:
|
|||
bool _scrollBottomFixed = false;
|
||||
int _addedTopScrollSkip = 0;
|
||||
|
||||
struct SpecialTabsMode final {
|
||||
bool enabled = false;
|
||||
bool searchIsActive = false;
|
||||
int topSkip = 0;
|
||||
};
|
||||
SpecialTabsMode _specialTabsMode;
|
||||
|
||||
};
|
||||
|
|
|
@ -1065,6 +1065,11 @@ std::unique_ptr<PeerListRow> ChooseTopicBoxController::createSearchRow(
|
|||
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)
|
||||
-> std::unique_ptr<Row> {
|
||||
const auto skip = _filter && !_filter(topic);
|
||||
|
|
|
@ -335,6 +335,9 @@ public:
|
|||
void loadMoreRows() override;
|
||||
std::unique_ptr<PeerListRow> createSearchRow(PeerListRowId id) override;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<PeerListRow> MakeRow(
|
||||
not_null<Data::ForumTopic*> topic);
|
||||
|
||||
private:
|
||||
class Row final : public PeerListRow {
|
||||
public:
|
||||
|
|
|
@ -30,10 +30,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_changes.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "ui/effects/outline_segments.h"
|
||||
#include "ui/widgets/menu/menu_multiline_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "history/history.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
||||
namespace {
|
||||
|
@ -1645,6 +1648,51 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
|||
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||
parent,
|
||||
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) {
|
||||
result->addAction(
|
||||
(participant->isUser()
|
||||
|
@ -1652,39 +1700,14 @@ base::unique_qptr<Ui::PopupMenu> ParticipantsBoxController::rowContextMenu(
|
|||
: participant->isBroadcast()
|
||||
? tr::lng_context_view_channel
|
||||
: tr::lng_context_view_group)(tr::now),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->showPeerInfo(participant); }),
|
||||
crl::guard(this, [=, this] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(participant, _navigation));
|
||||
}),
|
||||
(participant->isUser()
|
||||
? &st::menuIconProfile
|
||||
: &st::menuIconInfo));
|
||||
}
|
||||
if (const auto by = _additional.restrictedBy(participant)) {
|
||||
result->addAction(
|
||||
(_role == Role::Kicked
|
||||
? tr::lng_channel_banned_status_removed_by
|
||||
: tr::lng_channel_banned_status_restricted_by)(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
} else if (user) {
|
||||
if (const auto by = _additional.adminPromotedBy(user)) {
|
||||
result->addAction(
|
||||
tr::lng_channel_admin_status_promoted_by(
|
||||
tr::now,
|
||||
lt_user,
|
||||
by->name()),
|
||||
crl::guard(this, [=] {
|
||||
_navigation->parentController()->show(
|
||||
PrepareShortInfoBox(by, _navigation));
|
||||
}),
|
||||
&st::menuIconAdmin);
|
||||
}
|
||||
}
|
||||
if (_role == Role::Kicked) {
|
||||
if (_peer->isMegagroup()
|
||||
&& _additional.canRestrictParticipant(participant)) {
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "api/api_statistics.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
|
@ -46,6 +47,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/admin_log/history_admin_log_section.h"
|
||||
#include "info/bot/earn/info_bot_earn_widget.h"
|
||||
#include "info/channel_statistics/boosts/info_boosts_widget.h"
|
||||
#include "info/channel_statistics/earn/earn_format.h"
|
||||
#include "info/channel_statistics/earn/earn_icons.h"
|
||||
#include "info/channel_statistics/earn/info_channel_earn_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
@ -352,7 +356,8 @@ private:
|
|||
void fillPendingRequestsButton();
|
||||
|
||||
void fillBotUsernamesButton();
|
||||
void fillBotBalanceButton();
|
||||
void fillBotCurrencyButton();
|
||||
void fillBotCreditsButton();
|
||||
void fillBotEditIntroButton();
|
||||
void fillBotEditCommandsButton();
|
||||
void fillBotEditSettingsButton();
|
||||
|
@ -1174,7 +1179,8 @@ void Controller::fillManageSection() {
|
|||
|
||||
::AddSkip(container, 0);
|
||||
fillBotUsernamesButton();
|
||||
fillBotBalanceButton();
|
||||
fillBotCurrencyButton();
|
||||
fillBotCreditsButton();
|
||||
fillBotEditIntroButton();
|
||||
fillBotEditCommandsButton();
|
||||
fillBotEditSettingsButton();
|
||||
|
@ -1583,7 +1589,72 @@ void Controller::fillBotUsernamesButton() {
|
|||
{ &st::menuIconLinks });
|
||||
}
|
||||
|
||||
void Controller::fillBotBalanceButton() {
|
||||
void Controller::fillBotCurrencyButton() {
|
||||
Expects(_isBot);
|
||||
|
||||
struct State final {
|
||||
rpl::variable<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);
|
||||
|
||||
struct State final {
|
||||
|
@ -1593,7 +1664,7 @@ void Controller::fillBotBalanceButton() {
|
|||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
|
||||
state->balance = QString::number(balance);
|
||||
state->balance = Lang::FormatCountDecimal(balance);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
|
@ -1601,7 +1672,7 @@ void Controller::fillBotBalanceButton() {
|
|||
_controls.buttonsLayout,
|
||||
EditPeerInfoBox::CreateButton(
|
||||
_controls.buttonsLayout,
|
||||
tr::lng_manage_peer_bot_balance(),
|
||||
tr::lng_manage_peer_bot_balance_credits(),
|
||||
state->balance.value(),
|
||||
[controller = _navigation->parentController(), peer = _peer] {
|
||||
controller->showSection(Info::BotEarn::Make(peer));
|
||||
|
@ -1618,46 +1689,22 @@ void Controller::fillBotBalanceButton() {
|
|||
if (data.balance) {
|
||||
wrap->toggle(true, anim::type::normal);
|
||||
}
|
||||
state->balance = QString::number(data.balance);
|
||||
state->balance = Lang::FormatCountDecimal(data.balance);
|
||||
});
|
||||
}
|
||||
{
|
||||
constexpr auto kSizeShift = 3;
|
||||
constexpr auto kStrokeWidth = 5;
|
||||
|
||||
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||
icon->resize(Size(st::menuIconLinks.width() - kSizeShift));
|
||||
|
||||
auto colorized = [&] {
|
||||
auto f = QFile(Ui::Premium::Svg());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return QString();
|
||||
}
|
||||
return QString::fromUtf8(
|
||||
f.readAll()).replace(u"#fff"_q, u"#ffffff00"_q);
|
||||
}();
|
||||
colorized.replace(
|
||||
u"stroke=\"none\""_q,
|
||||
u"stroke=\"%1\""_q.arg(st::menuIconColor->c.name()));
|
||||
colorized.replace(
|
||||
u"stroke-width=\"1\""_q,
|
||||
u"stroke-width=\"%1\""_q.arg(kStrokeWidth));
|
||||
const auto svg = icon->lifetime().make_state<QSvgRenderer>(
|
||||
colorized.toUtf8());
|
||||
svg->setViewBox(svg->viewBox() + Margins(kStrokeWidth));
|
||||
|
||||
const auto starSize = Size(icon->height());
|
||||
|
||||
icon->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
const auto image = Ui::Earn::MenuIconCredits();
|
||||
icon->resize(image.size() / style::DevicePixelRatio());
|
||||
icon->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(icon);
|
||||
svg->render(&p, Rect(starSize));
|
||||
p.drawImage(0, 0, image);
|
||||
}, icon->lifetime());
|
||||
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
icon->moveToLeft(
|
||||
button->st().iconLeft + kSizeShift / 2.,
|
||||
button->st().iconLeft,
|
||||
(size.height() - icon->height()) / 2);
|
||||
}, icon->lifetime());
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/unixtime.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "core/application.h"
|
||||
#include "core/ui_integration.h" // Core::MarkedTextContext.
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
|
@ -51,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_credits.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_giveaway.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_layers.h" // st::boxDividerLabel.
|
||||
|
@ -264,8 +267,9 @@ private:
|
|||
class SingleRowController final : public PeerListController {
|
||||
public:
|
||||
SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status);
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked);
|
||||
|
||||
void prepare() override;
|
||||
void loadMoreRows() override;
|
||||
|
@ -273,8 +277,10 @@ public:
|
|||
Main::Session &session() const override;
|
||||
|
||||
private:
|
||||
const not_null<PeerData*> _peer;
|
||||
const not_null<Main::Session*> _session;
|
||||
const base::weak_ptr<Data::Thread> _thread;
|
||||
rpl::producer<QString> _status;
|
||||
Fn<void()> _clicked;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
@ -956,12 +962,11 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
|||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto &stUser = st::boostReplaceUserpic;
|
||||
const auto photoSize = st::boostReplaceUserpic.photoSize;
|
||||
const auto session = &row->peer()->session();
|
||||
content->add(object_ptr<Ui::CenterWrap<>>(
|
||||
content,
|
||||
object_ptr<Ui::UserpicButton>(content, channel, stUser))
|
||||
)->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
Settings::SubscriptionUserpic(content, channel, photoSize)));
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
@ -1145,36 +1150,59 @@ int Controller::descriptionTopSkipMin() const {
|
|||
}
|
||||
|
||||
SingleRowController::SingleRowController(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> status)
|
||||
: _peer(peer)
|
||||
, _status(std::move(status)) {
|
||||
not_null<Data::Thread*> thread,
|
||||
rpl::producer<QString> status,
|
||||
Fn<void()> clicked)
|
||||
: _session(&thread->session())
|
||||
, _thread(thread)
|
||||
, _status(std::move(status))
|
||||
, _clicked(std::move(clicked)) {
|
||||
}
|
||||
|
||||
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();
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
|
||||
if (_status) {
|
||||
std::move(
|
||||
_status
|
||||
) | rpl::start_with_next([=](const QString &status) {
|
||||
raw->setCustomStatus(status);
|
||||
delegate()->peerListUpdateRow(raw);
|
||||
}, _lifetime);
|
||||
}
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
delegate()->peerListRefreshRows();
|
||||
|
||||
if (topic) {
|
||||
topic->destroyed() | rpl::start_with_next([=] {
|
||||
while (delegate()->peerListFullRowsCount()) {
|
||||
delegate()->peerListRemoveRow(delegate()->peerListRowAt(0));
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void SingleRowController::loadMoreRows() {
|
||||
}
|
||||
|
||||
void SingleRowController::rowClicked(not_null<PeerListRow*> row) {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
if (const auto onstack = _clicked) {
|
||||
onstack();
|
||||
} else {
|
||||
ShowPeerInfoSync(row->peer());
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &SingleRowController::session() const {
|
||||
return _peer->session();
|
||||
return *_session;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -1187,14 +1215,29 @@ bool IsExpiredLink(const Api::InviteLink &data, TimeId now) {
|
|||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
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<
|
||||
PeerListContentDelegateSimple
|
||||
>();
|
||||
const auto controller = container->lifetime().make_state<
|
||||
SingleRowController
|
||||
>(peer, std::move(status));
|
||||
controller->setStyleOverrides(&st::peerListSingleRow);
|
||||
>(thread, std::move(status), std::move(clicked));
|
||||
controller->setStyleOverrides(thread->asTopic()
|
||||
? &st::chooseTopicList
|
||||
: &st::peerListSingleRow);
|
||||
const auto content = container->add(object_ptr<PeerListContent>(
|
||||
container,
|
||||
controller));
|
||||
|
|
|
@ -16,6 +16,10 @@ namespace Api {
|
|||
struct InviteLink;
|
||||
} // namespace Api
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -31,7 +35,14 @@ class BoxContent;
|
|||
void AddSinglePeerRow(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
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(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
|
|
|
@ -7,27 +7,30 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration
|
||||
#include "boxes/peers/edit_peer_invite_link.h" // PrepareRequestedRowStatus
|
||||
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "boxes/peers/edit_peer_requests_box.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_user.h"
|
||||
#include "history/view/history_view_requests_bar.h" // kRecentRequestsLimit
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/requests_list/info_requests_list_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/sender.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
@ -262,14 +265,10 @@ RequestsBoxController::~RequestsBoxController() = default;
|
|||
void RequestsBoxController::Start(
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
not_null<PeerData*> peer) {
|
||||
auto controller = std::make_unique<RequestsBoxController>(
|
||||
navigation,
|
||||
peer->migrateToOrMe());
|
||||
const auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->addButton(tr::lng_close(), [=] { box->closeBox(); });
|
||||
};
|
||||
navigation->parentController()->show(
|
||||
Box<PeerListBox>(std::move(controller), initBox));
|
||||
navigation->showSection(
|
||||
std::make_shared<Info::Memento>(
|
||||
peer->migrateToOrMe(),
|
||||
Info::Section::Type::RequestsList));
|
||||
}
|
||||
|
||||
Main::Session &RequestsBoxController::session() const {
|
||||
|
@ -289,6 +288,58 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createSearchRow(
|
|||
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() {
|
||||
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
|
||||
delegate()->peerListSetTitle(_peer->isBroadcast()
|
||||
|
@ -356,9 +407,7 @@ void RequestsBoxController::refreshDescription() {
|
|||
}
|
||||
|
||||
void RequestsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||
_navigation->parentController()->show(PrepareShortInfoBox(
|
||||
row->peer(),
|
||||
_navigation));
|
||||
_navigation->showPeerInfo(row->peer());
|
||||
}
|
||||
|
||||
void RequestsBoxController::rowElementClicked(
|
||||
|
@ -405,6 +454,7 @@ void RequestsBoxController::appendRow(
|
|||
not_null<UserData*> user,
|
||||
TimeId date) {
|
||||
if (!delegate()->peerListFindRow(user->id.value)) {
|
||||
_dates.emplace(user, date);
|
||||
if (auto row = createRow(user, date)) {
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
setDescriptionText(QString());
|
||||
|
@ -503,6 +553,7 @@ std::unique_ptr<PeerListRow> RequestsBoxController::createRow(
|
|||
const auto search = static_cast<RequestsBoxSearchController*>(
|
||||
searchController());
|
||||
date = search->dateForUser(user);
|
||||
_dates.emplace(user, date);
|
||||
}
|
||||
return std::make_unique<Row>(_helper.get(), user, date);
|
||||
}
|
||||
|
@ -574,6 +625,36 @@ TimeId RequestsBoxSearchController::dateForUser(not_null<UserData*> user) {
|
|||
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() {
|
||||
const auto i = _cache.find(_query);
|
||||
if (i != _cache.cend()) {
|
||||
|
|
|
@ -35,15 +35,32 @@ public:
|
|||
Main::Session &session() const override;
|
||||
void prepare() 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;
|
||||
|
||||
std::unique_ptr<PeerListRow> createSearchRow(
|
||||
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:
|
||||
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(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
|
@ -63,6 +80,8 @@ private:
|
|||
not_null<PeerData*> _peer;
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<not_null<UserData*>, TimeId> _dates;
|
||||
|
||||
TimeId _offsetDate = 0;
|
||||
UserData *_offsetUser = nullptr;
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
|
@ -82,7 +101,17 @@ public:
|
|||
void removeFromCache(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:
|
||||
struct SavedState : SavedStateBase {
|
||||
QString query;
|
||||
TimeId offsetDate = 0;
|
||||
UserData *offsetUser = nullptr;
|
||||
bool allLoaded = false;
|
||||
bool wasLoading = false;
|
||||
};
|
||||
struct Item {
|
||||
not_null<UserData*> user;
|
||||
TimeId date = 0;
|
||||
|
|
|
@ -534,15 +534,16 @@ void PeerShortInfoCover::handleStreamingUpdate(
|
|||
|
||||
v::match(update.data, [&](Information &update) {
|
||||
streamingReady(std::move(update));
|
||||
}, [&](const PreloadedVideo &update) {
|
||||
}, [&](const UpdateVideo &update) {
|
||||
}, [](PreloadedVideo) {
|
||||
}, [&](UpdateVideo update) {
|
||||
_videoPosition = update.position;
|
||||
_widget->update();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
}, [&](const UpdateAudio &update) {
|
||||
}, [&](const WaitingForData &update) {
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
}, [](PreloadedAudio) {
|
||||
}, [](UpdateAudio) {
|
||||
}, [](WaitingForData) {
|
||||
}, [](SpeedEstimate) {
|
||||
}, [](MutedByOther) {
|
||||
}, [](Finished) {
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -774,6 +775,10 @@ void PeerShortInfoBox::prepareRows() {
|
|||
result->setContextCopyText(contextCopyText);
|
||||
return result;
|
||||
};
|
||||
addInfoOneLine(
|
||||
tr::lng_settings_channel_label(),
|
||||
channelValue(),
|
||||
tr::lng_context_copy_link(tr::now));
|
||||
addInfoOneLine(
|
||||
tr::lng_info_link_label(),
|
||||
linkValue(),
|
||||
|
@ -835,6 +840,13 @@ rpl::producer<QString> PeerShortInfoBox::nameValue() const {
|
|||
}) | 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 {
|
||||
return _fields.value(
|
||||
) | rpl::map([](const PeerShortInfoFields &fields) {
|
||||
|
|
|
@ -37,6 +37,8 @@ enum class PeerShortInfoType {
|
|||
|
||||
struct PeerShortInfoFields {
|
||||
QString name;
|
||||
QString channelName;
|
||||
QString channelLink;
|
||||
QString phone;
|
||||
QString link;
|
||||
TextWithEntities about;
|
||||
|
@ -169,6 +171,7 @@ private:
|
|||
int fillRoundedTopHeight();
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> nameValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> channelValue() const;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> linkValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> phoneValue() const;
|
||||
[[nodiscard]] rpl::producer<QString> usernameValue() const;
|
||||
|
|
|
@ -202,6 +202,7 @@ void ProcessFullPhoto(
|
|||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
(UpdateFlag::Name
|
||||
| UpdateFlag::PersonalChannel
|
||||
| UpdateFlag::PhoneNumber
|
||||
| UpdateFlag::Username
|
||||
| UpdateFlag::About
|
||||
|
@ -209,8 +210,20 @@ void ProcessFullPhoto(
|
|||
) | rpl::map([=] {
|
||||
const auto user = peer->asUser();
|
||||
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{
|
||||
.name = peer->name(),
|
||||
.channelName = hasChannel ? channel->name() : QString(),
|
||||
.channelLink = (hasChannel
|
||||
? channel->session().createInternalLinkFull(channelUsername)
|
||||
: QString()),
|
||||
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
|
||||
.link = ((user || username.isEmpty())
|
||||
? QString()
|
||||
|
|
|
@ -77,7 +77,7 @@ bool operator==(const Descriptor &a, const Descriptor &b) {
|
|||
struct Preload {
|
||||
Descriptor descriptor;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
std::weak_ptr<ChatHelpers::Show> show;
|
||||
std::weak_ptr<Main::SessionShow> show;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Preload> &Preloads() {
|
||||
|
|
|
@ -27,13 +27,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> Report(
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportPhoto(
|
||||
not_null<PeerData*> peer,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data,
|
||||
not_null<PhotoData*> photo,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto source = v::match(data, [](const MessageIdsList &ids) {
|
||||
return Ui::ReportSource::Message;
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
const auto source = [&] {
|
||||
return peer->isUser()
|
||||
? (photo->hasVideo()
|
||||
? Ui::ReportSource::ProfileVideo
|
||||
|
@ -45,19 +43,14 @@ namespace {
|
|||
: (photo->hasVideo()
|
||||
? Ui::ReportSource::ChannelVideo
|
||||
: 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;
|
||||
return Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto show = box->uiShow();
|
||||
Ui::ReportReasonBox(box, *st, source, [=](Ui::ReportReason reason) {
|
||||
show->showBox(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
Ui::ReportDetailsBox(box, *st, [=](const QString &text) {
|
||||
Api::SendReport(show, peer, reason, text, data);
|
||||
Api::SendPhotoReport(show, peer, reason, text, photo);
|
||||
show->hideLayer();
|
||||
});
|
||||
}));
|
||||
|
@ -70,7 +63,7 @@ namespace {
|
|||
object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo) {
|
||||
return Report(peer, photo, nullptr);
|
||||
return ReportPhoto(peer, photo, nullptr);
|
||||
}
|
||||
|
||||
void ShowReportMessageBox(
|
||||
|
@ -86,7 +79,6 @@ void ShowReportMessageBox(
|
|||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
|
@ -206,6 +198,7 @@ void ShowReportMessageBox(
|
|||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "storage/storage_account.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/widgets/chat_filters_tabs_strip.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.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 "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat_filters.h"
|
||||
#include "data/data_game.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -86,11 +88,14 @@ public:
|
|||
void activateSkipColumn(int direction);
|
||||
void activateSkipPage(int pageHeight, int direction);
|
||||
void updateFilter(QString filter = QString());
|
||||
[[nodiscard]] bool isFilterEmpty() const;
|
||||
void selectActive();
|
||||
|
||||
rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
|
||||
rpl::producer<> searchRequests() const;
|
||||
|
||||
void applyChatFilter(FilterId id);
|
||||
|
||||
protected:
|
||||
void visibleTopBottomUpdated(
|
||||
int visibleTop,
|
||||
|
@ -171,7 +176,9 @@ private:
|
|||
int _upon = -1;
|
||||
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;
|
||||
std::vector<not_null<Dialogs::Row*>> _filtered;
|
||||
|
||||
|
@ -287,6 +294,10 @@ void ShareBox::prepare() {
|
|||
|
||||
_select->setQueryChangedCallback([=](const QString &query) {
|
||||
applyFilterUpdate(query);
|
||||
if (_chatsFilters) {
|
||||
updateScrollSkips();
|
||||
scrollToY(0);
|
||||
}
|
||||
});
|
||||
_select->setItemRemovedCallback([=](uint64 itemId) {
|
||||
if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
|
||||
|
@ -342,10 +353,32 @@ void ShareBox::prepare() {
|
|||
{ .suggestCustomEmoji = true });
|
||||
|
||||
_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 {
|
||||
return _select->isHidden() ? 0 : _select->height();
|
||||
return (_select->isHidden() ? 0 : _select->height())
|
||||
+ ((_chatsFilters && _inner && _inner->isFilterEmpty())
|
||||
? _chatsFilters->height()
|
||||
: 0);
|
||||
}
|
||||
|
||||
int ShareBox::getBottomScrollSkip() const {
|
||||
|
@ -676,9 +709,10 @@ ShareBox::Inner::Inner(
|
|||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _chatsIndexed(
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add)) {
|
||||
Dialogs::SortMode::Add))
|
||||
, _chatsIndexed(_defaultChatsIndexed.get()) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
@ -696,7 +730,7 @@ ShareBox::Inner::Inner(
|
|||
const auto self = _descriptor.session->user();
|
||||
const auto selfHistory = self->owner().history(self);
|
||||
if (_descriptor.filterCallback(selfHistory)) {
|
||||
_chatsIndexed->addToEnd(selfHistory);
|
||||
_defaultChatsIndexed->addToEnd(selfHistory);
|
||||
}
|
||||
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
|
||||
for (const auto &row : list->all()) {
|
||||
|
@ -704,7 +738,7 @@ ShareBox::Inner::Inner(
|
|||
if (!history->peer->isSelf()
|
||||
&& (history->asForum()
|
||||
|| _descriptor.filterCallback(history))) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
_defaultChatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -727,7 +761,7 @@ ShareBox::Inner::Inner(
|
|||
|
||||
_descriptor.session->changes().realtimeNameUpdates(
|
||||
) | rpl::start_with_next([=](const Data::NameUpdate &update) {
|
||||
_chatsIndexed->peerNameChanged(
|
||||
_defaultChatsIndexed->peerNameChanged(
|
||||
update.peer,
|
||||
update.oldFirstLetters);
|
||||
}, 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 {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
@ -1344,6 +1382,30 @@ rpl::producer<> ShareBox::Inner::searchRequests() const {
|
|||
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(
|
||||
const QString &query,
|
||||
const QVector<MTPPeer> &my,
|
||||
|
|
|
@ -174,6 +174,8 @@ private:
|
|||
bool _peopleFull = false;
|
||||
mtpRequestId _peopleRequest = 0;
|
||||
|
||||
RpWidget *_chatsFilters = nullptr;
|
||||
|
||||
using PeopleCache = QMap<QString, MTPcontacts_Found>;
|
||||
PeopleCache _peopleCache;
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "base/event_filter.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/peer_list_controllers.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_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.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_non_panel_process.h"
|
||||
#include "settings/settings_credits.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/chat/chat_style.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(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Ui::ChatStyle*> st,
|
||||
|
@ -213,7 +233,7 @@ auto GenerateGiftMedia(
|
|||
return tr::lng_action_gift_got_stars_text(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.convertStars,
|
||||
gift.info.starsConverted,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
auto description = data.text.empty()
|
||||
|
@ -280,7 +300,7 @@ void ShowSentToast(
|
|||
return tr::lng_gift_sent_about(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.stars,
|
||||
gift.info.stars,
|
||||
Ui::Text::RichLangValue);
|
||||
});
|
||||
const auto strong = window->showToast({
|
||||
|
@ -338,7 +358,10 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
|||
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
|
||||
return FillAmountAndCurrency(data.cost, data.currency, true);
|
||||
}, [&](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(
|
||||
tr::now,
|
||||
|
@ -508,14 +531,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
|||
const auto &gifts = api->starGifts();
|
||||
list.reserve(gifts.size());
|
||||
for (auto &gift : gifts) {
|
||||
list.push_back({
|
||||
.id = gift.id,
|
||||
.stars = gift.stars,
|
||||
.convertStars = gift.convertStars,
|
||||
.document = gift.document,
|
||||
.limitedCount = gift.limitedCount,
|
||||
.limitedLeft = gift.limitedLeft,
|
||||
});
|
||||
list.push_back({ .info = gift });
|
||||
}
|
||||
auto &map = Map[session];
|
||||
if (map.last != list) {
|
||||
|
@ -587,7 +603,8 @@ struct GiftPriceTabs {
|
|||
auto sameKey = 0;
|
||||
for (const auto &gift : gifts) {
|
||||
if (same) {
|
||||
const auto key = gift.stars * (gift.limitedCount ? -1 : 1);
|
||||
const auto key = gift.info.stars
|
||||
* (gift.info.limitedCount ? -1 : 1);
|
||||
if (!sameKey) {
|
||||
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.insert(begin(result) + 1, kPriceTabLimited);
|
||||
}
|
||||
if (!ranges::contains(result, gift.stars)) {
|
||||
result.push_back(gift.stars);
|
||||
if (!ranges::contains(result, gift.info.stars)) {
|
||||
result.push_back(gift.info.stars);
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
|
@ -838,16 +855,38 @@ void SendGift(
|
|||
const auto processNonPanelPaymentFormFactory
|
||||
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
|
||||
Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{
|
||||
.giftId = gift.id,
|
||||
.giftId = gift.info.id,
|
||||
.randomId = details.randomId,
|
||||
.message = details.text,
|
||||
.user = peer->asUser(),
|
||||
.limitedCount = gift.limitedCount,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
.anonymous = details.anonymous,
|
||||
}, 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(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
|
@ -873,7 +912,7 @@ void SendGiftBox(
|
|||
};
|
||||
}, [&](const GiftTypeStars &data) {
|
||||
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 available = width - padding.left() - padding.right();
|
||||
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 y = padding.top();
|
||||
state->buttons.resize(gifts.list.size());
|
||||
state->buttons.resize(count);
|
||||
for (auto &button : state->buttons) {
|
||||
if (!button) {
|
||||
button = std::make_unique<GiftButton>(raw, &state->delegate);
|
||||
|
@ -1057,9 +1109,9 @@ void SendGiftBox(
|
|||
}
|
||||
}
|
||||
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 &descriptor = gifts.list[i];
|
||||
const auto &descriptor = gifts.list[order[i]];
|
||||
button->setDescriptor(descriptor);
|
||||
|
||||
const auto last = !((i + 1) % perRow);
|
||||
|
@ -1076,27 +1128,22 @@ void SendGiftBox(
|
|||
|
||||
button->setClickedCallback([=] {
|
||||
const auto star = std::get_if<GiftTypeStars>(&descriptor);
|
||||
if (star && star->limitedCount && !star->limitedLeft) {
|
||||
window->showToast({
|
||||
.title = tr::lng_gift_sold_out_title(tr::now),
|
||||
.text = tr::lng_gift_sold_out_text(
|
||||
tr::now,
|
||||
lt_count_decimal,
|
||||
star->limitedCount,
|
||||
Ui::Text::RichLangValue),
|
||||
});
|
||||
if (star
|
||||
&& star->info.limitedCount
|
||||
&& !star->info.limitedLeft) {
|
||||
window->show(Box(SoldOutBox, window, *star));
|
||||
} else {
|
||||
window->show(
|
||||
Box(SendGiftBox, window, peer, api, descriptor));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (gifts.list.size() % perRow) {
|
||||
if (count % perRow) {
|
||||
y += padding.bottom() + single.height();
|
||||
} else {
|
||||
y += padding.bottom() - st::giftBoxGiftSkip.y();
|
||||
}
|
||||
raw->resize(raw->width(), gifts.list.empty() ? 0 : y);
|
||||
raw->resize(raw->width(), count ? y : 0);
|
||||
}, raw->lifetime());
|
||||
|
||||
return result;
|
||||
|
@ -1187,8 +1234,8 @@ void AddBlock(
|
|||
) | rpl::map([=](std::vector<GiftTypeStars> &&gifts, int price) {
|
||||
gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) {
|
||||
return (price == kPriceTabLimited)
|
||||
? (!gift.limitedCount)
|
||||
: (price && gift.stars != price);
|
||||
? (!gift.info.limitedCount)
|
||||
: (price && gift.info.stars != price);
|
||||
}), end(gifts));
|
||||
return GiftsDescriptor{
|
||||
gifts | ranges::to<std::vector<GiftDescriptor>>(),
|
||||
|
|
|
@ -409,9 +409,7 @@ callRatingStar: IconButton {
|
|||
icon: icon {{ "calls/call_rating", windowSubTextFg }};
|
||||
iconPosition: point(-1px, -1px);
|
||||
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 36px;
|
||||
}
|
||||
|
@ -1410,9 +1408,7 @@ groupCallRtmpShowButton: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
rippleAreaSize: 32px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
groupCallSettingsRtmpShowButton: IconButton(groupCallRtmpShowButton) {
|
||||
ripple: groupCallRipple;
|
||||
|
|
|
@ -9,7 +9,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "apiwrap.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 "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -169,7 +171,12 @@ void StartRtmpProcess::finish(JoinInfo info) {
|
|||
void StartRtmpProcess::createBox() {
|
||||
auto done = [=] {
|
||||
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 = [=] {
|
||||
const auto guard = base::make_weak(&_request->guard);
|
||||
|
|
|
@ -73,8 +73,8 @@ private:
|
|||
|
||||
SourceButton _widget;
|
||||
FlatLabel _label;
|
||||
RoundRect _selectedRect;
|
||||
RoundRect _activeRect;
|
||||
Ui::RoundRect _selectedRect;
|
||||
Ui::RoundRect _activeRect;
|
||||
tgcalls::DesktopCaptureSource _source;
|
||||
std::unique_ptr<Preview> _preview;
|
||||
rpl::event_stream<> _activations;
|
||||
|
|
|
@ -150,6 +150,8 @@ SendButton {
|
|||
inner: IconButton;
|
||||
record: icon;
|
||||
recordOver: icon;
|
||||
round: icon;
|
||||
roundOver: icon;
|
||||
sendDisabledFg: color;
|
||||
}
|
||||
|
||||
|
@ -331,9 +333,7 @@ stickersRemove: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(0px, 0px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
stickersUndoRemove: RoundButton(defaultLightButton) {
|
||||
width: -16px;
|
||||
|
@ -494,9 +494,7 @@ hashtagClose: IconButton {
|
|||
|
||||
rippleAreaPosition: point(5px, 5px);
|
||||
rippleAreaSize: 20px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
stickerPanWidthMin: 64px;
|
||||
|
@ -898,9 +896,7 @@ historyBusinessBotSettings: IconButton(defaultIconButton) {
|
|||
iconPosition: point(-1px, -1px);
|
||||
rippleAreaSize: 40px;
|
||||
rippleAreaPosition: point(4px, 9px);
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
height: 58px;
|
||||
width: 48px;
|
||||
}
|
||||
|
@ -927,9 +923,7 @@ historyReplyCancel: IconButton {
|
|||
|
||||
rippleAreaPosition: point(4px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
historyPinnedShowAll: IconButton(historyReplyCancel) {
|
||||
icon: icon {{ "pinned_show_all", historyReplyCancelFg }};
|
||||
|
@ -1058,9 +1052,7 @@ historyAttach: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaPosition: point(2px, 3px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
historyMessagesTTL: IconButtonWithText {
|
||||
|
@ -1082,6 +1074,13 @@ historyReplaceMedia: IconButton(historyAttach) {
|
|||
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 }};
|
||||
historyEmojiCircle: size(20px, 20px);
|
||||
|
@ -1169,6 +1168,10 @@ historyRecordVoiceOnceFg: icon {{ "voice_lock/audio_once_number", windowFgActive
|
|||
historyRecordVoiceOnceFgOver: icon {{ "voice_lock/audio_once_number", windowFgActive }};
|
||||
historyRecordVoiceOnceInactive: icon {{ "chat/audio_once", windowSubTextFg }};
|
||||
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);
|
||||
historyRecordVoiceRippleBgActive: lightButtonBgOver;
|
||||
historyRecordSignalRadius: 5px;
|
||||
|
@ -1214,6 +1217,7 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
|
|||
historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
|
||||
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
|
||||
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
|
||||
historyRecordLockRound: icon {{ "voice_lock/input_round_s", historyToDownFg }};
|
||||
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
|
||||
|
||||
historyRecordDelete: IconButton(historyAttach) {
|
||||
|
@ -1274,6 +1278,8 @@ historySend: SendButton {
|
|||
}
|
||||
record: historyRecordVoice;
|
||||
recordOver: historyRecordVoiceOver;
|
||||
round: historyRecordRound;
|
||||
roundOver: historyRecordRoundOver;
|
||||
sendDisabledFg: historyComposeIconFg;
|
||||
}
|
||||
|
||||
|
@ -1287,9 +1293,7 @@ defaultComposeFilesMenu: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaPosition: point(1px, 6px);
|
||||
rippleAreaSize: 42px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
defaultComposeFilesField: InputField(defaultInputField) {
|
||||
textMargins: margins(1px, 26px, 31px, 4px);
|
||||
|
@ -1349,9 +1353,7 @@ moreChatsBarClose: IconButton(defaultIconButton) {
|
|||
|
||||
rippleAreaPosition: point(0px, 4px);
|
||||
rippleAreaSize: 40px;
|
||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||
color: windowBgOver;
|
||||
}
|
||||
ripple: defaultRippleAnimationBgOver;
|
||||
}
|
||||
|
||||
reportReasonTopSkip: 8px;
|
||||
|
@ -1517,3 +1519,22 @@ pickLocationChooseOnMap: RoundButton(defaultActiveButton) {
|
|||
sendGifBox: Box(defaultBox) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -822,7 +822,7 @@ void StickersListFooter::mousePressEvent(QMouseEvent *e) {
|
|||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
_iconsMousePos = e ? e->globalPos() : QCursor::pos();
|
||||
_iconsMousePos = e->globalPos();
|
||||
updateSelected();
|
||||
|
||||
if (_selected == SpecialOver::Settings) {
|
||||
|
|
|
@ -192,9 +192,7 @@ std::unique_ptr<Lottie::SinglePlayer> LottieThumbnail(
|
|||
};
|
||||
const auto session = thumb
|
||||
? &thumb->owner()->session()
|
||||
: media
|
||||
? &media->owner()->session()
|
||||
: nullptr;
|
||||
: &media->owner()->session();
|
||||
return LottieCachedFromContent(
|
||||
method,
|
||||
baseKey,
|
||||
|
|
|
@ -35,6 +35,10 @@ base::options::toggle TabbedPanelShowOnClick({
|
|||
|
||||
const char kOptionTabbedPanelShowOnClick[] = "tabbed-panel-show-on-click";
|
||||
|
||||
bool ShowPanelOnClick() {
|
||||
return TabbedPanelShowOnClick.value();
|
||||
}
|
||||
|
||||
TabbedPanel::TabbedPanel(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace ChatHelpers {
|
|||
class TabbedSelector;
|
||||
|
||||
extern const char kOptionTabbedPanelShowOnClick[];
|
||||
[[nodiscard]] bool ShowPanelOnClick();
|
||||
|
||||
struct TabbedPanelDescriptor {
|
||||
Window::SessionController *regularWindow = nullptr;
|
||||
|
|
|
@ -189,6 +189,7 @@ void BotGameUrlClickHandler::onClick(ClickContext context) const {
|
|||
const auto game = media ? media->game() : nullptr;
|
||||
if (url.startsWith(u"tg://"_q, Qt::CaseInsensitive) || !_bot || !game) {
|
||||
openLink();
|
||||
return;
|
||||
}
|
||||
const auto bot = _bot;
|
||||
const auto title = game->title;
|
||||
|
|
|
@ -16,6 +16,7 @@ constexpr auto kDocumentLinkMediaProperty = 0x03;
|
|||
constexpr auto kSendReactionEmojiProperty = 0x04;
|
||||
constexpr auto kReactionsCountEmojiProperty = 0x05;
|
||||
constexpr auto kDocumentFilenameTooltipProperty = 0x06;
|
||||
constexpr auto kPhoneNumberLinkProperty = 0x07;
|
||||
|
||||
namespace Ui {
|
||||
class Show;
|
||||
|
|
|
@ -27,6 +27,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Core {
|
||||
namespace {
|
||||
|
||||
constexpr auto kInitialVideoQuality = 480; // Start with SD.
|
||||
|
||||
[[nodiscard]] WindowPosition Deserialize(const QByteArray &data) {
|
||||
QDataStream stream(data);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
@ -92,6 +94,21 @@ void LogPosition(const WindowPosition &position, const QString &name) {
|
|||
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
|
||||
|
||||
[[nodiscard]] WindowPosition AdjustToScale(
|
||||
|
@ -128,7 +145,8 @@ Settings::Settings()
|
|||
, _floatPlayerColumn(Window::Column::Second)
|
||||
, _floatPlayerCorner(RectPart::TopRight)
|
||||
, _dialogsWithChatWidthRatio(DefaultDialogsWidthRatio())
|
||||
, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio()) {
|
||||
, _dialogsNoChatWidthRatio(DefaultDialogsWidthRatio())
|
||||
, _videoQuality({ .height = kInitialVideoQuality }) {
|
||||
}
|
||||
|
||||
Settings::~Settings() = default;
|
||||
|
@ -226,7 +244,7 @@ QByteArray Settings::serialize() const {
|
|||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32) * 2;
|
||||
+ sizeof(qint32) * 7;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
|
@ -295,7 +313,7 @@ QByteArray Settings::serialize() const {
|
|||
<< qint32(_thirdSectionExtendedBy)
|
||||
<< qint32(_notifyFromAll ? 1 : 0)
|
||||
<< qint32(_nativeWindowFrame.current() ? 1 : 0)
|
||||
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0)
|
||||
<< qint32(0) // Legacy system dark mode
|
||||
<< _cameraDeviceId.current()
|
||||
<< qint32(_ipRevealWarning ? 1 : 0)
|
||||
<< qint32(_groupCallPushToTalk ? 1 : 0)
|
||||
|
@ -382,7 +400,12 @@ QByteArray Settings::serialize() const {
|
|||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken
|
||||
<< 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);
|
||||
|
@ -509,6 +532,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
|
||||
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
|
||||
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;
|
||||
if (!stream.atEnd()) {
|
||||
|
@ -590,6 +617,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
stream >> nativeWindowFrame;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
// Read over this one below, if was in the file.
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
|
@ -818,9 +846,24 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
if (!stream.atEnd()) {
|
||||
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()) {
|
||||
stream >> ivZoom;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> systemDarkModeEnabled;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
|
@ -1034,6 +1077,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
: (weatherInCelsius == 1);
|
||||
_tonsiteStorageToken = tonsiteStorageToken;
|
||||
_ivZoom = ivZoom;
|
||||
_skipToastsInFocus = (skipToastsInFocus == 1);
|
||||
_recordVideoMessages = (recordVideoMessages == 1);
|
||||
_videoQuality = DeserializeVideoQuality(videoQuality);
|
||||
_chatFiltersHorizontal = (chatFiltersHorizontal == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
@ -1360,6 +1407,7 @@ void Settings::resetOnLastLogout() {
|
|||
_flashBounceNotify = true;
|
||||
_notifyView = NotifyView::ShowPreview;
|
||||
//_nativeNotifications = std::nullopt;
|
||||
//_skipToastsInFocus = false;
|
||||
//_notificationsCount = 3;
|
||||
//_notificationsCorner = ScreenCorner::BottomRight;
|
||||
_includeMutedCounter = true;
|
||||
|
@ -1417,11 +1465,13 @@ void Settings::resetOnLastLogout() {
|
|||
_thirdColumnWidth = kDefaultThirdColumnWidth; // p-w
|
||||
_notifyFromAll = true;
|
||||
_tabbedReplacedWithInfo = false; // per-window
|
||||
_systemDarkModeEnabled = false;
|
||||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
_recordVideoMessages = false;
|
||||
_videoQuality = {};
|
||||
_chatFiltersHorizontal = false;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
@ -1479,6 +1529,14 @@ void Settings::setNativeNotifications(bool value) {
|
|||
: std::make_optional(value);
|
||||
}
|
||||
|
||||
bool Settings::skipToastsInFocus() const {
|
||||
return _skipToastsInFocus;
|
||||
}
|
||||
|
||||
void Settings::setSkipToastsInFocus(bool value) {
|
||||
_skipToastsInFocus = value;
|
||||
}
|
||||
|
||||
void Settings::setTranslateButtonEnabled(bool value) {
|
||||
_translateButtonEnabled = value;
|
||||
}
|
||||
|
@ -1557,6 +1615,7 @@ auto Settings::skipTranslationLanguagesValue() const
|
|||
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
|
||||
_rememberedDeleteMessageOnlyForYou = value;
|
||||
}
|
||||
|
||||
bool Settings::rememberedDeleteMessageOnlyForYou() const {
|
||||
return _rememberedDeleteMessageOnlyForYou;
|
||||
}
|
||||
|
@ -1564,13 +1623,40 @@ bool Settings::rememberedDeleteMessageOnlyForYou() const {
|
|||
int Settings::ivZoom() const {
|
||||
return _ivZoom.current();
|
||||
}
|
||||
|
||||
rpl::producer<int> Settings::ivZoomValue() const {
|
||||
return _ivZoom.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 kMax = 200;
|
||||
#endif
|
||||
_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
|
||||
|
|
|
@ -226,6 +226,9 @@ public:
|
|||
[[nodiscard]] bool nativeNotifications() const;
|
||||
void setNativeNotifications(bool value);
|
||||
|
||||
[[nodiscard]] bool skipToastsInFocus() const;
|
||||
void setSkipToastsInFocus(bool value);
|
||||
|
||||
[[nodiscard]] int notificationsCount() const {
|
||||
return _notificationsCount;
|
||||
}
|
||||
|
@ -631,6 +634,13 @@ public:
|
|||
return _floatPlayerCorner;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool recordVideoMessages() const {
|
||||
return _recordVideoMessages;
|
||||
}
|
||||
void setRecordVideoMessages(bool value) {
|
||||
_recordVideoMessages = value;
|
||||
}
|
||||
|
||||
void updateDialogsWidthRatio(float64 ratio, bool nochat);
|
||||
[[nodiscard]] float64 dialogsWidthRatio(bool nochat) const;
|
||||
|
||||
|
@ -925,6 +935,13 @@ public:
|
|||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
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 float64 DefaultDialogsWidthRatio();
|
||||
|
||||
|
@ -964,6 +981,7 @@ private:
|
|||
bool _flashBounceNotify = true;
|
||||
NotifyView _notifyView = NotifyView::ShowPreview;
|
||||
std::optional<bool> _nativeNotifications;
|
||||
bool _skipToastsInFocus = false;
|
||||
int _notificationsCount = 3;
|
||||
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
|
||||
bool _includeMutedCounter = true;
|
||||
|
@ -1024,7 +1042,7 @@ private:
|
|||
bool _notifyFromAll = true;
|
||||
rpl::variable<bool> _nativeWindowFrame = false;
|
||||
rpl::variable<std::optional<bool>> _systemDarkMode = std::nullopt;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = false;
|
||||
rpl::variable<bool> _systemDarkModeEnabled = true;
|
||||
rpl::variable<WindowTitleContent> _windowTitleContent;
|
||||
WindowPosition _windowPosition; // per-window
|
||||
bool _disableOpenGL = false;
|
||||
|
@ -1061,6 +1079,8 @@ private:
|
|||
std::optional<bool> _weatherInCelsius;
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
Media::VideoQuality _videoQuality;
|
||||
rpl::variable<bool> _chatFiltersHorizontal = false;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
@ -1071,6 +1091,8 @@ private:
|
|||
bool _rememberedFlashBounceNotifyFromTray = false;
|
||||
bool _dialogsWidthSetToZeroWithoutChat = false;
|
||||
|
||||
bool _recordVideoMessages = false;
|
||||
|
||||
QByteArray _photoEditorBrush;
|
||||
|
||||
};
|
||||
|
|
|
@ -179,6 +179,34 @@ auto PersonalChannelController::chosen() const
|
|||
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(
|
||||
not_null<Window::SessionController*> window,
|
||||
ChannelData *channel) {
|
||||
|
@ -475,6 +503,20 @@ bool ResolveUsernameOrPhone(
|
|||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
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 appnameParam = params.value(u"appname"_q);
|
||||
const auto myContext = context.value<ClickHandlerContext>();
|
||||
|
@ -580,6 +622,7 @@ bool ResolveUsernameOrPhone(
|
|||
.startAutoSubmit = myContext.botStartAutoSubmit,
|
||||
.botAppName = (appname.isEmpty() ? postParam : appname),
|
||||
.botAppForceConfirmation = myContext.mayShowConfirmation,
|
||||
.botAppFullScreen = (params.value(u"mode"_q) == u"fullscreen"_q),
|
||||
.attachBotUsername = params.value(u"attach"_q),
|
||||
.attachBotToggleCommand = (params.contains(u"startattach"_q)
|
||||
? params.value(u"startattach"_q)
|
||||
|
@ -969,6 +1012,17 @@ bool ShowStarsExamples(
|
|||
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(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
|
@ -1443,6 +1497,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
|||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
},
|
||||
{
|
||||
u"^about_popular_apps$"_q,
|
||||
ShowPopularAppsAbout,
|
||||
},
|
||||
};
|
||||
return Result;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "core/phone_click_handler.h"
|
||||
|
||||
#include "boxes/add_contact_box.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -48,6 +49,9 @@ public:
|
|||
|
||||
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||
|
||||
[[nodiscard]] QString firstName() const;
|
||||
[[nodiscard]] QString lastName() const;
|
||||
|
||||
protected:
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
@ -130,6 +134,18 @@ ResolvePhoneAction::ResolvePhoneAction(
|
|||
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) {
|
||||
const auto selected = isSelected() && _peer.current();
|
||||
const auto height = contentHeight();
|
||||
|
@ -275,6 +291,7 @@ PhoneClickHandler::PhoneClickHandler(
|
|||
QString text)
|
||||
: _session(session)
|
||||
, _text(text) {
|
||||
setProperty(kPhoneNumberLinkProperty, _text);
|
||||
}
|
||||
|
||||
void PhoneClickHandler::onClick(ClickContext context) const {
|
||||
|
@ -314,14 +331,29 @@ void PhoneClickHandler::onClick(ClickContext context) const {
|
|||
TextForMimeData::Simple(phone.trimmed()));
|
||||
}, &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->addAction(
|
||||
base::make_unique_q<ResolvePhoneAction>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
phone,
|
||||
controller));
|
||||
menu->addAction(std::move(resolvePhoneAction));
|
||||
|
||||
menu->popup(pos);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||
constexpr auto AppFile = "AyuGram"_cs;
|
||||
constexpr auto AppVersion = 5006003;
|
||||
constexpr auto AppVersionStr = "5.6.3";
|
||||
constexpr auto AppVersion = 5008002;
|
||||
constexpr auto AppVersionStr = "5.8.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -74,6 +74,11 @@ uint64 Credits::balance(PeerId peerId) const {
|
|||
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 {
|
||||
return _nonLockedBalance.value();
|
||||
}
|
||||
|
@ -128,4 +133,8 @@ void Credits::apply(PeerId peerId, uint64 balance) {
|
|||
_cachedPeerBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
void Credits::applyCurrency(PeerId peerId, uint64 balance) {
|
||||
_cachedPeerCurrencyBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -35,6 +35,9 @@ public:
|
|||
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel);
|
||||
|
||||
void applyCurrency(PeerId peerId, uint64 balance);
|
||||
[[nodiscard]] uint64 balanceCurrency(PeerId peerId) const;
|
||||
|
||||
void lock(int count);
|
||||
void unlock(int count);
|
||||
void withdrawLocked(int count);
|
||||
|
@ -50,6 +53,7 @@ private:
|
|||
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||
|
||||
base::flat_map<PeerId, uint64> _cachedPeerBalances;
|
||||
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
|
||||
|
||||
uint64 _balance = 0;
|
||||
uint64 _locked = 0;
|
||||
|
|
|
@ -112,6 +112,7 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
|||
).arg(streamAppVersion));
|
||||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
const auto streamPosition = stream.underlying().device()->pos();
|
||||
const auto peer = Serialize::readPeer(
|
||||
_session,
|
||||
streamAppVersion,
|
||||
|
@ -123,7 +124,8 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
|||
DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2."
|
||||
).arg(i + 1
|
||||
).arg(count));
|
||||
_list.clear();
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,10 +343,20 @@ void ScheduledMessages::apply(
|
|||
if (i == end(_data)) {
|
||||
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 j = list.itemById.find(id.v);
|
||||
const auto j = list.itemById.find(id);
|
||||
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();
|
||||
i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
|
|
|
@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/history_view_element.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/sponsored_message_bar.h"
|
||||
#include "ui/text/text_utilities.h" // Ui::Text::RichLangValue.
|
||||
|
||||
// AyuGram includes
|
||||
|
@ -77,6 +78,9 @@ void SponsoredMessages::clearOldRequests() {
|
|||
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
if (isTopBarFor(history)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
|
@ -198,9 +202,31 @@ void SponsoredMessages::inject(
|
|||
}
|
||||
|
||||
bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
|
||||
// AyuGram disableAds
|
||||
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) {
|
||||
|
@ -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(
|
||||
MTPchannels_GetSponsoredMessages(channel->inputChannel)
|
||||
MTPmessages_GetSponsoredMessages(history->peer->input)
|
||||
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
if (done) {
|
||||
|
@ -263,12 +287,62 @@ void SponsoredMessages::parse(
|
|||
list.postsBetween = postsBetween->v;
|
||||
list.state = State::InjectToMiddle;
|
||||
} else {
|
||||
list.state = State::AppendToEnd;
|
||||
list.state = history->peer->isChannel()
|
||||
? State::AppendToEnd
|
||||
: State::AppendToTopBar;
|
||||
}
|
||||
}, [](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(
|
||||
not_null<History*> history,
|
||||
List &list,
|
||||
|
@ -289,7 +363,9 @@ void SponsoredMessages::append(
|
|||
}, [&](const MTPDmessageMediaDocument &media) {
|
||||
if (const auto tlDocument = media.vdocument()) {
|
||||
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()
|
||||
|| d->isSilentVideo()
|
||||
|| d->isAnimation()
|
||||
|
@ -412,7 +488,7 @@ void SponsoredMessages::clearItems(not_null<History*> history) {
|
|||
|
||||
const SponsoredMessages::Entry *SponsoredMessages::find(
|
||||
const FullMsgId &fullId) const {
|
||||
if (!peerIsChannel(fullId.peer)) {
|
||||
if (!peerIsChannel(fullId.peer) && !peerIsUser(fullId.peer)) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto history = _session->data().history(fullId.peer);
|
||||
|
@ -440,11 +516,11 @@ void SponsoredMessages::view(const FullMsgId &fullId) {
|
|||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_ViewSponsoredMessage(
|
||||
channel->inputChannel,
|
||||
MTPmessages_ViewSponsoredMessage(
|
||||
entryPtr->item
|
||||
? entryPtr->item->history()->peer->input
|
||||
: _session->data().peer(fullId.peer)->input,
|
||||
MTP_bytes(randomId))
|
||||
).done([=] {
|
||||
auto &request = _viewRequests[randomId];
|
||||
|
@ -495,14 +571,14 @@ void SponsoredMessages::clicked(
|
|||
return;
|
||||
}
|
||||
const auto randomId = entryPtr->sponsored.randomId;
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
using Flag = MTPchannels_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPchannels_ClickSponsoredMessage(
|
||||
using Flag = MTPmessages_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPmessages_ClickSponsoredMessage(
|
||||
MTP_flags(Flag(0)
|
||||
| (isMedia ? Flag::f_media : 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)
|
||||
)).send();
|
||||
}
|
||||
|
@ -531,11 +607,7 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
|
|||
return;
|
||||
}
|
||||
|
||||
const auto history = entry->item->history();
|
||||
const auto channel = history->peer->asChannel();
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
const auto history = _session->data().history(fullId.peer);
|
||||
|
||||
const auto erase = [=] {
|
||||
const auto it = _data.find(history);
|
||||
|
@ -554,8 +626,8 @@ auto SponsoredMessages::createReportCallback(const FullMsgId &fullId)
|
|||
}
|
||||
|
||||
state->requestId = _session->api().request(
|
||||
MTPchannels_ReportSponsoredMessage(
|
||||
channel->inputChannel,
|
||||
MTPmessages_ReportSponsoredMessage(
|
||||
history->peer->input,
|
||||
MTP_bytes(entry->sponsored.randomId),
|
||||
MTP_bytes(optionId))
|
||||
).done([=](
|
||||
|
|
|
@ -18,6 +18,10 @@ namespace Main {
|
|||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
|
||||
class MediaPreload;
|
||||
|
@ -76,6 +80,7 @@ public:
|
|||
None,
|
||||
AppendToEnd,
|
||||
InjectToMiddle,
|
||||
AppendToTopBar,
|
||||
};
|
||||
struct Details {
|
||||
std::vector<TextWithEntities> info;
|
||||
|
@ -94,10 +99,15 @@ public:
|
|||
~SponsoredMessages();
|
||||
|
||||
[[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 clearItems(not_null<History*> history);
|
||||
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
||||
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);
|
||||
void inject(
|
||||
|
@ -122,6 +132,7 @@ private:
|
|||
FullMsgId itemFullId;
|
||||
SponsoredMessage sponsored;
|
||||
std::unique_ptr<MediaPreload> preload;
|
||||
std::unique_ptr<rpl::lifetime> optionalDestructionNotifier;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Entry> entries;
|
||||
|
@ -156,6 +167,8 @@ private:
|
|||
base::flat_map<not_null<History*>, Request> _requests;
|
||||
base::flat_map<RandomId, Request> _viewRequests;
|
||||
|
||||
rpl::event_stream<FullMsgId> _itemRemoved;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -300,6 +300,7 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
|||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
auto rating = quint64();
|
||||
const auto streamPosition = stream.underlying().device()->pos();
|
||||
const auto peer = Serialize::readPeer(
|
||||
_session,
|
||||
streamAppVersion,
|
||||
|
@ -313,6 +314,8 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
|||
} else {
|
||||
DEBUG_LOG(("Suggestions: "
|
||||
"Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count));
|
||||
DEBUG_LOG(("Failed bytes: %1.").arg(
|
||||
QString::fromUtf8(serialized.mid(streamPosition).toHex())));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -46,12 +46,15 @@ struct CreditsHistoryEntry final {
|
|||
Unsupported,
|
||||
PremiumBot,
|
||||
Ads,
|
||||
API,
|
||||
};
|
||||
|
||||
QString id;
|
||||
QString title;
|
||||
TextWithEntities description;
|
||||
QDateTime date;
|
||||
QDateTime firstSaleDate;
|
||||
QDateTime lastSaleDate;
|
||||
PhotoId photoId = 0;
|
||||
std::vector<CreditsHistoryMedia> extended;
|
||||
uint64 credits = 0;
|
||||
|
@ -59,23 +62,27 @@ struct CreditsHistoryEntry final {
|
|||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
QString successLink;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int convertStars = 0;
|
||||
bool converted = false;
|
||||
bool anonymous = false;
|
||||
bool savedToProfile = false;
|
||||
bool fromGiftsList = false;
|
||||
bool reaction = false;
|
||||
bool refunded = false;
|
||||
bool pending = false;
|
||||
bool failed = false;
|
||||
bool in = false;
|
||||
bool gift = false;
|
||||
int starsConverted = 0;
|
||||
int floodSkip = 0;
|
||||
bool converted : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool stargift : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool soldOutInfo : 1 = false;
|
||||
bool reaction : 1 = false;
|
||||
bool refunded : 1 = false;
|
||||
bool pending : 1 = false;
|
||||
bool failed : 1 = false;
|
||||
bool in : 1 = false;
|
||||
bool gift : 1 = false;
|
||||
};
|
||||
|
||||
struct CreditsStatusSlice final {
|
||||
|
|
|
@ -332,6 +332,8 @@ void DocumentData::setattributes(
|
|||
|
||||
validateLottieSticker();
|
||||
|
||||
auto wasVideoData = isVideoFile() ? std::move(_additional) : nullptr;
|
||||
|
||||
_videoPreloadPrefix = 0;
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
|
||||
|
@ -388,11 +390,21 @@ void DocumentData::setattributes(
|
|||
: VideoDocument;
|
||||
if (data.is_round_message()) {
|
||||
_additional = std::make_unique<RoundData>();
|
||||
} else if (const auto size = data.vpreload_prefix_size()) {
|
||||
if (size->v > 0 && size->v < kMaxAllowedPreloadPrefix) {
|
||||
_videoPreloadPrefix = size->v;
|
||||
} else {
|
||||
if (const auto size = data.vpreload_prefix_size()) {
|
||||
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()) {
|
||||
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() {
|
||||
if (type == FileDocument
|
||||
&& hasMimeType(u"application/x-tgsticker"_q)) {
|
||||
|
@ -631,6 +745,14 @@ bool DocumentData::emojiUsesTextColor() const {
|
|||
return (_flags & Flag::UseTextColor);
|
||||
}
|
||||
|
||||
void DocumentData::overrideEmojiUsesTextColor(bool value) {
|
||||
if (value) {
|
||||
_flags |= Flag::UseTextColor;
|
||||
} else {
|
||||
_flags &= ~Flag::UseTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::hasThumbnail() const {
|
||||
return _thumbnail.location.valid()
|
||||
&& !thumbnailFailed()
|
||||
|
@ -1384,6 +1506,16 @@ const RoundData *DocumentData::round() const {
|
|||
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 {
|
||||
return (_dc != 0 && _access != 0);
|
||||
}
|
||||
|
|
|
@ -31,11 +31,13 @@ struct Key;
|
|||
} // namespace Storage
|
||||
|
||||
namespace Media {
|
||||
namespace Streaming {
|
||||
class Loader;
|
||||
} // namespace Streaming
|
||||
struct VideoQuality;
|
||||
} // namespace Media
|
||||
|
||||
namespace Media::Streaming {
|
||||
class Loader;
|
||||
} // namespace Media::Streaming
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class DocumentMedia;
|
||||
|
@ -92,6 +94,11 @@ struct VoiceData : public DocumentAdditionalData {
|
|||
char wavemax = 0;
|
||||
};
|
||||
|
||||
struct VideoData : public DocumentAdditionalData {
|
||||
QString codec;
|
||||
std::vector<not_null<DocumentData*>> qualities;
|
||||
};
|
||||
|
||||
using RoundData = VoiceData;
|
||||
|
||||
namespace Serialize {
|
||||
|
@ -108,8 +115,16 @@ public:
|
|||
|
||||
void setattributes(
|
||||
const QVector<MTPDocumentAttribute> &attributes);
|
||||
void setVideoQualities(const QVector<MTPDocument> &list);
|
||||
|
||||
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]] QString loadingFilePath() const;
|
||||
|
@ -161,6 +176,8 @@ public:
|
|||
[[nodiscard]] const VoiceData *voice() const;
|
||||
[[nodiscard]] RoundData *round();
|
||||
[[nodiscard]] const RoundData *round() const;
|
||||
[[nodiscard]] VideoData *video();
|
||||
[[nodiscard]] const VideoData *video() const;
|
||||
|
||||
void forceIsStreamedAnimation();
|
||||
[[nodiscard]] bool isVoiceMessage() const;
|
||||
|
@ -189,6 +206,7 @@ public:
|
|||
[[nodiscard]] bool isPremiumSticker() const;
|
||||
[[nodiscard]] bool isPremiumEmoji() const;
|
||||
[[nodiscard]] bool emojiUsesTextColor() const;
|
||||
void overrideEmojiUsesTextColor(bool value);
|
||||
|
||||
[[nodiscard]] bool hasThumbnail() const;
|
||||
[[nodiscard]] bool thumbnailLoading() const;
|
||||
|
|
|
@ -87,6 +87,7 @@ struct FileReferenceAccumulator {
|
|||
push(data.vphoto());
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
push(data.vdocument());
|
||||
push(data.valt_documents());
|
||||
}, [&](const MTPDmessageMediaWebPage &data) {
|
||||
push(data.vwebpage());
|
||||
}, [&](const MTPDmessageMediaGame &data) {
|
||||
|
|
|
@ -112,7 +112,9 @@ private:
|
|||
static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip;
|
||||
|
||||
[[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)
|
||||
|
|
|
@ -551,6 +551,10 @@ DocumentData *Media::document() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool Media::hasQualitiesList() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
PhotoData *Media::photo() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -964,12 +968,14 @@ MediaFile::MediaFile(
|
|||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> document,
|
||||
bool skipPremiumEffect,
|
||||
bool hasQualitiesList,
|
||||
bool spoiler,
|
||||
crl::time ttlSeconds)
|
||||
: Media(parent)
|
||||
, _document(document)
|
||||
, _emoji(document->sticker() ? document->sticker()->alt : QString())
|
||||
, _skipPremiumEffect(skipPremiumEffect)
|
||||
, _hasQualitiesList(hasQualitiesList)
|
||||
, _spoiler(spoiler)
|
||||
, _ttlSeconds(ttlSeconds) {
|
||||
parent->history()->owner().registerDocumentItem(_document, parent);
|
||||
|
@ -999,6 +1005,7 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
|
|||
parent,
|
||||
_document,
|
||||
!_document->session().premium(),
|
||||
_hasQualitiesList,
|
||||
_spoiler,
|
||||
_ttlSeconds);
|
||||
}
|
||||
|
@ -1007,6 +1014,10 @@ DocumentData *MediaFile::document() const {
|
|||
return _document;
|
||||
}
|
||||
|
||||
bool MediaFile::hasQualitiesList() const {
|
||||
return _hasQualitiesList;
|
||||
}
|
||||
|
||||
bool MediaFile::uploading() const {
|
||||
return _document->uploading();
|
||||
}
|
||||
|
|