Merge v4.14.13

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp
#	Telegram/SourceFiles/history/view/media/history_view_photo.cpp
#	Telegram/SourceFiles/history/view/reactions/history_view_reactions_selector.cpp
#	Telegram/SourceFiles/mainwidget.cpp
#	Telegram/lib_ui
This commit is contained in:
ZavaruKitsu 2024-02-03 23:20:50 +03:00
commit 77d52bb1b3
251 changed files with 6957 additions and 2361 deletions

View file

@ -9,11 +9,38 @@ on:
- cron: '0 0 * * *' - cron: '0 0 * * *'
jobs: jobs:
noResponse: waiting-for-answer:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: lee-dohm/no-response@v0.5.0 - uses: lee-dohm/no-response@v0.5.0
with: with:
token: ${{ github.token }} token: ${{ github.token }}
# Label requiring a response
responseRequiredLabel: waiting for answer responseRequiredLabel: waiting for answer
needs-user-action:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
responseRequiredLabel: needs user action
cant-reproduce:
if: github.event_name != 'issue_comment'
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
responseRequiredLabel: cant reproduce
closeComment: >
This issue has been automatically closed because no developer succeeded to
reproduce the issue with the given reproduction steps. With only the
information that is currently in the issue, we don't have enough
information to take action. Please reach out if you find what's missing to
reproduce the issue so that we can investigate further.
Note that GitHub is a developer communication platform. If you're an ordinary
user seeking for help, get to support crew via `Settings -> Ask question` in
the application.

View file

@ -591,6 +591,7 @@ PRIVATE
data/data_groups.h data/data_groups.h
data/data_histories.cpp data/data_histories.cpp
data/data_histories.h data/data_histories.h
data/data_lastseen_status.h
data/data_location.cpp data/data_location.cpp
data/data_location.h data/data_location.h
data/data_media_rotation.cpp data/data_media_rotation.cpp
@ -828,6 +829,8 @@ PRIVATE
history/view/reactions/history_view_reactions_strip.h history/view/reactions/history_view_reactions_strip.h
history/view/reactions/history_view_reactions_tabs.cpp history/view/reactions/history_view_reactions_tabs.cpp
history/view/reactions/history_view_reactions_tabs.h history/view/reactions/history_view_reactions_tabs.h
history/view/history_view_about_view.cpp
history/view/history_view_about_view.h
history/view/history_view_bottom_info.cpp history/view/history_view_bottom_info.cpp
history/view/history_view_bottom_info.h history/view/history_view_bottom_info.h
history/view/history_view_contact_status.cpp history/view/history_view_contact_status.cpp
@ -1092,6 +1095,7 @@ PRIVATE
media/audio/media_audio.h media/audio/media_audio.h
media/audio/media_audio_capture.cpp media/audio/media_audio_capture.cpp
media/audio/media_audio_capture.h media/audio/media_audio_capture.h
media/audio/media_audio_capture_common.h
media/audio/media_audio_ffmpeg_loader.cpp media/audio/media_audio_ffmpeg_loader.cpp
media/audio/media_audio_ffmpeg_loader.h media/audio/media_audio_ffmpeg_loader.h
media/audio/media_audio_loader.cpp media/audio/media_audio_loader.cpp
@ -1307,8 +1311,6 @@ PRIVATE
platform/mac/touchbar/mac_touchbar_manager.mm platform/mac/touchbar/mac_touchbar_manager.mm
platform/mac/touchbar/mac_touchbar_media_view.h platform/mac/touchbar/mac_touchbar_media_view.h
platform/mac/touchbar/mac_touchbar_media_view.mm platform/mac/touchbar/mac_touchbar_media_view.mm
platform/win/audio_win.cpp
platform/win/audio_win.h
platform/win/file_utilities_win.cpp platform/win/file_utilities_win.cpp
platform/win/file_utilities_win.h platform/win/file_utilities_win.h
platform/win/launcher_win.cpp platform/win/launcher_win.cpp
@ -1332,7 +1334,6 @@ PRIVATE
platform/win/windows_autostart_task.h platform/win/windows_autostart_task.h
platform/win/windows_toast_activator.cpp platform/win/windows_toast_activator.cpp
platform/win/windows_toast_activator.h platform/win/windows_toast_activator.h
platform/platform_audio.h
platform/platform_file_utilities.h platform/platform_file_utilities.h
platform/platform_launcher.h platform/platform_launcher.h
platform/platform_integration.cpp platform/platform_integration.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -141,6 +141,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_status_last_week" = "last seen within a week"; "lng_status_last_week" = "last seen within a week";
"lng_status_last_month" = "last seen within a month"; "lng_status_last_month" = "last seen within a month";
"lng_status_lastseen_now" = "last seen just now"; "lng_status_lastseen_now" = "last seen just now";
"lng_status_lastseen_when" = "when?";
"lng_status_lastseen_minutes#one" = "last seen {count} minute ago"; "lng_status_lastseen_minutes#one" = "last seen {count} minute ago";
"lng_status_lastseen_minutes#other" = "last seen {count} minutes ago"; "lng_status_lastseen_minutes#other" = "last seen {count} minutes ago";
"lng_status_lastseen_hours#one" = "last seen {count} hour ago"; "lng_status_lastseen_hours#one" = "last seen {count} hour ago";
@ -168,6 +169,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_remember" = "Remember this choice"; "lng_remember" = "Remember this choice";
"lng_lastseen_show_title" = "Show Your Last Seen";
"lng_lastseen_show_about" = "To see **{user}'s** Last Seen time, either start\nshowing your own Last Seen time...";
"lng_lastseen_show_button" = "Show My Last Seen";
"lng_lastseen_or" = "or";
"lng_lastseen_premium_title" = "Upgrade to Premium";
"lng_lastseen_premium_about" = "Subscription will let you see **{user}'s** Last Seen\nstatus without showing yours.";
"lng_lastseen_premium_button" = "Subscribe to Telegram Premium";
"lng_lastseen_shown_toast" = "Your last seen time is now visible.";
"lng_readtime_show_title" = "Show Your Read Date";
"lng_readtime_show_about" = "To see when **{user}** read the message,\neither start showing your own read time...";
"lng_readtime_show_button" = "Show My Read Time";
"lng_readtime_or" = "or";
"lng_readtime_premium_title" = "Upgrade to Premium";
"lng_readtime_premium_about" = "Subscription will let you see **{user}'s** read time\nwithout showing yours.";
"lng_readtime_premium_button" = "Subscribe to Telegram Premium";
"lng_readtime_shown_toast" = "Your read times are now visible.";
"lng_channels_limit_title" = "Too Many Communities"; "lng_channels_limit_title" = "Too Many Communities";
"lng_channels_limit1#one" = "You are a member of **{count}** groups and channels."; "lng_channels_limit1#one" = "You are a member of **{count}** groups and channels.";
"lng_channels_limit1#other" = "You are a member of **{count}** groups and channels."; "lng_channels_limit1#other" = "You are a member of **{count}** groups and channels.";
@ -414,7 +433,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_username_invalid" = "This username is invalid."; "lng_username_invalid" = "This username is invalid.";
"lng_username_occupied" = "This username is already occupied."; "lng_username_occupied" = "This username is already occupied.";
"lng_username_too_short" = "This username is too short."; "lng_username_too_short" = "This username is too short.";
"lng_username_purchase_available" = "Sorry, this link is occupied by someone. But it's available for purchase through\nofficial {link}."; "lng_username_purchase_available" = "**This username is already taken.** However, it is currently available for purchase. {link}";
"lng_username_purchase_available_link" = "Learn more...";
"lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed."; "lng_username_bad_symbols" = "Only a-z, 0-9, and underscores allowed.";
"lng_username_available" = "This username is available."; "lng_username_available" = "This username is available.";
"lng_username_not_found" = "User @{user} not found."; "lng_username_not_found" = "User @{user} not found.";
@ -611,6 +631,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_call_accept_calls" = "Accept calls from this device"; "lng_settings_call_accept_calls" = "Accept calls from this device";
"lng_settings_call_device_default" = "Same as the System"; "lng_settings_call_device_default" = "Same as the System";
"lng_settings_section_devices" = "Speakers and Camera";
"lng_settings_devices_calls" = "Calls and video chats";
"lng_settings_devices_calls_same" = "Use the same devices for calls";
"lng_settings_devices_inactive" = "Unavailable";
"lng_settings_language" = "Language"; "lng_settings_language" = "Language";
"lng_settings_default_scale" = "Default interface scale"; "lng_settings_default_scale" = "Default interface scale";
"lng_settings_connection_type" = "Connection type"; "lng_settings_connection_type" = "Connection type";
@ -624,6 +649,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_phone_number_privacy" = "Phone number"; "lng_settings_phone_number_privacy" = "Phone number";
"lng_settings_forwards_privacy" = "Forwarded messages"; "lng_settings_forwards_privacy" = "Forwarded messages";
"lng_settings_profile_photo_privacy" = "Profile photo"; "lng_settings_profile_photo_privacy" = "Profile photo";
"lng_settings_messages_privacy" = "Messages";
"lng_settings_voices_privacy" = "Voice messages"; "lng_settings_voices_privacy" = "Voice messages";
"lng_settings_bio_privacy" = "Bio"; "lng_settings_bio_privacy" = "Bio";
"lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages."; "lng_settings_privacy_premium" = "Only subscribers of {link} can restrict receiving voice messages.";
@ -1060,6 +1086,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_contacts" = "My contacts"; "lng_edit_privacy_contacts" = "My contacts";
"lng_edit_privacy_close_friends" = "Close friends"; "lng_edit_privacy_close_friends" = "Close friends";
"lng_edit_privacy_nobody" = "Nobody"; "lng_edit_privacy_nobody" = "Nobody";
"lng_edit_privacy_premium" = "Premium users";
"lng_edit_privacy_exceptions" = "Add exceptions"; "lng_edit_privacy_exceptions" = "Add exceptions";
"lng_edit_privacy_exceptions_count#one" = "{count} user"; "lng_edit_privacy_exceptions_count#one" = "{count} user";
@ -1086,6 +1113,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above."; "lng_edit_privacy_lastseen_exceptions" = "These settings will override the values above.";
"lng_edit_privacy_lastseen_always_title" = "Always share with"; "lng_edit_privacy_lastseen_always_title" = "Always share with";
"lng_edit_privacy_lastseen_never_title" = "Never share with"; "lng_edit_privacy_lastseen_never_title" = "Never share with";
"lng_edit_lastseen_hide_read_time" = "Hide read time";
"lng_edit_lastseen_hide_read_time_about" = "Hide the time when you read messages from people who can't see your last seen. If you turn this on, their read time will also be hidden from you. This setting does not affect group chats.";
"lng_edit_lastseen_subscribe" = "Subscribe to Telegram Premium";
"lng_edit_lastseen_subscribe_about" = "If you subscribe to Premium, you will see other users' last seen and read time even if you hid yours from them (unless they specifically restricted it).";
"lng_edit_privacy_groups_title" = "Group invite settings"; "lng_edit_privacy_groups_title" = "Group invite settings";
"lng_edit_privacy_groups_header" = "Who can invite you to groups and channels"; "lng_edit_privacy_groups_header" = "Who can invite you to groups and channels";
@ -1157,6 +1188,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_edit_privacy_voices_always_title" = "Always allow"; "lng_edit_privacy_voices_always_title" = "Always allow";
"lng_edit_privacy_voices_never_title" = "Never allow"; "lng_edit_privacy_voices_never_title" = "Never allow";
"lng_messages_privacy_title" = "Messages";
"lng_messages_privacy_subtitle" = "Who can send me messages?";
"lng_messages_privacy_everyone" = "Everybody";
"lng_messages_privacy_restricted" = "My Contacts and Premium Users";
"lng_messages_privacy_about" = "You can restrict incoming messages to only contacts and Premium users.";
"lng_messages_privacy_premium_button" = "Subscribe to Telegram Premium";
"lng_messages_privacy_premium_about" = "Subscribe now to change this setting and get access to other exclusive features of Telegram Premium.";
"lng_messages_privacy_premium" = "Only subscribers of {link} can select this option.";
"lng_messages_privacy_premium_link" = "Telegram Premium";
"lng_self_destruct_title" = "Account self-destruction"; "lng_self_destruct_title" = "Account self-destruction";
"lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts."; "lng_self_destruct_description" = "If you don't come online at least once within this period, your account will be deleted along with all groups, messages and contacts.";
"lng_self_destruct_sessions_title" = "Session termination"; "lng_self_destruct_sessions_title" = "Session termination";
@ -1464,6 +1505,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_report_messages_none" = "Select Messages"; "lng_report_messages_none" = "Select Messages";
"lng_report_messages_count#one" = "Report {count} Message"; "lng_report_messages_count#one" = "Report {count} Message";
"lng_report_messages_count#other" = "Report {count} Messages"; "lng_report_messages_count#other" = "Report {count} Messages";
"lng_report_reaction" = "Report reaction";
"lng_report_and_ban" = "Ban and report";
"lng_report_reaction_title" = "Report reaction";
"lng_report_reaction_about" = "Are you sure you want to report reactions from this user?";
"lng_report_and_ban_button" = "Ban user";
"lng_report_details_about" = "Please enter any additional details relevant to your report."; "lng_report_details_about" = "Please enter any additional details relevant to your report.";
"lng_report_details" = "Additional Details"; "lng_report_details" = "Additional Details";
"lng_report_reason_spam" = "Spam"; "lng_report_reason_spam" = "Spam";
@ -1985,6 +2031,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity."; "lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity.";
"lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions"; "lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions";
"lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message."; "lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message.";
"lng_premium_summary_subtitle_tags_for_messages" = "Tags for Messages";
"lng_premium_summary_about_tags_for_messages" = "Organize your Saved Messages with tags for quicker access.";
"lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers"; "lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers";
"lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; "lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
"lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji"; "lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji";
@ -2405,6 +2453,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_in_dlg_contact" = "Contact"; "lng_in_dlg_contact" = "Contact";
"lng_in_dlg_audio" = "Voice message"; "lng_in_dlg_audio" = "Voice message";
"lng_in_dlg_video_message" = "Video message"; "lng_in_dlg_video_message" = "Video message";
"lng_in_dlg_voice_message_ttl" = "One-time Voice Message";
"lng_in_dlg_video_message_ttl" = "One-time Video Message";
"lng_in_dlg_file" = "File"; "lng_in_dlg_file" = "File";
"lng_in_dlg_sticker" = "Sticker"; "lng_in_dlg_sticker" = "Sticker";
"lng_in_dlg_sticker_emoji" = "{emoji} Sticker"; "lng_in_dlg_sticker_emoji" = "{emoji} Sticker";
@ -2492,7 +2542,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?"; "lng_record_listen_cancel_sure" = "Are you sure you want to discard your recorded voice message?";
"lng_record_lock_discard" = "Discard"; "lng_record_lock_discard" = "Discard";
"lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message."; "lng_record_hold_tip" = "Please hold the mouse button pressed to record a voice message.";
"lng_record_once_first_tooltip" = "Tap to set this message to **Play Once**."; "lng_record_once_first_tooltip" = "Click to set this message to **Play Once**.";
"lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once."; "lng_record_once_active_tooltip" = "The recipients will be able to listen to it only once.";
"lng_will_be_notified" = "Members will be notified when you post"; "lng_will_be_notified" = "Members will be notified when you post";
"lng_wont_be_notified" = "Members will not be notified when you post"; "lng_wont_be_notified" = "Members will not be notified when you post";
@ -2758,17 +2808,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_seen_reacted_all" = "Show All Reactions"; "lng_context_seen_reacted_all" = "Show All Reactions";
"lng_context_set_as_quick" = "Set as Quick"; "lng_context_set_as_quick" = "Set as Quick";
"lng_context_filter_by_tag" = "Filter by Tag"; "lng_context_filter_by_tag" = "Filter by Tag";
"lng_context_tag_add_name" = "Add Name";
"lng_context_tag_edit_name" = "Edit Name";
"lng_context_remove_tag" = "Remove Tag"; "lng_context_remove_tag" = "Remove Tag";
"lng_context_delete_from_disk" = "Delete from disk"; "lng_context_delete_from_disk" = "Delete from disk";
"lng_context_delete_all_files" = "Delete all files"; "lng_context_delete_all_files" = "Delete all files";
"lng_context_save_custom_sound" = "Save for notifications"; "lng_context_save_custom_sound" = "Save for notifications";
"lng_context_translate" = "Translate"; "lng_context_translate" = "Translate";
"lng_context_translate_selected" = "Translate Selected Text"; "lng_context_translate_selected" = "Translate Selected Text";
"lng_context_read_hidden" = "read";
"lng_context_read_show" = "show when";
"lng_add_tag_about" = "Tag this message with an emoji for quick search.";
"lng_subscribe_tag_about" = "Organize your Saved Messages with tags. {link}";
"lng_subscribe_tag_link" = "Learn More...";
"lng_edit_tag_about" = "You can label your emoji tag with a text name.";
"lng_edit_tag_name" = "Name";
"lng_add_tag_button" = "Add tags";
"lng_add_tag_phrase" = "to messages {arrow}";
"lng_add_tag_phrase_long" = "to your Saved Messages {arrow}";
"lng_unlock_tags" = "Unlock";
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**."; "lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**."; "lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**."; "lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
"lng_context_animated_reaction" = "This reaction is from **{name} pack**."; "lng_context_animated_reaction" = "This reaction is from **{name} pack**.";
"lng_context_animated_tag" = "This tag is from **{name} pack**.";
"lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**."; "lng_context_animated_reactions" = "Reactions contain emoji from **{name} pack**.";
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**."; "lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**."; "lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
@ -3406,7 +3471,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_rtmp_key_copy" = "Copy Stream Key"; "lng_group_call_rtmp_key_copy" = "Copy Stream Key";
"lng_group_call_rtmp_key_copied" = "Stream Key copied to clipboard."; "lng_group_call_rtmp_key_copied" = "Stream Key copied to clipboard.";
"lng_group_call_rtmp_key_warning" = "**Never share your Stream Key with anyone or show it on stream!**"; "lng_group_call_rtmp_key_warning" = "**Never share your Stream Key with anyone or show it on stream!**";
"lng_group_call_rtmp_info" = "To stream video with another app, enter these Server URL and Stream Key in your streaming app.\n\nOnce you start broadcasting in your streaming app, tap Start Streaming below."; "lng_group_call_rtmp_info" = "To stream video with another app, enter these Server URL and Stream Key in your streaming app.\n\nOnce you start broadcasting in your streaming app, click Start Streaming below.";
"lng_group_call_rtmp_start" = "Start Streaming"; "lng_group_call_rtmp_start" = "Start Streaming";
"lng_group_call_rtmp_revoke" = "Revoke Stream Key"; "lng_group_call_rtmp_revoke" = "Revoke Stream Key";
"lng_group_call_rtmp_revoke_sure" = "Are you sure you want to revoke your Server URL and Stream Key?"; "lng_group_call_rtmp_revoke_sure" = "Are you sure you want to revoke your Server URL and Stream Key?";
@ -3574,6 +3639,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them."; "lng_restricted_send_voice_messages" = "{user} restricted sending of voice messages to them.";
"lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them."; "lng_restricted_send_video_messages" = "{user} restricted sending of video messages to them.";
"lng_restricted_send_non_premium" = "Only Premium users can message {user}.";
"lng_restricted_send_non_premium_more" = "Learn more...";
"lng_send_non_premium_text" = "Subscribe to **Premium**\n to message {user}.";
"lng_send_non_premium_go" = "Get Premium";
"lng_send_non_premium_story" = "Replies restricted";
"lng_send_non_premium_unlock" = "unlock";
"lng_send_non_premium_message_toast" = "**{user}** only accepts messages from contacts and {link} subscribers.";
"lng_send_non_premium_message_toast_link" = "Telegram Premium";
"lng_exceptions_list_title" = "Exceptions"; "lng_exceptions_list_title" = "Exceptions";
"lng_removed_list_title" = "Removed users"; "lng_removed_list_title" = "Removed users";
@ -4241,7 +4315,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_hide_ads" = "Hide";
"lng_sponsored_title" = "What are sponsored messages?"; "lng_sponsored_title" = "What are sponsored messages?";
"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties cant spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; "lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you clicked on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties cant spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:";
"lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate together."; "lng_sponsored_info_description2" = "Sponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate together.";
"lng_sponsored_info_menu" = "About this ad"; "lng_sponsored_info_menu" = "About this ad";
"lng_sponsored_info_submenu" = "Advertiser: {text}"; "lng_sponsored_info_submenu" = "Advertiser: {text}";
@ -4410,6 +4484,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_stories_channel_archive_done_many#one" = "{count} story is hidden from the channel page."; "lng_stories_channel_archive_done_many#one" = "{count} story is hidden from the channel page.";
"lng_stories_channel_archive_done_many#other" = "{count} stories are hidden from the channel page."; "lng_stories_channel_archive_done_many#other" = "{count} stories are hidden from the channel page.";
"lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk."; "lng_stories_save_promo" = "Subscribe to {link} to download other people's unprotected stories to disk.";
"lng_stories_reaction_as_message" = "Send reaction as a private message";
"lng_stealth_mode_menu_item" = "Stealth Mode"; "lng_stealth_mode_menu_item" = "Stealth Mode";
"lng_stealth_mode_title" = "Stealth Mode"; "lng_stealth_mode_title" = "Stealth Mode";

View file

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

View file

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

View file

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

View file

@ -84,18 +84,61 @@ void GlobalPrivacy::dismissArchiveAndMuteSuggestion() {
u"AUTOARCHIVE_POPULAR"_q); u"AUTOARCHIVE_POPULAR"_q);
} }
void GlobalPrivacy::updateHideReadTime(bool hide) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hide,
newRequirePremiumCurrent());
}
bool GlobalPrivacy::hideReadTimeCurrent() const {
return _hideReadTime.current();
}
rpl::producer<bool> GlobalPrivacy::hideReadTime() const {
return _hideReadTime.value();
}
void GlobalPrivacy::updateNewRequirePremium(bool value) {
update(
archiveAndMuteCurrent(),
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
value);
}
bool GlobalPrivacy::newRequirePremiumCurrent() const {
return _newRequirePremium.current();
}
rpl::producer<bool> GlobalPrivacy::newRequirePremium() const {
return _newRequirePremium.value();
}
void GlobalPrivacy::updateArchiveAndMute(bool value) { void GlobalPrivacy::updateArchiveAndMute(bool value) {
update(value, unarchiveOnNewMessageCurrent()); update(
value,
unarchiveOnNewMessageCurrent(),
hideReadTimeCurrent(),
newRequirePremiumCurrent());
} }
void GlobalPrivacy::updateUnarchiveOnNewMessage( void GlobalPrivacy::updateUnarchiveOnNewMessage(
UnarchiveOnNewMessage value) { UnarchiveOnNewMessage value) {
update(archiveAndMuteCurrent(), value); update(
archiveAndMuteCurrent(),
value,
hideReadTimeCurrent(),
newRequirePremiumCurrent());
} }
void GlobalPrivacy::update( void GlobalPrivacy::update(
bool archiveAndMute, bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage) { UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime,
bool newRequirePremium) {
using Flag = MTPDglobalPrivacySettings::Flag; using Flag = MTPDglobalPrivacySettings::Flag;
_api.request(_requestId).cancel(); _api.request(_requestId).cancel();
@ -108,17 +151,26 @@ void GlobalPrivacy::update(
: Flag()) : Flag())
| (unarchiveOnNewMessage != UnarchiveOnNewMessage::AnyUnmuted | (unarchiveOnNewMessage != UnarchiveOnNewMessage::AnyUnmuted
? Flag::f_keep_archived_folders ? Flag::f_keep_archived_folders
: Flag())
| (hideReadTime ? Flag::f_hide_read_marks : Flag())
| ((newRequirePremium && _session->premium())
? Flag::f_new_noncontact_peers_require_premium
: Flag()); : Flag());
_requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings(
MTP_globalPrivacySettings(MTP_flags(flags)) MTP_globalPrivacySettings(MTP_flags(flags))
)).done([=](const MTPGlobalPrivacySettings &result) { )).done([=](const MTPGlobalPrivacySettings &result) {
_requestId = 0; _requestId = 0;
apply(result); apply(result);
}).fail([=] { }).fail([=](const MTP::Error &error) {
_requestId = 0; _requestId = 0;
if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {});
}
}).send(); }).send();
_archiveAndMute = archiveAndMute; _archiveAndMute = archiveAndMute;
_unarchiveOnNewMessage = unarchiveOnNewMessage; _unarchiveOnNewMessage = unarchiveOnNewMessage;
_hideReadTime = hideReadTime;
_newRequirePremium = newRequirePremium;
} }
void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
@ -129,6 +181,8 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) {
: data.is_keep_archived_folders() : data.is_keep_archived_folders()
? UnarchiveOnNewMessage::NotInFoldersUnmuted ? UnarchiveOnNewMessage::NotInFoldersUnmuted
: UnarchiveOnNewMessage::AnyUnmuted; : UnarchiveOnNewMessage::AnyUnmuted;
_hideReadTime = data.is_hide_read_marks();
_newRequirePremium = data.is_new_noncontact_peers_require_premium();
}); });
} }

