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 * * *'
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.

View file

@ -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

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_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 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_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";

View file

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

View file

@ -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"

View file

@ -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"

View file

@ -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<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) {
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();
});
}

View file

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

View file

@ -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*> 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<void()> 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<MTPReaction>(), // 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

View file

@ -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<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);
~MessagesSearch();
void searchMessages(const QString &query, PeerData *from);
void searchMessages(Request request);
void searchMore();
[[nodiscard]] rpl::producer<FoundMessages> messagesFounds() const;
@ -41,8 +61,7 @@ private:
base::flat_map<QString, TLMessages> _cacheOfStartByToken;
QString _query;
PeerData *_from = nullptr;
Request _request;
MsgId _offsetId;
int _searchInHistoryRequest = 0; // Not real mtpRequestId.

View file

@ -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*> 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() {

View file

@ -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<Request, RequestCompare>;
using Request = MessagesSearch::Request;
using CachedRequests = base::flat_set<Request>;
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_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<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)
: _peer(peer)
, _api(&peer->session().api().instance()) {
@ -494,4 +564,49 @@ bool PremiumGiftCodeOptions::giveawayGiftsPurchaseAvailable() const {
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

View file

@ -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<UserData*> user);
private:
void reloadPromo();
void reloadStickers();
void reloadCloudSet();
void requestPremiumRequiredSlice();
const not_null<Main::Session*> _session;
MTP::Sender _api;
@ -143,6 +148,11 @@ private:
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 {
@ -196,4 +206,13 @@ private:
};
enum class RequirePremiumState {
Unknown,
Yes,
No,
};
[[nodiscard]] RequirePremiumState ResolveRequiresPremiumToWrite(
not_null<PeerData*> peer,
History *maybeHistory);
} // namespace Api

View file

