diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 4d19e1048d..f414a55fd6 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -9,11 +9,38 @@ on: - cron: '0 0 * * *' jobs: - noResponse: + waiting-for-answer: runs-on: ubuntu-latest steps: - uses: lee-dohm/no-response@v0.5.0 with: token: ${{ github.token }} - # Label requiring a response 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. diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e2a19b5ff9..22ad802389 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -591,6 +591,7 @@ PRIVATE data/data_groups.h data/data_histories.cpp data/data_histories.h + data/data_lastseen_status.h data/data_location.cpp data/data_location.h data/data_media_rotation.cpp @@ -828,6 +829,8 @@ PRIVATE history/view/reactions/history_view_reactions_strip.h history/view/reactions/history_view_reactions_tabs.cpp 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.h history/view/history_view_contact_status.cpp @@ -1092,6 +1095,7 @@ PRIVATE media/audio/media_audio.h media/audio/media_audio_capture.cpp 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.h media/audio/media_audio_loader.cpp @@ -1307,8 +1311,6 @@ PRIVATE platform/mac/touchbar/mac_touchbar_manager.mm platform/mac/touchbar/mac_touchbar_media_view.h 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.h platform/win/launcher_win.cpp @@ -1332,7 +1334,6 @@ PRIVATE platform/win/windows_autostart_task.h platform/win/windows_toast_activator.cpp platform/win/windows_toast_activator.h - platform/platform_audio.h platform/platform_file_utilities.h platform/platform_launcher.h platform/platform_integration.cpp diff --git a/Telegram/Resources/icons/chat/large_lockedchat.png b/Telegram/Resources/icons/chat/large_lockedchat.png new file mode 100644 index 0000000000..812b705b30 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@2x.png b/Telegram/Resources/icons/chat/large_lockedchat@2x.png new file mode 100644 index 0000000000..30bf73e9b7 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@2x.png differ diff --git a/Telegram/Resources/icons/chat/large_lockedchat@3x.png b/Telegram/Resources/icons/chat/large_lockedchat@3x.png new file mode 100644 index 0000000000..a0174e1295 Binary files /dev/null and b/Telegram/Resources/icons/chat/large_lockedchat@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow.png b/Telegram/Resources/icons/dialogs/mini_arrow.png new file mode 100644 index 0000000000..81b5076f82 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow@2x.png b/Telegram/Resources/icons/dialogs/mini_arrow@2x.png new file mode 100644 index 0000000000..6e1e87682f Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_arrow@3x.png b/Telegram/Resources/icons/dialogs/mini_arrow@3x.png new file mode 100644 index 0000000000..36fd0e0c35 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_arrow@3x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock.png b/Telegram/Resources/icons/dialogs/mini_tag_lock.png new file mode 100644 index 0000000000..cb1e4d76a2 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png b/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png new file mode 100644 index 0000000000..cc875b7fa7 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png b/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png new file mode 100644 index 0000000000..68254340d4 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/mini_tag_lock@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter.png b/Telegram/Resources/icons/menu/tag_filter.png new file mode 100644 index 0000000000..1bef612c5b Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter@2x.png b/Telegram/Resources/icons/menu/tag_filter@2x.png new file mode 100644 index 0000000000..0561138816 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_filter@3x.png b/Telegram/Resources/icons/menu/tag_filter@3x.png new file mode 100644 index 0000000000..e8661460e7 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_filter@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove.png b/Telegram/Resources/icons/menu/tag_remove.png new file mode 100644 index 0000000000..0a495b4efe Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove@2x.png b/Telegram/Resources/icons/menu/tag_remove@2x.png new file mode 100644 index 0000000000..521ab6968f Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_remove@3x.png b/Telegram/Resources/icons/menu/tag_remove@3x.png new file mode 100644 index 0000000000..5ebfb2b911 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_remove@3x.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename.png b/Telegram/Resources/icons/menu/tag_rename.png new file mode 100644 index 0000000000..69040f6aaf Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename@2x.png b/Telegram/Resources/icons/menu/tag_rename@2x.png new file mode 100644 index 0000000000..2eb0fb82f9 Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename@2x.png differ diff --git a/Telegram/Resources/icons/menu/tag_rename@3x.png b/Telegram/Resources/icons/menu/tag_rename@3x.png new file mode 100644 index 0000000000..ec18a3f00d Binary files /dev/null and b/Telegram/Resources/icons/menu/tag_rename@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen.png b/Telegram/Resources/icons/settings/premium/large_lastseen.png new file mode 100644 index 0000000000..e47bd7935b Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png new file mode 100644 index 0000000000..305b6a4919 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png new file mode 100644 index 0000000000..0cd1e12ab0 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_lastseen@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime.png b/Telegram/Resources/icons/settings/premium/large_readtime.png new file mode 100644 index 0000000000..c06294a82e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@2x.png b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png new file mode 100644 index 0000000000..f30a355ee8 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/large_readtime@3x.png b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png new file mode 100644 index 0000000000..1e7d022a30 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/large_readtime@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags.png b/Telegram/Resources/icons/settings/premium/tags.png new file mode 100644 index 0000000000..9a761e7b08 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags@2x.png b/Telegram/Resources/icons/settings/premium/tags@2x.png new file mode 100644 index 0000000000..194aec6497 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/tags@3x.png b/Telegram/Resources/icons/settings/premium/tags@3x.png new file mode 100644 index 0000000000..f6b8511cfe Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/tags@3x.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s.png b/Telegram/Resources/icons/voice_lock/input_mic_s.png new file mode 100644 index 0000000000..86c1146a5d Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png b/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png new file mode 100644 index 0000000000..e70fd6a669 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s@2x.png differ diff --git a/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png b/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png new file mode 100644 index 0000000000..d7a4ad9814 Binary files /dev/null and b/Telegram/Resources/icons/voice_lock/input_mic_s@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 5373b3dc93..0d7f22776c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -141,6 +141,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_status_last_week" = "last seen within a week"; "lng_status_last_month" = "last seen within a month"; "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#other" = "last seen {count} minutes 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_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_limit1#one" = "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_occupied" = "This username is already occupied."; "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_available" = "This username is available."; "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_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_default_scale" = "Default interface scale"; "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_forwards_privacy" = "Forwarded messages"; "lng_settings_profile_photo_privacy" = "Profile photo"; +"lng_settings_messages_privacy" = "Messages"; "lng_settings_voices_privacy" = "Voice messages"; "lng_settings_bio_privacy" = "Bio"; "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_close_friends" = "Close friends"; "lng_edit_privacy_nobody" = "Nobody"; +"lng_edit_privacy_premium" = "Premium users"; "lng_edit_privacy_exceptions" = "Add exceptions"; "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_always_title" = "Always 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_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_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_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"; @@ -1464,6 +1505,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_report_messages_none" = "Select Messages"; "lng_report_messages_count#one" = "Report {count} Message"; "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" = "Additional Details"; "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_subtitle_infinite_reactions" = "Infinite Reactions"; "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_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly."; "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_audio" = "Voice 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_sticker" = "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_lock_discard" = "Discard"; "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_will_be_notified" = "Members will 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_set_as_quick" = "Set as Quick"; "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_delete_from_disk" = "Delete from disk"; "lng_context_delete_all_files" = "Delete all files"; "lng_context_save_custom_sound" = "Save for notifications"; "lng_context_translate" = "Translate"; "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_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_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_many#one" = "Reactions contain emoji from **{count} pack**."; "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_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_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_revoke" = "Revoke 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_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_removed_list_title" = "Removed users"; @@ -4241,7 +4315,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; -"lng_sponsored_info_description1" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; +"lng_sponsored_info_description1" = "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 can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:"; "lng_sponsored_info_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_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#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_reaction_as_message" = "Send reaction as a private message"; "lng_stealth_mode_menu_item" = "Stealth Mode"; "lng_stealth_mode_title" = "Stealth Mode"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 732d62eb24..c3069c3103 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="4.14.13.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 169be97b2c..eaac340706 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,14,6,0 - PRODUCTVERSION 4,14,6,0 + FILEVERSION 4,14,13,0 + PRODUCTVERSION 4,14,13,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "4.14.6.0" + VALUE "FileVersion", "4.14.13.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.14.6.0" + VALUE "ProductVersion", "4.14.13.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 6aca907d9a..8b304b6be0 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,14,6,0 - PRODUCTVERSION 4,14,6,0 + FILEVERSION 4,14,13,0 + PRODUCTVERSION 4,14,13,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "4.14.6.0" + VALUE "FileVersion", "4.14.13.0" VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "4.14.6.0" + VALUE "ProductVersion", "4.14.13.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_global_privacy.cpp b/Telegram/SourceFiles/api/api_global_privacy.cpp index 219d335f15..f958bdfe80 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.cpp +++ b/Telegram/SourceFiles/api/api_global_privacy.cpp @@ -84,18 +84,61 @@ void GlobalPrivacy::dismissArchiveAndMuteSuggestion() { u"AUTOARCHIVE_POPULAR"_q); } +void GlobalPrivacy::updateHideReadTime(bool hide) { + update( + archiveAndMuteCurrent(), + unarchiveOnNewMessageCurrent(), + hide, + newRequirePremiumCurrent()); +} + +bool GlobalPrivacy::hideReadTimeCurrent() const { + return _hideReadTime.current(); +} + +rpl::producer 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 GlobalPrivacy::newRequirePremium() const { + return _newRequirePremium.value(); +} + + void GlobalPrivacy::updateArchiveAndMute(bool value) { - update(value, unarchiveOnNewMessageCurrent()); + update( + value, + unarchiveOnNewMessageCurrent(), + hideReadTimeCurrent(), + newRequirePremiumCurrent()); } void GlobalPrivacy::updateUnarchiveOnNewMessage( UnarchiveOnNewMessage value) { - update(archiveAndMuteCurrent(), value); + update( + archiveAndMuteCurrent(), + value, + hideReadTimeCurrent(), + newRequirePremiumCurrent()); } void GlobalPrivacy::update( bool archiveAndMute, - UnarchiveOnNewMessage unarchiveOnNewMessage) { + UnarchiveOnNewMessage unarchiveOnNewMessage, + bool hideReadTime, + bool newRequirePremium) { using Flag = MTPDglobalPrivacySettings::Flag; _api.request(_requestId).cancel(); @@ -108,17 +151,26 @@ void GlobalPrivacy::update( : Flag()) | (unarchiveOnNewMessage != UnarchiveOnNewMessage::AnyUnmuted ? Flag::f_keep_archived_folders + : Flag()) + | (hideReadTime ? Flag::f_hide_read_marks : Flag()) + | ((newRequirePremium && _session->premium()) + ? Flag::f_new_noncontact_peers_require_premium : Flag()); _requestId = _api.request(MTPaccount_SetGlobalPrivacySettings( MTP_globalPrivacySettings(MTP_flags(flags)) )).done([=](const MTPGlobalPrivacySettings &result) { _requestId = 0; apply(result); - }).fail([=] { + }).fail([=](const MTP::Error &error) { _requestId = 0; + if (error.type() == u"PREMIUM_ACCOUNT_REQUIRED"_q) { + update(archiveAndMute, unarchiveOnNewMessage, hideReadTime, {}); + } }).send(); _archiveAndMute = archiveAndMute; _unarchiveOnNewMessage = unarchiveOnNewMessage; + _hideReadTime = hideReadTime; + _newRequirePremium = newRequirePremium; } void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { @@ -129,6 +181,8 @@ void GlobalPrivacy::apply(const MTPGlobalPrivacySettings &data) { : data.is_keep_archived_folders() ? UnarchiveOnNewMessage::NotInFoldersUnmuted : UnarchiveOnNewMessage::AnyUnmuted; + _hideReadTime = data.is_hide_read_marks(); + _newRequirePremium = data.is_new_noncontact_peers_require_premium(); }); } diff --git a/Telegram/SourceFiles/api/api_global_privacy.h b/Telegram/SourceFiles/api/api_global_privacy.h index 9e4b8e1215..de70ba9b97 100644 --- a/Telegram/SourceFiles/api/api_global_privacy.h +++ b/Telegram/SourceFiles/api/api_global_privacy.h @@ -41,12 +41,22 @@ public: [[nodiscard]] rpl::producer<> suggestArchiveAndMute() const; void dismissArchiveAndMuteSuggestion(); + void updateHideReadTime(bool hide); + [[nodiscard]] bool hideReadTimeCurrent() const; + [[nodiscard]] rpl::producer hideReadTime() const; + + void updateNewRequirePremium(bool value); + [[nodiscard]] bool newRequirePremiumCurrent() const; + [[nodiscard]] rpl::producer newRequirePremium() const; + private: void apply(const MTPGlobalPrivacySettings &data); void update( bool archiveAndMute, - UnarchiveOnNewMessage unarchiveOnNewMessage); + UnarchiveOnNewMessage unarchiveOnNewMessage, + bool hideReadTime, + bool newRequirePremium); const not_null _session; MTP::Sender _api; @@ -55,6 +65,8 @@ private: rpl::variable _unarchiveOnNewMessage = UnarchiveOnNewMessage::None; rpl::variable _showArchiveAndMute = false; + rpl::variable _hideReadTime = false; + rpl::variable _newRequirePremium = false; std::vector> _callbacks; }; diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index cc97e22c0b..4fbcaebf01 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "data/data_channel.h" #include "data/data_histories.h" +#include "data/data_message_reaction_id.h" #include "data/data_peer.h" #include "data/data_session.h" #include "history/history.h" @@ -43,6 +44,23 @@ constexpr auto kSearchPerPage = 50; 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 MessagesSearch::MessagesSearch(not_null history) @@ -54,9 +72,8 @@ MessagesSearch::~MessagesSearch() { base::take(_searchInHistoryRequest)); } -void MessagesSearch::searchMessages(const QString &query, PeerData *from) { - _query = query; - _from = from; +void MessagesSearch::searchMessages(Request request) { + _request = std::move(request); _offsetId = {}; searchRequest(); } @@ -69,8 +86,7 @@ void MessagesSearch::searchMore() { } void MessagesSearch::searchRequest() { - const auto nextToken = _query - + QString::number(_from ? _from->id.value : 0); + const auto nextToken = RequestToToken(_request); if (!_offsetId) { const auto it = _cacheOfStartByToken.find(nextToken); if (it != end(_cacheOfStartByToken)) { @@ -80,18 +96,21 @@ void MessagesSearch::searchRequest() { } } auto callback = [=](Fn finish) { - const auto flags = _from - ? MTP_flags(MTPmessages_Search::Flag::f_from_id) - : MTP_flags(0); + using Flag = MTPmessages_Search::Flag; + const auto from = _request.from; + const auto fromPeer = _history->peer->isUser() ? nullptr : from; + const auto savedPeer = _history->peer->isSelf() ? from : nullptr; _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, - MTP_string(_query), - (_from - ? _from->input - : MTP_inputPeerEmpty()), - MTPInputPeer(), // saved_peer_id - MTPVector(), // saved_reaction + MTP_string(_request.query), + (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()), + (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()), + MTP_vector_from_range(_request.tags | ranges::views::transform( + Data::ReactionToMTP + )), MTPint(), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index b97ec69aae..76046aaa91 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -7,10 +7,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "base/qt/qt_compare.h" +#include "data/data_message_reaction_id.h" + class HistoryItem; class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { struct FoundMessages { @@ -21,10 +28,23 @@ struct FoundMessages { class MessagesSearch final { public: + struct Request { + QString query; + PeerData *from = nullptr; + std::vector tags; + + friend inline bool operator==( + const Request &, + const Request &) = default; + friend inline auto operator<=>( + const Request &, + const Request &) = default; + }; + explicit MessagesSearch(not_null history); ~MessagesSearch(); - void searchMessages(const QString &query, PeerData *from); + void searchMessages(Request request); void searchMore(); [[nodiscard]] rpl::producer messagesFounds() const; @@ -41,8 +61,7 @@ private: base::flat_map _cacheOfStartByToken; - QString _query; - PeerData *_from = nullptr; + Request _request; MsgId _offsetId; int _searchInHistoryRequest = 0; // Not real mtpRequestId. diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index a1cb69d774..8451232f60 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -11,12 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL 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) : _apiSearch(history) { if (const auto migrated = history->migrateFrom()) { @@ -88,9 +82,9 @@ void MessagesSearchMerged::clear() { void MessagesSearchMerged::search(const Request &search) { if (_migratedSearch) { _waitingForTotal = true; - _migratedSearch->searchMessages(search.query, search.from); + _migratedSearch->searchMessages(search); } - _apiSearch.searchMessages(search.query, search.from); + _apiSearch.searchMessages(search); } void MessagesSearchMerged::searchMore() { diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.h b/Telegram/SourceFiles/api/api_messages_search_merged.h index d7c67713fb..d4c6fbc1c8 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.h +++ b/Telegram/SourceFiles/api/api_messages_search_merged.h @@ -12,19 +12,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class PeerData; +namespace Data { +struct ReactionId; +} // namespace Data + namespace Api { // Search in both of history and migrated history, if it exists. class MessagesSearchMerged final { public: - struct Request { - QString query; - PeerData *from = nullptr; - }; - struct RequestCompare { - bool operator()(const Request &a, const Request &b) const; - }; - using CachedRequests = std::set; + using Request = MessagesSearch::Request; + using CachedRequests = base::flat_set; MessagesSearchMerged(not_null history); diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 088c3cf876..72da2a4893 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -15,6 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_peer_values.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_app_config.h" #include "main/main_session.h" @@ -334,6 +338,72 @@ const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } +rpl::producer<> Premium::somePremiumRequiredResolved() const { + return _somePremiumRequiredResolved.events(); +} + +void Premium::resolvePremiumRequired(not_null 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(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 &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 &result) { + finish(result.v); + }).fail([=] { + finish({}); + }).send(); +} + PremiumGiftCodeOptions::PremiumGiftCodeOptions(not_null peer) : _peer(peer) , _api(&peer->session().api().instance()) { @@ -494,4 +564,49 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const { false); } +RequirePremiumState ResolveRequiresPremiumToWrite( + not_null 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 diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 1199b48194..e1c3a7b412 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_subscription_option.h" #include "mtproto/sender.h" +class History; class ApiWrap; namespace Main { @@ -103,10 +104,14 @@ public: [[nodiscard]] auto subscriptionOptions() const -> const Data::SubscriptionOptions &; + [[nodiscard]] rpl::producer<> somePremiumRequiredResolved() const; + void resolvePremiumRequired(not_null user); + private: void reloadPromo(); void reloadStickers(); void reloadCloudSet(); + void requestPremiumRequiredSlice(); const not_null _session; MTP::Sender _api; @@ -143,6 +148,11 @@ private: Data::SubscriptionOptions _subscriptionOptions; + rpl::event_stream<> _somePremiumRequiredResolved; + base::flat_set> _resolvePremiumRequiredUsers; + base::flat_set> _resolvePremiumRequestedUsers; + bool _premiumRequiredRequestScheduled = false; + }; class PremiumGiftCodeOptions final { @@ -196,4 +206,13 @@ private: }; +enum class RequirePremiumState { + Unknown, + Yes, + No, +}; +[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite( + not_null peer, + History *maybeHistory); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index cb8232561b..d6417e048d 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -174,14 +174,13 @@ bool SendProgressManager::skipRequest(const Key &key) const { return true; } const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor; - const auto online = user->onlineTill; - if (online == -2) { // last seen recently + const auto lastseen = user->lastseen(); + if (lastseen.isRecently()) { return false; - } else if (online < 0) { - return (-online < recently); - } else { - return (online < recently); + } else if (const auto value = lastseen.onlineTill()) { + return (value < recently); } + return true; } void SendProgressManager::done(mtpRequestId requestId) { diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index c862cc9ee1..b88fdf58e6 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -958,7 +958,9 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { } 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( self, Data::PeerUpdate::Flag::OnlineStatus); @@ -1243,8 +1245,8 @@ void Updates::applyUpdateNoPtsCheck(const MTPUpdate &update) { if (user && !requestingDifference()) { user->madeAction(base::unixtime::now()); } - ClearMediaAsExpired(item); } + ClearMediaAsExpired(item); } } else { // Perhaps it was an unread mention! @@ -1882,23 +1884,13 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserStatus: { auto &d = update.c_updateUserStatus(); - if (auto user = session().data().userLoaded(d.vuser_id())) { - switch (d.vstatus().type()) { - case mtpc_userStatusEmpty: user->onlineTill = 0; break; - case mtpc_userStatusRecently: - if (user->onlineTill > -10) { // don't modify pseudo-online - user->onlineTill = -2; - } - 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; + if (const auto user = session().data().userLoaded(d.vuser_id())) { + const auto now = LastseenFromMTP(d.vstatus(), user->lastseen()); + if (user->updateLastseen(now)) { + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); } - session().changes().peerUpdated( - user, - Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } if (UserId(d.vuser_id()) == session().userId()) { if (d.vstatus().type() == mtpc_userStatusOffline diff --git a/Telegram/SourceFiles/api/api_who_reacted.cpp b/Telegram/SourceFiles/api/api_who_reacted.cpp index f32358c060..27134dea98 100644 --- a/Telegram/SourceFiles/api/api_who_reacted.cpp +++ b/Telegram/SourceFiles/api/api_who_reacted.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "api/api_who_reacted.h" +#include "api/api_global_privacy.h" #include "history/history_item.h" #include "history/history.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_media_types.h" #include "data/data_message_reaction_id.h" +#include "data/data_peer_values.h" #include "lang/lang_keys.h" #include "main/main_app_config.h" #include "main/main_session.h" @@ -36,10 +38,11 @@ namespace { constexpr auto kContextReactionsLimit = 50; using Data::ReactionId; +using WhoReadState = Ui::WhoReadState; struct Peers { std::vector list; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const Peers &a, @@ -59,7 +62,7 @@ struct PeersWithReactions { std::vector list; std::vector read; int fullReactionsCount = 0; - bool unknown = false; + WhoReadState state = WhoReadState::Empty; friend inline bool operator==( const PeersWithReactions &a, @@ -68,7 +71,7 @@ struct PeersWithReactions { struct CachedRead { CachedRead() - : data(Peers{ .unknown = true }) { + : data(Peers{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -76,7 +79,7 @@ struct CachedRead { struct CachedReacted { CachedReacted() - : data(PeersWithReactions{ .unknown = true }) { + : data(PeersWithReactions{ .state = WhoReadState::Unknown }) { } rpl::variable data; mtpRequestId requestId = 0; @@ -186,6 +189,27 @@ struct State { context->cachedReacted.erase(j); } }, 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; } @@ -222,7 +246,38 @@ struct State { } const auto context = PreparedContextAt(weak.data(), session); 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( MTPmessages_GetMessageReadParticipants( item->history()->peer->input, @@ -243,8 +298,8 @@ struct State { }).fail([=] { auto &entry = context->cacheRead(item); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = Peers(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = Peers{ .state = WhoReadState::Empty }; } }).send(); } @@ -258,7 +313,7 @@ struct State { .list = peers.list | ranges::views::transform([](WhoReadPeer peer) { return PeerWithReaction{ .peerWithDate = peer }; }) | ranges::to_vector, - .unknown = peers.unknown, + .state = peers.state, }; result.read = std::move(peers.list); return result; @@ -319,8 +374,10 @@ struct State { }).fail([=] { auto &entry = context->cacheReacted(item, reaction); entry.requestId = 0; - if (entry.data.current().unknown) { - entry.data = PeersWithReactions(); + if (entry.data.current().state == WhoReadState::Unknown) { + entry.data = PeersWithReactions{ + .state = WhoReadState::Empty, + }; } }).send(); } @@ -336,8 +393,9 @@ struct State { WhoReactedIds(item, {}, context), WhoReadIds(item, context) ) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) { - if (reacted.unknown || read.unknown) { - return PeersWithReactions{ .unknown = true }; + if (reacted.state == WhoReadState::Unknown + || read.state == WhoReadState::Unknown) { + return PeersWithReactions{ .state = WhoReadState::Unknown}; } auto &list = reacted.list; for (const auto &peerWithDate : read.list) { @@ -531,16 +589,17 @@ rpl::producer WhoReacted( std::move( idsWithReactions ) | rpl::start_with_next([=](PeersWithReactions &&peers) { - if (peers.unknown) { + if (peers.state == WhoReadState::Unknown) { state->userpics.clear(); consumer.put_next(Ui::WhoReadContent{ .type = state->current.type, .fullReactionsCount = state->current.fullReactionsCount, .fullReadCount = state->current.fullReadCount, - .unknown = true, + .state = WhoReadState::Unknown, }); return; } + state->current.state = peers.state; state->current.fullReadCount = int(peers.read.size()); state->current.fullReactionsCount = peers.fullReactionsCount; if (whoReadIds) { @@ -631,6 +690,22 @@ bool WhoReadExists(not_null item) { } const auto history = item->history(); 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( + "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 megagroup = peer->asMegagroup(); if ((!chat && !megagroup) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index fb74e03f6b..512cc345e2 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1953,28 +1953,28 @@ void ApiWrap::saveDraftToCloudDelayed(not_null thread) { void ApiWrap::updatePrivacyLastSeens() { const auto now = base::unixtime::now(); - _session->data().enumerateUsers([&](UserData *user) { - if (user->isSelf() || !user->isLoaded()) { - return; - } - if (user->onlineTill <= 0) { - return; - } + if (!_session->premium()) { + _session->data().enumerateUsers([&](not_null user) { + if (user->isSelf() + || !user->isLoaded() + || user->lastseen().isHidden()) { + return; + } - if (user->onlineTill + 3 * 86400 >= now) { - user->onlineTill = -2; // recently - } else if (user->onlineTill + 7 * 86400 >= now) { - user->onlineTill = -3; // last week - } else if (user->onlineTill + 30 * 86400 >= now) { - user->onlineTill = -4; // last month - } else { - user->onlineTill = 0; - } - session().changes().peerUpdated( - user, - Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); - }); + const auto till = user->lastseen().onlineTill(); + user->updateLastseen((till + 3 * 86400 >= now) + ? Data::LastseenStatus::Recently(true) + : (till + 7 * 86400 >= now) + ? Data::LastseenStatus::WithinWeek(true) + : (till + 30 * 86400 >= now) + ? Data::LastseenStatus::WithinMonth(true) + : Data::LastseenStatus::LongAgo(true)); + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); + }); + } if (_contactsStatusesRequestId) { request(_contactsStatusesRequestId).cancel(); @@ -1982,20 +1982,17 @@ void ApiWrap::updatePrivacyLastSeens() { _contactsStatusesRequestId = request(MTPcontacts_GetStatuses( )).done([=](const MTPVector &result) { _contactsStatusesRequestId = 0; - for (const auto &item : result.v) { - Assert(item.type() == mtpc_contactStatus); - auto &data = item.c_contactStatus(); - if (auto user = _session->data().userLoaded(data.vuser_id())) { - auto oldOnlineTill = user->onlineTill; - auto newOnlineTill = OnlineTillFromStatus( + for (const auto &status : result.v) { + const auto &data = status.data(); + const auto userId = UserId(data.vuser_id()); + if (const auto user = _session->data().userLoaded(userId)) { + const auto status = LastseenFromMTP( data.vstatus(), - oldOnlineTill); - if (oldOnlineTill != newOnlineTill) { - user->onlineTill = newOnlineTill; + user->lastseen()); + if (user->updateLastseen(status)) { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } } } @@ -2004,22 +2001,6 @@ void ApiWrap::updatePrivacyLastSeens() { }).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 peer, bool revoke) { deleteHistory(peer, true, revoke); } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7e44d460c3..192e14f28a 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -258,10 +258,6 @@ public: void updateNotifySettingsDelayed(Data::DefaultNotify type); void saveDraftToCloudDelayed(not_null thread); - static int OnlineTillFromStatus( - const MTPUserStatus &status, - int currentOnlineTill); - void clearHistory(not_null peer, bool revoke); void deleteConversation(not_null peer, bool revoke); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 8fd1f5c2b0..9819c855cb 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -12,12 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/random.h" #include "ui/boxes/confirm_box.h" #include "boxes/abstract_box.h" -#include "boxes/peer_list_controllers.h" #include "boxes/premium_limits_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/edit_peer_common.h" #include "boxes/peers/edit_participant_box.h" -#include "boxes/peers/edit_participants_box.h" #include "core/application.h" #include "core/core_settings.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 "ui/controls/userpic_button.h" #include "ui/widgets/checkbox.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/special_fields.h" #include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" -#include "ui/text/text_options.h" #include "ui/text/text_utilities.h" -#include "ui/unread_badge.h" -#include "ui/ui_utility.h" #include "ui/painter.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_changes.h" -#include "data/data_cloud_file.h" #include "apiwrap.h" #include "api/api_invite_links.h" #include "api/api_peer_photo.h" +#include "api/api_self_destruct.h" #include "main/main_session.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" -#include "styles/style_boxes.h" -#include "styles/style_dialogs.h" -#include "styles/style_widgets.h" #include #include @@ -599,6 +589,8 @@ void GroupInfoBox::prepare() { addButton(tr::lng_cancel(), [this] { closeBox(); }); if (_type == Type::Group) { + _navigation->session().api().selfDestruct().reload(); + const auto top = addTopButton(st::infoTopBarMenu); const auto menu = top->lifetime().make_state>(); @@ -607,21 +599,21 @@ void GroupInfoBox::prepare() { top, st::popupMenuWithIcons); + const auto ttl = ttlPeriod(); const auto text = tr::lng_manage_messages_ttl_menu(tr::now) - + (_ttlPeriod - ? ('\t' + Ui::FormatTTLTiny(_ttlPeriod)) - : QString()); + + (ttl ? ('\t' + Ui::FormatTTLTiny(ttl)) : QString()); (*menu)->addAction( text, [=, show = uiShow()] { show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{ .show = show, - .startTtl = _ttlPeriod, + .startTtl = ttlPeriod(), .about = nullptr, .callback = crl::guard(this, [=]( TimeId t, Fn close) { _ttlPeriod = t; + _ttlPeriodOverridden = true; close(); }), })); @@ -687,6 +679,13 @@ void GroupInfoBox::submitName() { } } +TimeId GroupInfoBox::ttlPeriod() const { + return _ttlPeriodOverridden + ? _ttlPeriod + : _navigation->session().api().selfDestruct() + .periodDefaultHistoryTTLCurrent(); +} + void GroupInfoBox::createGroup( QPointer selectUsersBox, const QString &title, @@ -705,15 +704,13 @@ void GroupInfoBox::createGroup( } } _creationRequestId = _api.request(MTPmessages_CreateChat( - MTP_flags(_ttlPeriod - ? MTPmessages_CreateChat::Flag::f_ttl_period - : MTPmessages_CreateChat::Flags(0)), + MTP_flags(MTPmessages_CreateChat::Flag::f_ttl_period), MTP_vector(inputs), MTP_string(title), - MTP_int(_ttlPeriod) + MTP_int(ttlPeriod()) )).done([=](const MTPUpdates &result) { auto image = _photo->takeResultImage(); - const auto period = _ttlPeriod; + const auto period = ttlPeriod(); const auto navigation = _navigation; const auto done = _done; @@ -799,16 +796,17 @@ void GroupInfoBox::createChannel( ? Flag::f_megagroup : Flag::f_broadcast) | ((_type == Type::Forum) ? Flag::f_forum : Flag()) - | ((_type == Type::Megagroup && _ttlPeriod) + | ((_type == Type::Megagroup) ? MTPchannels_CreateChannel::Flag::f_ttl_period : MTPchannels_CreateChannel::Flags(0)); + const auto ttl = ttlPeriod(); _creationRequestId = _api.request(MTPchannels_CreateChannel( MTP_flags(flags), MTP_string(title), MTP_string(description), MTPInputGeoPoint(), // geo_point MTPstring(), // address - MTP_int((_type == Type::Megagroup) ? _ttlPeriod : 0) + MTP_int((_type == Type::Megagroup) ? ttl : 0) )).done([=](const MTPUpdates &result) { _navigation->session().api().applyUpdates(result); @@ -841,8 +839,8 @@ void GroupInfoBox::createChannel( channel, { std::move(image) }); } - if (_ttlPeriod && channel->isMegagroup()) { - channel->setMessagesTTL(_ttlPeriod); + if (ttl && channel->isMegagroup()) { + channel->setMessagesTTL(ttl); } channel->session().api().requestFullPeer(channel); _createdChannel = channel; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index ef70f7c3b5..6d19a2d7f7 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -132,6 +132,8 @@ private: void descriptionResized(); void updateMaxHeight(); + [[nodiscard]] TimeId ttlPeriod() const; + const not_null _navigation; MTP::Sender _api; @@ -150,6 +152,7 @@ private: bool _creatingInviteLink = false; ChannelData *_createdChannel = nullptr; TimeId _ttlPeriod = 0; + bool _ttlPeriodOverridden = false; }; diff --git a/Telegram/SourceFiles/boxes/delete_messages_box.cpp b/Telegram/SourceFiles/boxes/delete_messages_box.cpp index 99fe6b8835..0a939ace62 100644 --- a/Telegram/SourceFiles/boxes/delete_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/delete_messages_box.cpp @@ -221,7 +221,7 @@ void DeleteMessagesBox::prepare() { ? QString() : QString(" (%1)").arg(total)); }); - search->searchMessages(QString(), _moderateFrom); + search->searchMessages({ .from = _moderateFrom }); } } else { details.text = (_ids.size() == 1) diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp index 899c8be02a..f09b104484 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp @@ -7,15 +7,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #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/labels.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" #include "ui/text/text_utilities.h" +#include "ui/toast/toast.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "ui/painter.h" #include "ui/vertical_list.h" #include "history/history.h" #include "boxes/peer_list_controllers.h" +#include "settings/settings_common.h" +#include "settings/settings_premium.h" #include "settings/settings_privacy_security.h" #include "calls/calls_instance.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 "styles/style_settings.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" namespace { +namespace { + +void CreateRadiobuttonLock( + not_null widget, + const style::Checkbox &st) { + const auto lock = Ui::CreateChild(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 { public: @@ -340,7 +378,7 @@ void EditPrivacyBox::setupContent() { auto middle = _controller->setupMiddleWidget( _window, content, - std::move(optionValue)); + rpl::duplicate(optionValue)); if (middle) { content->add(std::move(middle)); } @@ -357,7 +395,11 @@ void EditPrivacyBox::setupContent() { _controller->exceptionsDescription() | Ui::Text::ToWithEntities(), 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)); } @@ -394,3 +436,119 @@ void EditPrivacyBox::setupContent() { setDimensions(st::boxWideWidth, height); }, content->lifetime()); } + +void EditMessagesPrivacyBox( + not_null box, + not_null 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(box)); + + Ui::AddSkip(inner, st::messagePrivacyTopSkip); + Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle()); + const auto group = std::make_shared( + privacy->newRequirePremiumCurrent() ? kOptionPremium : kOptionAll); + inner->add( + object_ptr( + inner, + group, + kOptionAll, + tr::lng_messages_privacy_everyone(tr::now), + st::messagePrivacyCheck), + st::settingsSendTypePadding); + const auto restricted = inner->add( + object_ptr( + 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; + const auto toast = std::make_shared(); + 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(); + }); + } +} diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h index d9ba7dab96..c715ef4734 100644 --- a/Telegram/SourceFiles/boxes/edit_privacy_box.h +++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_user_privacy.h" namespace Ui { +class GenericBox; class VerticalLayout; class FlatLabel; class LinkButton; @@ -74,7 +75,8 @@ public: } [[nodiscard]] virtual object_ptr setupBelowWidget( not_null controller, - not_null parent) { + not_null parent, + rpl::producer