View file

@ -41,12 +41,22 @@ public:
[[nodiscard]] rpl::producer<> suggestArchiveAndMute() const; [[nodiscard]] rpl::producer<> suggestArchiveAndMute() const;
void dismissArchiveAndMuteSuggestion(); void dismissArchiveAndMuteSuggestion();
void updateHideReadTime(bool hide);
[[nodiscard]] bool hideReadTimeCurrent() const;
[[nodiscard]] rpl::producer<bool> hideReadTime() const;
void updateNewRequirePremium(bool value);
[[nodiscard]] bool newRequirePremiumCurrent() const;
[[nodiscard]] rpl::producer<bool> newRequirePremium() const;
private: private:
void apply(const MTPGlobalPrivacySettings &data); void apply(const MTPGlobalPrivacySettings &data);
void update( void update(
bool archiveAndMute, bool archiveAndMute,
UnarchiveOnNewMessage unarchiveOnNewMessage); UnarchiveOnNewMessage unarchiveOnNewMessage,
bool hideReadTime,
bool newRequirePremium);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -55,6 +65,8 @@ private:
rpl::variable<UnarchiveOnNewMessage> _unarchiveOnNewMessage rpl::variable<UnarchiveOnNewMessage> _unarchiveOnNewMessage
= UnarchiveOnNewMessage::None; = UnarchiveOnNewMessage::None;
rpl::variable<bool> _showArchiveAndMute = false; rpl::variable<bool> _showArchiveAndMute = false;
rpl::variable<bool> _hideReadTime = false;
rpl::variable<bool> _newRequirePremium = false;
std::vector<Fn<void()>> _callbacks; std::vector<Fn<void()>> _callbacks;
}; };

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_message_reaction_id.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/history.h" #include "history/history.h"
@ -43,6 +44,23 @@ constexpr auto kSearchPerPage = 50;
return result; return result;
} }
[[nodiscard]] QString RequestToToken(
const MessagesSearch::Request &request) {
auto result = request.query;
if (request.from) {
result += '\n' + QString::number(request.from->id.value);
}
for (const auto &tag : request.tags) {
result += '\n';
if (const auto customId = tag.custom()) {
result += u"custom"_q + QString::number(customId);
} else {
result += u"emoji"_q + tag.emoji();
}
}
return result;
}
} // namespace } // namespace
MessagesSearch::MessagesSearch(not_null<History*> history) MessagesSearch::MessagesSearch(not_null<History*> history)
@ -54,9 +72,8 @@ MessagesSearch::~MessagesSearch() {
base::take(_searchInHistoryRequest)); base::take(_searchInHistoryRequest));
} }
void MessagesSearch::searchMessages(const QString &query, PeerData *from) { void MessagesSearch::searchMessages(Request request) {
_query = query; _request = std::move(request);
_from = from;
_offsetId = {}; _offsetId = {};
searchRequest(); searchRequest();
} }
@ -69,8 +86,7 @@ void MessagesSearch::searchMore() {
} }
void MessagesSearch::searchRequest() { void MessagesSearch::searchRequest() {
const auto nextToken = _query const auto nextToken = RequestToToken(_request);
+ QString::number(_from ? _from->id.value : 0);
if (!_offsetId) { if (!_offsetId) {
const auto it = _cacheOfStartByToken.find(nextToken); const auto it = _cacheOfStartByToken.find(nextToken);
if (it != end(_cacheOfStartByToken)) { if (it != end(_cacheOfStartByToken)) {
@ -80,18 +96,21 @@ void MessagesSearch::searchRequest() {
} }
} }
auto callback = [=](Fn<void()> finish) { auto callback = [=](Fn<void()> finish) {
const auto flags = _from using Flag = MTPmessages_Search::Flag;
? MTP_flags(MTPmessages_Search::Flag::f_from_id) const auto from = _request.from;
: MTP_flags(0); const auto fromPeer = _history->peer->isUser() ? nullptr : from;
const auto savedPeer = _history->peer->isSelf() ? from : nullptr;
_requestId = _history->session().api().request(MTPmessages_Search( _requestId = _history->session().api().request(MTPmessages_Search(
flags, MTP_flags((fromPeer ? Flag::f_from_id : Flag())
| (savedPeer ? Flag::f_saved_peer_id : Flag())
| (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
_history->peer->input, _history->peer->input,
MTP_string(_query), MTP_string(_request.query),
(_from (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
? _from->input (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
: MTP_inputPeerEmpty()), MTP_vector_from_range(_request.tags | ranges::views::transform(
MTPInputPeer(), // saved_peer_id Data::ReactionToMTP
MTPVector<MTPReaction>(), // saved_reaction )),
MTPint(), // top_msg_id MTPint(), // top_msg_id
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date

View file

@ -7,10 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "base/qt/qt_compare.h"
#include "data/data_message_reaction_id.h"
class HistoryItem; class HistoryItem;
class History; class History;
class PeerData; class PeerData;
namespace Data {
struct ReactionId;
} // namespace Data
namespace Api { namespace Api {
struct FoundMessages { struct FoundMessages {
@ -21,10 +28,23 @@ struct FoundMessages {
class MessagesSearch final { class MessagesSearch final {
public: public:
struct Request {
QString query;
PeerData *from = nullptr;
std::vector<Data::ReactionId> tags;
friend inline bool operator==(
const Request &,
const Request &) = default;
friend inline auto operator<=>(
const Request &,
const Request &) = default;
};
explicit MessagesSearch(not_null<History*> history); explicit MessagesSearch(not_null<History*> history);
~MessagesSearch(); ~MessagesSearch();
void searchMessages(const QString &query, PeerData *from); void searchMessages(Request request);
void searchMore(); void searchMore();
[[nodiscard]] rpl::producer<FoundMessages> messagesFounds() const; [[nodiscard]] rpl::producer<FoundMessages> messagesFounds() const;
@ -41,8 +61,7 @@ private:
base::flat_map<QString, TLMessages> _cacheOfStartByToken; base::flat_map<QString, TLMessages> _cacheOfStartByToken;
QString _query; Request _request;
PeerData *_from = nullptr;
MsgId _offsetId; MsgId _offsetId;
int _searchInHistoryRequest = 0; // Not real mtpRequestId. int _searchInHistoryRequest = 0; // Not real mtpRequestId.

View file

@ -11,12 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api { namespace Api {
bool MessagesSearchMerged::RequestCompare::operator()(
const Request &a,
const Request &b) const {
return (a.query < b.query) && (a.from < b.from);
}
MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history) MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
: _apiSearch(history) { : _apiSearch(history) {
if (const auto migrated = history->migrateFrom()) { if (const auto migrated = history->migrateFrom()) {
@ -88,9 +82,9 @@ void MessagesSearchMerged::clear() {
void MessagesSearchMerged::search(const Request &search) { void MessagesSearchMerged::search(const Request &search) {
if (_migratedSearch) { if (_migratedSearch) {
_waitingForTotal = true; _waitingForTotal = true;
_migratedSearch->searchMessages(search.query, search.from); _migratedSearch->searchMessages(search);
} }
_apiSearch.searchMessages(search.query, search.from); _apiSearch.searchMessages(search);
} }
void MessagesSearchMerged::searchMore() { void MessagesSearchMerged::searchMore() {

View file

@ -12,19 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History; class History;
class PeerData; class PeerData;
namespace Data {
struct ReactionId;
} // namespace Data
namespace Api { namespace Api {
// Search in both of history and migrated history, if it exists. // Search in both of history and migrated history, if it exists.
class MessagesSearchMerged final { class MessagesSearchMerged final {
public: public:
struct Request { using Request = MessagesSearch::Request;
QString query; using CachedRequests = base::flat_set<Request>;
PeerData *from = nullptr;
};
struct RequestCompare {
bool operator()(const Request &a, const Request &b) const;
};
using CachedRequests = std::set<Request, RequestCompare>;
MessagesSearchMerged(not_null<History*> history); MessagesSearchMerged(not_null<History*> history);

View file

@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/history_view_element.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -334,6 +338,72 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions; return _subscriptionOptions;
} }
rpl::producer<> Premium::somePremiumRequiredResolved() const {
return _somePremiumRequiredResolved.events();
}
void Premium::resolvePremiumRequired(not_null<UserData*> user) {
_resolvePremiumRequiredUsers.emplace(user);
if (!_premiumRequiredRequestScheduled
&& _resolvePremiumRequestedUsers.empty()) {
_premiumRequiredRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
}
}
void Premium::requestPremiumRequiredSlice() {
_premiumRequiredRequestScheduled = false;
if (!_resolvePremiumRequestedUsers.empty()
|| _resolvePremiumRequiredUsers.empty()) {
return;
}
constexpr auto kPerRequest = 100;
auto users = MTP_vector_from_range(_resolvePremiumRequiredUsers
| ranges::views::transform(&UserData::inputUser));
if (users.v.size() > kPerRequest) {
auto shortened = users.v;
shortened.resize(kPerRequest);
users = MTP_vector<MTPInputUser>(std::move(shortened));
const auto from = begin(_resolvePremiumRequiredUsers);
_resolvePremiumRequestedUsers = { from, from + kPerRequest };
_resolvePremiumRequiredUsers.erase(from, from + kPerRequest);
} else {
_resolvePremiumRequestedUsers
= base::take(_resolvePremiumRequiredUsers);
}
const auto finish = [=](const QVector<MTPBool> &list) {
constexpr auto me = UserDataFlag::MeRequiresPremiumToWrite;
constexpr auto known = UserDataFlag::RequirePremiumToWriteKnown;
constexpr auto mask = me | known;
auto index = 0;
for (const auto &user : base::take(_resolvePremiumRequestedUsers)) {
const auto require = (index < list.size())
&& mtpIsTrue(list[index++]);
user->setFlags((user->flags() & ~mask)
| known
| (require ? me : UserDataFlag()));
}
if (!_premiumRequiredRequestScheduled
&& !_resolvePremiumRequiredUsers.empty()) {
_premiumRequiredRequestScheduled = true;
crl::on_main(_session, [=] {
requestPremiumRequiredSlice();
});
}
_somePremiumRequiredResolved.fire({});
};
_session->api().request(
MTPusers_GetIsPremiumRequiredToContact(std::move(users))
).done([=](const MTPVector<MTPBool> &result) {
finish(result.v);
}).fail([=] {
finish({});
}).send();
}
PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer) PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null<PeerData*> peer)
: _peer(peer) : _peer(peer)
, _api(&peer->session().api().instance()) { , _api(&peer->session().api().instance()) {
@ -494,4 +564,49 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
false); false);
} }
RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<PeerData*> peer,
History *maybeHistory) {
const auto user = peer->asUser();
if (!user
|| !user->someRequirePremiumToWrite()
|| user->session().premium()) {
return RequirePremiumState::No;
} else if (user->requirePremiumToWriteKnown()) {
return user->meRequiresPremiumToWrite()
? RequirePremiumState::Yes
: RequirePremiumState::No;
} else if (user->flags() & UserDataFlag::MutualContact) {
return RequirePremiumState::No;
} else if (!maybeHistory) {
return RequirePremiumState::Unknown;
}
const auto update = [&](bool require) {
using Flag = UserDataFlag;
constexpr auto known = Flag::RequirePremiumToWriteKnown;
constexpr auto me = Flag::MeRequiresPremiumToWrite;
user->setFlags((user->flags() & ~me)
| known
| (require ? me : Flag()));
};
// We allow this potentially-heavy loop because in case we've opened
// the chat and have a lot of messages `requires_premium` will be known.
for (const auto &block : maybeHistory->blocks) {
for (const auto &view : block->messages) {
const auto item = view->data();
if (!item->out() && !item->isService()) {
update(false);
return RequirePremiumState::No;
}
}
}
if (user->isContact() // Here we know, that we're not in his contacts.
&& maybeHistory->loadedAtTop() // And no incoming messages.
&& maybeHistory->loadedAtBottom()) {
update(true);
}
return RequirePremiumState::Unknown;
}
} // namespace Api } // namespace Api

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_subscription_option.h" #include "data/data_subscription_option.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
class History;
class ApiWrap; class ApiWrap;
namespace Main { namespace Main {
@ -103,10 +104,14 @@ public:
[[nodiscard]] auto subscriptionOptions() const [[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &; -> const Data::SubscriptionOptions &;
[[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const;
void resolvePremiumRequired(not_null<UserData*> user);
private: private:
void reloadPromo(); void reloadPromo();
void reloadStickers(); void reloadStickers();
void reloadCloudSet(); void reloadCloudSet();
void requestPremiumRequiredSlice();
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
@ -143,6 +148,11 @@ private:
Data::SubscriptionOptions _subscriptionOptions; Data::SubscriptionOptions _subscriptionOptions;
rpl::event_stream<> _somePremiumRequiredResolved;
base::flat_set<not_null<UserData*>> _resolvePremiumRequiredUsers;
base::flat_set<not_null<UserData*>> _resolvePremiumRequestedUsers;
bool _premiumRequiredRequestScheduled = false;
}; };
class PremiumGiftCodeOptions final { class PremiumGiftCodeOptions final {
@ -196,4 +206,13 @@ private:
}; };
enum class RequirePremiumState {
Unknown,
Yes,
No,
};
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<PeerData*> peer,
History *maybeHistory);
} // namespace Api } // namespace Api

View file

@ -174,14 +174,13 @@ bool SendProgressManager::skipRequest(const Key &key) const {
return true; return true;
} }
const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor; const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor;
const auto online = user->onlineTill; const auto lastseen = user->lastseen();
if (online == -2) { // last seen recently if (lastseen.isRecently()) {
return false; return false;
} else if (online < 0) { } else if (const auto value = lastseen.onlineTill()) {
return (-online < recently); return (value < recently);
} else {
return (online < recently);
} }
return true;
} }
void SendProgressManager::done(mtpRequestId requestId) { void SendProgressManager::done(mtpRequestId requestId) {

View file

@ -958,7 +958,9 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) {
} }
const auto self = session().user(); const auto self = session().user();
self->onlineTill = base::unixtime::now() + (isOnline ? (config.onlineUpdatePeriod / 1000) : -1); const auto onlineFor = (config.onlineUpdatePeriod / 1000);
self->updateLastseen(Data::LastseenStatus::OnlineTill(
base::unixtime::now() + (isOnline ? onlineFor : -1)));
session().changes().peerUpdated( session().changes().peerUpdated(
self, self,
Data::PeerUpdate::Flag::OnlineStatus); Data::PeerUpdate::Flag::OnlineStatus);
@ -1243,8 +1245,8 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) {
if (user && !requestingDifference()) { if (user && !requestingDifference()) {
user->madeAction(base::unixtime::now()); user->madeAction(base::unixtime::now());
} }
ClearMediaAsExpired(item);
} }
ClearMediaAsExpired(item);
} }
} else { } else {
// Perhaps it was an unread mention! // Perhaps it was an unread mention!
@ -1882,23 +1884,13 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updateUserStatus: { case mtpc_updateUserStatus: {
auto &d = update.c_updateUserStatus(); auto &d = update.c_updateUserStatus();
if (auto user = session().data().userLoaded(d.vuser_id())) { if (const auto user = session().data().userLoaded(d.vuser_id())) {
switch (d.vstatus().type()) { const auto now = LastseenFromMTP(d.vstatus(), user->lastseen());
case mtpc_userStatusEmpty: user->onlineTill = 0; break; if (user->updateLastseen(now)) {
case mtpc_userStatusRecently: session().changes().peerUpdated(
if (user->onlineTill > -10) { // don't modify pseudo-online user,
user->onlineTill = -2; Data::PeerUpdate::Flag::OnlineStatus);
}
break;
case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
case mtpc_userStatusOffline: user->onlineTill = d.vstatus().c_userStatusOffline().vwas_online().v; break;
case mtpc_userStatusOnline: user->onlineTill = d.vstatus().c_userStatusOnline().vexpires().v; break;
} }
session().changes().peerUpdated(
user,
Data::PeerUpdate::Flag::OnlineStatus);
session().data().maybeStopWatchForOffline(user);
} }
if (UserId(d.vuser_id()) == session().userId()) { if (UserId(d.vuser_id()) == session().userId()) {
if (d.vstatus().type() == mtpc_userStatusOffline if (d.vstatus().type() == mtpc_userStatusOffline

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "api/api_who_reacted.h" #include "api/api_who_reacted.h"
#include "api/api_global_privacy.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_message_reaction_id.h" #include "data/data_message_reaction_id.h"
#include "data/data_peer_values.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -36,10 +38,11 @@ namespace {
constexpr auto kContextReactionsLimit = 50; constexpr auto kContextReactionsLimit = 50;
using Data::ReactionId; using Data::ReactionId;
using WhoReadState = Ui::WhoReadState;
struct Peers { struct Peers {
std::vector<WhoReadPeer> list; std::vector<WhoReadPeer> list;
bool unknown = false; WhoReadState state = WhoReadState::Empty;
friend inline bool operator==( friend inline bool operator==(
const Peers &a, const Peers &a,
@ -59,7 +62,7 @@ struct PeersWithReactions {
std::vector<PeerWithReaction> list; std::vector<PeerWithReaction> list;
std::vector<WhoReadPeer> read; std::vector<WhoReadPeer> read;
int fullReactionsCount = 0; int fullReactionsCount = 0;
bool unknown = false; WhoReadState state = WhoReadState::Empty;
friend inline bool operator==( friend inline bool operator==(
const PeersWithReactions &a, const PeersWithReactions &a,
@ -68,7 +71,7 @@ struct PeersWithReactions {
struct CachedRead { struct CachedRead {
CachedRead() CachedRead()
: data(Peers{ .unknown = true }) { : data(Peers{ .state = WhoReadState::Unknown }) {
} }
rpl::variable<Peers> data; rpl::variable<Peers> data;
mtpRequestId requestId = 0; mtpRequestId requestId = 0;
@ -76,7 +79,7 @@ struct CachedRead {
struct CachedReacted { struct CachedReacted {
CachedReacted() CachedReacted()
: data(PeersWithReactions{ .unknown = true }) { : data(PeersWithReactions{ .state = WhoReadState::Unknown }) {
} }
rpl::variable<PeersWithReactions> data; rpl::variable<PeersWithReactions> data;
mtpRequestId requestId = 0; mtpRequestId requestId = 0;
@ -186,6 +189,27 @@ struct State {
context->cachedReacted.erase(j); context->cachedReacted.erase(j);
} }
}, context->subscriptions[session]); }, context->subscriptions[session]);
Data::AmPremiumValue(
session
) | rpl::skip(1) | rpl::filter(
rpl::mappers::_1
) | rpl::start_with_next([=] {
for (auto &[item, cache] : context->cachedRead) {
if (cache.data.current().state == Ui::WhoReadState::MyHidden) {
cache.data = Peers{ .state = Ui::WhoReadState::Unknown };
}
}
}, context->subscriptions[session]);
session->api().globalPrivacy().hideReadTime(
) | rpl::skip(1) | rpl::filter(
!rpl::mappers::_1
) | rpl::start_with_next([=] {
for (auto &[item, cache] : context->cachedRead) {
if (cache.data.current().state == Ui::WhoReadState::MyHidden) {
cache.data = Peers{ .state = Ui::WhoReadState::Unknown };
}
}
}, context->subscriptions[session]);
return context; return context;
} }
@ -222,7 +246,38 @@ struct State {
} }
const auto context = PreparedContextAt(weak.data(), session); const auto context = PreparedContextAt(weak.data(), session);
auto &entry = context->cacheRead(item); auto &entry = context->cacheRead(item);
if (!entry.requestId) { if (entry.requestId) {
} else if (const auto user = item->history()->peer->asUser()) {
entry.requestId = session->api().request(
MTPmessages_GetOutboxReadDate(
user->input,
MTP_int(item->id)
)
).done([=](const MTPOutboxReadDate &result) {
const auto &data = result.data();
auto &entry = context->cacheRead(item);
entry.requestId = 0;
auto parsed = Peers();
parsed.list.push_back({
.peer = user->id,
.date = data.vdate().v,
});
entry.data = std::move(parsed);
}).fail([=](const MTP::Error &error) {
auto &entry = context->cacheRead(item);
entry.requestId = 0;
if (entry.data.current().state == WhoReadState::Unknown) {
const auto &text = error.type();
entry.data = (text == u"YOUR_PRIVACY_RESTRICTED"_q)
? Peers{ .state = WhoReadState::MyHidden }
: (text == u"USER_PRIVACY_RESTRICTED"_q)
? Peers{ .state = WhoReadState::HisHidden }
: (text == u"MESSAGE_TOO_OLD"_q)
? Peers{ .state = WhoReadState::TooOld }
: Peers{ .state = WhoReadState::Empty };
}
}).send();
} else {
entry.requestId = session->api().request( entry.requestId = session->api().request(
MTPmessages_GetMessageReadParticipants( MTPmessages_GetMessageReadParticipants(
item->history()->peer->input, item->history()->peer->input,
@ -243,8 +298,8 @@ struct State {
}).fail([=] { }).fail([=] {
auto &entry = context->cacheRead(item); auto &entry = context->cacheRead(item);
entry.requestId = 0; entry.requestId = 0;
if (entry.data.current().unknown) { if (entry.data.current().state == WhoReadState::Unknown) {
entry.data = Peers(); entry.data = Peers{ .state = WhoReadState::Empty };
} }
}).send(); }).send();
} }
@ -258,7 +313,7 @@ struct State {
.list = peers.list | ranges::views::transform([](WhoReadPeer peer) { .list = peers.list | ranges::views::transform([](WhoReadPeer peer) {
return PeerWithReaction{ .peerWithDate = peer }; return PeerWithReaction{ .peerWithDate = peer };
}) | ranges::to_vector, }) | ranges::to_vector,
.unknown = peers.unknown, .state = peers.state,
}; };
result.read = std::move(peers.list); result.read = std::move(peers.list);
return result; return result;
@ -319,8 +374,10 @@ struct State {
}).fail([=] { }).fail([=] {
auto &entry = context->cacheReacted(item, reaction); auto &entry = context->cacheReacted(item, reaction);
entry.requestId = 0; entry.requestId = 0;
if (entry.data.current().unknown) { if (entry.data.current().state == WhoReadState::Unknown) {
entry.data = PeersWithReactions(); entry.data = PeersWithReactions{
.state = WhoReadState::Empty,
};
} }
}).send(); }).send();
} }
@ -336,8 +393,9 @@ struct State {
WhoReactedIds(item, {}, context), WhoReactedIds(item, {}, context),
WhoReadIds(item, context) WhoReadIds(item, context)
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
if (reacted.unknown || read.unknown) { if (reacted.state == WhoReadState::Unknown
return PeersWithReactions{ .unknown = true }; || read.state == WhoReadState::Unknown) {
return PeersWithReactions{ .state = WhoReadState::Unknown};
} }
auto &list = reacted.list; auto &list = reacted.list;
for (const auto &peerWithDate : read.list) { for (const auto &peerWithDate : read.list) {
@ -531,16 +589,17 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
std::move( std::move(
idsWithReactions idsWithReactions
) | rpl::start_with_next([=](PeersWithReactions &&peers) { ) | rpl::start_with_next([=](PeersWithReactions &&peers) {
if (peers.unknown) { if (peers.state == WhoReadState::Unknown) {
state->userpics.clear(); state->userpics.clear();
consumer.put_next(Ui::WhoReadContent{ consumer.put_next(Ui::WhoReadContent{
.type = state->current.type, .type = state->current.type,
.fullReactionsCount = state->current.fullReactionsCount, .fullReactionsCount = state->current.fullReactionsCount,
.fullReadCount = state->current.fullReadCount, .fullReadCount = state->current.fullReadCount,
.unknown = true, .state = WhoReadState::Unknown,
}); });
return; return;
} }
state->current.state = peers.state;
state->current.fullReadCount = int(peers.read.size()); state->current.fullReadCount = int(peers.read.size());
state->current.fullReactionsCount = peers.fullReactionsCount; state->current.fullReactionsCount = peers.fullReactionsCount;
if (whoReadIds) { if (whoReadIds) {
@ -631,6 +690,22 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
} }
const auto history = item->history(); const auto history = item->history();
const auto peer = history->peer; const auto peer = history->peer;
if (const auto user = peer->asUser()) {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->readDatesPrivate()) {
return false;
}
const auto &appConfig = peer->session().account().appConfig();
const auto expirePeriod = appConfig.get<int>(
"pm_read_date_expire_period",
7 * 86400);
if (item->date() + int64(expirePeriod) <= int64(base::unixtime::now())) {
return false;
}
return true;
}
const auto chat = peer->asChat(); const auto chat = peer->asChat();
const auto megagroup = peer->asMegagroup(); const auto megagroup = peer->asMegagroup();
if ((!chat && !megagroup) if ((!chat && !megagroup)

View file

@ -1953,28 +1953,28 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<Data::Thread*> thread) {
void ApiWrap::updatePrivacyLastSeens() { void ApiWrap::updatePrivacyLastSeens() {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
_session->data().enumerateUsers([&](UserData *user) { if (!_session->premium()) {
if (user->isSelf() || !user->isLoaded()) { _session->data().enumerateUsers([&](not_null<UserData*> user) {
return; if (user->isSelf()
} || !user->isLoaded()
if (user->onlineTill <= 0) { || user->lastseen().isHidden()) {
return; return;
} }
if (user->onlineTill + 3 * 86400 >= now) { const auto till = user->lastseen().onlineTill();
user->onlineTill = -2; // recently user->updateLastseen((till + 3 * 86400 >= now)
} else if (user->onlineTill + 7 * 86400 >= now) { ? Data::LastseenStatus::Recently(true)
user->onlineTill = -3; // last week : (till + 7 * 86400 >= now)
} else if (user->onlineTill + 30 * 86400 >= now) { ? Data::LastseenStatus::WithinWeek(true)
user->onlineTill = -4; // last month : (till + 30 * 86400 >= now)
} else { ? Data::LastseenStatus::WithinMonth(true)
user->onlineTill = 0; : Data::LastseenStatus::LongAgo(true));
} session().changes().peerUpdated(
session().changes().peerUpdated( user,
user, Data::PeerUpdate::Flag::OnlineStatus);
Data::PeerUpdate::Flag::OnlineStatus); session().data().maybeStopWatchForOffline(user);
session().data().maybeStopWatchForOffline(user); });
}); }
if (_contactsStatusesRequestId) { if (_contactsStatusesRequestId) {
request(_contactsStatusesRequestId).cancel(); request(_contactsStatusesRequestId).cancel();
@ -1982,20 +1982,17 @@ void ApiWrap::updatePrivacyLastSeens() {
_contactsStatusesRequestId = request(MTPcontacts_GetStatuses( _contactsStatusesRequestId = request(MTPcontacts_GetStatuses(
)).done([=](const MTPVector<MTPContactStatus> &result) { )).done([=](const MTPVector<MTPContactStatus> &result) {
_contactsStatusesRequestId = 0; _contactsStatusesRequestId = 0;
for (const auto &item : result.v) { for (const auto &status : result.v) {
Assert(item.type() == mtpc_contactStatus); const auto &data = status.data();
auto &data = item.c_contactStatus(); const auto userId = UserId(data.vuser_id());
if (auto user = _session->data().userLoaded(data.vuser_id())) { if (const auto user = _session->data().userLoaded(userId)) {
auto oldOnlineTill = user->onlineTill; const auto status = LastseenFromMTP(
auto newOnlineTill = OnlineTillFromStatus(
data.vstatus(), data.vstatus(),
oldOnlineTill); user->lastseen());
if (oldOnlineTill != newOnlineTill) { if (user->updateLastseen(status)) {
user->onlineTill = newOnlineTill;
session().changes().peerUpdated( session().changes().peerUpdated(
user, user,
Data::PeerUpdate::Flag::OnlineStatus); Data::PeerUpdate::Flag::OnlineStatus);
session().data().maybeStopWatchForOffline(user);
} }
} }
} }
@ -2004,22 +2001,6 @@ void ApiWrap::updatePrivacyLastSeens() {
}).send(); }).send();
} }
int ApiWrap::OnlineTillFromStatus(
const MTPUserStatus &status,
int currentOnlineTill) {
switch (status.type()) {
case mtpc_userStatusEmpty: return 0;
case mtpc_userStatusRecently:
// Don't modify pseudo-online.
return (currentOnlineTill > -10) ? -2 : currentOnlineTill;
case mtpc_userStatusLastWeek: return -3;
case mtpc_userStatusLastMonth: return -4;
case mtpc_userStatusOffline: return status.c_userStatusOffline().vwas_online().v;
case mtpc_userStatusOnline: return status.c_userStatusOnline().vexpires().v;
}
Unexpected("Bad UserStatus type.");
}
void ApiWrap::clearHistory(not_null<PeerData*> peer, bool revoke) { void ApiWrap::clearHistory(not_null<PeerData*> peer, bool revoke) {
deleteHistory(peer, true, revoke); deleteHistory(peer, true, revoke);
} }

View file

@ -258,10 +258,6 @@ public:
void updateNotifySettingsDelayed(Data::DefaultNotify type); void updateNotifySettingsDelayed(Data::DefaultNotify type);
void saveDraftToCloudDelayed(not_null<Data::Thread*> thread); void saveDraftToCloudDelayed(not_null<Data::Thread*> thread);
static int OnlineTillFromStatus(
const MTPUserStatus &status,
int currentOnlineTill);
void clearHistory(not_null<PeerData*> peer, bool revoke); void clearHistory(not_null<PeerData*> peer, bool revoke);
void deleteConversation(not_null<PeerData*> peer, bool revoke); void deleteConversation(not_null<PeerData*> peer, bool revoke);

View file

@ -12,12 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/random.h" #include "base/random.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/premium_limits_box.h" #include "boxes/premium_limits_box.h"
#include "boxes/peers/add_participants_box.h" #include "boxes/peers/add_participants_box.h"
#include "boxes/peers/edit_peer_common.h" #include "boxes/peers/edit_peer_common.h"
#include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/edit_participants_box.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
@ -27,34 +25,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "menu/menu_ttl.h" #include "menu/menu_ttl.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/special_fields.h" #include "ui/widgets/fields/special_fields.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/unread_badge.h"
#include "ui/ui_utility.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_cloud_file.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_invite_links.h" #include "api/api_invite_links.h"
#include "api/api_peer_photo.h" #include "api/api_peer_photo.h"
#include "api/api_self_destruct.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
#include "styles/style_boxes.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
@ -599,6 +589,8 @@ void GroupInfoBox::prepare() {
addButton(tr::lng_cancel(), [this] { closeBox(); }); addButton(tr::lng_cancel(), [this] { closeBox(); });
if (_type == Type::Group) { if (_type == Type::Group) {
_navigation->session().api().selfDestruct().reload();
const auto top = addTopButton(st::infoTopBarMenu); const auto top = addTopButton(st::infoTopBarMenu);
const auto menu = const auto menu =
top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>(); top->lifetime().make_state<base::unique_qptr<Ui::PopupMenu>>();
@ -607,21 +599,21 @@ void GroupInfoBox::prepare() {
top, top,
st::popupMenuWithIcons); st::popupMenuWithIcons);
const auto ttl = ttlPeriod();
const auto text = tr::lng_manage_messages_ttl_menu(tr::now) const auto text = tr::lng_manage_messages_ttl_menu(tr::now)
+ (_ttlPeriod + (ttl ? ('\t' + Ui::FormatTTLTiny(ttl)) : QString());
? ('\t' + Ui::FormatTTLTiny(_ttlPeriod))
: QString());
(*menu)->addAction( (*menu)->addAction(
text, text,
[=, show = uiShow()] { [=, show = uiShow()] {
show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{ show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{
.show = show, .show = show,
.startTtl = _ttlPeriod, .startTtl = ttlPeriod(),
.about = nullptr, .about = nullptr,
.callback = crl::guard(this, [=]( .callback = crl::guard(this, [=](
TimeId t, TimeId t,
Fn<void()> close) { Fn<void()> close) {
_ttlPeriod = t; _ttlPeriod = t;
_ttlPeriodOverridden = true;
close(); close();
}), }),
})); }));
@ -687,6 +679,13 @@ void GroupInfoBox::submitName() {
} }
} }
TimeId GroupInfoBox::ttlPeriod() const {
return _ttlPeriodOverridden
? _ttlPeriod
: _navigation->session().api().selfDestruct()
.periodDefaultHistoryTTLCurrent();
}
void GroupInfoBox::createGroup( void GroupInfoBox::createGroup(
QPointer<Ui::BoxContent> selectUsersBox, QPointer<Ui::BoxContent> selectUsersBox,
const QString &title, const QString &title,
@ -705,15 +704,13 @@ void GroupInfoBox::createGroup(
} }
} }
_creationRequestId = _api.request(MTPmessages_CreateChat( _creationRequestId = _api.request(MTPmessages_CreateChat(
MTP_flags(_ttlPeriod MTP_flags(MTPmessages_CreateChat::Flag::f_ttl_period),
? MTPmessages_CreateChat::Flag::f_ttl_period
: MTPmessages_CreateChat::Flags(0)),
MTP_vector<TLUsers>(inputs), MTP_vector<TLUsers>(inputs),
MTP_string(title), MTP_string(title),
MTP_int(_ttlPeriod) MTP_int(ttlPeriod())
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
auto image = _photo->takeResultImage(); auto image = _photo->takeResultImage();
const auto period = _ttlPeriod; const auto period = ttlPeriod();
const auto navigation = _navigation; const auto navigation = _navigation;
const auto done = _done; const auto done = _done;
@ -799,16 +796,17 @@ void GroupInfoBox::createChannel(
? Flag::f_megagroup ? Flag::f_megagroup
: Flag::f_broadcast) : Flag::f_broadcast)
| ((_type == Type::Forum) ? Flag::f_forum : Flag()) | ((_type == Type::Forum) ? Flag::f_forum : Flag())
| ((_type == Type::Megagroup && _ttlPeriod) | ((_type == Type::Megagroup)
? MTPchannels_CreateChannel::Flag::f_ttl_period ? MTPchannels_CreateChannel::Flag::f_ttl_period
: MTPchannels_CreateChannel::Flags(0)); : MTPchannels_CreateChannel::Flags(0));
const auto ttl = ttlPeriod();
_creationRequestId = _api.request(MTPchannels_CreateChannel( _creationRequestId = _api.request(MTPchannels_CreateChannel(
MTP_flags(flags), MTP_flags(flags),
MTP_string(title), MTP_string(title),
MTP_string(description), MTP_string(description),
MTPInputGeoPoint(), // geo_point MTPInputGeoPoint(), // geo_point
MTPstring(), // address MTPstring(), // address
MTP_int((_type == Type::Megagroup) ? _ttlPeriod : 0) MTP_int((_type == Type::Megagroup) ? ttl : 0)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
_navigation->session().api().applyUpdates(result); _navigation->session().api().applyUpdates(result);
@ -841,8 +839,8 @@ void GroupInfoBox::createChannel(
channel, channel,
{ std::move(image) }); { std::move(image) });
} }
if (_ttlPeriod && channel->isMegagroup()) { if (ttl && channel->isMegagroup()) {
channel->setMessagesTTL(_ttlPeriod); channel->setMessagesTTL(ttl);
} }
channel->session().api().requestFullPeer(channel); channel->session().api().requestFullPeer(channel);
_createdChannel = channel; _createdChannel = channel;

View file

@ -132,6 +132,8 @@ private:
void descriptionResized(); void descriptionResized();
void updateMaxHeight(); void updateMaxHeight();
[[nodiscard]] TimeId ttlPeriod() const;
const not_null<Window::SessionNavigation*> _navigation; const not_null<Window::SessionNavigation*> _navigation;
MTP::Sender _api; MTP::Sender _api;
@ -150,6 +152,7 @@ private:
bool _creatingInviteLink = false; bool _creatingInviteLink = false;
ChannelData *_createdChannel = nullptr; ChannelData *_createdChannel = nullptr;
TimeId _ttlPeriod = 0; TimeId _ttlPeriod = 0;
bool _ttlPeriodOverridden = false;
}; };

View file

@ -221,7 +221,7 @@ void DeleteMessagesBox::prepare() {
? QString() ? QString()
: QString(" (%1)").arg(total)); : QString(" (%1)").arg(total));
}); });
search->searchMessages(QString(), _moderateFrom); search->searchMessages({ .from = _moderateFrom });
} }
} else { } else {
details.text = (_ids.size() == 1) details.text = (_ids.size() == 1)

View file

@ -7,15 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/edit_privacy_box.h" #include "boxes/edit_privacy_box.h"
#include "api/api_global_privacy.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/painter.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "history/history.h" #include "history/history.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "settings/settings_common.h"
#include "settings/settings_premium.h"
#include "settings/settings_privacy_security.h" #include "settings/settings_privacy_security.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "base/binary_guard.h" #include "base/binary_guard.h"
@ -28,8 +35,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
namespace { namespace {
namespace {
void CreateRadiobuttonLock(
not_null<Ui::RpWidget*> widget,
const style::Checkbox &st) {
const auto lock = Ui::CreateChild<Ui::RpWidget>(widget.get());
lock->setAttribute(Qt::WA_TransparentForMouseEvents);
lock->resize(st::defaultRadio.diameter, st::defaultRadio.diameter);
widget->sizeValue(
) | rpl::start_with_next([=, &st](QSize size) {
lock->move(st.checkPosition);
}, lock->lifetime());
lock->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(lock);
auto hq = PainterHighQualityEnabler(p);
const auto &icon = st::messagePrivacyLock;
const auto size = st::defaultRadio.diameter;
const auto image = icon.instance(st::checkboxFg->c);
p.drawImage(QRectF(
(size - icon.width()) / 2.,
(size - icon.height()) / 2.,
icon.width(),
icon.height()), image);
}, lock->lifetime());
}
} // namespace
class PrivacyExceptionsBoxController : public ChatsListBoxController { class PrivacyExceptionsBoxController : public ChatsListBoxController {
public: public:
@ -340,7 +378,7 @@ void EditPrivacyBox::setupContent() {
auto middle = _controller->setupMiddleWidget( auto middle = _controller->setupMiddleWidget(
_window, _window,
content, content,
std::move(optionValue)); rpl::duplicate(optionValue));
if (middle) { if (middle) {
content->add(std::move(middle)); content->add(std::move(middle));
} }
@ -357,7 +395,11 @@ void EditPrivacyBox::setupContent() {
_controller->exceptionsDescription() | Ui::Text::ToWithEntities(), _controller->exceptionsDescription() | Ui::Text::ToWithEntities(),
st::defaultVerticalListSkip); st::defaultVerticalListSkip);
if (auto below = _controller->setupBelowWidget(_window, content)) { auto below = _controller->setupBelowWidget(
_window,
content,
rpl::duplicate(optionValue));
if (below) {
content->add(std::move(below)); content->add(std::move(below));
} }
@ -394,3 +436,119 @@ void EditPrivacyBox::setupContent() {
setDimensions(st::boxWideWidth, height); setDimensions(st::boxWideWidth, height);
}, content->lifetime()); }, content->lifetime());
} }
void EditMessagesPrivacyBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller) {
box->setTitle(tr::lng_messages_privacy_title());
box->setWidth(st::boxWideWidth);
constexpr auto kOptionAll = 0;
constexpr auto kOptionPremium = 1;
const auto premium = controller->session().premium();
const auto privacy = &controller->session().api().globalPrivacy();
const auto inner = box->verticalLayout();
inner->add(object_ptr<Ui::PlainShadow>(box));
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll);
inner->add(
object_ptr<Ui::Radiobutton>(
inner,
group,
kOptionAll,
tr::lng_messages_privacy_everyone(tr::now),
st::messagePrivacyCheck),
st::settingsSendTypePadding);
const auto restricted = inner->add(
object_ptr<Ui::Radiobutton>(
inner,
group,
kOptionPremium,
tr::lng_messages_privacy_restricted(tr::now),
st::messagePrivacyCheck),
st::settingsSendTypePadding + style::margins(
0,
st::messagePrivacyRadioSkip,
0,
st::messagePrivacyBottomSkip));
using WeakToast = base::weak_ptr<Ui::Toast::Instance>;
const auto toast = std::make_shared<WeakToast>();
const auto showToast = [=] {
auto link = Ui::Text::Link(
Ui::Text::Semibold(
tr::lng_messages_privacy_premium_link(tr::now)));
(*toast) = controller->showToast({
.text = tr::lng_messages_privacy_premium(
tr::now,
lt_link,
link,
Ui::Text::WithEntities),
.st = &st::defaultMultilineToast,
.duration = Ui::Toast::kDefaultDuration * 2,
.multiline = true,
.filter = crl::guard(&controller->session(), [=](
const ClickHandlerPtr &,
Qt::MouseButton button) {
if (button == Qt::LeftButton) {
if (const auto strong = toast->get()) {
strong->hideAnimated();
(*toast) = nullptr;
Settings::ShowPremium(
controller,
u"noncontact_peers_require_premium"_q);
return true;
}
}
return false;
}),
});
};
if (!premium) {
CreateRadiobuttonLock(restricted, st::messagePrivacyCheck);
group->setChangedCallback([=](int value) {
if (value == kOptionPremium) {
group->setValue(kOptionAll);
showToast();
}
});
}
Ui::AddDividerText(inner, tr::lng_messages_privacy_about());
if (!premium) {
Ui::AddSkip(inner);
Settings::AddButtonWithIcon(
inner,
tr::lng_messages_privacy_premium_button(),
st::messagePrivacySubscribe,
{ .icon = &st::menuBlueIconPremium }
)->setClickedCallback([=] {
Settings::ShowPremium(
controller,
u"noncontact_peers_require_premium"_q);
});
Ui::AddSkip(inner);
Ui::AddDividerText(inner, tr::lng_messages_privacy_premium_about());
box->addButton(tr::lng_about_done(), [=] {
box->closeBox();
});
} else {
box->addButton(tr::lng_settings_save(), [=] {
if (controller->session().premium()) {
privacy->updateNewRequirePremium(
group->value() == kOptionPremium);
box->closeBox();
} else {
showToast();
}
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
}

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_user_privacy.h" #include "api/api_user_privacy.h"
namespace Ui { namespace Ui {
class GenericBox;
class VerticalLayout; class VerticalLayout;
class FlatLabel; class FlatLabel;
class LinkButton; class LinkButton;
@ -74,7 +75,8 @@ public:
} }
[[nodiscard]] virtual object_ptr<Ui::RpWidget> setupBelowWidget( [[nodiscard]] virtual object_ptr<Ui::RpWidget> setupBelowWidget(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<QWidget*> parent) { not_null<QWidget*> parent,
rpl::producer<Option> option) {
return { nullptr }; return { nullptr };
} }
@ -146,3 +148,7 @@ private:
Value _value; Value _value;
}; };
void EditMessagesPrivacyBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller);

View file

@ -83,6 +83,31 @@ GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
return result; return result;
} }
[[nodiscard]] Fn<TextWithEntities(TextWithEntities)> BoostsForGiftText(
const std::vector<not_null<UserData*>> users) {
Expects(!users.empty());
const auto session = &users.front()->session();
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
return [=, count = users.size()](TextWithEntities text) {
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_reward(
tr::now,
lt_count,
count * BoostsForGift(session),
lt_emoji,
emoji,
Ui::Text::RichLangValue));
return text;
};
}
using TagUser1 = lngtag_user; using TagUser1 = lngtag_user;
using TagUser2 = lngtag_second_user; using TagUser2 = lngtag_second_user;
using TagUser3 = lngtag_name; using TagUser3 = lngtag_name;
@ -293,16 +318,21 @@ void GiftBox(
std::move(titleLabel)), std::move(titleLabel)),
st::premiumGiftTitlePadding); st::premiumGiftTitlePadding);
auto textLabel = object_ptr<Ui::FlatLabel>( auto textLabel = object_ptr<Ui::FlatLabel>(box, st::premiumPreviewAbout);
box, tr::lng_premium_gift_about(
tr::lng_premium_gift_about( lt_user,
lt_user, user->session().changes().peerFlagsValue(
user->session().changes().peerFlagsValue( user,
user, Data::PeerUpdate::Flag::Name
Data::PeerUpdate::Flag::Name ) | rpl::map([=] { return TextWithEntities{ user->firstName }; }),
) | rpl::map([=] { return TextWithEntities{ user->firstName }; }), Ui::Text::RichLangValue
Ui::Text::RichLangValue), ) | rpl::map(
st::premiumPreviewAbout); BoostsForGiftText({ user })
) | rpl::start_with_next([
raw = textLabel.data(),
session = &user->session()](const TextWithEntities &t) {
raw->setMarkedText(t, Core::MarkedTextContext{ .session = session });
}, textLabel->lifetime());
textLabel->setTextColorOverride(stTitle.textFg->c); textLabel->setTextColorOverride(stTitle.textFg->c);
textLabel->resizeToWidth(available); textLabel->resizeToWidth(available);
box->addRow( box->addRow(
@ -476,11 +506,6 @@ void GiftsBox(
// About. // About.
{ {
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
auto text = rpl::conditional( auto text = rpl::conditional(
state->isPaymentComplete.value(), state->isPaymentComplete.value(),
ComplexAboutLabel( ComplexAboutLabel(
@ -505,18 +530,7 @@ void GiftsBox(
tr::lng_premium_gifts_about_user2, tr::lng_premium_gifts_about_user2,
tr::lng_premium_gifts_about_user3, tr::lng_premium_gifts_about_user3,
tr::lng_premium_gifts_about_user_more tr::lng_premium_gifts_about_user_more
) | rpl::map([=, count = users.size()](TextWithEntities text) { ) | rpl::map(BoostsForGiftText(users))
text.append('\n');
text.append('\n');
text.append(tr::lng_premium_gifts_about_reward(
tr::now,
lt_count,
count * BoostsForGift(session),
lt_emoji,
emoji,
Ui::Text::RichLangValue));
return text;
})
); );
const auto label = box->addRow( const auto label = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>( object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(

View file

@ -232,9 +232,7 @@ void PeerListBox::resizeEvent(QResizeEvent *e) {
void PeerListBox::paintEvent(QPaintEvent *e) { void PeerListBox::paintEvent(QPaintEvent *e) {
auto p = QPainter(this); auto p = QPainter(this);
const auto &bg = (_controller->listSt() const auto &bg = _controller->computeListSt().bg;
? *_controller->listSt()
: st::peerListBox).bg;
const auto fill = QRect( const auto fill = QRect(
0, 0,
_addedTopScrollSkip, _addedTopScrollSkip,
@ -544,6 +542,12 @@ bool PeerListRow::checked() const {
return _checkbox && _checkbox->checked(); return _checkbox && _checkbox->checked();
} }
void PeerListRow::preloadUserpic() {
if (_peer) {
_peer->loadUserpic();
}
}
void PeerListRow::setCustomStatus(const QString &status, bool active) { void PeerListRow::setCustomStatus(const QString &status, bool active) {
setStatusText(status); setStatusText(status);
_statusType = active ? StatusType::CustomActive : StatusType::Custom; _statusType = active ? StatusType::CustomActive : StatusType::Custom;
@ -1249,6 +1253,16 @@ not_null<PeerListRow*> PeerListContent::rowAt(int index) const {
return _rows[index].get(); return _rows[index].get();
} }
int PeerListContent::searchRowsCount() const {
return _searchRows.size();
}
not_null<PeerListRow*> PeerListContent::searchRowAt(int index) const {
Expects(index >= 0 && index < _searchRows.size());
return _searchRows[index].get();
}
void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) { void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) {
_description = std::move(description); _description = std::move(description);
if (_description) { if (_description) {
@ -1343,6 +1357,7 @@ void PeerListContent::refreshRows() {
if (_mouseSelection) { if (_mouseSelection) {
selectByMouse(QCursor::pos()); selectByMouse(QCursor::pos());
} }
loadProfilePhotos();
update(); update();
} }
@ -1897,7 +1912,9 @@ void PeerListContent::mouseLeftGeometry() {
} }
void PeerListContent::loadProfilePhotos() { void PeerListContent::loadProfilePhotos() {
if (_visibleTop >= _visibleBottom) return; if (_visibleTop >= _visibleBottom) {
return;
}
auto yFrom = _visibleTop; auto yFrom = _visibleTop;
auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount; auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount;
@ -1914,10 +1931,7 @@ void PeerListContent::loadProfilePhotos() {
if (to > rowsCount) to = rowsCount; if (to > rowsCount) to = rowsCount;
for (auto index = from; index != to; ++index) { for (auto index = from; index != to; ++index) {
const auto row = getRow(RowIndex(index)); getRow(RowIndex(index))->preloadUserpic();
if (!row->special()) {
row->peer()->loadUserpic();
}
} }
} }
} }

View file

@ -101,6 +101,8 @@ public:
[[nodiscard]] virtual auto generateNameWords() const [[nodiscard]] virtual auto generateNameWords() const
-> const base::flat_set<QString> &; -> const base::flat_set<QString> &;
virtual void preloadUserpic();
void setCustomStatus(const QString &status, bool active = false); void setCustomStatus(const QString &status, bool active = false);
void clearCustomStatus(); void clearCustomStatus();
@ -330,6 +332,8 @@ public:
virtual void peerListScrollToTop() = 0; virtual void peerListScrollToTop() = 0;
virtual int peerListFullRowsCount() = 0; virtual int peerListFullRowsCount() = 0;
virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
virtual int peerListSearchRowsCount() = 0;
virtual not_null<PeerListRow*> peerListSearchRowAt(int index) = 0;
virtual std::optional<QPoint> peerListLastRowMousePosition() = 0; virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0; virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0; virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
@ -625,6 +629,8 @@ public:
void convertRowToSearchResult(not_null<PeerListRow*> row); void convertRowToSearchResult(not_null<PeerListRow*> row);
int fullRowsCount() const; int fullRowsCount() const;
not_null<PeerListRow*> rowAt(int index) const; not_null<PeerListRow*> rowAt(int index) const;
int searchRowsCount() const;
not_null<PeerListRow*> searchRowAt(int index) const;
void setDescription(object_ptr<Ui::FlatLabel> description); void setDescription(object_ptr<Ui::FlatLabel> description);
void setSearchLoading(object_ptr<Ui::FlatLabel> loading); void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults); void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
@ -906,6 +912,12 @@ public:
not_null<PeerListRow*> peerListRowAt(int index) override { not_null<PeerListRow*> peerListRowAt(int index) override {
return _content->rowAt(index); return _content->rowAt(index);
} }
int peerListSearchRowsCount() override {
return _content->searchRowsCount();
}
not_null<PeerListRow*> peerListSearchRowAt(int index) override {
return _content->searchRowAt(index);
}
void peerListRefreshRows() override { void peerListRefreshRows() override {
_content->refreshRows(); _content->refreshRows();
} }

View file

@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "api/api_premium.h"
#include "base/random.h" #include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h" #include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/effects/round_checkbox.h" #include "ui/effects/round_checkbox.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h" #include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
@ -19,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/data_peer_values.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -44,10 +48,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_profile.h" #include "styles/style_profile.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
#include "styles/style_chat_helpers.h"
#include "data/data_stories.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
namespace { namespace {
@ -257,9 +258,88 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId; return _timer.isActive() || _requestId;
} }
ChatsListBoxController::Row::Row(not_null<History*> history) RecipientRow::RecipientRow(
: PeerListRow(history->peer) not_null<PeerData*> peer,
, _history(history) { const style::PeerListItem *maybeLockedSt,
History *maybeHistory)
: PeerListRow(peer)
, _maybeHistory(maybeHistory)
, _resolvePremiumRequired(maybeLockedSt != nullptr) {
if (maybeLockedSt
&& (Api::ResolveRequiresPremiumToWrite(peer, maybeHistory)
== Api::RequirePremiumState::Yes)) {
_lockedSt = maybeLockedSt;
}
}
PaintRoundImageCallback RecipientRow::generatePaintUserpicCallback(
bool forceRound) {
auto result = PeerListRow::generatePaintUserpicCallback(forceRound);
if (const auto st = _lockedSt) {
return [=](Painter &p, int x, int y, int outerWidth, int size) {
result(p, x, y, outerWidth, size);
PaintPremiumRequiredLock(p, st, x, y, outerWidth, size);
};
}
return result;
}
bool RecipientRow::refreshLock(
not_null<const style::PeerListItem*> maybeLockedSt) {
if (const auto user = peer()->asUser()) {
const auto locked = _resolvePremiumRequired
&& (Api::ResolveRequiresPremiumToWrite(user, _maybeHistory)
== Api::RequirePremiumState::Yes);
if (this->locked() != locked) {
setLocked(locked ? maybeLockedSt.get() : nullptr);
return true;
}
}
return false;
}
void RecipientRow::preloadUserpic() {
PeerListRow::preloadUserpic();
if (!_resolvePremiumRequired) {
return;
} else if (Api::ResolveRequiresPremiumToWrite(peer(), _maybeHistory)
== Api::RequirePremiumState::Unknown) {
const auto user = peer()->asUser();
user->session().api().premium().resolvePremiumRequired(user);
}
}
void TrackPremiumRequiredChanges(
not_null<PeerListController*> controller,
rpl::lifetime &lifetime) {
const auto session = &controller->session();
rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved()
) | rpl::start_with_next([=] {
const auto st = &controller->computeListSt().item;
const auto delegate = controller->delegate();
const auto process = [&](not_null<PeerListRow*> raw) {
if (static_cast<RecipientRow*>(raw.get())->refreshLock(st)) {
delegate->peerListUpdateRow(raw);
}
};
auto count = delegate->peerListFullRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate->peerListRowAt(i));
}
count = delegate->peerListSearchRowsCount();
for (auto i = 0; i != count; ++i) {
process(delegate->peerListSearchRowAt(i));
}
}, lifetime);
}
ChatsListBoxController::Row::Row(
not_null<History*> history,
const style::PeerListItem *maybeLockedSt)
: RecipientRow(history->peer, maybeLockedSt, history) {
} }
ChatsListBoxController::ChatsListBoxController( ChatsListBoxController::ChatsListBoxController(
@ -420,7 +500,7 @@ void PeerListStories::process(not_null<PeerListRow*> row) {
bool PeerListStories::handleClick(not_null<PeerData*> peer) { bool PeerListStories::handleClick(not_null<PeerData*> peer) {
const auto point = _delegate->peerListLastRowMousePosition(); const auto point = _delegate->peerListLastRowMousePosition();
const auto &st = _controller->listSt()->item; const auto &st = _controller->computeListSt().item;
if (point && point->x() < st.photoPosition.x() + st.photoSize) { if (point && point->x() < st.photoPosition.x() + st.photoSize) {
if (const auto window = peer->session().tryResolveWindow()) { if (const auto window = peer->session().tryResolveWindow()) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
@ -437,9 +517,9 @@ bool PeerListStories::handleClick(not_null<PeerData*> peer) {
void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) { void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
_delegate = delegate; _delegate = delegate;
_unreadBrush = PeerListStoriesGradient(*_controller->listSt()); _unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
style::PaletteChanged() | rpl::start_with_next([=] { style::PaletteChanged() | rpl::start_with_next([=] {
_unreadBrush = PeerListStoriesGradient(*_controller->listSt()); _unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
updateColors(); updateColors();
}, _lifetime); }, _lifetime);
@ -598,7 +678,9 @@ void ContactsBoxController::sortByOnline() {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
const auto key = [&](const PeerListRow &row) { const auto key = [&](const PeerListRow &row) {
const auto user = row.peer()->asUser(); const auto user = row.peer()->asUser();
return user ? (std::min(user->onlineTill, now) + 1) : TimeId(); return user
? (std::min(user->lastseen().onlineTill(), now + 1) + 1)
: TimeId();
}; };
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) { const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
return key(a) > key(b); return key(a) > key(b);
@ -627,14 +709,40 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
return std::make_unique<PeerListRow>(user); return std::make_unique<PeerListRow>(user);
} }
RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user) {
return {
.text = tr::lng_send_non_premium_message_toast(
tr::now,
lt_user,
TextWithEntities{ user->shortName() },
lt_link,
Ui::Text::Link(
Ui::Text::Bold(
tr::lng_send_non_premium_message_toast_link(
tr::now))),
Ui::Text::RichLangValue),
};
}
ChooseRecipientBoxController::ChooseRecipientBoxController( ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback, FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter) Fn<bool(not_null<Data::Thread*>)> filter)
: ChatsListBoxController(session) : ChooseRecipientBoxController({
, _session(session) .session = session,
, _callback(std::move(callback)) .callback = std::move(callback),
, _filter(std::move(filter)) { .filter = std::move(filter),
}) {
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
ChooseRecipientArgs &&args)
: ChatsListBoxController(args.session)
, _session(args.session)
, _callback(std::move(args.callback))
, _filter(std::move(args.filter))
, _premiumRequiredError(std::move(args.premiumRequiredError)) {
} }
Main::Session &ChooseRecipientBoxController::session() const { Main::Session &ChooseRecipientBoxController::session() const {
@ -643,9 +751,21 @@ Main::Session &ChooseRecipientBoxController::session() const {
void ChooseRecipientBoxController::prepareViewHook() { void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_forward_choose()); delegate()->peerListSetTitle(tr::lng_forward_choose());
if (_premiumRequiredError) {
TrackPremiumRequiredChanges(this, lifetime());
}
}
bool ChooseRecipientBoxController::showLockedError(
not_null<PeerListRow*> row) {
return RecipientRow::ShowLockedError(this, row, _premiumRequiredError);
} }
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) { void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
if (showLockedError(row)) {
return;
}
auto guard = base::make_weak(this); auto guard = base::make_weak(this);
const auto peer = row->peer(); const auto peer = row->peer();
if (const auto forum = peer->forum()) { if (const auto forum = peer->forum()) {
@ -696,6 +816,21 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
bool RecipientRow::ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error) {
if (!static_cast<RecipientRow*>(row.get())->locked()) {
return false;
}
::Settings::ShowPremiumPromoToast(
controller->delegate()->peerListUiShow(),
ChatHelpers::ResolveWindowDefault(),
error(row->peer()->asUser()).text,
u"require_premium"_q);
return true;
}
QString ChooseRecipientBoxController::savedMessagesChatStatus() const { QString ChooseRecipientBoxController::savedMessagesChatStatus() const {
return tr::lng_saved_forward_here(tr::now); return tr::lng_saved_forward_here(tr::now);
} }
@ -706,8 +841,17 @@ auto ChooseRecipientBoxController::createRow(
const auto skip = _filter const auto skip = _filter
? !_filter(history) ? !_filter(history)
: ((peer->isBroadcast() && !Data::CanSendAnything(peer)) : ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| (peer->isUser() && !Data::CanSendAnything(peer))); || peer->isRepliesChat()
return skip ? nullptr : std::make_unique<Row>(history); || (peer->isUser() && (_premiumRequiredError
? !peer->asUser()->canSendIgnoreRequirePremium()
: !Data::CanSendAnything(peer))));
if (skip) {
return nullptr;
}
auto result = std::make_unique<Row>(
history,
_premiumRequiredError ? &computeListSt().item : nullptr);
return result;
} }
ChooseTopicSearchController::ChooseTopicSearchController( ChooseTopicSearchController::ChooseTopicSearchController(
@ -925,3 +1069,26 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
const auto skip = _filter && !_filter(topic); const auto skip = _filter && !_filter(topic);
return skip ? nullptr : std::make_unique<Row>(topic); return skip ? nullptr : std::make_unique<Row>(topic);
}; };
void PaintPremiumRequiredLock(
Painter &p,
not_null<const style::PeerListItem*> st,
int x,
int y,
int outerWidth,
int size) {
auto hq = PainterHighQualityEnabler(p);
const auto &check = st->checkbox.check;
auto pen = check.border->p;
pen.setWidthF(check.width);
p.setPen(pen);
p.setBrush(st::premiumButtonBg2);
const auto &icon = st::stickersPremiumLock;
const auto width = icon.width();
const auto height = icon.height();
const auto rect = QRect(
QPoint(x + size - width, y + size - height),
icon.size());
p.drawEllipse(rect);
icon.paintInCenter(p, rect);
}

View file

@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History; class History;
namespace style {
struct PeerListItem;
} // namespace style
namespace Data { namespace Data {
class Thread; class Thread;
class Forum; class Forum;
@ -89,19 +93,64 @@ private:
}; };
struct RecipientPremiumRequiredError {
TextWithEntities text;
};
[[nodiscard]] RecipientPremiumRequiredError WritePremiumRequiredError(
not_null<UserData*> user);
class RecipientRow : public PeerListRow {
public:
explicit RecipientRow(
not_null<PeerData*> peer,
const style::PeerListItem *maybeLockedSt = nullptr,
History *maybeHistory = nullptr);
bool refreshLock(not_null<const style::PeerListItem*> maybeLockedSt);
[[nodiscard]] static bool ShowLockedError(
not_null<PeerListController*> controller,
not_null<PeerListRow*> row,
Fn<RecipientPremiumRequiredError(not_null<UserData*>)> error);
[[nodiscard]] History *maybeHistory() const {
return _maybeHistory;
}
[[nodiscard]] bool locked() const {
return _lockedSt != nullptr;
}
void setLocked(const style::PeerListItem *lockedSt) {
_lockedSt = lockedSt;
}
PaintRoundImageCallback generatePaintUserpicCallback(
bool forceRound) override;
void preloadUserpic() override;
private:
History *_maybeHistory = nullptr;
const style::PeerListItem *_lockedSt = nullptr;
bool _resolvePremiumRequired = false;
};
void TrackPremiumRequiredChanges(
not_null<PeerListController*> controller,
rpl::lifetime &lifetime);
class ChatsListBoxController : public PeerListController { class ChatsListBoxController : public PeerListController {
public: public:
class Row : public PeerListRow { class Row : public RecipientRow {
public: public:
Row(not_null<History*> history); Row(
not_null<History*> history,
const style::PeerListItem *maybeLockedSt = nullptr);
not_null<History*> history() const { [[nodiscard]] not_null<History*> history() const {
return _history; return maybeHistory();
} }
private:
not_null<History*> _history;
}; };
ChatsListBoxController(not_null<Main::Session*> session); ChatsListBoxController(not_null<Main::Session*> session);
@ -207,6 +256,15 @@ private:
}; };
struct ChooseRecipientArgs {
not_null<Main::Session*> session;
FnMut<void(not_null<Data::Thread*>)> callback;
Fn<bool(not_null<Data::Thread*>)> filter;
using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
};
class ChooseRecipientBoxController class ChooseRecipientBoxController
: public ChatsListBoxController : public ChatsListBoxController
, public base::has_weak_ptr { , public base::has_weak_ptr {
@ -215,6 +273,7 @@ public:
not_null<Main::Session*> session, not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback, FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter = nullptr); Fn<bool(not_null<Data::Thread*>)> filter = nullptr);
explicit ChooseRecipientBoxController(ChooseRecipientArgs &&args);
Main::Session &session() const override; Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
@ -225,10 +284,14 @@ protected:
void prepareViewHook() override; void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override; std::unique_ptr<Row> createRow(not_null<History*> history) override;
bool showLockedError(not_null<PeerListRow*> row);
private: private:
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback; FnMut<void(not_null<Data::Thread*>)> _callback;
Fn<bool(not_null<Data::Thread*>)> _filter; Fn<bool(not_null<Data::Thread*>)> _filter;
Fn<RecipientPremiumRequiredError(
not_null<UserData*>)> _premiumRequiredError;
}; };
@ -305,3 +368,11 @@ private:
Fn<bool(not_null<Data::ForumTopic*>)> _filter; Fn<bool(not_null<Data::ForumTopic*>)> _filter;
}; };
void PaintPremiumRequiredLock(
Painter &p,
not_null<const style::PeerListItem*> st,
int x,
int y,
int outerWidth,
int size);

View file

@ -281,9 +281,7 @@ void PeerListsBox::resizeEvent(QResizeEvent *e) {
void PeerListsBox::paintEvent(QPaintEvent *e) { void PeerListsBox::paintEvent(QPaintEvent *e) {
auto p = QPainter(this); auto p = QPainter(this);
const auto &bg = (firstController()->listSt() const auto &bg = firstController()->computeListSt().bg;
? *firstController()->listSt()
: st::peerListBox).bg;
for (const auto &rect : e->region()) { for (const auto &rect : e->region()) {
p.fillRect(rect, bg); p.fillRect(rect, bg);
} }

View file

@ -267,6 +267,10 @@ void AddParticipantsBoxController::subscribeToMigration() {
} }
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto premiumRequiredError = WritePremiumRequiredError;
if (RecipientRow::ShowLockedError(this, row, premiumRequiredError)) {
return;
}
const auto &serverConfig = session().serverConfig(); const auto &serverConfig = session().serverConfig();
auto count = fullCount(); auto count = fullCount();
auto limit = _peer && (_peer->isChat() || _peer->isMegagroup()) auto limit = _peer && (_peer->isChat() || _peer->isMegagroup())
@ -292,6 +296,8 @@ void AddParticipantsBoxController::itemDeselectedHook(
void AddParticipantsBoxController::prepareViewHook() { void AddParticipantsBoxController::prepareViewHook() {
updateTitle(); updateTitle();
TrackPremiumRequiredChanges(this, lifetime());
} }
int AddParticipantsBoxController::alreadyInCount() const { int AddParticipantsBoxController::alreadyInCount() const {
@ -332,8 +338,10 @@ std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(
if (user->isSelf()) { if (user->isSelf()) {
return nullptr; return nullptr;
} }
auto result = std::make_unique<PeerListRow>(user); const auto already = isAlreadyIn(user);
if (isAlreadyIn(user)) { const auto maybeLockedSt = already ? nullptr : &computeListSt().item;
auto result = std::make_unique<RecipientRow>(user, maybeLockedSt);
if (already) {
result->setDisabledState(PeerListRow::State::DisabledChecked); result->setDisabledState(PeerListRow::State::DisabledChecked);
} }
return result; return result;

View file

@ -143,7 +143,7 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
? tr::lng_status_bot_reads_all ? tr::lng_status_bot_reads_all
: tr::lng_status_bot_not_reads_all)(tr::now); : tr::lng_status_bot_not_reads_all)(tr::now);
} }
return Data::OnlineText(_user->onlineTill, base::unixtime::now()); return Data::OnlineText(_user->lastseen(), base::unixtime::now());
}(); }();
p.setFont(st::contactsStatusFont); p.setFont(st::contactsStatusFont);
p.setPen(st::contactsStatusFg); p.setPen(st::contactsStatusFg);

View file

@ -522,9 +522,23 @@ not_null<Ui::SlideWrap<>*> Controller::addRequestedListBlock(
lt_count_decimal, lt_count_decimal,
std::move(requestedCount))); std::move(requestedCount)));
const auto delegate = container->lifetime().make_state< class Delegate final : public PeerListContentDelegateSimple {
PeerListContentDelegateSimple public:
>(); explicit Delegate(std::shared_ptr<Main::SessionShow> show)
: _show(std::move(show)) {
}
std::shared_ptr<Main::SessionShow> peerListUiShow() override {
return _show;
}
private:
const std::shared_ptr<Main::SessionShow> _show;
};
const auto delegate = container->lifetime().make_state<Delegate>(
this->delegate()->peerListUiShow());
const auto controller = container->lifetime().make_state< const auto controller = container->lifetime().make_state<
Controller Controller
>(_peer, _data.current().admin, _data.value(), Role::Requested); >(_peer, _data.current().admin, _data.value(), Role::Requested);
@ -1194,6 +1208,11 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
} }
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) { auto filterCallback = [](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
return true;
}
}
return Data::CanSendTexts(thread); return Data::CanSendTexts(thread);
}; };
auto object = Box<ShareBox>(ShareBox::Descriptor{ auto object = Box<ShareBox>(ShareBox::Descriptor{
@ -1201,6 +1220,7 @@ object_ptr<Ui::BoxContent> ShareInviteLinkBox(
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.premiumRequiredError = SharePremiumRequiredError(),
}); });
*box = Ui::MakeWeak(object.data()); *box = Ui::MakeWeak(object.data());
return object; return object;

View file

@ -585,9 +585,8 @@ void Controller::checkUsernameAvailability() {
showUsernameError(tr::lng_create_channel_link_invalid()); showUsernameError(tr::lng_create_channel_link_invalid());
} else if (type == u"USERNAME_PURCHASE_AVAILABLE"_q) { } else if (type == u"USERNAME_PURCHASE_AVAILABLE"_q) {
_goodUsername = false; _goodUsername = false;
_usernameCheckInfo.fire({ _usernameCheckInfo.fire(
.type = UsernameCheckInfo::Type::PurchaseAvailable, UsernameCheckInfo::PurchaseAvailable(checking, _peer));
});
} else if (type == u"USERNAME_OCCUPIED"_q && checking != username) { } else if (type == u"USERNAME_OCCUPIED"_q && checking != username) {
showUsernameError(tr::lng_create_channel_link_occupied()); showUsernameError(tr::lng_create_channel_link_occupied());
} }

View file

@ -110,6 +110,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_subtitle_emoji_status(); return tr::lng_premium_summary_subtitle_emoji_status();
case PremiumPreview::InfiniteReactions: case PremiumPreview::InfiniteReactions:
return tr::lng_premium_summary_subtitle_infinite_reactions(); return tr::lng_premium_summary_subtitle_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_subtitle_tags_for_messages();
case PremiumPreview::Stickers: case PremiumPreview::Stickers:
return tr::lng_premium_summary_subtitle_premium_stickers(); return tr::lng_premium_summary_subtitle_premium_stickers();
case PremiumPreview::AnimatedEmoji: case PremiumPreview::AnimatedEmoji:
@ -146,6 +148,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_emoji_status(); return tr::lng_premium_summary_about_emoji_status();
case PremiumPreview::InfiniteReactions: case PremiumPreview::InfiniteReactions:
return tr::lng_premium_summary_about_infinite_reactions(); return tr::lng_premium_summary_about_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_about_tags_for_messages();
case PremiumPreview::Stickers: case PremiumPreview::Stickers:
return tr::lng_premium_summary_about_premium_stickers(); return tr::lng_premium_summary_about_premium_stickers();
case PremiumPreview::AnimatedEmoji: case PremiumPreview::AnimatedEmoji:
@ -471,6 +475,7 @@ struct VideoPreviewDocument {
return "advanced_chat_management"; return "advanced_chat_management";
case PremiumPreview::EmojiStatus: return "emoji_status"; case PremiumPreview::EmojiStatus: return "emoji_status";
case PremiumPreview::InfiniteReactions: return "infinite_reactions"; case PremiumPreview::InfiniteReactions: return "infinite_reactions";
case PremiumPreview::TagsForMessages: return "saved_tags";
case PremiumPreview::ProfileBadge: return "profile_badge"; case PremiumPreview::ProfileBadge: return "profile_badge";
case PremiumPreview::AnimatedUserpics: return "animated_userpics"; case PremiumPreview::AnimatedUserpics: return "animated_userpics";
case PremiumPreview::RealTimeTranslation: return "translations"; case PremiumPreview::RealTimeTranslation: return "translations";

View file

@ -61,6 +61,7 @@ enum class PremiumPreview {
AnimatedUserpics, AnimatedUserpics,
RealTimeTranslation, RealTimeTranslation,
Wallpapers, Wallpapers,
TagsForMessages,
kCount, kCount,
}; };

View file

@ -507,7 +507,6 @@ void ReactionsSettingsBox(
}; };
auto firstCheckedButton = (Ui::RpWidget*)(nullptr); auto firstCheckedButton = (Ui::RpWidget*)(nullptr);
const auto premiumPossible = controller->session().premiumPossible();
auto list = reactions.list(Data::Reactions::Type::Active); auto list = reactions.list(Data::Reactions::Type::Active);
if (const auto favorite = reactions.favorite()) { if (const auto favorite = reactions.favorite()) {
if (favorite->id.custom()) { if (favorite->id.custom()) {
@ -520,11 +519,6 @@ void ReactionsSettingsBox(
rpl::single<QString>(base::duplicate(r.title)), rpl::single<QString>(base::duplicate(r.title)),
st::settingsButton)); st::settingsButton));
const auto premium = r.premium;
if (premium && !premiumPossible) {
continue;
}
const auto iconSize = st::settingsReactionSize; const auto iconSize = st::settingsReactionSize;
const auto left = button->st().iconLeft; const auto left = button->st().iconLeft;
auto iconPositionValue = button->sizeValue( auto iconPositionValue = button->sizeValue(
@ -556,12 +550,6 @@ void ReactionsSettingsBox(
&button->lifetime()); &button->lifetime());
} }
button->setClickedCallback([=, id = r.id] { button->setClickedCallback([=, id = r.id] {
if (premium && !controller->session().premium()) {
ShowPremiumPreviewBox(
controller,
PremiumPreview::InfiniteReactions);
return;
}
checkButton(button); checkButton(button);
state->selectedId = id; state->selectedId = id;
}); });

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "api/api_premium.h"
#include "base/random.h" #include "base/random.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "base/qthelp_url.h" #include "base/qthelp_url.h"
@ -30,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_context_menu.h" // CopyPostLink. #include "history/view/history_view_context_menu.h" // CopyPostLink.
#include "settings/settings_premium.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
@ -38,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_game.h" #include "data/data_game.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_peer_values.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_forum.h" #include "data/data_forum.h"
@ -96,19 +99,23 @@ protected:
private: private:
struct Chat { struct Chat {
Chat( Chat(
not_null<PeerData*> peer, not_null<History*> history,
const style::PeerListItem &st, const style::PeerListItem &st,
Fn<void()> updateCallback); Fn<void()> updateCallback);
not_null<History*> history;
not_null<PeerData*> peer; not_null<PeerData*> peer;
Data::ForumTopic *topic = nullptr; Data::ForumTopic *topic = nullptr;
rpl::lifetime topicLifetime; rpl::lifetime topicLifetime;
Ui::RoundImageCheckbox checkbox; Ui::RoundImageCheckbox checkbox;
Ui::Text::String name; Ui::Text::String name;
Ui::Animations::Simple nameActive; Ui::Animations::Simple nameActive;
bool locked = false;
}; };
void invalidateCache(); void invalidateCache();
bool showLockedError(not_null<Chat*> chat);
void refreshLockedRows();
[[nodiscard]] int displayedChatsCount() const; [[nodiscard]] int displayedChatsCount() const;
[[nodiscard]] not_null<Data::Thread*> chatThread( [[nodiscard]] not_null<Data::Thread*> chatThread(
@ -117,12 +124,14 @@ private:
void paintChat(Painter &p, not_null<Chat*> chat, int index); void paintChat(Painter &p, not_null<Chat*> chat, int index);
void updateChat(not_null<PeerData*> peer); void updateChat(not_null<PeerData*> peer);
void updateChatName(not_null<Chat*> chat); void updateChatName(not_null<Chat*> chat);
void initChatLocked(not_null<Chat*> chat);
void repaintChat(not_null<PeerData*> peer); void repaintChat(not_null<PeerData*> peer);
int chatIndex(not_null<PeerData*> peer) const; int chatIndex(not_null<PeerData*> peer) const;
void repaintChatAtIndex(int index); void repaintChatAtIndex(int index);
Chat *getChatAtIndex(int index); Chat *getChatAtIndex(int index);
void loadProfilePhotos(int yFrom); void loadProfilePhotos();
void preloadUserpic(not_null<Dialogs::Entry*> entry);
void changeCheckState(Chat *chat); void changeCheckState(Chat *chat);
void chooseForumTopic(not_null<Data::Forum*> forum); void chooseForumTopic(not_null<Data::Forum*> forum);
enum class ChangeStateWay { enum class ChangeStateWay {
@ -153,6 +162,7 @@ private:
int _columnCount = 4; int _columnCount = 4;
int _active = -1; int _active = -1;
int _upon = -1; int _upon = -1;
int _visibleTop = 0;
std::unique_ptr<Dialogs::IndexedList> _chatsIndexed; std::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
QString _filter; QString _filter;
@ -485,7 +495,6 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
}; };
Ui::FillForwardOptions( Ui::FillForwardOptions(
std::move(createView), std::move(createView),
_descriptor.forwardOptions.messagesCount,
_forwardOptions, _forwardOptions,
[=](Ui::ForwardOptions value) { _forwardOptions = value; }, [=](Ui::ForwardOptions value) { _forwardOptions = value; },
_menu->lifetime()); _menu->lifetime());
@ -512,7 +521,10 @@ void ShareBox::createButtons() {
const auto send = addButton(tr::lng_share_confirm(), [=] { const auto send = addButton(tr::lng_share_confirm(), [=] {
submit({}); submit({});
}); });
_forwardOptions.hasCaptions = _descriptor.forwardOptions.hasCaptions; _forwardOptions.sendersCount
= _descriptor.forwardOptions.sendersCount;
_forwardOptions.captionsCount
= _descriptor.forwardOptions.captionsCount;
send->setAcceptBoth(); send->setAcceptBoth();
send->clicks( send->clicks(
@ -565,7 +577,7 @@ void ShareBox::innerSelectedChanged(
void ShareBox::submit(Api::SendOptions options) { void ShareBox::submit(Api::SendOptions options) {
if (const auto onstack = _descriptor.submitCallback) { if (const auto onstack = _descriptor.submitCallback) {
const auto forwardOptions = (_forwardOptions.hasCaptions const auto forwardOptions = (_forwardOptions.captionsCount
&& _forwardOptions.dropCaptions) && _forwardOptions.dropCaptions)
? Data::ForwardOptions::NoNamesAndCaptions ? Data::ForwardOptions::NoNamesAndCaptions
: _forwardOptions.dropNames : _forwardOptions.dropNames
@ -649,6 +661,16 @@ ShareBox::Inner::Inner(
_rowHeight = st::shareRowHeight; _rowHeight = st::shareRowHeight;
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
if (_descriptor.premiumRequiredError) {
const auto session = _descriptor.session;
rpl::merge(
Data::AmPremiumValue(session) | rpl::to_empty,
session->api().premium().somePremiumRequiredResolved()
) | rpl::start_with_next([=] {
refreshLockedRows();
}, lifetime());
}
const auto self = _descriptor.session->user(); const auto self = _descriptor.session->user();
const auto selfHistory = self->owner().history(self); const auto selfHistory = self->owner().history(self);
if (_descriptor.filterCallback(selfHistory)) { if (_descriptor.filterCallback(selfHistory)) {
@ -705,10 +727,52 @@ void ShareBox::Inner::invalidateCache() {
} }
} }
bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
if (!chat->locked) {
return false;
}
::Settings::ShowPremiumPromoToast(
Main::MakeSessionShow(_show, _descriptor.session),
ChatHelpers::ResolveWindowDefault(),
_descriptor.premiumRequiredError(chat->peer->asUser()).text,
u"require_premium"_q);
return true;
}
void ShareBox::Inner::refreshLockedRows() {
auto changed = false;
for (const auto &[peer, data] : _dataMap) {
const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(
history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) {
data->locked = locked;
changed = true;
}
}
for (const auto &data : d_byUsernameFiltered) {
const auto history = data->history;
const auto locked = (Api::ResolveRequiresPremiumToWrite(
history->peer,
history
) == Api::RequirePremiumState::Yes);
if (data->locked != locked) {
data->locked = locked;
changed = true;
}
}
if (changed) {
update();
}
}
void ShareBox::Inner::visibleTopBottomUpdated( void ShareBox::Inner::visibleTopBottomUpdated(
int visibleTop, int visibleTop,
int visibleBottom) { int visibleBottom) {
loadProfilePhotos(visibleTop); _visibleTop = visibleTop;
loadProfilePhotos();
} }
void ShareBox::Inner::activateSkipRow(int direction) { void ShareBox::Inner::activateSkipRow(int direction) {
@ -760,6 +824,18 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions()); chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
} }
void ShareBox::Inner::initChatLocked(not_null<Chat*> chat) {
if (_descriptor.premiumRequiredError) {
const auto history = chat->history;
if (Api::ResolveRequiresPremiumToWrite(
history->peer,
history
) == Api::RequirePremiumState::Yes) {
chat->locked = true;
}
}
}
void ShareBox::Inner::repaintChatAtIndex(int index) { void ShareBox::Inner::repaintChatAtIndex(int index) {
if (index < 0) return; if (index < 0) return;
@ -829,11 +905,11 @@ int ShareBox::Inner::chatIndex(not_null<PeerData*> peer) const {
return -1; return -1;
} }
void ShareBox::Inner::loadProfilePhotos(int yFrom) { void ShareBox::Inner::loadProfilePhotos() {
if (!parentWidget()) return; if (!parentWidget()) {
if (yFrom < 0) { return;
yFrom = 0;
} }
auto yFrom = std::max(_visibleTop, 0);
if (auto part = (yFrom % _rowHeight)) { if (auto part = (yFrom % _rowHeight)) {
yFrom -= part; yFrom -= part;
} }
@ -853,20 +929,42 @@ void ShareBox::Inner::loadProfilePhotos(int yFrom) {
if (((*i)->index() * _rowHeight) >= yTo) { if (((*i)->index() * _rowHeight) >= yTo) {
break; break;
} }
(*i)->entry()->chatListPreloadData(); preloadUserpic((*i)->entry());
} }
} }
} else if (!_filtered.empty()) { } else {
int from = yFrom / _rowHeight; const auto from = std::max(yFrom / _rowHeight, 0);
if (from < 0) from = 0; const auto to = std::max((yTo / _rowHeight) + 1, from);
if (from < _filtered.size()) {
int to = (yTo / _rowHeight) + 1;
if (to > _filtered.size()) to = _filtered.size();
for (; from < to; ++from) { const auto fto = std::min(to, int(_filtered.size()));
_filtered[from]->entry()->chatListPreloadData(); const auto ffrom = std::min(from, fto);
} for (auto i = ffrom; i != fto; ++i) {
preloadUserpic(_filtered[i]->entry());
} }
const auto uto = std::min(
to - int(_filtered.size()),
int(d_byUsernameFiltered.size()));
const auto ufrom = std::min(
std::max(from - int(_filtered.size()), 0),
uto);
for (auto i = ufrom; i != uto; ++i) {
preloadUserpic(d_byUsernameFiltered[i]->history);
}
}
}
void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
entry->chatListPreloadData();
const auto history = entry->asHistory();
if (!_descriptor.premiumRequiredError || !history) {
return;
} else if (Api::ResolveRequiresPremiumToWrite(
history->peer,
history
) == Api::RequirePremiumState::Unknown) {
const auto user = history->peer->asUser();
_descriptor.session->api().premium().resolvePremiumRequired(user);
} }
} }
@ -877,15 +975,19 @@ auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
if (const auto data = static_cast<Chat*>(row->attached)) { if (const auto data = static_cast<Chat*>(row->attached)) {
return data; return data;
} }
const auto peer = row->history()->peer; const auto history = row->history();
const auto peer = history->peer;
if (const auto i = _dataMap.find(peer); i != end(_dataMap)) { if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
row->attached = i->second.get(); row->attached = i->second.get();
return i->second.get(); return i->second.get();
} }
const auto &[i, ok] = _dataMap.emplace( const auto &[i, ok] = _dataMap.emplace(
peer, peer,
std::make_unique<Chat>(peer, _st.item, [=] { repaintChat(peer); })); std::make_unique<Chat>(history, _st.item, [=] {
repaintChat(peer);
}));
updateChatName(i->second.get()); updateChatName(i->second.get());
initChatLocked(i->second.get());
row->attached = i->second.get(); row->attached = i->second.get();
return i->second.get(); return i->second.get();
} }
@ -919,6 +1021,16 @@ void ShareBox::Inner::paintChat(
auto photoTop = st::sharePhotoTop; auto photoTop = st::sharePhotoTop;
chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth); chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
if (chat->locked) {
PaintPremiumRequiredLock(
p,
&_st.item,
x + photoLeft,
y + photoTop,
outerWidth,
_st.item.checkbox.imageRadius * 2);
}
auto nameActive = chat->nameActive.value((index == _active) ? 1. : 0.); auto nameActive = chat->nameActive.value((index == _active) ? 1. : 0.);
p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameActive)); p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameActive));
@ -929,10 +1041,11 @@ void ShareBox::Inner::paintChat(
} }
ShareBox::Inner::Chat::Chat( ShareBox::Inner::Chat::Chat(
not_null<PeerData*> peer, not_null<History*> history,
const style::PeerListItem &st, const style::PeerListItem &st,
Fn<void()> updateCallback) Fn<void()> updateCallback)
: peer(peer) : history(history)
, peer(history->peer)
, checkbox( , checkbox(
st.checkbox, st.checkbox,
updateCallback, updateCallback,
@ -1062,7 +1175,7 @@ void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
} }
void ShareBox::Inner::changeCheckState(Chat *chat) { void ShareBox::Inner::changeCheckState(Chat *chat) {
if (!chat) { if (!chat || showLockedError(chat)) {
return; return;
} else if (!_filter.isEmpty()) { } else if (!_filter.isEmpty()) {
const auto history = chat->peer->owner().history(chat->peer); const auto history = chat->peer->owner().history(chat->peer);
@ -1194,8 +1307,8 @@ void ShareBox::Inner::updateFilter(QString filter) {
_searchRequests.fire({}); _searchRequests.fire({});
} }
setActive(-1); setActive(-1);
loadProfilePhotos();
update(); update();
loadProfilePhotos(0);
} }
} }
@ -1234,10 +1347,11 @@ void ShareBox::Inner::peopleReceived(
} }
_byUsernameFiltered.push_back(peer); _byUsernameFiltered.push_back(peer);
d_byUsernameFiltered.push_back(std::make_unique<Chat>( d_byUsernameFiltered.push_back(std::make_unique<Chat>(
peer, history,
_st.item, _st.item,
[=] { repaintChat(peer); })); [=] { repaintChat(peer); }));
updateChatName(d_byUsernameFiltered.back().get()); updateChatName(d_byUsernameFiltered.back().get());
initChatLocked(d_byUsernameFiltered.back().get());
} }
} }
}; };
@ -1256,6 +1370,7 @@ void ShareBox::Inner::refresh() {
} else { } else {
resize(width(), st::noContactsHeight); resize(width(), st::noContactsHeight);
} }
loadProfilePhotos();
update(); update();
} }
@ -1525,6 +1640,11 @@ void FastShareMessage(
const auto requiredRight = item->requiredSendRight(); const auto requiredRight = item->requiredSendRight();
const auto requiresInline = item->requiresSendInlineRight(); const auto requiresInline = item->requiresSendInlineRight();
auto filterCallback = [=](not_null<Data::Thread*> thread) { auto filterCallback = [=](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
return true;
}
}
return Data::CanSend(thread, requiredRight) return Data::CanSend(thread, requiredRight)
&& (!requiresInline && (!requiresInline
|| Data::CanSend(thread, ChatRestriction::SendInline)) || Data::CanSend(thread, ChatRestriction::SendInline))
@ -1543,14 +1663,20 @@ void FastShareMessage(
msgIds), msgIds),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.forwardOptions = { .forwardOptions = {
.messagesCount = int(msgIds.size()), .sendersCount = ItemsForwardSendersCount(items),
.captionsCount = ItemsForwardCaptionsCount(items),
.show = !hasOnlyForcedForwardedInfo, .show = !hasOnlyForcedForwardedInfo,
.hasCaptions = hasCaptions,
}, },
.premiumRequiredError = SharePremiumRequiredError(),
}), }),
Ui::LayerOption::CloseOther); Ui::LayerOption::CloseOther);
} }
auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)> {
return WritePremiumRequiredError;
}
void ShareGameScoreByHash( void ShareGameScoreByHash(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
const QString &hash) { const QString &hash) {

View file

@ -69,6 +69,10 @@ void FastShareMessage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item); not_null<HistoryItem*> item);
struct RecipientPremiumRequiredError;
[[nodiscard]] auto SharePremiumRequiredError()
-> Fn<RecipientPremiumRequiredError(not_null<UserData*>)>;
class ShareBox final : public Ui::BoxContent { class ShareBox final : public Ui::BoxContent {
public: public:
using CopyCallback = Fn<void()>; using CopyCallback = Fn<void()>;
@ -96,11 +100,14 @@ public:
const style::PeerList *st = nullptr; const style::PeerList *st = nullptr;
const style::InputField *stLabel = nullptr; const style::InputField *stLabel = nullptr;
struct { struct {
int messagesCount = 0; int sendersCount = 0;
int captionsCount = 0;
bool show = false; bool show = false;
bool hasCaptions = false;
} forwardOptions; } forwardOptions;
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle; HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
}; };
ShareBox(QWidget*, Descriptor &&descriptor); ShareBox(QWidget*, Descriptor &&descriptor);