@ -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) {

View file

@ -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

View file

@ -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<WhoReadPeer> list;
bool unknown = false;
WhoReadState state = WhoReadState::Empty;
friend inline bool operator==(
const Peers &a,
@ -59,7 +62,7 @@ struct PeersWithReactions {
std::vector<PeerWithReaction> list;
std::vector<WhoReadPeer> 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<Peers> data;
mtpRequestId requestId = 0;
@ -76,7 +79,7 @@ struct CachedRead {
struct CachedReacted {
CachedReacted()
: data(PeersWithReactions{ .unknown = true }) {
: data(PeersWithReactions{ .state = WhoReadState::Unknown }) {
}
rpl::variable<PeersWithReactions> 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<Ui::WhoReadContent> 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<HistoryItem*> 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<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 megagroup = peer->asMegagroup();
if ((!chat && !megagroup)

View file

@ -1953,28 +1953,28 @@ void ApiWrap::saveDraftToCloudDelayed(not_null<Data::Thread*> 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<UserData*> 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<MTPContactStatus> &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<PeerData*> peer, bool revoke) {
deleteHistory(peer, true, revoke);
}

View file

@ -258,10 +258,6 @@ public:
void updateNotifySettingsDelayed(Data::DefaultNotify type);
void saveDraftToCloudDelayed(not_null<Data::Thread*> thread);
static int OnlineTillFromStatus(
const MTPUserStatus &status,
int currentOnlineTill);
void clearHistory(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 "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 <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
@ -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<base::unique_qptr<Ui::PopupMenu>>();
@ -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<void()> 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<Ui::BoxContent> 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<TLUsers>(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;

View file

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

View file

@ -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)

View file

@ -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<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 {
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<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"
namespace Ui {
class GenericBox;
class VerticalLayout;
class FlatLabel;
class LinkButton;
@ -74,7 +75,8 @@ public:
}
[[nodiscard]] virtual object_ptr<Ui::RpWidget> setupBelowWidget(
not_null<Window::SessionController*> controller,
not_null<QWidget*> parent) {
not_null<QWidget*> parent,
rpl::producer<Option> option) {
return { nullptr };
}
@ -146,3 +148,7 @@ private:
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;
}
[[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 TagUser2 = lngtag_second_user;
using TagUser3 = lngtag_name;
@ -293,16 +318,21 @@ void GiftBox(
std::move(titleLabel)),
st::premiumGiftTitlePadding);
auto textLabel = object_ptr<Ui::FlatLabel>(
box,
tr::lng_premium_gift_about(
lt_user,
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; }),
Ui::Text::RichLangValue),
st::premiumPreviewAbout);
auto textLabel = object_ptr<Ui::FlatLabel>(box, st::premiumPreviewAbout);
tr::lng_premium_gift_about(
lt_user,
user->session().changes().peerFlagsValue(
user,
Data::PeerUpdate::Flag::Name
) | rpl::map([=] { return TextWithEntities{ user->firstName }; }),
Ui::Text::RichLangValue
) | rpl::map(
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->resizeToWidth(available);
box->addRow(
@ -476,11 +506,6 @@ void GiftsBox(
// About.
{
const auto emoji = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::premiumGiftsBoostIcon,
QMargins(0, st::premiumGiftsUserpicBadgeInner, 0, 0),
false));
auto text = rpl::conditional(
state->isPaymentComplete.value(),
ComplexAboutLabel(
@ -505,18 +530,7 @@ void GiftsBox(
tr::lng_premium_gifts_about_user2,
tr::lng_premium_gifts_about_user3,
tr::lng_premium_gifts_about_user_more
) | rpl::map([=, 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;
})
) | rpl::map(BoostsForGiftText(users))
);
const auto label = box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(

View file

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

View file

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

View file

@ -8,10 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h"
#include "api/api_chat_participants.h"
#include "api/api_premium.h"
#include "base/random.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "settings/settings_premium.h"
#include "ui/boxes/confirm_box.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/checkbox.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/ui_utility.h"
#include "main/main_session.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_stories.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_profile.h"
#include "styles/style_dialogs.h"
#include "data/data_stories.h"
#include "dialogs/ui/dialogs_stories_content.h"
#include "dialogs/ui/dialogs_stories_list.h"
#include "styles/style_chat_helpers.h"
namespace {
@ -257,9 +258,88 @@ bool PeerListGlobalSearchController::isLoading() {
return _timer.isActive() || _requestId;
}
ChatsListBoxController::Row::Row(not_null<History*> history)
: PeerListRow(history->peer)
, _history(history) {
RecipientRow::RecipientRow(
not_null<PeerData*> peer,
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(
@ -420,7 +500,7 @@ void PeerListStories::process(not_null<PeerListRow*> row) {
bool PeerListStories::handleClick(not_null<PeerData*> peer) {
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 (const auto window = peer->session().tryResolveWindow()) {
if (const auto user = peer->asUser()) {
@ -437,9 +517,9 @@ bool PeerListStories::handleClick(not_null<PeerData*> peer) {
void PeerListStories::prepare(not_null<PeerListDelegate*> delegate) {
_delegate = delegate;
_unreadBrush = PeerListStoriesGradient(*_controller->listSt());
_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
style::PaletteChanged() | rpl::start_with_next([=] {
_unreadBrush = PeerListStoriesGradient(*_controller->listSt());
_unreadBrush = PeerListStoriesGradient(_controller->computeListSt());
updateColors();
}, _lifetime);
@ -598,7 +678,9 @@ void ContactsBoxController::sortByOnline() {
const auto now = base::unixtime::now();
const auto key = [&](const PeerListRow &row) {
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) {
return key(a) > key(b);
@ -627,14 +709,40 @@ std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
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(
not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter)
: ChatsListBoxController(session)
, _session(session)
, _callback(std::move(callback))
, _filter(std::move(filter)) {
: ChooseRecipientBoxController({
.session = session,
.callback = std::move(callback),
.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 {
@ -643,9 +751,21 @@ Main::Session &ChooseRecipientBoxController::session() const {
void ChooseRecipientBoxController::prepareViewHook() {
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) {
if (showLockedError(row)) {
return;
}
auto guard = base::make_weak(this);
const auto peer = row->peer();
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 {
return tr::lng_saved_forward_here(tr::now);
}
@ -706,8 +841,17 @@ auto ChooseRecipientBoxController::createRow(
const auto skip = _filter
? !_filter(history)
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|| (peer->isUser() && !Data::CanSendAnything(peer)));
return skip ? nullptr : std::make_unique<Row>(history);
|| peer->isRepliesChat()
|| (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(
@ -925,3 +1069,26 @@ auto ChooseTopicBoxController::createRow(not_null<Data::ForumTopic*> topic)
const auto skip = _filter && !_filter(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;
namespace style {
struct PeerListItem;
} // namespace style
namespace Data {
class Thread;
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 {
public:
class Row : public PeerListRow {
class Row : public RecipientRow {
public:
Row(not_null<History*> history);
Row(
not_null<History*> history,
const style::PeerListItem *maybeLockedSt = nullptr);
not_null<History*> history() const {
return _history;
[[nodiscard]] not_null<History*> history() const {
return maybeHistory();
}
private:
not_null<History*> _history;
};
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
: public ChatsListBoxController
, public base::has_weak_ptr {
@ -215,6 +273,7 @@ public:
not_null<Main::Session*> session,
FnMut<void(not_null<Data::Thread*>)> callback,
Fn<bool(not_null<Data::Thread*>)> filter = nullptr);
explicit ChooseRecipientBoxController(ChooseRecipientArgs &&args);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@ -225,10 +284,14 @@ protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
bool showLockedError(not_null<PeerListRow*> row);
private:
const not_null<Main::Session*> _session;
FnMut<void(not_null<Data::Thread*>)> _callback;
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;
};
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) {
auto p = QPainter(this);
const auto &bg = (firstController()->listSt()
? *firstController()->listSt()
: st::peerListBox).bg;
const auto &bg = firstController()->computeListSt().bg;
for (const auto &rect : e->region()) {
p.fillRect(rect, bg);
}

View file

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

View file

@ -143,7 +143,7 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) {
? tr::lng_status_bot_reads_all
: 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.setPen(st::contactsStatusFg);

View file

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

View file

@ -585,9 +585,8 @@ void Controller::checkUsernameAvailability() {
showUsernameError(tr::lng_create_channel_link_invalid());
} else if (type == u"USERNAME_PURCHASE_AVAILABLE"_q) {
_goodUsername = false;
_usernameCheckInfo.fire({
.type = UsernameCheckInfo::Type::PurchaseAvailable,
});
_usernameCheckInfo.fire(
UsernameCheckInfo::PurchaseAvailable(checking, _peer));
} else if (type == u"USERNAME_OCCUPIED"_q && checking != username) {
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();
case PremiumPreview::InfiniteReactions:
return tr::lng_premium_summary_subtitle_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_subtitle_tags_for_messages();
case PremiumPreview::Stickers:
return tr::lng_premium_summary_subtitle_premium_stickers();
case PremiumPreview::AnimatedEmoji:
@ -146,6 +148,8 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
return tr::lng_premium_summary_about_emoji_status();
case PremiumPreview::InfiniteReactions:
return tr::lng_premium_summary_about_infinite_reactions();
case PremiumPreview::TagsForMessages:
return tr::lng_premium_summary_about_tags_for_messages();
case PremiumPreview::Stickers:
return tr::lng_premium_summary_about_premium_stickers();
case PremiumPreview::AnimatedEmoji:
@ -471,6 +475,7 @@ struct VideoPreviewDocument {
return "advanced_chat_management";
case PremiumPreview::EmojiStatus: return "emoji_status";
case PremiumPreview::InfiniteReactions: return "infinite_reactions";
case PremiumPreview::TagsForMessages: return "saved_tags";
case PremiumPreview::ProfileBadge: return "profile_badge";
case PremiumPreview::AnimatedUserpics: return "animated_userpics";
case PremiumPreview::RealTimeTranslation: return "translations";

View file

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

View file

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

View file

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

View file

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

View file

@ -33,17 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
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 {
public:
UsernameEditor(not_null<Ui::RpWidget*>, not_null<PeerData*> peer);
@ -268,9 +257,8 @@ void UsernameEditor::checkInfoPurchaseAvailable() {
_username->showError();
_errorText = u".bad."_q;
_checkInfoChanged.fire({
.type = UsernameCheckInfo::Type::PurchaseAvailable,
});
_checkInfoChanged.fire(
UsernameCheckInfo::PurchaseAvailable(_checkUsername, _peer));
}
void UsernameEditor::updateFail(const QString &error) {
@ -424,9 +412,7 @@ void AddUsernameCheckLabel(
container->widthValue()
) | rpl::start_with_next([=](const UsernameCheckInfo &info, int w) {
using Type = UsernameCheckInfo::Type;
label->setMarkedText((info.type == Type::PurchaseAvailable)
? PurchaseAvailableText()
: info.text);
label->setMarkedText(info.text);
const auto &color = (info.type == Type::Good)
? st::boxTextFgGood
: (info.type == Type::Error)
@ -437,3 +423,25 @@ void AddUsernameCheckLabel(
}, label->lifetime());
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);
struct UsernameCheckInfo final {
[[nodiscard]] static UsernameCheckInfo PurchaseAvailable(
const QString &username,
not_null<PeerData*> peer);
enum class Type {
Good,
Error,
Default,
PurchaseAvailable,
};
Type type;
TextWithEntities text;

View file

@ -25,8 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio_track.h"
#include "base/platform/base_platform_info.h"
#include "calls/calls_panel.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.h"
#include "data/data_user.h"
#include "data/data_session.h"
@ -215,6 +215,22 @@ Call::Call(
, _api(&_user->session().mtp())
, _type(type)
, _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(
std::make_unique<Webrtc::VideoTrack>(
StartVideoState(video)))
@ -228,6 +244,7 @@ Call::Call(
_discardByTimeoutTimer.callOnce(config.callRingTimeoutMs);
startWaitingTrack();
}
setupMediaDevices();
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() {
static const auto hasDevices = [] {
return !Webrtc::GetVideoInputList().empty();
const auto cameraId = [] {
return Core::App().mediaDevices().defaultId(
Webrtc::DeviceType::Camera);
};
const auto started = _videoOutgoing->state();
if (!hasDevices()) {
if (cameraId().isEmpty()) {
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
}
_videoOutgoing->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
if (state != Webrtc::VideoState::Inactive
&& !hasDevices()
&& cameraId().isEmpty()
&& !_videoCaptureIsScreencast) {
_errors.fire({ ErrorType::NoCamera });
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
@ -455,6 +493,20 @@ void Call::setupOutgoingVideo() {
}
}
}, _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 {
@ -848,6 +900,34 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
const auto versionString = version.toStdString();
const auto &settings = Core::App().settings();
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 = {
.version = versionString,
.config = tgcalls::Config{
@ -866,8 +946,8 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
std::move(encryptionKeyValue),
(_type == Type::Outgoing)),
.mediaDevicesConfig = tgcalls::MediaDevicesConfig{
.audioInputId = settings.callInputDeviceId().toStdString(),
.audioOutputId = settings.callOutputDeviceId().toStdString(),
.audioInputId = captureDeviceIdInitial.value.toStdString(),
.audioOutputId = playbackDeviceIdInitial.value.toStdString(),
.inputVolume = 1.f,//settings.callInputVolume() / 100.f,
.outputVolume = 1.f,//settings.callOutputVolume() / 100.f,
},
@ -898,7 +978,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
});
},
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
saveSetDeviceIdCallback),
};
if (Logs::DebugEnabled()) {
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) {
// if (_instance) {
// if (input) {
@ -1168,10 +1225,11 @@ void Call::toggleCameraSharing(bool enabled) {
}
_delegate->callRequestPermissionsOrFail(crl::guard(this, [=] {
toggleScreenSharing(std::nullopt);
const auto deviceId = Core::App().settings().callVideoInputDeviceId();
_videoCaptureDeviceId = deviceId;
_videoCaptureDeviceId = _cameraDeviceId.current().value;
if (_videoCapture) {
_videoCapture->switchToDevice(deviceId.toStdString(), false);
_videoCapture->switchToDevice(
_videoCaptureDeviceId.toStdString(),
false);
if (_instance) {
_instance->sendVideoDeviceUpdated();
}

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/bytes.h"
#include "mtproto/sender.h"
#include "mtproto/mtproto_auth_key.h"
#include "webrtc/webrtc_device_resolver.h"
namespace Media {
namespace Audio {
@ -190,11 +191,9 @@ public:
QString getDebugLog() const;
void setCurrentAudioDevice(bool input, const QString &deviceId);
//void setAudioVolume(bool input, float level);
void setAudioDuckingEnabled(bool enabled);
void setCurrentCameraDevice(const QString &deviceId);
[[nodiscard]] QString videoDeviceId() const {
return _videoCaptureDeviceId;
}
@ -250,6 +249,7 @@ private:
void setSignalBarCount(int count);
void destroyController();
void setupMediaDevices();
void setupOutgoingVideo();
void updateRemoteMediaState(
tgcalls::AudioState audio,
@ -271,6 +271,11 @@ private:
base::DelayedCallTimer _finishByTimeoutTimer;
base::Timer _discardByTimeoutTimer;
Fn<void(Webrtc::DeviceResolvedId)> _setDeviceIdCallback;
Webrtc::DeviceResolver _playbackDeviceId;
Webrtc::DeviceResolver _captureDeviceId;
Webrtc::DeviceResolver _cameraDeviceId;
rpl::variable<bool> _muted = false;
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() {
auto semaphore = std::make_unique<crl::semaphore>();
const auto raw = semaphore.get();
@ -846,7 +832,7 @@ std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
if (deviceId) {
result->switchToDevice(
(deviceId->isEmpty()
? Core::App().settings().callVideoInputDeviceId()
? Core::App().settings().cameraDeviceId()
: *deviceId).toStdString(),
isScreenCapture);
}
@ -854,7 +840,7 @@ std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
}
const auto startDeviceId = (deviceId && !deviceId->isEmpty())
? *deviceId
: Core::App().settings().callVideoInputDeviceId();
: Core::App().settings().cameraDeviceId();
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
tgcalls::VideoCaptureInterface::Create(
tgcalls::StaticThreads::getThreads(),

View file

@ -103,8 +103,6 @@ public:
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
void setCurrentAudioDevice(bool input, const QString &deviceId);
[[nodiscard]] FnMut<void()> addAsyncWaiter();
[[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 "media/streaming/media_streaming_utility.h"
#include "window/main_window.h"
#include "webrtc/webrtc_environment.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "styles/style_calls.h"
#include "styles/style_chat.h"
@ -238,13 +238,14 @@ void Panel::initControls() {
}
});
_screencast->entity()->setClickedCallback([=] {
const auto env = &Core::App().mediaDevices();
if (!_call) {
return;
} else if (!Webrtc::DesktopCaptureAllowed()) {
} else if (!env->desktopCaptureAllowed()) {
if (auto box = Group::ScreenSharingPrivacyRequestBox()) {
_layerBg->showBox(std::move(box));
}
} else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) {
} else if (const auto source = env->uniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) {
_call->toggleScreenSharing(std::nullopt);
} else {

View file

@ -29,7 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/global_shortcuts.h"
#include "base/random.h"
#include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_media_devices.h"
#include "webrtc/webrtc_create_adm.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 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(
not_null<PeerData*> peer,
CallId id,
@ -590,12 +581,27 @@ GroupCall::GroupCall(
, _scheduleDate(info.scheduleDate)
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
, _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(); })
, _connectingSoundTimer([=] { playConnectingSoundOnce(); })
, _listenersHidden(info.rtmp)
, _rtmp(info.rtmp)
, _rtmpVolume(Group::kDefaultVolume)
, _mediaDevices(CreateMediaDevices()) {
, _rtmpVolume(Group::kDefaultVolume) {
_muted.value(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState previous, MuteState state) {
@ -2058,28 +2064,28 @@ void GroupCall::applyOtherParticipantUpdate(
}
void GroupCall::setupMediaDevices() {
_mediaDevices->audioInputId(
) | rpl::start_with_next([=](QString id) {
_audioInputId = id;
if (_instance) {
_instance->setAudioInputDevice(id.toStdString());
}
_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);
_mediaDevices->audioOutputId(
) | rpl::start_with_next([=](QString id) {
_audioOutputId = id;
if (_instance) {
_instance->setAudioOutputDevice(id.toStdString());
}
_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);
_mediaDevices->videoInputId(
) | rpl::start_with_next([=](QString id) {
_cameraInputId = id;
if (_cameraCapture) {
_cameraCapture->switchToDevice(id.toStdString(), false);
}
_cameraDeviceId.changes() | rpl::filter([=] {
return _cameraCapture != nullptr;
}) | rpl::start_with_next([=](const Webrtc::DeviceResolvedId &deviceId) {
_cameraCapture->switchToDevice(deviceId.value.toStdString(), false);
}, _lifetime);
}
@ -2117,7 +2123,7 @@ bool GroupCall::emitShareCameraError() {
return emitError(Error::DisabledNoCamera);
} else if (mutedByAdmin()) {
return emitError(Error::MutedNoCamera);
} else if (Webrtc::GetVideoInputList().empty()) {
} else if (_cameraDeviceId.current().value.isEmpty()) {
return emitError(Error::NoCamera);
}
return false;
@ -2126,7 +2132,7 @@ bool GroupCall::emitShareCameraError() {
void GroupCall::emitShareCameraError(Error error) {
_cameraState = Webrtc::VideoState::Inactive;
if (error == Error::CameraFailed
&& Webrtc::GetVideoInputList().empty()) {
&& _cameraDeviceId.current().value.isEmpty()) {
error = Error::NoCamera;
}
_errors.fire_copy(error);
@ -2180,7 +2186,7 @@ void GroupCall::setupOutgoingVideo() {
return;
} else if (!_cameraCapture) {
_cameraCapture = _delegate->groupCallGetVideoCapture(
_cameraInputId);
_cameraDeviceId.current().value);
if (!_cameraCapture) {
return emitShareCameraError(Error::CameraFailed);
}
@ -2192,7 +2198,7 @@ void GroupCall::setupOutgoingVideo() {
});
} else {
_cameraCapture->switchToDevice(
_cameraInputId.toStdString(),
_cameraDeviceId.current().value.toStdString(),
false);
}
if (_instance) {
@ -2338,6 +2344,32 @@ bool GroupCall::tryCreateController() {
const auto weak = base::make_weak(&_instanceGuard);
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 = {
.threads = tgcalls::StaticThreads::getThreads(),
.config = tgcalls::GroupConfig{
@ -2360,10 +2392,10 @@ bool GroupCall::tryCreateController() {
}
crl::on_main(weak, [=] { audioLevelsUpdated(data); });
},
.initialInputDeviceId = _audioInputId.toStdString(),
.initialOutputDeviceId = _audioOutputId.toStdString(),
.initialInputDeviceId = captureDeviceIdInitial.value.toStdString(),
.initialOutputDeviceId = playbackDeviceIdInitial.value.toStdString(),
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
settings.callAudioBackend()),
saveSetDeviceIdCallback),
.videoCapture = _cameraCapture,
.requestCurrentTime = [=, call = base::make_weak(this)](
std::function<void(int64_t)> done) {
@ -3290,14 +3322,6 @@ void GroupCall::requestVideoQuality(
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) {
if (_rtmp) {
_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 "mtproto/sender.h"
#include "mtproto/mtproto_auth_key.h"
#include "webrtc/webrtc_device_resolver.h"
class History;
@ -381,7 +382,6 @@ public:
return _videoIsWorking.value();
}
void setCurrentAudioDevice(bool input, const QString &deviceId);
[[nodiscard]] bool isSharingScreen() const;
[[nodiscard]] rpl::producer<bool> isSharingScreenValue() const;
[[nodiscard]] bool isScreenPaused() const;
@ -667,6 +667,11 @@ private:
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<GlobalShortcutValue> _pushToTalk;
base::Timer _pushToTalkCancelTimer;
@ -677,11 +682,6 @@ private:
bool _reloadedStaleCall = false;
int _rtmpVolume = 0;
std::unique_ptr<Webrtc::MediaDevices> _mediaDevices;
QString _audioInputId;
QString _audioOutputId;
QString _cameraInputId;
rpl::lifetime _lifetime;
};

View file

@ -54,8 +54,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/power_save_blocker.h"
#include "apiwrap.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_media_devices.h" // UniqueDesktopCaptureSource.
#include "webrtc/webrtc_audio_input_tester.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h"
@ -1374,9 +1374,10 @@ void Panel::chooseShareScreenSource() {
return;
}
const auto choose = [=] {
if (!Webrtc::DesktopCaptureAllowed()) {
const auto env = &Core::App().mediaDevices();
if (!env->desktopCaptureAllowed()) {
screenSharingPrivacyRequest();
} else if (const auto source = Webrtc::UniqueDesktopCaptureSource()) {
} else if (const auto source = env->uniqueDesktopCaptureSource()) {
if (_call->isSharingScreen()) {
_call->toggleScreenSharing(std::nullopt);
} else {
@ -2003,7 +2004,8 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
}
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
|| _call->state() == State::Connecting);
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_chat.h"
#include "data/data_group_call.h"
#include "data/data_user.h"
#include "calls/group/calls_group_rtmp.h"
#include "ui/toast/toast.h"
#include "data/data_changes.h"
#include "core/application.h"
#include "core/core_settings.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 "main/main_session.h"
#include "apiwrap.h"
@ -191,6 +192,11 @@ object_ptr<ShareBox> ShareInviteLinkBox(
show->showToast(tr::lng_share_done(tr::now));
};
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);
};
@ -227,6 +233,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
.st = &st::groupCallShareBoxList,
.stLabel = &st::groupCallField,
.scheduleBoxStyle = scheduleStyle(),
.premiumRequiredError = SharePremiumRequiredError(),
});
*box = result.data();
return result;
@ -243,8 +250,7 @@ void SettingsBox(
const auto weakBox = Ui::MakeWeak(box);
struct State {
rpl::event_stream<QString> outputNameStream;
rpl::event_stream<QString> inputNameStream;
std::unique_ptr<Webrtc::DeviceResolver> deviceId;
std::unique_ptr<Webrtc::AudioInputTester> micTester;
Ui::LevelMeter *micTestLevel = nullptr;
float micLevel = 0.;
@ -288,42 +294,43 @@ void SettingsBox(
Ui::AddSkip(layout);
}
auto playbackIdWithFallback = Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callPlaybackDeviceIdValue(),
Core::App().settings().playbackDeviceIdValue());
AddButtonWithLabel(
layout,
tr::lng_group_call_speakers(),
rpl::single(
CurrentAudioOutputName()
) | rpl::then(
state->outputNameStream.events()
),
PlaybackDeviceNameValue(rpl::duplicate(playbackIdWithFallback)),
st::groupCallSettingsButton
)->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioOutputBox(crl::guard(box, [=](
const QString &id,
const QString &name) {
state->outputNameStream.fire_copy(name);
}), &st::groupCallCheckbox, &st::groupCallRadio));
box->getDelegate()->show(ChoosePlaybackDeviceBox(
rpl::duplicate(playbackIdWithFallback),
crl::guard(box, [=](const QString &id) {
Core::App().settings().setCallPlaybackDeviceId(id);
Core::App().saveSettingsDelayed();
}),
&st::groupCallCheckbox,
&st::groupCallRadio));
});
if (!rtmp) {
auto captureIdWithFallback = Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue());
AddButtonWithLabel(
layout,
tr::lng_group_call_microphone(),
rpl::single(
CurrentAudioInputName()
) | rpl::then(
state->inputNameStream.events()
),
CaptureDeviceNameValue(rpl::duplicate(captureIdWithFallback)),
st::groupCallSettingsButton
)->addClickHandler([=] {
box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=](
const QString &id,
const QString &name) {
state->inputNameStream.fire_copy(name);
if (state->micTester) {
state->micTester->setDeviceId(id);
}
}), &st::groupCallCheckbox, &st::groupCallRadio));
box->getDelegate()->show(ChooseCaptureDeviceBox(
rpl::duplicate(captureIdWithFallback),
crl::guard(box, [=](const QString &id) {
Core::App().settings().setCallCaptureDeviceId(id);
Core::App().saveSettingsDelayed();
}),
&st::groupCallCheckbox,
&st::groupCallRadio));
});
state->micTestLevel = box->addRow(
@ -764,9 +771,14 @@ void SettingsBox(
box->setShowFinishedCallback([=] {
// Means we finished showing the 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>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId());
state->deviceId->value());
state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
});
});
@ -873,10 +885,13 @@ std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
MicLevelTester::MicLevelTester(Fn<void()> show)
: _show(std::move(show))
, _timer([=] { check(); })
, _tester(
std::make_unique<Webrtc::AudioInputTester>(
Core::App().settings().callAudioBackend(),
Core::App().settings().callInputDeviceId())) {
, _deviceId(std::make_unique<Webrtc::DeviceResolver>(
&Core::App().mediaDevices(),
Webrtc::DeviceType::Capture,
Webrtc::DeviceIdValueWithFallback(
Core::App().settings().callCaptureDeviceIdValue(),
Core::App().settings().captureDeviceIdValue())))
, _tester(std::make_unique<Webrtc::AudioInputTester>(_deviceId->value())) {
_timer.callEach(kMicrophoneTooltipCheckInterval);
}

View file

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

View file

@ -71,7 +71,6 @@ ComposeIcons {
menuSpoilerOff: icon;
stripBubble: icon;
stripPremiumLocked: icon;
stripExpandPanel: icon;
stripExpandDropdown: icon;
}
@ -123,6 +122,8 @@ EmojiPan {
removeSet: IconButton;
boxLabel: FlatLabel;
icons: ComposeIcons;
about: FlatLabel;
aboutPadding: margins;
autocompleteBottomSkip: pixels;
}
@ -208,6 +209,8 @@ ComposeControls {
files: ComposeFiles;
premium: PremiumLimits;
boxField: InputField;
restrictionLabel: FlatLabel;
premiumRequired: ComposePremiumRequired;
}
ReportBox {
@ -249,6 +252,13 @@ defaultWhoRead: WhoRead {
iconPosition: point(15px, 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) {
width: 320px;
@ -600,10 +610,6 @@ defaultComposeIcons: ComposeIcons {
{ "chat/reactions_bubble_shadow", windowShadowFg },
{ "chat/reactions_bubble", windowBg },
};
stripPremiumLocked: icon{
{ "chat/reactions_premium_bg", historyPeerArchiveUserpicBg },
{ "chat/reactions_premium_star", historyPeerUserpicFg },
};
stripExpandPanel: icon{
{ "chat/reactions_round_big", windowBgRipple },
{ "chat/reactions_expand_panel", windowSubTextFg },
@ -613,6 +619,14 @@ defaultComposeIcons: ComposeIcons {
{ "chat/reactions_expand_panel", windowSubTextFg },
};
}
defaultEmojiPanAbout: FlatLabel(defaultFlatLabel) {
minWidth: 10px;
align: align(top);
textFg: windowSubTextFg;
style: TextStyle(defaultTextStyle) {
font: font(11px);
}
}
defaultEmojiPan: EmojiPan {
margin: margins(7px, 0px, 7px, 0px);
padding: margins(7px, 0px, 4px, 7px);
@ -654,6 +668,8 @@ defaultEmojiPan: EmojiPan {
removeSet: stickerPanRemoveSet;
boxLabel: boxLabel;
icons: defaultComposeIcons;
about: defaultEmojiPanAbout;
aboutPadding: margins(12px, 2px, 12px, 2px);
autocompleteBottomSkip: 0px;
}
statusEmojiPan: EmojiPan(defaultEmojiPan) {
@ -1113,6 +1129,7 @@ historyRecordLockBodyShadow: icon {{ "voice_lock/record_lock_body_shadow", histo
historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }};
historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
historyRecordDelete: IconButton(historyAttach) {
@ -1211,6 +1228,11 @@ defaultComposeFiles: ComposeFiles {
nameFg: historyFileNameInFg;
statusFg: mediaInFg;
}
defaultRestrictionLabel: FlatLabel(defaultFlatLabel) {
minWidth: 12px;
textFg: placeholderFg;
align: align(top);
}
defaultComposeControls: ComposeControls {
bg: historyComposeAreaBg;
radius: 0px;
@ -1227,6 +1249,7 @@ defaultComposeControls: ComposeControls {
files: defaultComposeFiles;
premium: defaultPremiumLimits;
boxField: defaultInputField;
restrictionLabel: defaultRestrictionLabel;
}
moreChatsBarHeight: 48px;
@ -1291,3 +1314,22 @@ ttlMediaButton: RoundButton(defaultActiveButton) {
textTop: 6px;
}
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);
}
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
const auto session = &this->session();
const auto check = [&](Window::Controller *window) {
if (const auto controller = window->sessionController()) {
if (&controller->session() == session) {
return controller;
ResolveWindow ResolveWindowDefault() {
return [](not_null<Main::Session*> session, WindowUsage usage)
-> Window::SessionController* {
const auto check = [&](Window::Controller *window) {
if (const auto controller = window->sessionController()) {
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;
} 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 nullptr;
}
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
return ResolveWindowDefault()(&session(), usage);
}
} // namespace ChatHelpers

View file

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

View file

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

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwindow.h"
#include "main/main_session.h"
#include "settings/settings_premium.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
@ -904,3 +905,68 @@ base::unique_qptr<Ui::RpWidget> CreateDisabledFieldView(
});
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(
QWidget *parent,
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) {
const auto both = clip.intersected(
context.clip.marginsRemoved(
{ context.radius, 0, context.radius, 0 }));
{ 0/*context.radius*/, 0, context.radius, 0 }));
if (both.isEmpty()) {
return;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
@ -661,6 +662,17 @@ bool CopyPeerId(
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(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@ -1032,6 +1044,10 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
{
u"^copy:(.+)$"_q,
CopyPeerId
},
{
u"about_tags"_q,
ShowSearchTagsPromo
}
};
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 AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4014006;
constexpr auto AppVersionStr = "4.14.6";
constexpr auto AppVersion = 4014013;
constexpr auto AppVersionStr = "4.14.13";
constexpr auto AppBetaVersion = false;
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_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/chat/attach/attach_prepare.h"
namespace {
@ -113,6 +114,9 @@ bool CanSendAnyOf(
if (const auto user = peer->asUser()) {
if (user->isInaccessible() || user->isRepliesChat()) {
return false;
} else if (user->meRequiresPremiumToWrite()
&& !user->session().premium()) {
return false;
} else if (rights
& ~(ChatRestriction::SendVoiceMessages
| ChatRestriction::SendVideoMessages
@ -167,6 +171,13 @@ std::optional<QString> RestrictionError(
using Flag = ChatRestriction;
if (const auto restricted = peer->amRestricted(restriction)) {
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)
? tr::lng_restricted_send_voice_messages(
tr::now,

View file

@ -99,7 +99,7 @@ MTPInputMedia WebPageForMTP(
bool required) {
using Flag = MTPDinputMediaWebPage::Flag;
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.forceSmallMedia ? Flag::f_force_small_media : Flag())),
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 = [&] {
using namespace Ui::Text;
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()) {
return u"GIF"_q;
} else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now);
} 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();
!name.isEmpty()) {
return name;
@ -1020,13 +1024,19 @@ TextWithEntities MediaFile::notificationText() const {
}
const auto type = [&] {
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()) {
return u"GIF"_q;
} else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now);
} 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()) {
return _document->filename();
} else if (_document->isAudioFile()) {
@ -1077,13 +1087,19 @@ TextForMimeData MediaFile::clipboardText() const {
return tr::lng_in_dlg_sticker(tr::now);
} else if (_document->isAnimation()) {
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;
} else if (_document->isVideoFile()) {
return tr::lng_in_dlg_video(tr::now);
} 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()) {
return tr::lng_in_dlg_audio_file(tr::now) + addName;
}

View file

@ -31,6 +31,15 @@ ReactionId SearchTagFromQuery(const QString &query) {
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) {
if (id.empty()) {
return {};

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