View file

@ -33,17 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
[[nodiscard]] TextWithEntities PurchaseAvailableText() {
constexpr auto kUsernameAuction = "auction";
return tr::lng_username_purchase_available(
tr::now,
lt_link,
Ui::Text::Link(
'@' + QString(kUsernameAuction),
u"https://t.me/"_q + kUsernameAuction),
Ui::Text::RichLangValue);
}
class UsernameEditor final : public Ui::RpWidget { class UsernameEditor final : public Ui::RpWidget {
public: public:
UsernameEditor(not_null<Ui::RpWidget*>, not_null<PeerData*> peer); UsernameEditor(not_null<Ui::RpWidget*>, not_null<PeerData*> peer);
@ -268,9 +257,8 @@ void UsernameEditor::checkInfoPurchaseAvailable() {
_username->showError(); _username->showError();
_errorText = u".bad."_q; _errorText = u".bad."_q;
_checkInfoChanged.fire({ _checkInfoChanged.fire(
.type = UsernameCheckInfo::Type::PurchaseAvailable, UsernameCheckInfo::PurchaseAvailable(_checkUsername, _peer));
});
} }
void UsernameEditor::updateFail(const QString &error) { void UsernameEditor::updateFail(const QString &error) {
@ -424,9 +412,7 @@ void AddUsernameCheckLabel(
container->widthValue() container->widthValue()
) | rpl::start_with_next([=](const UsernameCheckInfo &info, int w) { ) | rpl::start_with_next([=](const UsernameCheckInfo &info, int w) {
using Type = UsernameCheckInfo::Type; using Type = UsernameCheckInfo::Type;
label->setMarkedText((info.type == Type::PurchaseAvailable) label->setMarkedText(info.text);
? PurchaseAvailableText()
: info.text);
const auto &color = (info.type == Type::Good) const auto &color = (info.type == Type::Good)
? st::boxTextFgGood ? st::boxTextFgGood
: (info.type == Type::Error) : (info.type == Type::Error)
@ -437,3 +423,25 @@ void AddUsernameCheckLabel(
}, label->lifetime()); }, label->lifetime());
Ui::AddSkip(container, skip); Ui::AddSkip(container, skip);
} }
UsernameCheckInfo UsernameCheckInfo::PurchaseAvailable(
const QString &username,
not_null<PeerData*> peer) {
if (const auto fragmentLink = AppConfig::FragmentLink(&peer->session())) {
return {
.type = UsernameCheckInfo::Type::Default,
.text = tr::lng_username_purchase_available(
tr::now,
lt_link,
Ui::Text::Link(
tr::lng_username_purchase_available_link(tr::now),
(*fragmentLink) + u"/username/"_q + username),
Ui::Text::RichLangValue),
};
} else {
return {
.type = UsernameCheckInfo::Type::Error,
.text = { u"INTERNAL_SERVER_ERROR"_q },
};
}
}

View file

@ -19,11 +19,14 @@ void UsernamesBox(
not_null<PeerData*> peer); not_null<PeerData*> peer);
struct UsernameCheckInfo final { struct UsernameCheckInfo final {
[[nodiscard]] static UsernameCheckInfo PurchaseAvailable(
const QString &username,
not_null<PeerData*> peer);
enum class Type { enum class Type {
Good, Good,
Error, Error,
Default, Default,
PurchaseAvailable,
}; };
Type type; Type type;
TextWithEntities text; TextWithEntities text;

View file

@ -25,8 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio_track.h" #include "media/audio/media_audio_track.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "calls/calls_panel.h" #include "calls/calls_panel.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_create_adm.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -215,6 +215,22 @@ Call::Call(
, _api(&_user->session().mtp()) , _api(&_user->session().mtp())
, _type(type) , _type(type)
, _discardByTimeoutTimer([=] { hangup(); }) , _discardByTimeoutTimer([=] { hangup(); })
, _playbackDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Playback,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callPlaybackDeviceIdValue(),
Core::App().settings().playbackDeviceIdValue()))
, _captureDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Capture,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue()))
, _cameraDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Camera,
Core::App().settings().cameraDeviceIdValue())
, _videoIncoming( , _videoIncoming(
std::make_unique<Webrtc::VideoTrack>( std::make_unique<Webrtc::VideoTrack>(
StartVideoState(video))) StartVideoState(video)))
@ -228,6 +244,7 @@ Call::Call(
_discardByTimeoutTimer.callOnce(config.callRingTimeoutMs); _discardByTimeoutTimer.callOnce(config.callRingTimeoutMs);
startWaitingTrack(); startWaitingTrack();
} }
setupMediaDevices();
setupOutgoingVideo(); setupOutgoingVideo();
} }
@ -410,18 +427,39 @@ void Call::setMuted(bool mute) {
} }
} }
void Call::setupMediaDevices() {
_playbackDeviceId.changes() | rpl::filter([=] {
return _instance && _setDeviceIdCallback;
}) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
_setDeviceIdCallback(deviceId);
// Value doesn't matter here, just trigger reading of the new value.
_instance->setAudioOutputDevice(deviceId.value.toStdString());
}, _lifetime);
_captureDeviceId.changes() | rpl::filter([=] {
return _instance && _setDeviceIdCallback;
}) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
_setDeviceIdCallback(deviceId);
// Value doesn't matter here, just trigger reading of the new value.
_instance->setAudioInputDevice(deviceId.value.toStdString());
}, _lifetime);
}
void Call::setupOutgoingVideo() { void Call::setupOutgoingVideo() {
static const auto hasDevices = [] { const auto cameraId = [] {
return !Webrtc::GetVideoInputList().empty(); return Core::App().mediaDevices().defaultId(
Webrtc::DeviceType::Camera);
}; };
const auto started = _videoOutgoing->state(); const auto started = _videoOutgoing->state();
if (!hasDevices()) { if (cameraId().isEmpty()) {
_videoOutgoing->setState(Webrtc::VideoState::Inactive); _videoOutgoing->setState(Webrtc::VideoState::Inactive);
} }
_videoOutgoing->stateValue( _videoOutgoing->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) { ) | rpl::start_with_next([=](Webrtc::VideoState state) {
if (state != Webrtc::VideoState::Inactive if (state != Webrtc::VideoState::Inactive
&& !hasDevices() && cameraId().isEmpty()
&& !_videoCaptureIsScreencast) { && !_videoCaptureIsScreencast) {
_errors.fire({ ErrorType::NoCamera }); _errors.fire({ ErrorType::NoCamera });
_videoOutgoing->setState(Webrtc::VideoState::Inactive); _videoOutgoing->setState(Webrtc::VideoState::Inactive);
@ -455,6 +493,20 @@ void Call::setupOutgoingVideo() {
} }
} }
}, _lifetime); }, _lifetime);
_cameraDeviceId.changes(
) | rpl::filter([=] {
return !_videoCaptureIsScreencast;
}) | rpl::start_with_next([=](Webrtc::DeviceResolvedId deviceId) {
const auto &id = deviceId.value;
_videoCaptureDeviceId = id;
if (_videoCapture) {
_videoCapture->switchToDevice(id.toStdString(), false);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}
}
}, _lifetime);
} }
not_null<Webrtc::VideoTrack*> Call::videoIncoming() const { not_null<Webrtc::VideoTrack*> Call::videoIncoming() const {
@ -848,6 +900,34 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
const auto versionString = version.toStdString(); const auto versionString = version.toStdString();
const auto &settings = Core::App().settings(); const auto &settings = Core::App().settings();
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
_setDeviceIdCallback = nullptr;
const auto playbackDeviceIdInitial = _playbackDeviceId.current();
const auto captureDeviceIdInitial = _captureDeviceId.current();
const auto saveSetDeviceIdCallback = [=](
Fn<void(Webrtc::DeviceResolvedId)> setDeviceIdCallback) {
setDeviceIdCallback(playbackDeviceIdInitial);
setDeviceIdCallback(captureDeviceIdInitial);
crl::on_main(weak, [=] {
_setDeviceIdCallback = std::move(setDeviceIdCallback);
const auto playback = _playbackDeviceId.current();
if (_instance && playback != playbackDeviceIdInitial) {
_setDeviceIdCallback(playback);
// Value doesn't matter here, just trigger reading of the...
_instance->setAudioOutputDevice(
playback.value.toStdString());
}
const auto capture = _captureDeviceId.current();
if (_instance && capture != captureDeviceIdInitial) {
_setDeviceIdCallback(capture);
// Value doesn't matter here, just trigger reading of the...
_instance->setAudioInputDevice(capture.value.toStdString());
}
});
};
tgcalls::Descriptor descriptor = { tgcalls::Descriptor descriptor = {
.version = versionString, .version = versionString,
.config = tgcalls::Config{ .config = tgcalls::Config{
@ -866,8 +946,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
std::move(encryptionKeyValue), std::move(encryptionKeyValue),
(_type == Type::Outgoing)), (_type == Type::Outgoing)),
.mediaDevicesConfig = tgcalls::MediaDevicesConfig{ .mediaDevicesConfig = tgcalls::MediaDevicesConfig{
.audioInputId = settings.callInputDeviceId().toStdString(), .audioInputId = captureDeviceIdInitial.value.toStdString(),
.audioOutputId = settings.callOutputDeviceId().toStdString(), .audioOutputId = playbackDeviceIdInitial.value.toStdString(),
.inputVolume = 1.f,//settings.callInputVolume() / 100.f, .inputVolume = 1.f,//settings.callInputVolume() / 100.f,
.outputVolume = 1.f,//settings.callOutputVolume() / 100.f, .outputVolume = 1.f,//settings.callOutputVolume() / 100.f,
}, },
@ -898,7 +978,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
}); });
}, },
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()), saveSetDeviceIdCallback),
}; };
if (Logs::DebugEnabled()) { if (Logs::DebugEnabled()) {
const auto callLogFolder = cWorkingDir() + u"DebugLogs"_q; const auto callLogFolder = cWorkingDir() + u"DebugLogs"_q;
@ -1096,29 +1176,6 @@ void Call::setState(State state) {
} }
} }
void Call::setCurrentAudioDevice(bool input, const QString &deviceId) {
if (_instance) {
const auto id = deviceId.toStdString();
if (input) {
_instance->setAudioInputDevice(id);
} else {
_instance->setAudioOutputDevice(id);
}
}
}
void Call::setCurrentCameraDevice(const QString &deviceId) {
if (!_videoCaptureIsScreencast) {
_videoCaptureDeviceId = deviceId;
if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString(), false);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}
}
}
}
//void Call::setAudioVolume(bool input, float level) { //void Call::setAudioVolume(bool input, float level) {
// if (_instance) { // if (_instance) {
// if (input) { // if (input) {
@ -1168,10 +1225,11 @@ void Call::toggleCameraSharing(bool enabled) {
} }
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] { _delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
toggleScreenSharing(std::nullopt); toggleScreenSharing(std::nullopt);
const auto deviceId = Core::App().settings().callVideoInputDeviceId(); _videoCaptureDeviceId = _cameraDeviceId.current().value;
_videoCaptureDeviceId = deviceId;
if (_videoCapture) { if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString(), false); _videoCapture->switchToDevice(
_videoCaptureDeviceId.toStdString(),
false);
if (_instance) { if (_instance) {
_instance->sendVideoDeviceUpdated(); _instance->sendVideoDeviceUpdated();
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h" #include "base/bytes.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
#include "mtproto/mtproto_auth_key.h" #include "mtproto/mtproto_auth_key.h"
#include "webrtc/webrtc_device_resolver.h"
namespace Media { namespace Media {
namespace Audio { namespace Audio {
@ -190,11 +191,9 @@ public:
QString getDebugLog() const; QString getDebugLog() const;
void setCurrentAudioDevice(bool input, const QString &deviceId);
//void setAudioVolume(bool input, float level); //void setAudioVolume(bool input, float level);
void setAudioDuckingEnabled(bool enabled); void setAudioDuckingEnabled(bool enabled);
void setCurrentCameraDevice(const QString &deviceId);
[[nodiscard]] QString videoDeviceId() const { [[nodiscard]] QString videoDeviceId() const {
return _videoCaptureDeviceId; return _videoCaptureDeviceId;
} }
@ -250,6 +249,7 @@ private:
void setSignalBarCount(int count); void setSignalBarCount(int count);
void destroyController(); void destroyController();
void setupMediaDevices();
void setupOutgoingVideo(); void setupOutgoingVideo();
void updateRemoteMediaState( void updateRemoteMediaState(
tgcalls::AudioState audio, tgcalls::AudioState audio,
@ -271,6 +271,11 @@ private:
base::DelayedCallTimer _finishByTimeoutTimer; base::DelayedCallTimer _finishByTimeoutTimer;
base::Timer _discardByTimeoutTimer; base::Timer _discardByTimeoutTimer;
Fn<void(Webrtc::DeviceResolvedId)> _setDeviceIdCallback;
Webrtc::DeviceResolver _playbackDeviceId;
Webrtc::DeviceResolver _captureDeviceId;
Webrtc::DeviceResolver _cameraDeviceId;
rpl::variable<bool> _muted = false; rpl::variable<bool> _muted = false;
DhConfig _dhConfig; DhConfig _dhConfig;

View file

@ -522,20 +522,6 @@ void Instance::showInfoPanel(not_null<GroupCall*> call) {
} }
} }
void Instance::setCurrentAudioDevice(bool input, const QString &deviceId) {
if (input) {
Core::App().settings().setCallInputDeviceId(deviceId);
} else {
Core::App().settings().setCallOutputDeviceId(deviceId);
}
Core::App().saveSettingsDelayed();
if (const auto call = currentCall()) {
call->setCurrentAudioDevice(input, deviceId);
} else if (const auto group = currentGroupCall()) {
group->setCurrentAudioDevice(input, deviceId);
}
}
FnMut<void()> Instance::addAsyncWaiter() { FnMut<void()> Instance::addAsyncWaiter() {
auto semaphore = std::make_unique<crl::semaphore>(); auto semaphore = std::make_unique<crl::semaphore>();
const auto raw = semaphore.get(); const auto raw = semaphore.get();
@ -846,7 +832,7 @@ std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
if (deviceId) { if (deviceId) {
result->switchToDevice( result->switchToDevice(
(deviceId->isEmpty() (deviceId->isEmpty()
? Core::App().settings().callVideoInputDeviceId() ? Core::App().settings().cameraDeviceId()
: *deviceId).toStdString(), : *deviceId).toStdString(),
isScreenCapture); isScreenCapture);
} }
@ -854,7 +840,7 @@ std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
} }
const auto startDeviceId = (deviceId && !deviceId->isEmpty()) const auto startDeviceId = (deviceId && !deviceId->isEmpty())
? *deviceId ? *deviceId
: Core::App().settings().callVideoInputDeviceId(); : Core::App().settings().cameraDeviceId();
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>( auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
tgcalls::VideoCaptureInterface::Create( tgcalls::VideoCaptureInterface::Create(
tgcalls::StaticThreads::getThreads(), tgcalls::StaticThreads::getThreads(),

View file

@ -103,8 +103,6 @@ public:
-> std::shared_ptr<tgcalls::VideoCaptureInterface>; -> std::shared_ptr<tgcalls::VideoCaptureInterface>;
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true); void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
void setCurrentAudioDevice(bool input, const QString &deviceId);
[[nodiscard]] FnMut<void()> addAsyncWaiter(); [[nodiscard]] FnMut<void()> addAsyncWaiter();
[[nodiscard]] bool isSharingScreen() const; [[nodiscard]] bool isSharingScreen() const;

View file

@ -48,8 +48,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/power_save_blocker.h" #include "base/power_save_blocker.h"
#include "media/streaming/media_streaming_utility.h" #include "media/streaming/media_streaming_utility.h"
#include "window/main_window.h" #include "window/main_window.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "styles/style_calls.h" #include "styles/style_calls.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -238,13 +238,14 @@ void Panel::initControls() {
} }
}); });
_screencast->entity()->setClickedCallback([=] { _screencast->entity()->setClickedCallback([=] {
const auto env = &Core::App().mediaDevices();
if (!_call) { if (!_call) {
return; return;
} else if (!Webrtc::DesktopCaptureAllowed()) { } else if (!env->desktopCaptureAllowed()) {
if (auto box = Group::ScreenSharingPrivacyRequestBox()) { if (auto box = Group::ScreenSharingPrivacyRequestBox()) {
_layerBg->showBox(std::move(box)); _layerBg->showBox(std::move(box));
} }
} else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) { } else if (const auto source = env->uniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) { if (_call->isSharingScreen()) {
_call->toggleScreenSharing(std::nullopt); _call->toggleScreenSharing(std::nullopt);
} else { } else {

View file

@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/global_shortcuts.h" #include "base/global_shortcuts.h"
#include "base/random.h" #include "base/random.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_create_adm.h"
#include <tgcalls/group/GroupInstanceCustomImpl.h> #include <tgcalls/group/GroupInstanceCustomImpl.h>
@ -52,14 +51,6 @@ constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000);
constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums.
constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums.
[[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
const auto &settings = Core::App().settings();
return Webrtc::CreateMediaDevices(
settings.callInputDeviceId(),
settings.callOutputDeviceId(),
settings.callVideoInputDeviceId());
}
[[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant(
not_null<PeerData*> peer, not_null<PeerData*> peer,
CallId id, CallId id,
@ -590,12 +581,27 @@ GroupCall::GroupCall(
, _scheduleDate(info.scheduleDate) , _scheduleDate(info.scheduleDate)
, _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _lastSpokeCheckTimer([=] { checkLastSpoke(); })
, _checkJoinedTimer([=] { checkJoined(); }) , _checkJoinedTimer([=] { checkJoined(); })
, _playbackDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Playback,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callPlaybackDeviceIdValue(),
Core::App().settings().playbackDeviceIdValue()))
, _captureDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Capture,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue()))
, _cameraDeviceId(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Camera,
Webrtc::DeviceIdOrDefault(Core::App().settings().cameraDeviceIdValue()))
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
, _connectingSoundTimer([=] { playConnectingSoundOnce(); }) , _connectingSoundTimer([=] { playConnectingSoundOnce(); })
, _listenersHidden(info.rtmp) , _listenersHidden(info.rtmp)
, _rtmp(info.rtmp) , _rtmp(info.rtmp)
, _rtmpVolume(Group::kDefaultVolume) , _rtmpVolume(Group::kDefaultVolume) {
, _mediaDevices(CreateMediaDevices()) {
_muted.value( _muted.value(
) | rpl::combine_previous( ) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState previous, MuteState state) { ) | rpl::start_with_next([=](MuteState previous, MuteState state) {
@ -2058,28 +2064,28 @@ void GroupCall::applyOtherParticipantUpdate(
} }
void GroupCall::setupMediaDevices() { void GroupCall::setupMediaDevices() {
_mediaDevices->audioInputId( _playbackDeviceId.changes() | rpl::filter([=] {
) | rpl::start_with_next([=](QString id) { return _instance && _setDeviceIdCallback;
_audioInputId = id; }) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
if (_instance) { _setDeviceIdCallback(deviceId);
_instance->setAudioInputDevice(id.toStdString());
} // Value doesn't matter here, just trigger reading of the new value.
_instance->setAudioOutputDevice(deviceId.value.toStdString());
}, _lifetime); }, _lifetime);
_mediaDevices->audioOutputId( _captureDeviceId.changes() | rpl::filter([=] {
) | rpl::start_with_next([=](QString id) { return _instance && _setDeviceIdCallback;
_audioOutputId = id; }) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
if (_instance) { _setDeviceIdCallback(deviceId);
_instance->setAudioOutputDevice(id.toStdString());
} // Value doesn't matter here, just trigger reading of the new value.
_instance->setAudioInputDevice(deviceId.value.toStdString());
}, _lifetime); }, _lifetime);
_mediaDevices->videoInputId( _cameraDeviceId.changes() | rpl::filter([=] {
) | rpl::start_with_next([=](QString id) { return _cameraCapture != nullptr;
_cameraInputId = id; }) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
if (_cameraCapture) { _cameraCapture->switchToDevice(deviceId.value.toStdString(), false);
_cameraCapture->switchToDevice(id.toStdString(), false);
}
}, _lifetime); }, _lifetime);
} }
@ -2117,7 +2123,7 @@ bool GroupCall::emitShareCameraError() {
return emitError(Error::DisabledNoCamera); return emitError(Error::DisabledNoCamera);
} else if (mutedByAdmin()) { } else if (mutedByAdmin()) {
return emitError(Error::MutedNoCamera); return emitError(Error::MutedNoCamera);
} else if (Webrtc::GetVideoInputList().empty()) { } else if (_cameraDeviceId.current().value.isEmpty()) {
return emitError(Error::NoCamera); return emitError(Error::NoCamera);
} }
return false; return false;
@ -2126,7 +2132,7 @@ bool GroupCall::emitShareCameraError() {
void GroupCall::emitShareCameraError(Error error) { void GroupCall::emitShareCameraError(Error error) {
_cameraState = Webrtc::VideoState::Inactive; _cameraState = Webrtc::VideoState::Inactive;
if (error == Error::CameraFailed if (error == Error::CameraFailed
&& Webrtc::GetVideoInputList().empty()) { && _cameraDeviceId.current().value.isEmpty()) {
error = Error::NoCamera; error = Error::NoCamera;
} }
_errors.fire_copy(error); _errors.fire_copy(error);
@ -2180,7 +2186,7 @@ void GroupCall::setupOutgoingVideo() {
return; return;
} else if (!_cameraCapture) { } else if (!_cameraCapture) {
_cameraCapture = _delegate->groupCallGetVideoCapture( _cameraCapture = _delegate->groupCallGetVideoCapture(
_cameraInputId); _cameraDeviceId.current().value);
if (!_cameraCapture) { if (!_cameraCapture) {
return emitShareCameraError(Error::CameraFailed); return emitShareCameraError(Error::CameraFailed);
} }
@ -2192,7 +2198,7 @@ void GroupCall::setupOutgoingVideo() {
}); });
} else { } else {
_cameraCapture->switchToDevice( _cameraCapture->switchToDevice(
_cameraInputId.toStdString(), _cameraDeviceId.current().value.toStdString(),
false); false);
} }
if (_instance) { if (_instance) {
@ -2338,6 +2344,32 @@ bool GroupCall::tryCreateController() {
const auto weak = base::make_weak(&_instanceGuard); const auto weak = base::make_weak(&_instanceGuard);
const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>(); const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
const auto playbackDeviceIdInitial = _playbackDeviceId.current();
const auto captureDeviceIdInitial = _captureDeviceId.current();
const auto saveSetDeviceIdCallback = [=](
Fn<void(Webrtc::DeviceResolvedId)> setDeviceIdCallback) {
setDeviceIdCallback(playbackDeviceIdInitial);
setDeviceIdCallback(captureDeviceIdInitial);
crl::on_main(weak, [=] {
_setDeviceIdCallback = std::move(setDeviceIdCallback);
const auto playback = _playbackDeviceId.current();
if (_instance && playback != playbackDeviceIdInitial) {
_setDeviceIdCallback(playback);
// Value doesn't matter here, just trigger reading of the...
_instance->setAudioOutputDevice(
playback.value.toStdString());
}
const auto capture = _captureDeviceId.current();
if (_instance && capture != captureDeviceIdInitial) {
_setDeviceIdCallback(capture);
// Value doesn't matter here, just trigger reading of the...
_instance->setAudioInputDevice(capture.value.toStdString());
}
});
};
tgcalls::GroupInstanceDescriptor descriptor = { tgcalls::GroupInstanceDescriptor descriptor = {
.threads = tgcalls::StaticThreads::getThreads(), .threads = tgcalls::StaticThreads::getThreads(),
.config = tgcalls::GroupConfig{ .config = tgcalls::GroupConfig{
@ -2360,10 +2392,10 @@ bool GroupCall::tryCreateController() {
} }
crl::on_main(weak, [=] { audioLevelsUpdated(data); }); crl::on_main(weak, [=] { audioLevelsUpdated(data); });
}, },
.initialInputDeviceId = _audioInputId.toStdString(), .initialInputDeviceId = captureDeviceIdInitial.value.toStdString(),
.initialOutputDeviceId = _audioOutputId.toStdString(), .initialOutputDeviceId = playbackDeviceIdInitial.value.toStdString(),
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()), saveSetDeviceIdCallback),
.videoCapture = _cameraCapture, .videoCapture = _cameraCapture,
.requestCurrentTime = [=, call = base::make_weak(this)]( .requestCurrentTime = [=, call = base::make_weak(this)](
std::function<void(int64_t)> done) { std::function<void(int64_t)> done) {
@ -3290,14 +3322,6 @@ void GroupCall::requestVideoQuality(
updateRequestedVideoChannelsDelayed(); updateRequestedVideoChannelsDelayed();
} }
void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
if (input) {
_mediaDevices->switchToAudioInput(deviceId);
} else {
_mediaDevices->switchToAudioOutput(deviceId);
}
}
void GroupCall::toggleMute(const Group::MuteRequest &data) { void GroupCall::toggleMute(const Group::MuteRequest &data) {
if (_rtmp) { if (_rtmp) {
_rtmpVolume = data.mute ? 0 : Group::kDefaultVolume; _rtmpVolume = data.mute ? 0 : Group::kDefaultVolume;

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h" #include "base/bytes.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
#include "mtproto/mtproto_auth_key.h" #include "mtproto/mtproto_auth_key.h"
#include "webrtc/webrtc_device_resolver.h"
class History; class History;
@ -381,7 +382,6 @@ public:
return _videoIsWorking.value(); return _videoIsWorking.value();
} }
void setCurrentAudioDevice(bool input, const QString &deviceId);
[[nodiscard]] bool isSharingScreen() const; [[nodiscard]] bool isSharingScreen() const;
[[nodiscard]] rpl::producer<bool> isSharingScreenValue() const; [[nodiscard]] rpl::producer<bool> isSharingScreenValue() const;
[[nodiscard]] bool isScreenPaused() const; [[nodiscard]] bool isScreenPaused() const;
@ -667,6 +667,11 @@ private:
crl::time _lastSendProgressUpdate = 0; crl::time _lastSendProgressUpdate = 0;
Fn<void(Webrtc::DeviceResolvedId)> _setDeviceIdCallback;
Webrtc::DeviceResolver _playbackDeviceId;
Webrtc::DeviceResolver _captureDeviceId;
Webrtc::DeviceResolver _cameraDeviceId;
std::shared_ptr<GlobalShortcutManager> _shortcutManager; std::shared_ptr<GlobalShortcutManager> _shortcutManager;
std::shared_ptr<GlobalShortcutValue> _pushToTalk; std::shared_ptr<GlobalShortcutValue> _pushToTalk;
base::Timer _pushToTalkCancelTimer; base::Timer _pushToTalkCancelTimer;
@ -677,11 +682,6 @@ private:
bool _reloadedStaleCall = false; bool _reloadedStaleCall = false;
int _rtmpVolume = 0; int _rtmpVolume = 0;
std::unique_ptr<Webrtc::MediaDevices> _mediaDevices;
QString _audioInputId;
QString _audioOutputId;
QString _cameraInputId;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -54,8 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/power_save_blocker.h" #include "base/power_save_blocker.h"
#include "apiwrap.h" // api().kick. #include "apiwrap.h" // api().kick.
#include "api/api_chat_participants.h" // api().kick. #include "api/api_chat_participants.h" // api().kick.
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h" // UniqueDesktopCaptureSource.
#include "webrtc/webrtc_audio_input_tester.h" #include "webrtc/webrtc_audio_input_tester.h"
#include "styles/style_calls.h" #include "styles/style_calls.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -1374,9 +1374,10 @@ void Panel::chooseShareScreenSource() {
return; return;
} }
const auto choose = [=] { const auto choose = [=] {
if (!Webrtc::DesktopCaptureAllowed()) { const auto env = &Core::App().mediaDevices();
if (!env->desktopCaptureAllowed()) {
screenSharingPrivacyRequest(); screenSharingPrivacyRequest();
} else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) { } else if (const auto source = env->uniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) { if (_call->isSharingScreen()) {
_call->toggleScreenSharing(std::nullopt); _call->toggleScreenSharing(std::nullopt);
} else { } else {
@ -2003,7 +2004,8 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
} }
void Panel::showStickedTooltip() { void Panel::showStickedTooltip() {
static const auto kHasCamera = !Webrtc::GetVideoInputList().empty(); static const auto kHasCamera = !Core::App().mediaDevices().defaultId(
Webrtc::DeviceType::Camera).isEmpty();
const auto callReady = (_call->state() == State::Joined const auto callReady = (_call->state() == State::Joined
|| _call->state() == State::Connecting); || _call->state() == State::Connecting);
if (!(_stickedTooltipsShown & StickedTooltip::Camera) if (!(_stickedTooltipsShown & StickedTooltip::Camera)

View file

@ -35,13 +35,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_user.h"
#include "calls/group/calls_group_rtmp.h" #include "calls/group/calls_group_rtmp.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "webrtc/webrtc_audio_input_tester.h" #include "webrtc/webrtc_audio_input_tester.h"
#include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_device_resolver.h"
#include "settings/settings_calls.h" #include "settings/settings_calls.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -191,6 +192,11 @@ object_ptr<ShareBox> ShareInviteLinkBox(
show->showToast(tr::lng_share_done(tr::now)); show->showToast(tr::lng_share_done(tr::now));
}; };
auto filterCallback = [](not_null<Data::Thread*> thread) { auto filterCallback = [](not_null<Data::Thread*> thread) {
if (const auto user = thread->peer()->asUser()) {
if (user->canSendIgnoreRequirePremium()) {
return true;
}
}
return Data::CanSend(thread, ChatRestriction::SendOther); return Data::CanSend(thread, ChatRestriction::SendOther);
}; };
@ -227,6 +233,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
.st = &st::groupCallShareBoxList, .st = &st::groupCallShareBoxList,
.stLabel = &st::groupCallField, .stLabel = &st::groupCallField,
.scheduleBoxStyle = scheduleStyle(), .scheduleBoxStyle = scheduleStyle(),
.premiumRequiredError = SharePremiumRequiredError(),
}); });
*box = result.data(); *box = result.data();
return result; return result;
@ -243,8 +250,7 @@ void SettingsBox(
const auto weakBox = Ui::MakeWeak(box); const auto weakBox = Ui::MakeWeak(box);
struct State { struct State {
rpl::event_stream<QString> outputNameStream; std::unique_ptr<Webrtc::DeviceResolver> deviceId;
rpl::event_stream<QString> inputNameStream;
std::unique_ptr<Webrtc::AudioInputTester> micTester; std::unique_ptr<Webrtc::AudioInputTester> micTester;
Ui::LevelMeter *micTestLevel = nullptr; Ui::LevelMeter *micTestLevel = nullptr;
float micLevel = 0.; float micLevel = 0.;
@ -288,42 +294,43 @@ void SettingsBox(
Ui::AddSkip(layout); Ui::AddSkip(layout);
} }
auto playbackIdWithFallback = Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callPlaybackDeviceIdValue(),
Core::App().settings().playbackDeviceIdValue());
AddButtonWithLabel( AddButtonWithLabel(
layout, layout,
tr::lng_group_call_speakers(), tr::lng_group_call_speakers(),
rpl::single( PlaybackDeviceNameValue(rpl::duplicate(playbackIdWithFallback)),
CurrentAudioOutputName()
) | rpl::then(
state->outputNameStream.events()
),
st::groupCallSettingsButton st::groupCallSettingsButton
)->addClickHandler([=] { )->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioOutputBox(crl::guard(box, [=]( box->getDelegate()->show(ChoosePlaybackDeviceBox(
const QString &id, rpl::duplicate(playbackIdWithFallback),
const QString &name) { crl::guard(box, [=](const QString &id) {
state->outputNameStream.fire_copy(name); Core::App().settings().setCallPlaybackDeviceId(id);
}), &st::groupCallCheckbox, &st::groupCallRadio)); Core::App().saveSettingsDelayed();
}),
&st::groupCallCheckbox,
&st::groupCallRadio));
}); });
if (!rtmp) { if (!rtmp) {
auto captureIdWithFallback = Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue());
AddButtonWithLabel( AddButtonWithLabel(
layout, layout,
tr::lng_group_call_microphone(), tr::lng_group_call_microphone(),
rpl::single( CaptureDeviceNameValue(rpl::duplicate(captureIdWithFallback)),
CurrentAudioInputName()
) | rpl::then(
state->inputNameStream.events()
),
st::groupCallSettingsButton st::groupCallSettingsButton
)->addClickHandler([=] { )->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=]( box->getDelegate()->show(ChooseCaptureDeviceBox(
const QString &id, rpl::duplicate(captureIdWithFallback),
const QString &name) { crl::guard(box, [=](const QString &id) {
state->inputNameStream.fire_copy(name); Core::App().settings().setCallCaptureDeviceId(id);
if (state->micTester) { Core::App().saveSettingsDelayed();
state->micTester->setDeviceId(id); }),
} &st::groupCallCheckbox,
}), &st::groupCallCheckbox, &st::groupCallRadio)); &st::groupCallRadio));
}); });
state->micTestLevel = box->addRow( state->micTestLevel = box->addRow(
@ -764,9 +771,14 @@ void SettingsBox(
box->setShowFinishedCallback([=] { box->setShowFinishedCallback([=] {
// Means we finished showing the box. // Means we finished showing the box.
crl::on_main(box, [=] { crl::on_main(box, [=] {
state->deviceId = std::make_unique<Webrtc::DeviceResolver>(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Capture,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue()));
state->micTester = std::make_unique<Webrtc::AudioInputTester>( state->micTester = std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(), state->deviceId->value());
Core::App().settings().callInputDeviceId());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval); state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
}); });
}); });
@ -873,10 +885,13 @@ std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
MicLevelTester::MicLevelTester(Fn<void()> show) MicLevelTester::MicLevelTester(Fn<void()> show)
: _show(std::move(show)) : _show(std::move(show))
, _timer([=] { check(); }) , _timer([=] { check(); })
, _tester( , _deviceId(std::make_unique<Webrtc::DeviceResolver>(
std::make_unique<Webrtc::AudioInputTester>( &Core::App().mediaDevices(),
Core::App().settings().callAudioBackend(), Webrtc::DeviceType::Capture,
Core::App().settings().callInputDeviceId())) { Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue())))
, _tester(std::make_unique<Webrtc::AudioInputTester>(_deviceId->value())) {
_timer.callEach(kMicrophoneTooltipCheckInterval); _timer.callEach(kMicrophoneTooltipCheckInterval);
} }

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Webrtc { namespace Webrtc {
class AudioInputTester; class AudioInputTester;
class DeviceResolver;
} // namespace Webrtc } // namespace Webrtc
namespace Calls { namespace Calls {
@ -38,6 +39,7 @@ private:
Fn<void()> _show; Fn<void()> _show;
base::Timer _timer; base::Timer _timer;
std::unique_ptr<Webrtc::DeviceResolver> _deviceId;
std::unique_ptr<Webrtc::AudioInputTester> _tester; std::unique_ptr<Webrtc::AudioInputTester> _tester;
int _loudCount = 0; int _loudCount = 0;
int _quietCount = 0; int _quietCount = 0;

View file

@ -71,7 +71,6 @@ ComposeIcons {
menuSpoilerOff: icon; menuSpoilerOff: icon;
stripBubble: icon; stripBubble: icon;
stripPremiumLocked: icon;
stripExpandPanel: icon; stripExpandPanel: icon;
stripExpandDropdown: icon; stripExpandDropdown: icon;
} }
@ -123,6 +122,8 @@ EmojiPan {
removeSet: IconButton; removeSet: IconButton;
boxLabel: FlatLabel; boxLabel: FlatLabel;
icons: ComposeIcons; icons: ComposeIcons;
about: FlatLabel;
aboutPadding: margins;
autocompleteBottomSkip: pixels; autocompleteBottomSkip: pixels;
} }
@ -208,6 +209,8 @@ ComposeControls {
files: ComposeFiles; files: ComposeFiles;
premium: PremiumLimits; premium: PremiumLimits;
boxField: InputField; boxField: InputField;
restrictionLabel: FlatLabel;
premiumRequired: ComposePremiumRequired;
} }
ReportBox { ReportBox {
@ -249,6 +252,13 @@ defaultWhoRead: WhoRead {
iconPosition: point(15px, 7px); iconPosition: point(15px, 7px);
itemPadding: margins(44px, 9px, 17px, 7px); itemPadding: margins(44px, 9px, 17px, 7px);
} }
whenReadStyle: TextStyle(defaultTextStyle) {
font: font(12px);
}
whenReadPadding: margins(34px, 3px, 17px, 4px);
whenReadIconPosition: point(8px, 0px);
whenReadSkip: 3px;
whenReadShowPadding: margins(6px, 0px, 6px, 2px);
switchPmButton: RoundButton(defaultBoxButton) { switchPmButton: RoundButton(defaultBoxButton) {
width: 320px; width: 320px;
@ -600,10 +610,6 @@ defaultComposeIcons: ComposeIcons {
{ "chat/reactions_bubble_shadow", windowShadowFg }, { "chat/reactions_bubble_shadow", windowShadowFg },
{ "chat/reactions_bubble", windowBg }, { "chat/reactions_bubble", windowBg },
}; };
stripPremiumLocked: icon{
{ "chat/reactions_premium_bg", historyPeerArchiveUserpicBg },
{ "chat/reactions_premium_star", historyPeerUserpicFg },
};
stripExpandPanel: icon{ stripExpandPanel: icon{
{ "chat/reactions_round_big", windowBgRipple }, { "chat/reactions_round_big", windowBgRipple },
{ "chat/reactions_expand_panel", windowSubTextFg }, { "chat/reactions_expand_panel", windowSubTextFg },
@ -613,6 +619,14 @@ defaultComposeIcons: ComposeIcons {
{ "chat/reactions_expand_panel", windowSubTextFg }, { "chat/reactions_expand_panel", windowSubTextFg },
}; };
} }
defaultEmojiPanAbout: FlatLabel(defaultFlatLabel) {
minWidth: 10px;
align: align(top);
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(11px);
}
}
defaultEmojiPan: EmojiPan { defaultEmojiPan: EmojiPan {
margin: margins(7px, 0px, 7px, 0px); margin: margins(7px, 0px, 7px, 0px);
padding: margins(7px, 0px, 4px, 7px); padding: margins(7px, 0px, 4px, 7px);
@ -654,6 +668,8 @@ defaultEmojiPan: EmojiPan {
removeSet: stickerPanRemoveSet; removeSet: stickerPanRemoveSet;
boxLabel: boxLabel; boxLabel: boxLabel;
icons: defaultComposeIcons; icons: defaultComposeIcons;
about: defaultEmojiPanAbout;
aboutPadding: margins(12px, 2px, 12px, 2px);
autocompleteBottomSkip: 0px; autocompleteBottomSkip: 0px;
} }
statusEmojiPan: EmojiPan(defaultEmojiPan) { statusEmojiPan: EmojiPan(defaultEmojiPan) {
@ -1113,6 +1129,7 @@ historyRecordLockBodyShadow: icon {{ "voice_lock/record_lock_body_shadow", histo
historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}; historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }};
historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px); historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
historyRecordDelete: IconButton(historyAttach) { historyRecordDelete: IconButton(historyAttach) {
@ -1211,6 +1228,11 @@ defaultComposeFiles: ComposeFiles {
nameFg: historyFileNameInFg; nameFg: historyFileNameInFg;
statusFg: mediaInFg; statusFg: mediaInFg;
} }
defaultRestrictionLabel: FlatLabel(defaultFlatLabel) {
minWidth: 12px;
textFg: placeholderFg;
align: align(top);
}
defaultComposeControls: ComposeControls { defaultComposeControls: ComposeControls {
bg: historyComposeAreaBg; bg: historyComposeAreaBg;
radius: 0px; radius: 0px;
@ -1227,6 +1249,7 @@ defaultComposeControls: ComposeControls {
files: defaultComposeFiles; files: defaultComposeFiles;
premium: defaultPremiumLimits; premium: defaultPremiumLimits;
boxField: defaultInputField; boxField: defaultInputField;
restrictionLabel: defaultRestrictionLabel;
} }
moreChatsBarHeight: 48px; moreChatsBarHeight: 48px;
@ -1291,3 +1314,22 @@ ttlMediaButton: RoundButton(defaultActiveButton) {
textTop: 6px; textTop: 6px;
} }
ttlMediaButtonBottomSkip: 14px; ttlMediaButtonBottomSkip: 14px;
editTagAbout: FlatLabel(defaultFlatLabel) {
minWidth: 256px;
}
editTagField: InputField(defaultInputField) {
textBg: transparent;
textMargins: margins(24px, 10px, 32px, 2px);
placeholderFg: placeholderFg;
placeholderFgActive: placeholderFgActive;
placeholderFgError: placeholderFgActive;
placeholderMargins: margins(2px, 0px, 2px, 0px);
placeholderScale: 0.;
heightMin: 36px;
}
editTagLimit: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}

View file

@ -18,29 +18,35 @@ rpl::producer<bool> Show::adjustShadowLeft() const {
return rpl::single(false); return rpl::single(false);
} }
Window::SessionController *Show::resolveWindow(WindowUsage usage) const { ResolveWindow ResolveWindowDefault() {
const auto session = &this->session(); return [](not_null<Main::Session*> session, WindowUsage usage)
const auto check = [&](Window::Controller *window) { -> Window::SessionController* {
if (const auto controller = window->sessionController()) { const auto check = [&](Window::Controller *window) {
if (&controller->session() == session) { if (const auto controller = window->sessionController()) {
return controller; if (&controller->session() == session) {
return controller;
}
} }
return (Window::SessionController*)nullptr;
};
auto &app = Core::App();
if (const auto a = check(app.activeWindow())) {
return a;
} else if (const auto b = check(app.activePrimaryWindow())) {
return b;
} else if (const auto c = check(app.windowFor(&session->account()))) {
return c;
} else if (const auto d = check(
app.ensureSeparateWindowForAccount(
&session->account()))) {
return d;
} }
return (Window::SessionController*)nullptr; return nullptr;
}; };
auto &app = Core::App(); }
if (const auto a = check(app.activeWindow())) {
return a; Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
} else if (const auto b = check(app.activePrimaryWindow())) { return ResolveWindowDefault()(&session(), usage);
return b;
} else if (const auto c = check(app.windowFor(&session->account()))) {
return c;
} else if (const auto d = check(
app.ensureSeparateWindowForAccount(
&session->account()))) {
return d;
}
return nullptr;
} }
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -44,6 +44,11 @@ enum class WindowUsage {
PremiumPromo, PremiumPromo,
}; };
using ResolveWindow = Fn<Window::SessionController*(
not_null<Main::Session*>,
WindowUsage)>;
[[nodiscard]] ResolveWindow ResolveWindowDefault();
class Show : public Main::SessionShow { class Show : public Main::SessionShow {
public: public:
virtual void activate() = 0; virtual void activate() = 0;

View file

@ -331,7 +331,7 @@ void FieldAutocomplete::showFiltered(
plainQuery = base::StringViewMid(query, 1); plainQuery = base::StringViewMid(query, 1);
break; break;
} }
bool resetScroll = (_type != type || _filter != plainQuery); const auto resetScroll = (_type != type || _filter != plainQuery);
if (resetScroll) { if (resetScroll) {
_type = type; _type = type;
_filter = TextUtilities::RemoveAccents(plainQuery.toString()); _filter = TextUtilities::RemoveAccents(plainQuery.toString());
@ -342,10 +342,11 @@ void FieldAutocomplete::showFiltered(
} }
void FieldAutocomplete::showStickers(EmojiPtr emoji) { void FieldAutocomplete::showStickers(EmojiPtr emoji) {
bool resetScroll = (_emoji != emoji); const auto resetScroll = (_emoji != emoji);
_emoji = emoji; if (resetScroll || emoji) {
_type = Type::Stickers; _emoji = emoji;
if (!emoji) { _type = Type::Stickers;
} else if (!emoji) {
rowsUpdated( rowsUpdated(
base::take(_mrows), base::take(_mrows),
base::take(_hrows), base::take(_hrows),

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_premium.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -904,3 +905,68 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
}); });
return result; return result;
} }
base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
text,
st::historySendPremiumRequired);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(raw).fillRect(clip, st::windowBg);
}, raw->lifetime());
raw->sizeValue(
) | rpl::start_with_next([=](QSize size) {
const auto &st = st::historyComposeField;
const auto width = size.width();
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(
margins.left(),
(size.height() - label->height()) / 2,
width);
}, label->lifetime());
return result;
}
base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller) {
auto result = base::make_unique_q<Ui::RpWidget>(parent);
const auto raw = result.get();
const auto label = CreateChild<Ui::FlatLabel>(
result.get(),
tr::lng_restricted_send_non_premium(
tr::now,
lt_user,
user->shortName()),
st::historySendPremiumRequired);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto link = CreateChild<Ui::LinkButton>(
result.get(),
tr::lng_restricted_send_non_premium_more(tr::now));
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(raw).fillRect(clip, st::windowBg);
}, raw->lifetime());
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto &st = st::historyComposeField;
const auto margins = (st.textMargins + st.placeholderMargins);
const auto available = width - margins.left() - margins.right();
label->resizeToWidth(available);
label->moveToLeft(margins.left(), margins.top(), width);
link->move(
(width - link->width()) / 2,
label->y() + label->height());
}, label->lifetime());
link->setClickedCallback([=] {
Settings::ShowPremium(controller, u"require_premium"_q);
});
return result;
}

View file

@ -143,3 +143,10 @@ private:
[[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView( [[nodiscard]] base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
QWidget *parent, QWidget *parent,
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> TextErrorSendRestriction(
QWidget *parent,
const QString &text);
[[nodiscard]] base::unique_qptr<Ui::RpWidget> PremiumRequiredSendRestriction(
QWidget *parent,
not_null<UserData*> user,
not_null<Window::SessionController*> controller);

View file

@ -635,7 +635,7 @@ void StickersListFooter::paint(
if (context.expanding) { if (context.expanding) {
const auto both = clip.intersected( const auto both = clip.intersected(
context.clip.marginsRemoved( context.clip.marginsRemoved(
{ context.radius, 0, context.radius, 0 })); { 0/*context.radius*/, 0, context.radius, 0 }));
if (both.isEmpty()) { if (both.isEmpty()) {
return; return;
} }

View file

@ -88,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "payments/payments_checkout_process.h" #include "payments/payments_checkout_process.h"
#include "export/export_manager.h" #include "export/export_manager.h"
#include "webrtc/webrtc_environment.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
@ -150,6 +151,7 @@ Application::Application()
, _private(std::make_unique<Private>()) , _private(std::make_unique<Private>())
, _platformIntegration(Platform::Integration::Create()) , _platformIntegration(Platform::Integration::Create())
, _batterySaving(std::make_unique<base::BatterySaving>()) , _batterySaving(std::make_unique<base::BatterySaving>())
, _mediaDevices(std::make_unique<Webrtc::Environment>())
, _databases(std::make_unique<Storage::Databases>()) , _databases(std::make_unique<Storage::Databases>())
, _animationsManager(std::make_unique<Ui::Animations::Manager>()) , _animationsManager(std::make_unique<Ui::Animations::Manager>())
, _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); }) , _clearEmojiImageLoaderTimer([=] { clearEmojiSourceImages(); })
@ -1321,7 +1323,7 @@ Window::Controller *Application::ensureSeparateWindowForPeer(
std::make_unique<Window::Controller>(peer, showAtMsgId) std::make_unique<Window::Controller>(peer, showAtMsgId)
).first->second.get(); ).first->second.get();
processCreatedWindow(result); processCreatedWindow(result);
result->widget()->show(); result->firstShow();
result->finishFirstShow(); result->finishFirstShow();
return activate(result); return activate(result);
} }
@ -1341,7 +1343,7 @@ Window::Controller *Application::ensureSeparateWindowForAccount(
std::make_unique<Window::Controller>(account) std::make_unique<Window::Controller>(account)
).first->second.get(); ).first->second.get();
processCreatedWindow(result); processCreatedWindow(result);
result->widget()->show(); result->firstShow();
result->finishFirstShow(); result->finishFirstShow();
return activate(result); return activate(result);
} }

View file

@ -101,6 +101,10 @@ namespace Calls {
class Instance; class Instance;
} // namespace Calls } // namespace Calls
namespace Webrtc {
class Environment;
} // namespace Webrtc
namespace Core { namespace Core {
struct LocalUrlHandler; struct LocalUrlHandler;
@ -238,6 +242,9 @@ public:
[[nodiscard]] Media::Audio::Instance &audio() { [[nodiscard]] Media::Audio::Instance &audio() {
return *_audio; return *_audio;
} }
[[nodiscard]] Webrtc::Environment &mediaDevices() {
return *_mediaDevices;
}
// Langpack and emoji keywords. // Langpack and emoji keywords.
[[nodiscard]] Lang::Instance &langpack() { [[nodiscard]] Lang::Instance &langpack() {
@ -383,6 +390,7 @@ private:
const std::unique_ptr<Private> _private; const std::unique_ptr<Private> _private;
const std::unique_ptr<Platform::Integration> _platformIntegration; const std::unique_ptr<Platform::Integration> _platformIntegration;
const std::unique_ptr<base::BatterySaving> _batterySaving; const std::unique_ptr<base::BatterySaving> _batterySaving;
const std::unique_ptr<Webrtc::Environment> _mediaDevices;
const std::unique_ptr<Storage::Databases> _databases; const std::unique_ptr<Storage::Databases> _databases;
const std::unique_ptr<Ui::Animations::Manager> _animationsManager; const std::unique_ptr<Ui::Animations::Manager> _animationsManager;

View file

@ -33,6 +33,12 @@ namespace {
void SearchByHashtag(ClickContext context, const QString &tag) { void SearchByHashtag(ClickContext context, const QString &tag) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto delegate = my.elementDelegate
? my.elementDelegate()
: nullptr) {
delegate->elementSearchInList(tag, my.itemId);
return;
}
const auto controller = my.sessionWindow.get(); const auto controller = my.sessionWindow.get();
if (!controller) { if (!controller) {
return; return;
@ -287,7 +293,9 @@ void BotCommandClickHandler::onClick(ClickContext context) const {
return; return;
} }
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto delegate = my.elementDelegate ? my.elementDelegate() : nullptr) { if (const auto delegate = my.elementDelegate
? my.elementDelegate()
: nullptr) {
delegate->elementSendBotCommand(_cmd, my.itemId); delegate->elementSendBotCommand(_cmd, my.itemId);
} else if (const auto controller = my.sessionWindow.get()) { } else if (const auto controller = my.sessionWindow.get()) {
auto &data = controller->session().data(); auto &data = controller->session().data();

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/gl/gl_detection.h" #include "ui/gl/gl_detection.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_create_adm.h"
#include "webrtc/webrtc_device_common.h"
#include "window/section_widget.h" #include "window/section_widget.h"
// AyuGram includes // AyuGram includes
@ -163,8 +164,8 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(_downloadPath.current()) + Serialize::stringSize(_downloadPath.current())
+ Serialize::bytearraySize(_downloadPathBookmark) + Serialize::bytearraySize(_downloadPathBookmark)
+ sizeof(qint32) * 9 + sizeof(qint32) * 9
+ Serialize::stringSize(_callOutputDeviceId) + Serialize::stringSize(QString()) // legacy call output device id
+ Serialize::stringSize(_callInputDeviceId) + Serialize::stringSize(QString()) // legacy call input device id
+ sizeof(qint32) * 5; + sizeof(qint32) * 5;
for (const auto &[key, value] : _soundOverrides) { for (const auto &[key, value] : _soundOverrides) {
size += Serialize::stringSize(key) + Serialize::stringSize(value); size += Serialize::stringSize(key) + Serialize::stringSize(value);
@ -174,7 +175,7 @@ QByteArray Settings::serialize() const {
+ sizeof(qint32) + sizeof(qint32)
+ (_dictionariesEnabled.current().size() * sizeof(quint64)) + (_dictionariesEnabled.current().size() * sizeof(quint64))
+ sizeof(qint32) * 12 + sizeof(qint32) * 12
+ Serialize::stringSize(_callVideoInputDeviceId) + Serialize::stringSize(_cameraDeviceId.current())
+ sizeof(qint32) * 2 + sizeof(qint32) * 2
+ Serialize::bytearraySize(_groupCallPushToTalkShortcut) + Serialize::bytearraySize(_groupCallPushToTalkShortcut)
+ sizeof(qint64) + sizeof(qint64)
@ -198,7 +199,7 @@ QByteArray Settings::serialize() const {
+ (_accountsOrder.size() * sizeof(quint64)) + (_accountsOrder.size() * sizeof(quint64))
+ sizeof(qint32) * 7 + sizeof(qint32) * 7
+ (skipLanguages.size() * sizeof(quint64)) + (skipLanguages.size() * sizeof(quint64))
+ sizeof(qint32) + sizeof(qint32) * 2
+ sizeof(quint64) + sizeof(quint64)
+ sizeof(qint32) * 3 + sizeof(qint32) * 3
+ Serialize::bytearraySize(mediaViewPosition) + Serialize::bytearraySize(mediaViewPosition)
@ -208,6 +209,11 @@ QByteArray Settings::serialize() const {
for (const auto &id : _recentEmojiSkip) { for (const auto &id : _recentEmojiSkip) {
size += Serialize::stringSize(id); size += Serialize::stringSize(id);
} }
size += sizeof(qint32) * 2
+ Serialize::stringSize(_playbackDeviceId.current())
+ Serialize::stringSize(_captureDeviceId.current())
+ Serialize::stringSize(_callPlaybackDeviceId.current())
+ Serialize::stringSize(_callCaptureDeviceId.current());
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -232,8 +238,8 @@ QByteArray Settings::serialize() const {
<< qint32(_notificationsCount) << qint32(_notificationsCount)
<< static_cast<qint32>(_notificationsCorner) << static_cast<qint32>(_notificationsCorner)
<< qint32(_autoLock) << qint32(_autoLock)
<< _callOutputDeviceId << QString() // legacy call output device id
<< _callInputDeviceId << QString() // legacy call input device id
<< qint32(_callOutputVolume) << qint32(_callOutputVolume)
<< qint32(_callInputVolume) << qint32(_callInputVolume)
<< qint32(_callAudioDuckingEnabled ? 1 : 0) << qint32(_callAudioDuckingEnabled ? 1 : 0)
@ -277,7 +283,7 @@ QByteArray Settings::serialize() const {
<< qint32(_notifyFromAll ? 1 : 0) << qint32(_notifyFromAll ? 1 : 0)
<< qint32(_nativeWindowFrame.current() ? 1 : 0) << qint32(_nativeWindowFrame.current() ? 1 : 0)
<< qint32(_systemDarkModeEnabled.current() ? 1 : 0) << qint32(_systemDarkModeEnabled.current() ? 1 : 0)
<< _callVideoInputDeviceId << _cameraDeviceId.current()
<< qint32(_ipRevealWarning ? 1 : 0) << qint32(_ipRevealWarning ? 1 : 0)
<< qint32(_groupCallPushToTalk ? 1 : 0) << qint32(_groupCallPushToTalk ? 1 : 0)
<< _groupCallPushToTalkShortcut << _groupCallPushToTalkShortcut
@ -331,9 +337,7 @@ QByteArray Settings::serialize() const {
} }
stream stream
<< qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0); << qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0)
stream
<< qint32(_translateChatEnabled.current() ? 1 : 0) << qint32(_translateChatEnabled.current() ? 1 : 0)
<< quint64(QLocale::Language(_translateToRaw.current())) << quint64(QLocale::Language(_translateToRaw.current()))
<< qint32(_windowTitleContent.current().hideChatName ? 1 : 0) << qint32(_windowTitleContent.current().hideChatName ? 1 : 0)
@ -343,14 +347,20 @@ QByteArray Settings::serialize() const {
<< qint32(_ignoreBatterySaving.current() ? 1 : 0) << qint32(_ignoreBatterySaving.current() ? 1 : 0)
<< quint64(_macRoundIconDigest.value_or(0)) << quint64(_macRoundIconDigest.value_or(0))
<< qint32(_storiesClickTooltipHidden.current() ? 1 : 0) << qint32(_storiesClickTooltipHidden.current() ? 1 : 0)
<< qint32(_recentEmojiSkip.size()) << qint32(_recentEmojiSkip.size());
<< qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0);
for (const auto &id : _recentEmojiSkip) { for (const auto &id : _recentEmojiSkip) {
stream << id; stream << id;
} }
stream stream
<< qint32(_trayIconMonochrome.current() ? 1 : 0); << qint32(_trayIconMonochrome.current() ? 1 : 0)
<< qint32(_ttlVoiceClickTooltipHidden.current() ? 1 : 0)
<< _playbackDeviceId.current()
<< _captureDeviceId.current()
<< _callPlaybackDeviceId.current()
<< _callCaptureDeviceId.current();
} }
Ensures(result.size() == size);
return result; return result;
} }
@ -381,9 +391,13 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 notificationsCount = _notificationsCount; qint32 notificationsCount = _notificationsCount;
qint32 notificationsCorner = static_cast<qint32>(_notificationsCorner); qint32 notificationsCorner = static_cast<qint32>(_notificationsCorner);
qint32 autoLock = _autoLock; qint32 autoLock = _autoLock;
QString callOutputDeviceId = _callOutputDeviceId; QString playbackDeviceId = _playbackDeviceId.current();
QString callInputDeviceId = _callInputDeviceId; QString captureDeviceId = _captureDeviceId.current();
QString callVideoInputDeviceId = _callVideoInputDeviceId; QString cameraDeviceId = _cameraDeviceId.current();
QString legacyCallPlaybackDeviceId = _callPlaybackDeviceId.current();
QString legacyCallCaptureDeviceId = _callCaptureDeviceId.current();
QString callPlaybackDeviceId = _callPlaybackDeviceId.current();
QString callCaptureDeviceId = _callCaptureDeviceId.current();
qint32 callOutputVolume = _callOutputVolume; qint32 callOutputVolume = _callOutputVolume;
qint32 callInputVolume = _callInputVolume; qint32 callInputVolume = _callInputVolume;
qint32 callAudioDuckingEnabled = _callAudioDuckingEnabled ? 1 : 0; qint32 callAudioDuckingEnabled = _callAudioDuckingEnabled ? 1 : 0;
@ -423,7 +437,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0; qint32 groupCallPushToTalk = _groupCallPushToTalk ? 1 : 0;
QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut; QByteArray groupCallPushToTalkShortcut = _groupCallPushToTalkShortcut;
qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay; qint64 groupCallPushToTalkDelay = _groupCallPushToTalkDelay;
qint32 callAudioBackend = 0; qint32 legacyCallAudioBackend = 0;
qint32 disableCallsLegacy = 0; qint32 disableCallsLegacy = 0;
QByteArray windowPosition; QByteArray windowPosition;
std::vector<RecentEmojiPreload> recentEmojiPreload; std::vector<RecentEmojiPreload> recentEmojiPreload;
@ -481,8 +495,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
>> notificationsCount >> notificationsCount
>> notificationsCorner >> notificationsCorner
>> autoLock >> autoLock
>> callOutputDeviceId >> legacyCallPlaybackDeviceId
>> callInputDeviceId >> legacyCallCaptureDeviceId
>> callOutputVolume >> callOutputVolume
>> callInputVolume >> callInputVolume
>> callAudioDuckingEnabled >> callAudioDuckingEnabled
@ -545,7 +559,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
stream >> systemDarkModeEnabled; stream >> systemDarkModeEnabled;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> callVideoInputDeviceId; stream >> cameraDeviceId;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> ipRevealWarning; stream >> ipRevealWarning;
@ -557,7 +571,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
>> groupCallPushToTalkDelay; >> groupCallPushToTalkDelay;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> callAudioBackend; stream >> legacyCallAudioBackend;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> disableCallsLegacy; stream >> disableCallsLegacy;
@ -672,7 +686,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
}); });
} }
} }
}
if (!stream.atEnd()) {
stream >> rememberedDeleteMessageOnlyForYou; stream >> rememberedDeleteMessageOnlyForYou;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -720,6 +735,24 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> ttlVoiceClickTooltipHidden; stream >> ttlVoiceClickTooltipHidden;
} }
if (!stream.atEnd()) {
stream
>> playbackDeviceId
>> captureDeviceId;
}
if (!stream.atEnd()) {
stream
>> callPlaybackDeviceId
>> callCaptureDeviceId;
} else {
const auto &defaultId = Webrtc::kDefaultDeviceId;
callPlaybackDeviceId = (legacyCallPlaybackDeviceId == defaultId)
? QString()
: legacyCallPlaybackDeviceId;
callCaptureDeviceId = (legacyCallCaptureDeviceId == defaultId)
? QString()
: legacyCallCaptureDeviceId;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -764,9 +797,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_countUnreadMessages = (countUnreadMessages == 1); _countUnreadMessages = (countUnreadMessages == 1);
_notifyAboutPinned = (notifyAboutPinned == 1); _notifyAboutPinned = (notifyAboutPinned == 1);
_autoLock = autoLock; _autoLock = autoLock;
_callOutputDeviceId = callOutputDeviceId; _playbackDeviceId = playbackDeviceId;
_callInputDeviceId = callInputDeviceId; _captureDeviceId = captureDeviceId;
_callVideoInputDeviceId = callVideoInputDeviceId; const auto kOldDefault = u"default"_q;
_cameraDeviceId = cameraDeviceId;
_callPlaybackDeviceId = callPlaybackDeviceId;
_callCaptureDeviceId = callCaptureDeviceId;
_callOutputVolume = callOutputVolume; _callOutputVolume = callOutputVolume;
_callInputVolume = callInputVolume; _callInputVolume = callInputVolume;
_callAudioDuckingEnabled = (callAudioDuckingEnabled == 1); _callAudioDuckingEnabled = (callAudioDuckingEnabled == 1);
@ -962,10 +998,6 @@ void Settings::setTabbedReplacedWithInfo(bool enabled) {
} }
} }
Webrtc::Backend Settings::callAudioBackend() const {
return Webrtc::Backend::OpenAL;
}
void Settings::setDialogsWidthRatio(float64 ratio) { void Settings::setDialogsWidthRatio(float64 ratio) {
_dialogsWidthRatio = ratio; _dialogsWidthRatio = ratio;
} }
@ -1223,9 +1255,11 @@ void Settings::resetOnLastLogout() {
_notifyAboutPinned = true; _notifyAboutPinned = true;
//_autoLock = 3600; //_autoLock = 3600;
//_callOutputDeviceId = u"default"_q; //_playbackDeviceId = QString();
//_callInputDeviceId = u"default"_q; //_captureDeviceId = QString();
//_callVideoInputDeviceId = u"default"_q; //_cameraDeviceId = QString();
//_callPlaybackDeviceId = QString();
//_callCaptureDeviceId = QString();
//_callOutputVolume = 100; //_callOutputVolume = 100;
//_callInputVolume = 100; //_callInputVolume = 100;
//_callAudioDuckingEnabled = true; //_callAudioDuckingEnabled = true;

View file

@ -29,10 +29,6 @@ namespace Window {
enum class Column; enum class Column;
} // namespace Window } // namespace Window
namespace Webrtc {
enum class Backend;
} // namespace Webrtc
namespace Calls::Group { namespace Calls::Group {
enum class StickedTooltip; enum class StickedTooltip;
} // namespace Calls::Group } // namespace Calls::Group
@ -269,30 +265,68 @@ public:
void setAutoLock(int value) { void setAutoLock(int value) {
_autoLock = value; _autoLock = value;
} }
[[nodiscard]] QString callOutputDeviceId() const {
return _callOutputDeviceId.isEmpty() [[nodiscard]] QString playbackDeviceId() const {
? u"default"_q return _playbackDeviceId.current();
: _callOutputDeviceId;
} }
void setCallOutputDeviceId(const QString &value) { [[nodiscard]] rpl::producer<QString> playbackDeviceIdChanges() const {
_callOutputDeviceId = value; return _playbackDeviceId.changes();
} }
[[nodiscard]] QString callInputDeviceId() const { [[nodiscard]] rpl::producer<QString> playbackDeviceIdValue() const {
return _callInputDeviceId.isEmpty() return _playbackDeviceId.value();
? u"default"_q
: _callInputDeviceId;
} }
void setCallInputDeviceId(const QString &value) { void setPlaybackDeviceId(const QString &value) {
_callInputDeviceId = value; _playbackDeviceId = value;
} }
[[nodiscard]] QString callVideoInputDeviceId() const { [[nodiscard]] QString captureDeviceId() const {
return _callVideoInputDeviceId.isEmpty() return _captureDeviceId.current();
? u"default"_q
: _callVideoInputDeviceId;
} }
void setCallVideoInputDeviceId(const QString &value) { [[nodiscard]] rpl::producer<QString> captureDeviceIdChanges() const {
_callVideoInputDeviceId = value; return _captureDeviceId.changes();
} }
[[nodiscard]] rpl::producer<QString> captureDeviceIdValue() const {
return _captureDeviceId.value();
}
void setCaptureDeviceId(const QString &value) {
_captureDeviceId = value;
}
[[nodiscard]] QString cameraDeviceId() const {
return _cameraDeviceId.current();
}
[[nodiscard]] rpl::producer<QString> cameraDeviceIdChanges() const {
return _cameraDeviceId.changes();
}
[[nodiscard]] rpl::producer<QString> cameraDeviceIdValue() const {
return _cameraDeviceId.value();
}
void setCameraDeviceId(const QString &value) {
_cameraDeviceId = value;
}
[[nodiscard]] QString callPlaybackDeviceId() const {
return _callPlaybackDeviceId.current();
}
[[nodiscard]] rpl::producer<QString> callPlaybackDeviceIdChanges() const {
return _callPlaybackDeviceId.changes();
}
[[nodiscard]] rpl::producer<QString> callPlaybackDeviceIdValue() const {
return _callPlaybackDeviceId.value();
}
void setCallPlaybackDeviceId(const QString &value) {
_callPlaybackDeviceId = value;
}
[[nodiscard]] QString callCaptureDeviceId() const {
return _callCaptureDeviceId.current();
}
[[nodiscard]] rpl::producer<QString> callCaptureDeviceIdChanges() const {
return _callCaptureDeviceId.changes();
}
[[nodiscard]] rpl::producer<QString> callCaptureDeviceIdValue() const {
return _callCaptureDeviceId.value();
}
void setCallCaptureDeviceId(const QString &value) {
_callCaptureDeviceId = value;
}
[[nodiscard]] int callOutputVolume() const { [[nodiscard]] int callOutputVolume() const {
return _callOutputVolume; return _callOutputVolume;
} }
@ -311,7 +345,6 @@ public:
void setCallAudioDuckingEnabled(bool value) { void setCallAudioDuckingEnabled(bool value) {
_callAudioDuckingEnabled = value; _callAudioDuckingEnabled = value;
} }
[[nodiscard]] Webrtc::Backend callAudioBackend() const;
[[nodiscard]] bool disableCallsLegacy() const { [[nodiscard]] bool disableCallsLegacy() const {
return _disableCallsLegacy; return _disableCallsLegacy;
} }
@ -881,9 +914,11 @@ private:
bool _countUnreadMessages = true; bool _countUnreadMessages = true;
rpl::variable<bool> _notifyAboutPinned = true; rpl::variable<bool> _notifyAboutPinned = true;
int _autoLock = 3600; int _autoLock = 3600;
QString _callOutputDeviceId = u"default"_q; rpl::variable<QString> _playbackDeviceId;
QString _callInputDeviceId = u"default"_q; rpl::variable<QString> _captureDeviceId;
QString _callVideoInputDeviceId = u"default"_q; rpl::variable<QString> _cameraDeviceId;
rpl::variable<QString> _callPlaybackDeviceId;
rpl::variable<QString> _callCaptureDeviceId;
int _callOutputVolume = 100; int _callOutputVolume = 100;
int _callInputVolume = 100; int _callInputVolume = 100;
bool _callAudioDuckingEnabled = true; bool _callAudioDuckingEnabled = true;

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "boxes/connection_box.h" #include "boxes/connection_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h" #include "boxes/sessions_box.h"
#include "boxes/language_box.h" #include "boxes/language_box.h"
@ -661,6 +662,17 @@ bool CopyPeerId(
return true; return true;
} }
bool ShowSearchTagsPromo(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
ShowPremiumPreviewBox(controller, PremiumPreview::TagsForMessages);
return true;
}
void ExportTestChatTheme( void ExportTestChatTheme(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) { not_null<const Data::CloudTheme*> theme) {
@ -1032,6 +1044,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
{ {
u"^copy:(.+)$"_q, u"^copy:(.+)$"_q,
CopyPeerId CopyPeerId
},
{
u"about_tags"_q,
ShowSearchTagsPromo
} }
}; };
return Result; return Result;

View file

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

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
namespace { namespace {
@ -113,6 +114,9 @@ bool CanSendAnyOf(
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->isInaccessible() || user->isRepliesChat()) { if (user->isInaccessible() || user->isRepliesChat()) {
return false; return false;
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return false;
} else if (rights } else if (rights
& ~(ChatRestriction::SendVoiceMessages & ~(ChatRestriction::SendVoiceMessages
| ChatRestriction::SendVideoMessages | ChatRestriction::SendVideoMessages
@ -167,6 +171,13 @@ std::optional<QString> RestrictionError(
using Flag = ChatRestriction; using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) { if (const auto restricted = peer->amRestricted(restriction)) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return tr::lng_restricted_send_non_premium(
tr::now,
lt_user,
user->shortName());
}
const auto result = (restriction == Flag::SendVoiceMessages) const auto result = (restriction == Flag::SendVoiceMessages)
? tr::lng_restricted_send_voice_messages( ? tr::lng_restricted_send_voice_messages(
tr::now, tr::now,

View file

@ -99,7 +99,7 @@ MTPInputMedia WebPageForMTP(
bool required) { bool required) {
using Flag = MTPDinputMediaWebPage::Flag; using Flag = MTPDinputMediaWebPage::Flag;
return MTP_inputMediaWebPage( return MTP_inputMediaWebPage(
MTP_flags((required ? Flag() : Flag::f_optional) MTP_flags(((false && required) ? Flag() : Flag::f_optional)
| (draft.forceLargeMedia ? Flag::f_force_large_media : Flag()) | (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
| (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())), | (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
MTP_string(draft.url)); MTP_string(draft.url));

View file

@ -0,0 +1,130 @@
/*
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 Data {
inline constexpr auto kLifeStartDate = 1375315200; // Let it be 01.08.2013.
class LastseenStatus final {
public:
LastseenStatus() = default;
[[nodiscard]] static LastseenStatus Recently(bool byMe = false) {
return LastseenStatus(kRecentlyValue, false, byMe);
}
[[nodiscard]] static LastseenStatus WithinWeek(bool byMe = false) {
return LastseenStatus(kWithinWeekValue, false, byMe);
}
[[nodiscard]] static LastseenStatus WithinMonth(bool byMe = false) {
return LastseenStatus(kWithinMonthValue, false, byMe);
}
[[nodiscard]] static LastseenStatus LongAgo(bool byMe = false) {
return LastseenStatus(kLongAgoValue, false, byMe);
}
[[nodiscard]] static LastseenStatus OnlineTill(
TimeId till,
bool local = false,
bool hiddenByMe = false) {
return (till >= kLifeStartDate + kSpecialValueSkip)
? LastseenStatus(till - kLifeStartDate, !local, hiddenByMe)
: LongAgo(hiddenByMe);
}
[[nodiscard]] bool isHidden() const {
return !_available;
}
[[nodiscard]] bool isRecently() const {
return !_available && (_value == kRecentlyValue);
}
[[nodiscard]] bool isWithinWeek() const {
return !_available && (_value == kWithinWeekValue);
}
[[nodiscard]] bool isWithinMonth() const {
return !_available && (_value == kWithinMonthValue);
}
[[nodiscard]] bool isLongAgo() const {
return !_available && (_value == kLongAgoValue);
}
[[nodiscard]] bool isHiddenByMe() const {
return _hiddenByMe;
}
[[nodiscard]] bool isOnline(TimeId now) const {
return (_value >= kSpecialValueSkip)
&& (kLifeStartDate + _value > now);
}
[[nodiscard]] bool isLocalOnlineValue() const {
return !_available && (_value >= kSpecialValueSkip);
}
[[nodiscard]] TimeId onlineTill() const {
return (_value >= kSpecialValueSkip)
? (kLifeStartDate + _value)
: 0;
}
[[nodiscard]] uint32 serialize() const {
return (_value & 0x3FFFFFFF)
| (_available << 30)
| (_hiddenByMe << 31);
}
[[nodiscard]] static LastseenStatus FromSerialized(uint32 value) {
auto result = LastseenStatus();
result._value = value & 0x3FFFFFFF;
result._available = (value >> 30) & 1;
result._hiddenByMe = (value >> 31) & 1;
return result.valid() ? result : LastseenStatus();
}
[[nodiscard]] static LastseenStatus FromLegacy(int32 value) {
if (value == -2) {
return LastseenStatus::Recently();
} else if (value == -3) {
return LastseenStatus::WithinWeek();
} else if (value == -4) {
return LastseenStatus::WithinMonth();
} else if (value < -30) {
return LastseenStatus::OnlineTill(-value, true);
} else if (value > 0) {
return LastseenStatus::OnlineTill(value);
}
return LastseenStatus();
}
friend inline constexpr auto operator<=>(
LastseenStatus,
LastseenStatus) = default;
friend inline constexpr bool operator==(
LastseenStatus a,
LastseenStatus b) = default;
private:
static constexpr auto kLongAgoValue = uint32(0);
static constexpr auto kRecentlyValue = uint32(1);
static constexpr auto kWithinWeekValue = uint32(2);
static constexpr auto kWithinMonthValue = uint32(3);
static constexpr auto kSpecialValueSkip = uint32(4);
static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip;
[[nodiscard]] bool valid() const {
return !_available || (_value >= kSpecialValueSkip);
}
LastseenStatus(uint32 value, bool available, bool hiddenByMe)
: _value(value)
, _available(available ? 1 : 0)
, _hiddenByMe(hiddenByMe ? 1 : 0) {
}
uint32 _value : 30 = 0;
uint32 _available : 1 = 0;
uint32 _hiddenByMe : 1 = 0;
};
} // namespace Data

View file

@ -983,13 +983,17 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
const auto type = [&] { const auto type = [&] {
using namespace Ui::Text; using namespace Ui::Text;
if (_document->isVideoMessage()) { if (_document->isVideoMessage()) {
return tr::lng_in_dlg_video_message(tr::now); return (item->media() && item->media()->ttlSeconds())
? tr::lng_in_dlg_video_message_ttl(tr::now)
: tr::lng_in_dlg_video_message(tr::now);
} else if (_document->isAnimation()) { } else if (_document->isAnimation()) {
return u"GIF"_q; return u"GIF"_q;
} else if (_document->isVideoFile()) { } else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now); return tr::lng_in_dlg_video(tr::now);
} else if (_document->isVoiceMessage()) { } else if (_document->isVoiceMessage()) {
return tr::lng_in_dlg_audio(tr::now); return (item->media() && item->media()->ttlSeconds())
? tr::lng_in_dlg_voice_message_ttl(tr::now)
: tr::lng_in_dlg_audio(tr::now);
} else if (const auto name = FormatSongNameFor(_document).string(); } else if (const auto name = FormatSongNameFor(_document).string();
!name.isEmpty()) { !name.isEmpty()) {
return name; return name;
@ -1020,13 +1024,19 @@ TextWithEntities MediaFile::notificationText() const {
} }
const auto type = [&] { const auto type = [&] {
if (_document->isVideoMessage()) { if (_document->isVideoMessage()) {
return tr::lng_in_dlg_video_message(tr::now); const auto media = parent()->media();
return (media && media->ttlSeconds())
? tr::lng_in_dlg_video_message_ttl(tr::now)
: tr::lng_in_dlg_video_message(tr::now);
} else if (_document->isAnimation()) { } else if (_document->isAnimation()) {
return u"GIF"_q; return u"GIF"_q;
} else if (_document->isVideoFile()) { } else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now); return tr::lng_in_dlg_video(tr::now);
} else if (_document->isVoiceMessage()) { } else if (_document->isVoiceMessage()) {
return tr::lng_in_dlg_audio(tr::now); const auto media = parent()->media();
return (media && media->ttlSeconds())
? tr::lng_in_dlg_voice_message_ttl(tr::now)
: tr::lng_in_dlg_audio(tr::now);
} else if (!_document->filename().isEmpty()) { } else if (!_document->filename().isEmpty()) {
return _document->filename(); return _document->filename();
} else if (_document->isAudioFile()) { } else if (_document->isAudioFile()) {
@ -1077,13 +1087,19 @@ TextForMimeData MediaFile::clipboardText() const {
return tr::lng_in_dlg_sticker(tr::now); return tr::lng_in_dlg_sticker(tr::now);
} else if (_document->isAnimation()) { } else if (_document->isAnimation()) {
if (_document->isVideoMessage()) { if (_document->isVideoMessage()) {
return tr::lng_in_dlg_video_message(tr::now); const auto media = parent()->media();
return (media && media->ttlSeconds())
? tr::lng_in_dlg_video_message_ttl(tr::now)
: tr::lng_in_dlg_video_message(tr::now);
} }
return u"GIF"_q; return u"GIF"_q;
} else if (_document->isVideoFile()) { } else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now); return tr::lng_in_dlg_video(tr::now);
} else if (_document->isVoiceMessage()) { } else if (_document->isVoiceMessage()) {
return tr::lng_in_dlg_audio(tr::now) + addName; const auto media = parent()->media();
return ((media && media->ttlSeconds())
? tr::lng_in_dlg_voice_message_ttl
: tr::lng_in_dlg_audio)(tr::now) + addName;;
} else if (_document->isSong()) { } else if (_document->isSong()) {
return tr::lng_in_dlg_audio_file(tr::now) + addName; return tr::lng_in_dlg_audio_file(tr::now) + addName;
} }

View file

@ -31,6 +31,15 @@ ReactionId SearchTagFromQuery(const QString &query) {
return {}; return {};
} }
std::vector<ReactionId> SearchTagsFromQuery(
const QString &query) {
auto result = std::vector<ReactionId>();
if (const auto tag = SearchTagFromQuery(query)) {
result.push_back(tag);
}
return result;
}
QString ReactionEntityData(const ReactionId &id) { QString ReactionEntityData(const ReactionId &id) {
if (id.empty()) { if (id.empty()) {
return {}; return {};

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