Merge tag 'v5.6.3' into dev
|
@ -402,6 +402,8 @@ PRIVATE
|
|||
boxes/sessions_box.h
|
||||
boxes/share_box.cpp
|
||||
boxes/share_box.h
|
||||
boxes/star_gift_box.cpp
|
||||
boxes/star_gift_box.h
|
||||
boxes/sticker_set_box.cpp
|
||||
boxes/sticker_set_box.h
|
||||
boxes/stickers_box.cpp
|
||||
|
@ -649,6 +651,8 @@ PRIVATE
|
|||
data/data_lastseen_status.h
|
||||
data/data_location.cpp
|
||||
data/data_location.h
|
||||
data/data_media_preload.cpp
|
||||
data/data_media_preload.h
|
||||
data/data_media_rotation.cpp
|
||||
data/data_media_rotation.h
|
||||
data/data_media_types.cpp
|
||||
|
@ -684,6 +688,7 @@ PRIVATE
|
|||
data/data_replies_list.h
|
||||
data/data_reply_preview.cpp
|
||||
data/data_reply_preview.h
|
||||
data/data_report.h
|
||||
data/data_saved_messages.cpp
|
||||
data/data_saved_messages.h
|
||||
data/data_saved_sublist.cpp
|
||||
|
@ -1027,6 +1032,10 @@ PRIVATE
|
|||
info/media/info_media_widget.h
|
||||
info/members/info_members_widget.cpp
|
||||
info/members/info_members_widget.h
|
||||
info/peer_gifts/info_peer_gifts_common.cpp
|
||||
info/peer_gifts/info_peer_gifts_common.h
|
||||
info/peer_gifts/info_peer_gifts_widget.cpp
|
||||
info/peer_gifts/info_peer_gifts_widget.h
|
||||
info/polls/info_polls_results_inner_widget.cpp
|
||||
info/polls/info_polls_results_inner_widget.h
|
||||
info/polls/info_polls_results_widget.cpp
|
||||
|
|
Before Width: | Height: | Size: 1 MiB After Width: | Height: | Size: 1 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 832 KiB After Width: | Height: | Size: 935 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 234 KiB |
|
@ -582,3 +582,55 @@ div.toast_shown {
|
|||
.bot_button_column_separator {
|
||||
width: 2px
|
||||
}
|
||||
|
||||
.reactions {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.reactions .reaction {
|
||||
display: inline-flex;
|
||||
height: 20px;
|
||||
border-radius: 15px;
|
||||
background-color: #e8f5fc;
|
||||
color: #168acd;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.reactions .reaction.active {
|
||||
background-color: #40a6e2;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reactions .reaction.paid {
|
||||
background-color: #fdf6e1;
|
||||
color: #c58523;
|
||||
}
|
||||
|
||||
.reactions .reaction.active.paid {
|
||||
background-color: #ecae0a;
|
||||
color: #fdf6e1;
|
||||
}
|
||||
|
||||
.reactions .reaction .emoji {
|
||||
line-height: 20px;
|
||||
margin: 0 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic:not(:first-child) {
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.reactions .reaction .userpic .initials {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.reactions .reaction .count {
|
||||
margin-right: 8px;
|
||||
line-height: 20px;
|
||||
}
|
BIN
Telegram/Resources/icons/inline_button_copy.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/inline_button_copy@3x.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
|
@ -12,6 +12,7 @@ body {
|
|||
margin: 0;
|
||||
background-color: var(--td-window-bg);
|
||||
color: var(--td-window-fg);
|
||||
zoom: var(--td-zoom-percentage);
|
||||
}
|
||||
|
||||
html.custom_scroll ::-webkit-scrollbar {
|
||||
|
|
|
@ -453,6 +453,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_username_app_not_found" = "Bot application not found.";
|
||||
"lng_username_link" = "This link opens a chat with you:";
|
||||
"lng_username_copied" = "Link copied to clipboard.";
|
||||
"lng_username_text_copied" = "Username copied to clipboard.";
|
||||
|
||||
"lng_usernames_edit" = "click to edit";
|
||||
"lng_usernames_active" = "active";
|
||||
|
@ -487,6 +488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
|
||||
"lng_collectible_phone_copy" = "Copy Phone Number";
|
||||
"lng_collectible_learn_more" = "Learn More";
|
||||
"lng_collectible_phone_copied" = "Phone number copied to clipboard.";
|
||||
|
||||
"lng_settings_section_info" = "Info";
|
||||
|
||||
|
@ -508,6 +510,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_alert_linux" = "Draw attention to the window";
|
||||
"lng_settings_badge_title" = "Badge counter";
|
||||
"lng_settings_include_muted" = "Include muted chats in unread count";
|
||||
"lng_settings_include_muted_folders" = "Include muted chats in folder counters";
|
||||
"lng_settings_count_unread" = "Count unread messages";
|
||||
"lng_settings_events_title" = "Events";
|
||||
"lng_settings_events_joined" = "Contact joined Telegram";
|
||||
|
@ -1322,6 +1325,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_peer_gifts#one" = "{count} gift";
|
||||
"lng_profile_peer_gifts#other" = "{count} gifts";
|
||||
"lng_profile_participants_section" = "Members";
|
||||
"lng_profile_subscribers_section" = "Subscribers";
|
||||
"lng_profile_add_contact" = "Add Contact";
|
||||
|
@ -1376,6 +1381,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_copy_fullname" = "Copy Name";
|
||||
"lng_profile_photo_by_you" = "photo set by you";
|
||||
"lng_profile_public_photo" = "public photo";
|
||||
"lng_profile_administrators#one" = "{count} administrator";
|
||||
"lng_profile_administrators#other" = "{count} administrators";
|
||||
"lng_profile_manage" = "Channel settings";
|
||||
|
||||
"lng_invite_upgrade_title" = "Upgrade to Premium";
|
||||
"lng_invite_upgrade_group_invite#one" = "{users} only accepts invitations to groups from Contacts and **Premium** users.";
|
||||
|
@ -1659,6 +1667,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"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_details_optional" = "Add Comment (Optional)";
|
||||
"lng_report_details_non_optional" = "Add Comment";
|
||||
"lng_report_details_message_about" = "Please help us by telling what is wrong with the message you have selected";
|
||||
"lng_report_reason_spam" = "Spam";
|
||||
"lng_report_reason_fake" = "Fake Account";
|
||||
"lng_report_reason_violence" = "Violence";
|
||||
|
@ -1852,8 +1863,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_proximity_distance_km#other" = "{count} km";
|
||||
"lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot.";
|
||||
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
|
||||
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
|
||||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_sent_subtitle" = "Gift for {user}";
|
||||
"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star.";
|
||||
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars.";
|
||||
"lng_action_gift_premium_months#one" = "{count} Month Premium";
|
||||
"lng_action_gift_premium_months#other" = "{count} Months Premium";
|
||||
"lng_action_gift_premium_about" = "Subscription for exclusive Telegram features.";
|
||||
"lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile.";
|
||||
"lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile.";
|
||||
"lng_action_suggested_photo_button" = "View Photo";
|
||||
|
@ -1915,6 +1937,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_similar_channels_premium_all_link" = "Telegram Premium";
|
||||
"lng_similar_channels_show_more" = "Show more channels";
|
||||
|
||||
"lng_peer_gifts_title" = "Gifts";
|
||||
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users.";
|
||||
"lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings.";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
"lng_premium_gift_duration_years#one" = "for {count} year";
|
||||
|
@ -2415,6 +2441,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_box_history_entry_gift_name" = "Received Gift";
|
||||
"lng_credits_box_history_entry_giveaway_name" = "Received Prize";
|
||||
"lng_credits_box_history_entry_gift_sent" = "Sent Gift";
|
||||
"lng_credits_box_history_entry_gift_converted" = "Converted Gift";
|
||||
"lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}";
|
||||
"lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}";
|
||||
"lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}";
|
||||
|
@ -2461,6 +2488,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_credits_small_balance_about" = "Buy **Stars** and use them on **{bot}** and other miniapps.";
|
||||
"lng_credits_small_balance_reaction" = "Buy **Stars** and send them to {channel} to support their posts.";
|
||||
"lng_credits_small_balance_subscribe" = "Buy **Stars** and subscribe to **{channel}** and other channels.";
|
||||
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
|
||||
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
|
||||
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
|
||||
"lng_credits_enough" = "You have enough stars at the moment. {link}";
|
||||
|
@ -2980,6 +3008,53 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_stars_incoming" = "Use Stars to unlock content and services on Telegram.";
|
||||
"lng_gift_until" = "Until";
|
||||
|
||||
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
|
||||
"lng_gift_premium_subtitle" = "Gift Premium";
|
||||
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
|
||||
"lng_gift_premium_features" = "See Features >";
|
||||
"lng_gift_premium_label" = "Premium";
|
||||
"lng_gift_stars_subtitle" = "Gift Stars";
|
||||
"lng_gift_stars_about" = "Give {name} gifts that can be kept on your profile or converted to Stars. {link}";
|
||||
"lng_gift_stars_link" = "What are Stars >";
|
||||
"lng_gift_stars_limited" = "limited";
|
||||
"lng_gift_stars_sold_out" = "sold out";
|
||||
"lng_gift_stars_tabs_all" = "All Gifts";
|
||||
"lng_gift_stars_tabs_limited" = "Limited";
|
||||
"lng_gift_send_title" = "Send a Gift";
|
||||
"lng_gift_send_message" = "Enter Message";
|
||||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message.";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
|
||||
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
|
||||
"lng_gift_limited_of_one" = "unique";
|
||||
"lng_gift_limited_of_count" = "1 of {amount}";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_hidden_hint" = "This gift is hidden. Only you can see it.";
|
||||
"lng_gift_visible_hint" = "This gift is visible to visitors of your page.";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_availability_left#one" = "{count} of {amount} left";
|
||||
"lng_gift_availability_left#other" = "{count} of {amount} left";
|
||||
"lng_gift_availability_none" = "None of {amount} left";
|
||||
"lng_gift_display_on_page" = "Display on my Page";
|
||||
"lng_gift_display_on_page_hide" = "Hide from my Page";
|
||||
"lng_gift_convert_to_stars#one" = "Convert to {count} Star";
|
||||
"lng_gift_convert_to_stars#other" = "Convert to {count} Stars";
|
||||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_text#one" = "Do you want to convert this gift from {user} to **{count} Star**?\n\nThis action cannot be undone.";
|
||||
"lng_gift_convert_sure_text#other" = "Do you want to convert this gift from {user} to **{count} Stars**?\n\nThis action cannot be undone.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_got_stars#one" = "You got **{count} Star** for this gift.";
|
||||
"lng_gift_got_stars#other" = "You got **{count} Stars** for this gift.";
|
||||
"lng_gift_sold_out_title" = "Sold Out!";
|
||||
"lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
|
||||
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold.";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
|
@ -3222,6 +3297,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_replies_discussion_started" = "Discussion started";
|
||||
"lng_replies_no_comments" = "No comments here yet...";
|
||||
|
||||
"lng_verification_codes" = "Verification Codes";
|
||||
|
||||
"lng_archived_name" = "Archived chats";
|
||||
"lng_archived_add" = "Archive";
|
||||
"lng_archived_remove" = "Unarchive";
|
||||
|
@ -3246,7 +3323,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_open_link" = "Open";
|
||||
"lng_allow_bot_pass" = "Allow {bot_name} to pass your Telegram name and ID (not your phone number) to the web pages you open via this bot?";
|
||||
"lng_allow_bot" = "Allow";
|
||||
"lng_allow_bot_webview" = "{bot_name} would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info.";
|
||||
"lng_allow_bot_webview_details" = "More about this bot {emoji}";
|
||||
"lng_allow_bot_webview_details_about" = "To launch this web app, you will connect to its website.\n\nIt will be able to access your **IP address** and basic device info.";
|
||||
"lng_url_auth_open_confirm" = "Do you want to open {link}?";
|
||||
"lng_url_auth_login_option" = "Log in to {domain} as {user}";
|
||||
"lng_url_auth_allow_messages" = "Allow {bot} to send me messages";
|
||||
|
@ -3498,6 +3576,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_context_animated_reactions_many#one" = "Reactions contain emoji from **{count} pack**.";
|
||||
"lng_context_animated_reactions_many#other" = "Reactions contain emoji from **{count} packs**.";
|
||||
|
||||
"lng_context_noforwards_info_channel" = "Copying and forwarding is not allowed in this channel.";
|
||||
"lng_context_noforwards_info_group" = "Copying and forwarding is not allowed in this group.";
|
||||
"lng_context_noforwards_info_bot" = "Copying and forwarding is not allowed from this bot.";
|
||||
|
||||
"lng_context_spoiler_effect" = "Hide with Spoiler";
|
||||
"lng_context_disable_spoiler" = "Remove Spoiler";
|
||||
"lng_context_make_paid" = "Make This Content Paid";
|
||||
|
@ -3608,6 +3690,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_reply_header_short" = "Reply";
|
||||
"lng_reply_quote_selected" = "Quote Selected";
|
||||
"lng_reply_from_private_chat" = "This reply is from a private chat.";
|
||||
"lng_reply_quote_long_title" = "Quote too long!";
|
||||
"lng_reply_quote_long_text" = "The selected text is too long to quote.";
|
||||
"lng_link_options_header" = "Link Preview Settings";
|
||||
"lng_link_header_short" = "Link";
|
||||
"lng_link_move_up" = "Move Up";
|
||||
|
@ -4233,6 +4317,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_rights_restriction_for_all" = "This option is disabled for all members in Group Permissions.";
|
||||
"lng_rights_permission_for_all" = "This option is enabled for all members in Group Permissions.";
|
||||
"lng_rights_permission_unavailable" = "This permission is not available in public groups.";
|
||||
"lng_rights_permission_in_discuss" = "This permission is not available in discussion groups.";
|
||||
"lng_rights_permission_cant_edit" = "You cannot change this permission.";
|
||||
"lng_rights_user_restrictions" = "User permissions";
|
||||
"lng_rights_user_restrictions_header" = "What can this member do?";
|
||||
|
@ -5550,6 +5635,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_qr_box_quality1" = "Normal";
|
||||
"lng_qr_box_quality2" = "High";
|
||||
"lng_qr_box_quality3" = "Very High";
|
||||
"lng_qr_box_transparent_background" = "Transparent Background";
|
||||
"lng_qr_box_font_size" = "Font size";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.5.5.0" />
|
||||
Version="5.6.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,5,5,0
|
||||
PRODUCTVERSION 5,5,5,0
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.5.5.0"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.5.5.0"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,5,5,0
|
||||
PRODUCTVERSION 5,5,5,0
|
||||
FILEVERSION 5,6,3,0
|
||||
PRODUCTVERSION 5,6,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -53,10 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||
VALUE "FileVersion", "5.5.5.0"
|
||||
VALUE "FileVersion", "5.6.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.5.5.0"
|
||||
VALUE "ProductVersion", "5.6.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -39,6 +39,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
namespace Api {
|
||||
namespace {
|
||||
|
||||
|
@ -503,11 +506,19 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
|
|||
bot->session().attachWebView().open({
|
||||
.bot = bot,
|
||||
.context = { .controller = controller },
|
||||
.button = {.text = button->text, .url = button->data },
|
||||
.button = { .text = button->text, .url = button->data },
|
||||
.source = InlineBots::WebViewSourceButton{ .simple = true },
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case ButtonType::CopyText: {
|
||||
const auto text = QString::fromUtf8(button->data);
|
||||
if (!text.isEmpty()) {
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -275,7 +275,6 @@ void ConfirmSubscriptionBox(
|
|||
: 0;
|
||||
state->api->request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(formId),
|
||||
MTP_inputInvoiceChatInviteSubscription(MTP_string(hash)))
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_peer.h"
|
||||
|
@ -69,10 +70,12 @@ constexpr auto kTransactionsLimit = 100;
|
|||
}, [](const auto &) {
|
||||
return PeerId(0);
|
||||
}).value;
|
||||
const auto stargift = tl.data().vstargift();
|
||||
const auto incoming = (int64(tl.data().vstars().v) >= 0);
|
||||
return Data::CreditsHistoryEntry{
|
||||
.id = qs(tl.data().vid()),
|
||||
.title = qs(tl.data().vtitle().value_or_empty()),
|
||||
.description = qs(tl.data().vdescription().value_or_empty()),
|
||||
.description = { qs(tl.data().vdescription().value_or_empty()) },
|
||||
.date = base::unixtime::parse(tl.data().vdate().v),
|
||||
.photoId = photo ? photo->id : 0,
|
||||
.extended = std::move(extended),
|
||||
|
@ -81,6 +84,9 @@ constexpr auto kTransactionsLimit = 100;
|
|||
.barePeerId = barePeerId,
|
||||
.bareGiveawayMsgId = uint64(
|
||||
tl.data().vgiveaway_post_id().value_or_empty()),
|
||||
.bareGiftStickerId = (stargift
|
||||
? owner->processDocument(stargift->data().vsticker())->id
|
||||
: 0),
|
||||
.peerType = tl.data().vpeer().match([](const HistoryPeerTL &) {
|
||||
return Data::CreditsHistoryEntry::PeerType::Peer;
|
||||
}, [](const MTPDstarsTransactionPeerPlayMarket &) {
|
||||
|
@ -104,12 +110,16 @@ constexpr auto kTransactionsLimit = 100;
|
|||
? base::unixtime::parse(tl.data().vtransaction_date()->v)
|
||||
: QDateTime(),
|
||||
.successLink = qs(tl.data().vtransaction_url().value_or_empty()),
|
||||
.convertStars = int(stargift
|
||||
? stargift->data().vconvert_stars().v
|
||||
: 0),
|
||||
.converted = stargift && incoming,
|
||||
.reaction = tl.data().is_reaction(),
|
||||
.refunded = tl.data().is_refund(),
|
||||
.pending = tl.data().is_pending(),
|
||||
.failed = tl.data().is_failed(),
|
||||
.in = (int64(tl.data().vstars().v) >= 0),
|
||||
.gift = tl.data().is_gift(),
|
||||
.in = incoming,
|
||||
.gift = tl.data().is_gift() || stargift.has_value(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -239,6 +249,8 @@ void CreditsStatus::request(
|
|||
_peer->isSelf() ? MTP_inputPeerSelf() : _peer->input
|
||||
)).done([=](const TLResult &result) {
|
||||
_requestId = 0;
|
||||
const auto balance = result.data().vbalance().v;
|
||||
_peer->session().credits().apply(_peer->id, balance);
|
||||
if (const auto onstack = done) {
|
||||
onstack(StatusFromTL(result, _peer));
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
|
|||
MTP_int(dimensions.width()),
|
||||
MTP_int(dimensions.height()),
|
||||
MTPint(), // preload_prefix_size
|
||||
MTPdouble())); // video_start_ts
|
||||
MTPdouble(), // video_start_ts
|
||||
MTPstring())); // video_codec
|
||||
} else {
|
||||
attributes.push_back(MTP_documentAttributeImageSize(
|
||||
MTP_int(dimensions.width()),
|
||||
|
|
|
@ -550,6 +550,24 @@ Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
|
|||
};
|
||||
}
|
||||
|
||||
std::vector<GiftOptionData> PremiumGiftCodeOptions::optionsForPeer() const {
|
||||
auto result = std::vector<GiftOptionData>();
|
||||
|
||||
if (!_optionsForOnePerson.currency.isEmpty()) {
|
||||
const auto count = int(_optionsForOnePerson.months.size());
|
||||
result.reserve(count);
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
Assert(i < _optionsForOnePerson.totalCosts.size());
|
||||
result.push_back({
|
||||
.cost = _optionsForOnePerson.totalCosts[i],
|
||||
.currency = _optionsForOnePerson.currency,
|
||||
.months = _optionsForOnePerson.months[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
||||
const auto it = _subscriptionOptions.find(amount);
|
||||
if (it != end(_subscriptionOptions)) {
|
||||
|
@ -571,6 +589,41 @@ Data::PremiumSubscriptionOptions PremiumGiftCodeOptions::options(int amount) {
|
|||
}
|
||||
}
|
||||
|
||||
auto PremiumGiftCodeOptions::requestStarGifts()
|
||||
-> rpl::producer<rpl::no_value, QString> {
|
||||
return [=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
_api.request(MTPpayments_GetStarGifts(
|
||||
MTP_int(0)
|
||||
)).done([=](const MTPpayments_StarGifts &result) {
|
||||
result.match([&](const MTPDpayments_starGifts &data) {
|
||||
_giftsHash = data.vhash().v;
|
||||
const auto &list = data.vgifts().v;
|
||||
const auto session = &_peer->session();
|
||||
auto gifts = std::vector<StarGift>();
|
||||
gifts.reserve(list.size());
|
||||
for (const auto &gift : list) {
|
||||
if (auto parsed = FromTL(session, gift)) {
|
||||
gifts.push_back(std::move(*parsed));
|
||||
}
|
||||
}
|
||||
_gifts = std::move(gifts);
|
||||
}, [&](const MTPDpayments_starGiftsNotModified &) {
|
||||
});
|
||||
consumer.put_done();
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
consumer.put_error_copy(error.type());
|
||||
}).send();
|
||||
|
||||
return lifetime;
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<StarGift> &PremiumGiftCodeOptions::starGifts() const {
|
||||
return _gifts;
|
||||
}
|
||||
|
||||
int PremiumGiftCodeOptions::giveawayBoostsPerPremium() const {
|
||||
constexpr auto kFallbackCount = 4;
|
||||
return _peer->session().appConfig().get<int>(
|
||||
|
@ -705,4 +758,56 @@ rpl::producer<DocumentData*> RandomHelloStickerValue(
|
|||
}) | rpl::take(1) | rpl::map(random));
|
||||
}
|
||||
|
||||
std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift) {
|
||||
const auto &data = gift.data();
|
||||
const auto document = session->data().processDocument(
|
||||
data.vsticker());
|
||||
const auto remaining = data.vavailability_remains();
|
||||
const auto total = data.vavailability_total();
|
||||
if (!document->sticker()) {
|
||||
return {};
|
||||
}
|
||||
return StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.stars = int64(data.vstars().v),
|
||||
.convertStars = int64(data.vconvert_stars().v),
|
||||
.document = document,
|
||||
.limitedLeft = remaining.value_or_empty(),
|
||||
.limitedCount = total.value_or_empty(),
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
return UserStarGift{
|
||||
.gift = std::move(*parsed),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
.entities = Api::EntitiesFromMTP(
|
||||
session,
|
||||
data.vmessage()->data().ventities().v),
|
||||
}
|
||||
: TextWithEntities()),
|
||||
.convertStars = int64(data.vconvert_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.anonymous = data.is_name_hidden(),
|
||||
.hidden = data.is_unsaved(),
|
||||
.mine = to->isSelf(),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -67,6 +67,33 @@ struct GiveawayInfo {
|
|||
}
|
||||
};
|
||||
|
||||
struct GiftOptionData {
|
||||
int64 cost = 0;
|
||||
QString currency;
|
||||
int months = 0;
|
||||
};
|
||||
|
||||
struct StarGift {
|
||||
uint64 id = 0;
|
||||
int64 stars = 0;
|
||||
int64 convertStars = 0;
|
||||
not_null<DocumentData*> document;
|
||||
int limitedLeft = 0;
|
||||
int limitedCount = 0;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
StarGift gift;
|
||||
TextWithEntities message;
|
||||
int64 convertStars = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool anonymous = false;
|
||||
bool hidden = false;
|
||||
bool mine = false;
|
||||
};
|
||||
|
||||
class Premium final {
|
||||
public:
|
||||
explicit Premium(not_null<ApiWrap*> api);
|
||||
|
@ -171,6 +198,7 @@ public:
|
|||
PremiumGiftCodeOptions(not_null<PeerData*> peer);
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> request();
|
||||
[[nodiscard]] std::vector<GiftOptionData> optionsForPeer() const;
|
||||
[[nodiscard]] Data::PremiumSubscriptionOptions options(int amount);
|
||||
[[nodiscard]] const std::vector<int> &availablePresets() const;
|
||||
[[nodiscard]] int monthsFromPreset(int monthsIndex);
|
||||
|
@ -187,6 +215,9 @@ public:
|
|||
[[nodiscard]] int giveawayPeriodMax() const;
|
||||
[[nodiscard]] bool giveawayGiftsPurchaseAvailable() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<rpl::no_value, QString> requestStarGifts();
|
||||
[[nodiscard]] const std::vector<StarGift> &starGifts() const;
|
||||
|
||||
private:
|
||||
struct Token final {
|
||||
int users = 0;
|
||||
|
@ -206,7 +237,7 @@ private:
|
|||
base::flat_map<Amount, PremiumSubscriptionOptions> _subscriptionOptions;
|
||||
struct {
|
||||
std::vector<int> months;
|
||||
std::vector<float64> totalCosts;
|
||||
std::vector<int64> totalCosts;
|
||||
QString currency;
|
||||
} _optionsForOnePerson;
|
||||
|
||||
|
@ -214,6 +245,9 @@ private:
|
|||
|
||||
base::flat_map<Token, Store> _stores;
|
||||
|
||||
int32 _giftsHash = 0;
|
||||
std::vector<StarGift> _gifts;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
||||
};
|
||||
|
@ -242,4 +276,11 @@ enum class RequirePremiumState {
|
|||
[[nodiscard]] rpl::producer<DocumentData*> RandomHelloStickerValue(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] std::optional<StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -10,10 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_report.h"
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/show.h"
|
||||
|
||||
namespace Api {
|
||||
|
@ -40,15 +41,11 @@ MTPreportReason ReasonToTL(const Ui::ReportReason &reason) {
|
|||
} // namespace
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data) {
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data) {
|
||||
auto done = [=] {
|
||||
show->showToast(tr::lng_report_thanks(tr::now));
|
||||
};
|
||||
|
@ -58,18 +55,6 @@ void SendReport(
|
|||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](const MessageIdsList &ids) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(ids.size());
|
||||
for (const auto &fullId : ids) {
|
||||
apiIds.push_back(MTP_int(fullId.msg));
|
||||
}
|
||||
peer->session().api().request(MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](not_null<PhotoData*> photo) {
|
||||
peer->session().api().request(MTPaccount_ReportProfilePhoto(
|
||||
peer->input,
|
||||
|
@ -77,14 +62,93 @@ void SendReport(
|
|||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
}, [&](StoryId id) {
|
||||
peer->session().api().request(MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(1, MTP_int(id)),
|
||||
ReasonToTL(reason),
|
||||
MTP_string(comment)
|
||||
)).done(std::move(done)).send();
|
||||
});
|
||||
}
|
||||
|
||||
auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)> {
|
||||
using TLChoose = MTPDreportResultChooseOption;
|
||||
using TLAddComment = MTPDreportResultAddComment;
|
||||
using TLReported = MTPDreportResultReported;
|
||||
using Result = ReportResult;
|
||||
|
||||
struct State final {
|
||||
#ifdef _DEBUG
|
||||
~State() {
|
||||
qDebug() << "Messages or Stories Report ~State().";
|
||||
}
|
||||
#endif
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
|
||||
return [=](
|
||||
Data::ReportInput reportInput,
|
||||
Fn<void(Result)> done) {
|
||||
auto apiIds = QVector<MTPint>();
|
||||
apiIds.reserve(reportInput.ids.size() + reportInput.stories.size());
|
||||
for (const auto &id : reportInput.ids) {
|
||||
apiIds.push_back(MTP_int(id));
|
||||
}
|
||||
for (const auto &story : reportInput.stories) {
|
||||
apiIds.push_back(MTP_int(story));
|
||||
}
|
||||
|
||||
const auto received = [=](
|
||||
const MTPReportResult &result,
|
||||
mtpRequestId requestId) {
|
||||
if (state->requestId != requestId) {
|
||||
return;
|
||||
}
|
||||
state->requestId = 0;
|
||||
done(result.match([&](const TLChoose &data) {
|
||||
const auto t = qs(data.vtitle());
|
||||
auto list = Result::Options();
|
||||
list.reserve(data.voptions().v.size());
|
||||
for (const auto &tl : data.voptions().v) {
|
||||
list.emplace_back(Result::Option{
|
||||
.id = tl.data().voption().v,
|
||||
.text = qs(tl.data().vtext()),
|
||||
});
|
||||
}
|
||||
return Result{ .options = std::move(list), .title = t };
|
||||
}, [&](const TLAddComment &data) -> Result {
|
||||
return {
|
||||
.commentOption = ReportResult::CommentOption{
|
||||
.optional = data.is_optional(),
|
||||
.id = data.voption().v,
|
||||
}
|
||||
};
|
||||
}, [&](const TLReported &data) -> Result {
|
||||
return { .successful = true };
|
||||
}));
|
||||
};
|
||||
|
||||
const auto fail = [=](const MTP::Error &error) {
|
||||
state->requestId = 0;
|
||||
done({ .error = error.type() });
|
||||
};
|
||||
|
||||
if (!reportInput.stories.empty()) {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPstories_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
} else {
|
||||
state->requestId = peer->session().api().request(
|
||||
MTPmessages_Report(
|
||||
peer->input,
|
||||
MTP_vector<MTPint>(apiIds),
|
||||
MTP_bytes(reportInput.optionId),
|
||||
MTP_string(reportInput.comment))
|
||||
).done(received).fail(fail).send();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryItem;
|
||||
class PeerData;
|
||||
class PhotoData;
|
||||
|
||||
|
@ -15,17 +16,41 @@ class Show;
|
|||
enum class ReportReason;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
struct ReportInput;
|
||||
} // namespace Data
|
||||
|
||||
namespace Api {
|
||||
|
||||
struct ReportResult final {
|
||||
using Id = QByteArray;
|
||||
struct Option final {
|
||||
Id id = 0;
|
||||
QString text;
|
||||
};
|
||||
using Options = std::vector<Option>;
|
||||
Options options;
|
||||
QString title;
|
||||
QString error;
|
||||
QString comment;
|
||||
struct CommentOption {
|
||||
bool optional = false;
|
||||
Id id = 0;
|
||||
};
|
||||
std::optional<CommentOption> commentOption;
|
||||
bool successful = false;
|
||||
};
|
||||
|
||||
void SendReport(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
Ui::ReportReason reason,
|
||||
const QString &comment,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data);
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data);
|
||||
|
||||
[[nodiscard]] auto CreateReportMessagesOrStoriesCallback(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer)
|
||||
-> Fn<void(Data::ReportInput, Fn<void(ReportResult)>)>;
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -547,7 +547,7 @@ void SendConfirmedFile(
|
|||
MTP_flags(Flag::f_document
|
||||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
|
@ -572,7 +572,7 @@ void SendConfirmedFile(
|
|||
| (isVoice ? Flag::f_voice : Flag())
|
||||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPDocument(), // alt_document
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/premium_limits_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
|
@ -371,6 +372,14 @@ void EditCaptionBox::StartPhotoEdit(
|
|||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::showFinished() {
|
||||
if (const auto raw = _autocomplete.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepare() {
|
||||
const auto button = addButton(tr::lng_settings_save(), [=] { save(); });
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
|
@ -525,6 +534,7 @@ void EditCaptionBox::setupField() {
|
|||
_field.get(),
|
||||
Window::GifPauseReason::Layer,
|
||||
allow);
|
||||
setupFieldAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_field,
|
||||
|
@ -562,6 +572,55 @@ void EditCaptionBox::setupField() {
|
|||
});
|
||||
}
|
||||
|
||||
void EditCaptionBox::setupFieldAutocomplete() {
|
||||
const auto parent = getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||
.parent = parent,
|
||||
.show = _controller->uiShow(),
|
||||
.field = _field.get(),
|
||||
.peer = _historyItem->history()->peer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
});
|
||||
const auto raw = _autocomplete.get();
|
||||
const auto scheduled = std::make_shared<bool>();
|
||||
const auto recountPostponed = [=] {
|
||||
if (*scheduled) {
|
||||
return;
|
||||
}
|
||||
*scheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
*scheduled = false;
|
||||
|
||||
auto field = Ui::MapFrom(parent, this, _field->geometry());
|
||||
_autocomplete->setBoundings(QRect(
|
||||
field.x() - _field->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
width(),
|
||||
(field.y()
|
||||
+ st::defaultComposeFiles.caption.textMargins.top()
|
||||
+ st::defaultComposeFiles.caption.placeholderShift
|
||||
+ st::defaultComposeFiles.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)_field.get(); w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditCaptionBox::setInitialText() {
|
||||
_field->setTextWithTags(
|
||||
_initialText,
|
||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class FieldAutocomplete;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Window {
|
||||
|
@ -68,6 +69,8 @@ public:
|
|||
bool invertCaption,
|
||||
Fn<void()> saved);
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
@ -81,6 +84,7 @@ private:
|
|||
void setupEditEventHandler();
|
||||
void setupPhotoEditorEventHandler();
|
||||
void setupField();
|
||||
void setupFieldAutocomplete();
|
||||
void setupControls();
|
||||
void setInitialText();
|
||||
|
||||
|
@ -115,6 +119,8 @@ private:
|
|||
const base::unique_qptr<Ui::InputField> _field;
|
||||
const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
|
||||
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
|
||||
base::unique_qptr<Ui::AbstractSinglePreview> _content;
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
base::unique_qptr<QObject> _emojiFilter;
|
||||
|
|
|
@ -356,11 +356,12 @@ void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
|
|||
|
||||
auto PrivacyExceptionsBoxController::createRow(not_null<History*> history)
|
||||
-> std::unique_ptr<Row> {
|
||||
if (history->peer->isSelf() || history->peer->isRepliesChat()) {
|
||||
const auto peer = history->peer;
|
||||
if (peer->isSelf() || peer->isRepliesChat() || peer->isVerifyCodes()) {
|
||||
return nullptr;
|
||||
} else if (!history->peer->isUser()
|
||||
&& !history->peer->isChat()
|
||||
&& !history->peer->isMegagroup()) {
|
||||
} else if (!peer->isUser()
|
||||
&& !peer->isChat()
|
||||
&& !peer->isMegagroup()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Row>(history);
|
||||
|
|
|
@ -131,10 +131,13 @@ ExceptionRow::ExceptionRow(not_null<History*> history) : Row(history) {
|
|||
}
|
||||
|
||||
QString ExceptionRow::generateName() {
|
||||
return peer()->isSelf()
|
||||
const auto peer = this->peer();
|
||||
return peer->isSelf()
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: peer()->isRepliesChat()
|
||||
: peer->isRepliesChat()
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->isVerifyCodes()
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: Row::generateName();
|
||||
}
|
||||
|
||||
|
@ -152,10 +155,11 @@ PaintRoundImageCallback ExceptionRow::generatePaintUserpicCallback(
|
|||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
|
|
@ -122,9 +122,11 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
|||
top += st.height;
|
||||
}
|
||||
for (auto &[history, userpic, name, button] : _removePeer) {
|
||||
const auto savedMessages = history->peer->isSelf();
|
||||
const auto repliesMessages = history->peer->isRepliesChat();
|
||||
if (savedMessages || repliesMessages) {
|
||||
const auto peer = history->peer;
|
||||
const auto savedMessages = peer->isSelf();
|
||||
const auto repliesMessages = peer->isRepliesChat();
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
if (savedMessages || repliesMessages || verifyCodes) {
|
||||
if (savedMessages) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(
|
||||
p,
|
||||
|
@ -132,13 +134,21 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
|||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
} else if (repliesMessages) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(
|
||||
p,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
userpic,
|
||||
iconLeft,
|
||||
top + iconTop,
|
||||
width(),
|
||||
st.photoSize);
|
||||
}
|
||||
p.setPen(st::contactsNameFg);
|
||||
p.drawTextLeft(
|
||||
|
@ -147,7 +157,9 @@ void FilterChatsPreview::paintEvent(QPaintEvent *e) {
|
|||
width(),
|
||||
(savedMessages
|
||||
? tr::lng_saved_messages(tr::now)
|
||||
: tr::lng_replies_messages(tr::now)));
|
||||
: repliesMessages
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: tr::lng_verification_codes(tr::now)));
|
||||
} else {
|
||||
history->peer->paintUserpicLeft(
|
||||
p,
|
||||
|
|
|
@ -337,12 +337,13 @@ PaintRoundImageCallback ChatRow::generatePaintUserpicCallback(
|
|||
int y,
|
||||
int outerWidth,
|
||||
int size) mutable {
|
||||
using namespace Ui;
|
||||
if (forceRound && peer->isForum()) {
|
||||
ForceRoundUserpicCallback(peer)(p, x, y, outerWidth, size);
|
||||
} else if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
|
|
@ -64,11 +64,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr auto kUserpicsMax = size_t(3);
|
||||
|
||||
using GiftOption = Data::PremiumSubscriptionOption;
|
||||
using GiftOptions = Data::PremiumSubscriptionOptions;
|
||||
|
||||
[[nodiscard]] QString CreateMessageLink(
|
||||
not_null<Main::Session*> session,
|
||||
PeerId peerId,
|
||||
|
@ -87,638 +82,6 @@ using GiftOptions = Data::PremiumSubscriptionOptions;
|
|||
return QString();
|
||||
};
|
||||
|
||||
GiftOptions GiftOptionFromTL(const MTPDuserFull &data) {
|
||||
auto result = GiftOptions();
|
||||
const auto gifts = data.vpremium_gifts();
|
||||
if (!gifts) {
|
||||
return result;
|
||||
}
|
||||
result = Api::PremiumSubscriptionOptionsFromTL(gifts->v);
|
||||
for (auto &option : result) {
|
||||
option.costPerMonth = tr::lng_premium_gift_per(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
option.costPerMonth);
|
||||
}
|
||||
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;
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> ComplexAboutLabel(
|
||||
const std::vector<not_null<UserData*>> &users,
|
||||
tr::phrase<TagUser1> phrase1,
|
||||
tr::phrase<TagUser1, TagUser2> phrase2,
|
||||
tr::phrase<TagUser1, TagUser2, TagUser3> phrase3,
|
||||
tr::phrase<lngtag_count, TagUser1, TagUser2, TagUser3> phraseMore) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto count = users.size();
|
||||
const auto nameValue = [&](not_null<UserData*> user) {
|
||||
return user->session().changes().peerFlagsValue(
|
||||
user,
|
||||
Data::PeerUpdate::Flag::Name
|
||||
) | rpl::map([=] { return TextWithEntities{ user->firstName }; });
|
||||
};
|
||||
if (count == 1) {
|
||||
return phrase1(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
Ui::Text::RichLangValue);
|
||||
} else if (count == 2) {
|
||||
return phrase2(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
Ui::Text::RichLangValue);
|
||||
} else if (count == 3) {
|
||||
return phrase3(
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
lt_name,
|
||||
nameValue(users[2]),
|
||||
Ui::Text::RichLangValue);
|
||||
} else {
|
||||
return phraseMore(
|
||||
lt_count,
|
||||
rpl::single(count - kUserpicsMax) | tr::to_count(),
|
||||
lt_user,
|
||||
nameValue(users.front()),
|
||||
lt_second_user,
|
||||
nameValue(users[1]),
|
||||
lt_name,
|
||||
nameValue(users[2]),
|
||||
Ui::Text::RichLangValue);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> CircleBadge(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const QString &text) {
|
||||
const auto widget = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
|
||||
const auto full = Rect(st::premiumGiftsUserpicBadgeSize);
|
||||
const auto inner = full - Margins(st::premiumGiftsUserpicBadgeInner);
|
||||
auto gradient = QLinearGradient(
|
||||
QPointF(0, full.height()),
|
||||
QPointF(full.width(), 0));
|
||||
gradient.setStops(Ui::Premium::GiftGradientStops());
|
||||
|
||||
widget->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(widget);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawEllipse(full);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(gradient);
|
||||
p.drawEllipse(inner);
|
||||
p.setFont(st::premiumGiftsUserpicBadgeFont);
|
||||
p.setPen(st::premiumButtonFg);
|
||||
p.drawText(full, text, style::al_center);
|
||||
}, widget->lifetime());
|
||||
widget->resize(full.size());
|
||||
return widget;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> UserpicsContainer(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::vector<not_null<UserData*>> users) {
|
||||
Expects(!users.empty());
|
||||
|
||||
if (users.size() == 1) {
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
parent.get(),
|
||||
users.front(),
|
||||
st::defaultUserpicButton);
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
return userpic;
|
||||
}
|
||||
|
||||
const auto &singleSize = st::defaultUserpicButton.size;
|
||||
|
||||
const auto container = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
const auto single = singleSize.width();
|
||||
const auto shift = single - st::boostReplaceUserpicsShift;
|
||||
const auto maxWidth = users.size() * (single - shift) + shift;
|
||||
container->resize(maxWidth, singleSize.height());
|
||||
container->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
const auto diff = (single - st::premiumGiftsUserpicButton.size.width())
|
||||
/ 2;
|
||||
for (auto i = 0; i < users.size(); i++) {
|
||||
const auto bg = Ui::CreateChild<Ui::RpWidget>(container);
|
||||
bg->resize(singleSize);
|
||||
bg->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = QPainter(bg);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::boxBg);
|
||||
p.drawEllipse(bg->rect());
|
||||
}, bg->lifetime());
|
||||
bg->moveToLeft(std::max(0, i * (single - shift)), 0);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
|
||||
bg,
|
||||
users[i],
|
||||
st::premiumGiftsUserpicButton);
|
||||
userpic->moveToLeft(diff, diff);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void GiftBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<UserData*> user,
|
||||
GiftOptions options) {
|
||||
const auto boxWidth = st::boxWideWidth;
|
||||
box->setWidth(boxWidth);
|
||||
box->setNoContentMargin(true);
|
||||
const auto buttonsParent = box->verticalLayout().get();
|
||||
|
||||
struct State {
|
||||
rpl::event_stream<QString> buttonText;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto userpicPadding = st::premiumGiftUserpicPadding;
|
||||
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
buttonsParent,
|
||||
userpicPadding.top()
|
||||
+ userpicPadding.bottom()
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
top,
|
||||
true);
|
||||
|
||||
const auto userpic = UserpicsContainer(top, { user });
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
top->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
userpic->moveToLeft(
|
||||
(width - userpic->width()) / 2,
|
||||
userpicPadding.top());
|
||||
|
||||
const auto center = top->rect().center();
|
||||
const auto size = QSize(
|
||||
userpic->width() * Ui::Premium::MiniStars::kSizeFactor,
|
||||
userpic->height());
|
||||
const auto ministarsRect = QRect(
|
||||
QPoint(center.x() - size.width(), center.y() - size.height()),
|
||||
QPoint(center.x() + size.width(), center.y() + size.height()));
|
||||
stars->setPosition(ministarsRect.topLeft());
|
||||
stars->setSize(ministarsRect.size());
|
||||
}, userpic->lifetime());
|
||||
|
||||
top->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(top);
|
||||
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, top->lifetime());
|
||||
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
buttonsParent,
|
||||
st::infoTopBarClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
buttonsParent->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0, width);
|
||||
}, close->lifetime());
|
||||
|
||||
// Header.
|
||||
const auto &padding = st::premiumGiftAboutPadding;
|
||||
const auto available = boxWidth - padding.left() - padding.right();
|
||||
const auto &stTitle = st::premiumPreviewAboutTitle;
|
||||
auto titleLabel = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_premium_gift_title(),
|
||||
stTitle);
|
||||
titleLabel->resizeToWidth(available);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
std::move(titleLabel)),
|
||||
st::premiumGiftTitlePadding);
|
||||
|
||||
auto textLabel = Ui::CreateLabelWithCustomEmoji(
|
||||
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
|
||||
) | rpl::map(
|
||||
BoostsForGiftText({ user })
|
||||
),
|
||||
{ .session = &user->session() },
|
||||
st::premiumPreviewAbout);
|
||||
textLabel->setTextColorOverride(stTitle.textFg->c);
|
||||
textLabel->resizeToWidth(available);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(textLabel)),
|
||||
padding);
|
||||
|
||||
// List.
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
|
||||
const auto groupValueChangedCallback = [=](int value) {
|
||||
Expects(value < options.size() && value >= 0);
|
||||
auto text = tr::lng_premium_gift_button(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
options[value].costTotal);
|
||||
state->buttonText.fire(std::move(text));
|
||||
};
|
||||
group->setChangedCallback(groupValueChangedCallback);
|
||||
Ui::Premium::AddGiftOptions(
|
||||
buttonsParent,
|
||||
group,
|
||||
options,
|
||||
st::premiumGiftOption);
|
||||
|
||||
// Footer.
|
||||
auto terms = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_premium_gift_terms(
|
||||
lt_link,
|
||||
tr::lng_premium_gift_terms_link(
|
||||
) | rpl::map([=](const QString &t) {
|
||||
return Ui::Text::Link(t, 1);
|
||||
}),
|
||||
Ui::Text::WithEntities),
|
||||
st::premiumGiftTerms);
|
||||
terms->setLink(1, std::make_shared<LambdaClickHandler>([=] {
|
||||
box->closeBox();
|
||||
Settings::ShowPremium(&user->session(), QString());
|
||||
}));
|
||||
terms->resizeToWidth(available);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(box, std::move(terms)),
|
||||
st::premiumGiftTermsPadding);
|
||||
|
||||
// Button.
|
||||
const auto &stButton = st::premiumGiftBox;
|
||||
box->setStyle(stButton);
|
||||
auto raw = Settings::CreateSubscribeButton({
|
||||
controller,
|
||||
box,
|
||||
[] { return u"gift"_q; },
|
||||
state->buttonText.events(),
|
||||
Ui::Premium::GiftGradientStops(),
|
||||
[=] {
|
||||
const auto value = group->current();
|
||||
return (value < options.size() && value >= 0)
|
||||
? options[value].botUrl
|
||||
: QString();
|
||||
},
|
||||
});
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
|
||||
box->setShowFinishedCallback([raw = button.data()] {
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
|
||||
groupValueChangedCallback(0);
|
||||
|
||||
Data::PeerPremiumValue(
|
||||
user
|
||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
||||
box->closeBox();
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void GiftsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
std::vector<not_null<UserData*>> users,
|
||||
not_null<Api::PremiumGiftCodeOptions*> api,
|
||||
const QString &ref) {
|
||||
Expects(!users.empty());
|
||||
|
||||
const auto boxWidth = st::boxWideWidth;
|
||||
box->setWidth(boxWidth);
|
||||
box->setNoContentMargin(true);
|
||||
const auto buttonsParent = box->verticalLayout().get();
|
||||
const auto session = &users.front()->session();
|
||||
|
||||
struct State {
|
||||
rpl::event_stream<QString> buttonText;
|
||||
rpl::variable<bool> confirmButtonBusy = false;
|
||||
rpl::variable<bool> isPaymentComplete = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
const auto userpicPadding = st::premiumGiftUserpicPadding;
|
||||
const auto top = box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
buttonsParent,
|
||||
userpicPadding.top()
|
||||
+ userpicPadding.bottom()
|
||||
+ st::defaultUserpicButton.size.height()));
|
||||
|
||||
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
|
||||
const auto stars = box->lifetime().make_state<ColoredMiniStars>(
|
||||
top,
|
||||
true);
|
||||
|
||||
const auto maxWithUserpic = std::min(users.size(), kUserpicsMax);
|
||||
const auto userpics = UserpicsContainer(
|
||||
top,
|
||||
{ users.begin(), users.begin() + maxWithUserpic });
|
||||
top->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
userpics->moveToLeft(
|
||||
(width - userpics->width()) / 2,
|
||||
userpicPadding.top());
|
||||
|
||||
const auto center = top->rect().center();
|
||||
const auto size = QSize(
|
||||
userpics->width() * Ui::Premium::MiniStars::kSizeFactor,
|
||||
userpics->height());
|
||||
const auto ministarsRect = QRect(
|
||||
QPoint(center.x() - size.width(), center.y() - size.height()),
|
||||
QPoint(center.x() + size.width(), center.y() + size.height()));
|
||||
stars->setPosition(ministarsRect.topLeft());
|
||||
stars->setSize(ministarsRect.size());
|
||||
}, userpics->lifetime());
|
||||
if (const auto rest = users.size() - maxWithUserpic; rest > 0) {
|
||||
const auto badge = CircleBadge(
|
||||
userpics,
|
||||
QChar('+') + QString::number(rest));
|
||||
badge->moveToRight(0, userpics->height() - badge->height());
|
||||
}
|
||||
|
||||
top->paintRequest(
|
||||
) | rpl::start_with_next([=](const QRect &r) {
|
||||
auto p = QPainter(top);
|
||||
|
||||
p.fillRect(r, Qt::transparent);
|
||||
stars->paint(p);
|
||||
}, top->lifetime());
|
||||
|
||||
const auto close = Ui::CreateChild<Ui::IconButton>(
|
||||
buttonsParent,
|
||||
st::infoTopBarClose);
|
||||
close->setClickedCallback([=] { box->closeBox(); });
|
||||
|
||||
buttonsParent->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
close->moveToRight(0, 0, width);
|
||||
}, close->lifetime());
|
||||
|
||||
// Header.
|
||||
const auto &padding = st::premiumGiftAboutPadding;
|
||||
const auto available = boxWidth - padding.left() - padding.right();
|
||||
const auto &stTitle = st::premiumPreviewAboutTitle;
|
||||
auto titleLabel = object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
rpl::conditional(
|
||||
state->isPaymentComplete.value(),
|
||||
tr::lng_premium_gifts_about_paid_title(),
|
||||
tr::lng_premium_gift_title()),
|
||||
stTitle);
|
||||
titleLabel->resizeToWidth(available);
|
||||
box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
std::move(titleLabel)),
|
||||
st::premiumGiftTitlePadding);
|
||||
|
||||
// About.
|
||||
{
|
||||
auto text = rpl::conditional(
|
||||
state->isPaymentComplete.value(),
|
||||
ComplexAboutLabel(
|
||||
users,
|
||||
tr::lng_premium_gifts_about_paid1,
|
||||
tr::lng_premium_gifts_about_paid2,
|
||||
tr::lng_premium_gifts_about_paid3,
|
||||
tr::lng_premium_gifts_about_paid_more
|
||||
) | rpl::map([count = users.size()](TextWithEntities text) {
|
||||
text.append('\n');
|
||||
text.append('\n');
|
||||
text.append(tr::lng_premium_gifts_about_paid_below(
|
||||
tr::now,
|
||||
lt_count,
|
||||
float64(count),
|
||||
Ui::Text::RichLangValue));
|
||||
return text;
|
||||
}),
|
||||
ComplexAboutLabel(
|
||||
users,
|
||||
tr::lng_premium_gifts_about_user1,
|
||||
tr::lng_premium_gifts_about_user2,
|
||||
tr::lng_premium_gifts_about_user3,
|
||||
tr::lng_premium_gifts_about_user_more
|
||||
) | rpl::map(BoostsForGiftText(users))
|
||||
);
|
||||
const auto label = box->addRow(
|
||||
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
|
||||
box,
|
||||
Ui::CreateLabelWithCustomEmoji(
|
||||
box,
|
||||
std::move(text),
|
||||
{ .session = session },
|
||||
st::premiumPreviewAbout)),
|
||||
padding)->entity();
|
||||
label->setTextColorOverride(stTitle.textFg->c);
|
||||
label->resizeToWidth(available);
|
||||
}
|
||||
|
||||
// List.
|
||||
const auto optionsContainer = buttonsParent->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
buttonsParent,
|
||||
object_ptr<Ui::VerticalLayout>(buttonsParent)));
|
||||
const auto options = api->options(users.size());
|
||||
const auto group = std::make_shared<Ui::RadiobuttonGroup>();
|
||||
const auto groupValueChangedCallback = [=](int value) {
|
||||
Expects(value < options.size() && value >= 0);
|
||||
auto text = tr::lng_premium_gift_button(
|
||||
tr::now,
|
||||
lt_cost,
|
||||
options[value].costTotal);
|
||||
state->buttonText.fire(std::move(text));
|
||||
};
|
||||
group->setChangedCallback(groupValueChangedCallback);
|
||||
Ui::Premium::AddGiftOptions(
|
||||
optionsContainer->entity(),
|
||||
group,
|
||||
options,
|
||||
st::premiumGiftOption);
|
||||
optionsContainer->toggleOn(
|
||||
state->isPaymentComplete.value() | rpl::map(!rpl::mappers::_1),
|
||||
anim::type::instant);
|
||||
|
||||
// Summary.
|
||||
{
|
||||
{
|
||||
// Will be hidden after payment.
|
||||
const auto content = optionsContainer->entity();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddDivider(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSubsectionTitle(
|
||||
content,
|
||||
tr::lng_premium_gifts_summary_subtitle());
|
||||
}
|
||||
const auto content = box->addRow(
|
||||
object_ptr<Ui::VerticalLayout>(box),
|
||||
{});
|
||||
auto buttonCallback = [=](PremiumFeature section) {
|
||||
stars->setPaused(true);
|
||||
const auto previewBoxShown = [=](
|
||||
not_null<Ui::BoxContent*> previewBox) {
|
||||
previewBox->boxClosing(
|
||||
) | rpl::start_with_next(crl::guard(box, [=] {
|
||||
stars->setPaused(false);
|
||||
}), previewBox->lifetime());
|
||||
};
|
||||
|
||||
ShowPremiumPreviewBox(
|
||||
controller->uiShow(),
|
||||
section,
|
||||
previewBoxShown,
|
||||
true);
|
||||
};
|
||||
Settings::AddSummaryPremium(
|
||||
content,
|
||||
controller,
|
||||
ref,
|
||||
std::move(buttonCallback));
|
||||
}
|
||||
|
||||
// Footer.
|
||||
{
|
||||
box->addRow(
|
||||
object_ptr<Ui::DividerLabel>(
|
||||
box,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_premium_gifts_terms(
|
||||
lt_link,
|
||||
tr::lng_payments_terms_link(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/tos"_q);
|
||||
}),
|
||||
lt_policy,
|
||||
tr::lng_premium_gifts_terms_policy(
|
||||
) | rpl::map([](const QString &t) {
|
||||
using namespace Ui::Text;
|
||||
return Link(t, u"https://telegram.org/privacy"_q);
|
||||
}),
|
||||
Ui::Text::RichLangValue),
|
||||
st::premiumGiftTerms),
|
||||
st::defaultBoxDividerLabelPadding),
|
||||
{});
|
||||
}
|
||||
|
||||
// Button.
|
||||
const auto &stButton = st::premiumGiftBox;
|
||||
box->setStyle(stButton);
|
||||
auto raw = Settings::CreateSubscribeButton({
|
||||
controller,
|
||||
box,
|
||||
[=] { return ref; },
|
||||
rpl::combine(
|
||||
state->buttonText.events(),
|
||||
state->confirmButtonBusy.value(),
|
||||
state->isPaymentComplete.value()
|
||||
) | rpl::map([](const QString &text, bool busy, bool paid) {
|
||||
return busy
|
||||
? QString()
|
||||
: paid
|
||||
? tr::lng_close(tr::now)
|
||||
: text;
|
||||
}),
|
||||
Ui::Premium::GiftGradientStops(),
|
||||
});
|
||||
raw->setClickedCallback([=] {
|
||||
if (state->confirmButtonBusy.current()) {
|
||||
return;
|
||||
}
|
||||
if (state->isPaymentComplete.current()) {
|
||||
return box->closeBox();
|
||||
}
|
||||
auto invoice = api->invoice(
|
||||
users.size(),
|
||||
api->monthsFromPreset(group->current()));
|
||||
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ users };
|
||||
|
||||
state->confirmButtonBusy = true;
|
||||
const auto show = box->uiShow();
|
||||
const auto weak = Ui::MakeWeak(box.get());
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->window()->setFocus();
|
||||
state->confirmButtonBusy = false;
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
state->isPaymentComplete = true;
|
||||
Ui::StartFireworks(box->parentWidget());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Payments::CheckoutProcess::Start(std::move(invoice), done);
|
||||
});
|
||||
{
|
||||
using namespace Info::Statistics;
|
||||
const auto loadingAnimation = InfiniteRadialAnimationWidget(
|
||||
raw,
|
||||
raw->height() / 2);
|
||||
AddChildToWidgetCenter(raw, loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
auto button = object_ptr<Ui::GradientButton>::fromRaw(raw);
|
||||
button->resizeToWidth(boxWidth - rect::m::sum::h(stButton.buttonPadding));
|
||||
box->setShowFinishedCallback([raw = button.data()] {
|
||||
raw->startGlareAnimation();
|
||||
});
|
||||
box->addButton(std::move(button));
|
||||
|
||||
groupValueChangedCallback(0);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::GiftCodeLink MakeGiftCodeLink(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &slug) {
|
||||
|
@ -793,16 +156,55 @@ void GiftsBox(
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakeHiddenPeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionNavigation*> controller) {
|
||||
auto result = object_ptr<Ui::RpWidget>(parent);
|
||||
const auto raw = result.data();
|
||||
|
||||
const auto &st = st::giveawayGiftCodeUserpic;
|
||||
raw->resize(raw->width(), st.photoSize);
|
||||
|
||||
const auto userpic = Ui::CreateChild<Ui::RpWidget>(raw);
|
||||
const auto usize = st.photoSize;
|
||||
userpic->resize(usize, usize);
|
||||
userpic->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(userpic);
|
||||
Ui::EmptyUserpic::PaintHiddenAuthor(p, 0, 0, usize, usize);
|
||||
}, userpic->lifetime());
|
||||
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||
raw,
|
||||
tr::lng_gift_from_hidden(),
|
||||
st::giveawayGiftCodeValue);
|
||||
raw->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto position = st::giveawayGiftCodeNamePosition;
|
||||
label->resizeToNaturalWidth(width - position.x());
|
||||
label->moveToLeft(position.x(), position.y(), width);
|
||||
const auto top = (raw->height() - userpic->height()) / 2;
|
||||
userpic->moveToLeft(0, top, width);
|
||||
}, label->lifetime());
|
||||
|
||||
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
label->setTextColorOverride(st::windowFg->c);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
object_ptr<Ui::RpWidget> value,
|
||||
style::margins valueMargins) {
|
||||
table->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeLabel),
|
||||
(label
|
||||
? object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeLabel)
|
||||
: object_ptr<Ui::FlatLabel>(nullptr)),
|
||||
std::move(value),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
valueMargins);
|
||||
|
@ -957,180 +359,6 @@ void ShowAlreadyPremiumToast(
|
|||
|
||||
} // namespace
|
||||
|
||||
GiftPremiumValidator::GiftPremiumValidator(
|
||||
not_null<Window::SessionController*> controller)
|
||||
: _controller(controller)
|
||||
, _api(&_controller->session().mtp()) {
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::cancel() {
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showChoosePeerBox(const QString &ref) {
|
||||
if (_manyGiftsLifetime) {
|
||||
return;
|
||||
}
|
||||
using namespace Api;
|
||||
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
|
||||
_controller->session().user());
|
||||
const auto show = _controller->uiShow();
|
||||
api->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
show->showToast(error);
|
||||
}, [=] {
|
||||
const auto maxAmount = *ranges::max_element(api->availablePresets());
|
||||
|
||||
class Controller final : public ContactsBoxController {
|
||||
public:
|
||||
Controller(
|
||||
not_null<Main::Session*> session,
|
||||
Fn<bool(int)> checkErrorCallback)
|
||||
: ContactsBoxController(session)
|
||||
, _checkErrorCallback(std::move(checkErrorCallback)) {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<PeerListRow> createRow(
|
||||
not_null<UserData*> user) override {
|
||||
if (user->isSelf()
|
||||
|| user->isBot()
|
||||
|| user->isServiceUser()
|
||||
|| user->isInaccessible()) {
|
||||
return nullptr;
|
||||
}
|
||||
return ContactsBoxController::createRow(user);
|
||||
}
|
||||
|
||||
void rowClicked(not_null<PeerListRow*> row) override {
|
||||
const auto checked = !row->checked();
|
||||
if (checked
|
||||
&& _checkErrorCallback
|
||||
&& _checkErrorCallback(
|
||||
delegate()->peerListSelectedRowsCount())) {
|
||||
return;
|
||||
}
|
||||
delegate()->peerListSetRowChecked(row, checked);
|
||||
}
|
||||
|
||||
private:
|
||||
const Fn<bool(int)> _checkErrorCallback;
|
||||
|
||||
};
|
||||
auto initBox = [=](not_null<PeerListBox*> peersBox) {
|
||||
const auto ignoreClose = peersBox->lifetime().make_state<bool>(0);
|
||||
|
||||
auto process = [=] {
|
||||
const auto selected = peersBox->collectSelectedRows();
|
||||
const auto users = ranges::views::all(
|
||||
selected
|
||||
) | ranges::views::transform([](not_null<PeerData*> p) {
|
||||
return p->asUser();
|
||||
}) | ranges::views::filter([](UserData *u) -> bool {
|
||||
return u;
|
||||
}) | ranges::to<std::vector<not_null<UserData*>>>();
|
||||
if (users.empty()) {
|
||||
show->showToast(
|
||||
tr::lng_settings_gift_premium_choose(tr::now));
|
||||
return;
|
||||
}
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
(*ignoreClose) = true;
|
||||
peersBox->closeBox();
|
||||
};
|
||||
|
||||
peersBox->setTitle(tr::lng_premium_gift_title());
|
||||
peersBox->addButton(
|
||||
tr::lng_settings_gift_premium_users_confirm(),
|
||||
std::move(process));
|
||||
peersBox->addButton(tr::lng_cancel(), [=] {
|
||||
peersBox->closeBox();
|
||||
});
|
||||
peersBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!(*ignoreClose)) {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}
|
||||
}, peersBox->lifetime());
|
||||
};
|
||||
|
||||
auto listController = std::make_unique<Controller>(
|
||||
&_controller->session(),
|
||||
[=](int count) {
|
||||
if (count <= maxAmount) {
|
||||
return false;
|
||||
}
|
||||
show->showToast(tr::lng_settings_gift_premium_users_error(
|
||||
tr::now,
|
||||
lt_count,
|
||||
maxAmount));
|
||||
return true;
|
||||
});
|
||||
show->showBox(
|
||||
Box<PeerListBox>(
|
||||
std::move(listController),
|
||||
std::move(initBox)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
|
||||
}, _manyGiftsLifetime);
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showChosenPeerBox(
|
||||
not_null<UserData*> user,
|
||||
const QString &ref) {
|
||||
if (_manyGiftsLifetime) {
|
||||
return;
|
||||
}
|
||||
using namespace Api;
|
||||
const auto api = _manyGiftsLifetime.make_state<PremiumGiftCodeOptions>(
|
||||
_controller->session().user());
|
||||
const auto show = _controller->uiShow();
|
||||
api->request(
|
||||
) | rpl::start_with_error_done([=](const QString &error) {
|
||||
show->showToast(error);
|
||||
}, [=] {
|
||||
const auto users = std::vector<not_null<UserData*>>{ user };
|
||||
const auto giftBox = show->show(
|
||||
Box(GiftsBox, _controller, users, api, ref));
|
||||
giftBox->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
_manyGiftsLifetime.destroy();
|
||||
}, giftBox->lifetime());
|
||||
}, _manyGiftsLifetime);
|
||||
}
|
||||
|
||||
void GiftPremiumValidator::showBox(not_null<UserData*> user) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
_requestId = _api.request(MTPusers_GetFullUser(
|
||||
user->inputUser
|
||||
)).done([=](const MTPusers_UserFull &result) {
|
||||
if (!_requestId) {
|
||||
// Canceled.
|
||||
return;
|
||||
}
|
||||
_requestId = 0;
|
||||
// _controller->api().processFullPeer(peer, result);
|
||||
_controller->session().data().processUsers(result.data().vusers());
|
||||
_controller->session().data().processChats(result.data().vchats());
|
||||
|
||||
const auto &fullUser = result.data().vfull_user().data();
|
||||
auto options = GiftOptionFromTL(fullUser);
|
||||
if (!options.empty()) {
|
||||
_controller->show(
|
||||
Box(GiftBox, _controller, user, std::move(options)));
|
||||
}
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
rpl::producer<QString> GiftDurationValue(int months) {
|
||||
return GiftDurationPhrase(months)(
|
||||
lt_count,
|
||||
|
@ -1708,6 +936,77 @@ void ResolveGiveawayInfo(
|
|||
crl::guard(controller, show));
|
||||
}
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry) {
|
||||
auto table = container->add(
|
||||
object_ptr<Ui::TableLayout>(
|
||||
container,
|
||||
st::giveawayGiftCodeTable),
|
||||
st::giveawayGiftCodeTableMargin);
|
||||
const auto peerId = PeerId(entry.barePeerId);
|
||||
if (peerId) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
controller,
|
||||
peerId);
|
||||
} else {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_credits_box_history_entry_peer_in(),
|
||||
MakeHiddenPeerTableValue(table, controller),
|
||||
st::giveawayGiftCodePeerMargin);
|
||||
}
|
||||
if (!entry.date.isNull()) {
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_link_label_date(),
|
||||
rpl::single(Ui::Text::WithEntities(langDateTime(entry.date))));
|
||||
}
|
||||
if (entry.limitedCount > 0) {
|
||||
auto amount = rpl::single(TextWithEntities{
|
||||
QString::number(entry.limitedCount)
|
||||
});
|
||||
AddTableRow(
|
||||
table,
|
||||
tr::lng_gift_availability(),
|
||||
((entry.limitedLeft > 0)
|
||||
? tr::lng_gift_availability_left(
|
||||
lt_count,
|
||||
rpl::single(entry.limitedLeft * 1.),
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)
|
||||
: tr::lng_gift_availability_none(
|
||||
lt_amount,
|
||||
std::move(amount),
|
||||
Ui::Text::WithEntities)));
|
||||
}
|
||||
if (!entry.description.empty()) {
|
||||
const auto session = &controller->session();
|
||||
const auto makeContext = [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = std::move(update),
|
||||
};
|
||||
};
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
table,
|
||||
rpl::single(entry.description),
|
||||
st::giveawayGiftMessage,
|
||||
st::defaultPopupMenu,
|
||||
makeContext);
|
||||
label->setSelectable(true);
|
||||
table->addRow(
|
||||
nullptr,
|
||||
std::move(label),
|
||||
st::giveawayGiftCodeLabelMargin,
|
||||
st::giveawayGiftCodeValueMargin);
|
||||
}
|
||||
}
|
||||
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
|
|
@ -9,8 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Api {
|
||||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
@ -29,29 +27,9 @@ class VerticalLayout;
|
|||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
class SessionNavigation;
|
||||
} // namespace Window
|
||||
|
||||
class GiftPremiumValidator final {
|
||||
public:
|
||||
GiftPremiumValidator(not_null<Window::SessionController*> controller);
|
||||
|
||||
void showBox(not_null<UserData*> user);
|
||||
void showChoosePeerBox(const QString &ref);
|
||||
void showChosenPeerBox(not_null<UserData*> user, const QString &ref);
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
rpl::lifetime _manyGiftsLifetime;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> GiftDurationValue(int months);
|
||||
[[nodiscard]] QString GiftDuration(int months);
|
||||
|
||||
|
@ -76,6 +54,10 @@ void ResolveGiveawayInfo(
|
|||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
|
|
@ -447,6 +447,8 @@ void PeerListBox::addSelectItem(
|
|||
? tr::lng_saved_short(tr::now)
|
||||
: (respect && peer->isRepliesChat())
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: (respect && peer->isVerifyCodes())
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer->shortName();
|
||||
addSelectItem(
|
||||
peer->id.value,
|
||||
|
@ -625,6 +627,8 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
|||
? tr::lng_saved_messages(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: generateName();
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
@ -695,6 +699,8 @@ QString PeerListRow::generateShortName() {
|
|||
? tr::lng_saved_short(tr::now)
|
||||
: _isRepliesMessagesChat
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: _isVerifyCodesChat
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer()->shortName();
|
||||
}
|
||||
|
||||
|
@ -715,10 +721,11 @@ PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback(
|
|||
return ForceRoundUserpicCallback(peer);
|
||||
}
|
||||
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
|
||||
using namespace Ui;
|
||||
if (saved) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
|
||||
} else if (replies) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
|
||||
} else {
|
||||
peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
|
||||
}
|
||||
|
@ -757,12 +764,14 @@ int PeerListRow::paintNameIconGetWidth(
|
|||
int availableWidth,
|
||||
int outerWidth,
|
||||
bool selected) {
|
||||
if (special()
|
||||
if (_skipPeerBadge
|
||||
|| special()
|
||||
|| !_savedMessagesStatus.isEmpty()
|
||||
|| _isRepliesMessagesChat) {
|
||||
|| _isRepliesMessagesChat
|
||||
|| _isVerifyCodesChat) {
|
||||
return 0;
|
||||
}
|
||||
return _bagde.drawGetWidth(
|
||||
return _badge.drawGetWidth(
|
||||
p,
|
||||
QRect(
|
||||
nameLeft,
|
||||
|
@ -874,12 +883,13 @@ void PeerListRow::paintDisabledCheckUserpic(
|
|||
auto iconBorderPen = st.checkbox.check.border->p;
|
||||
iconBorderPen.setWidth(st.checkbox.selectWidth);
|
||||
|
||||
const auto size = userpicRadius * 2;
|
||||
if (!_savedMessagesStatus.isEmpty()) {
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else if (_isRepliesMessagesChat) {
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, size);
|
||||
} else {
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
|
||||
peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, size);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1069,10 +1079,13 @@ void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
|
|||
void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
|
||||
const auto savedMessagesStatus = _controller->savedMessagesChatStatus();
|
||||
if (!savedMessagesStatus.isEmpty() && !row->special()) {
|
||||
if (row->peer()->isSelf()) {
|
||||
const auto peer = row->peer();
|
||||
if (peer->isSelf()) {
|
||||
row->setSavedMessagesChatStatus(savedMessagesStatus);
|
||||
} else if (row->peer()->isRepliesChat()) {
|
||||
} else if (peer->isRepliesChat()) {
|
||||
row->setIsRepliesMessagesChat(true);
|
||||
} else if (peer->isVerifyCodes()) {
|
||||
row->setIsVerifyCodesChat(true);
|
||||
}
|
||||
}
|
||||
_rowsById.emplace(row->id(), row);
|
||||
|
|
|
@ -200,6 +200,9 @@ public:
|
|||
void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
|
||||
_isRepliesMessagesChat = isRepliesMessagesChat;
|
||||
}
|
||||
void setIsVerifyCodesChat(bool isVerifyCodesChat) {
|
||||
_isVerifyCodesChat = isVerifyCodesChat;
|
||||
}
|
||||
|
||||
template <typename UpdateCallback>
|
||||
void setChecked(
|
||||
|
@ -251,6 +254,10 @@ public:
|
|||
return _nameFirstLetters;
|
||||
}
|
||||
|
||||
void setSkipPeerBadge(bool skip) {
|
||||
_skipPeerBadge = skip;
|
||||
}
|
||||
|
||||
virtual void lazyInitialize(const style::PeerListItem &st);
|
||||
virtual void paintStatusText(
|
||||
Painter &p,
|
||||
|
@ -288,7 +295,7 @@ private:
|
|||
std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
|
||||
Ui::Text::String _name;
|
||||
Ui::Text::String _status;
|
||||
Ui::PeerBadge _bagde;
|
||||
Ui::PeerBadge _badge;
|
||||
StatusType _statusType = StatusType::Online;
|
||||
crl::time _statusValidTill = 0;
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
@ -299,6 +306,8 @@ private:
|
|||
bool _initialized : 1 = false;
|
||||
bool _isSearchResult : 1 = false;
|
||||
bool _isRepliesMessagesChat : 1 = false;
|
||||
bool _isVerifyCodesChat : 1 = false;
|
||||
bool _skipPeerBadge : 1 = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -842,6 +842,7 @@ auto ChooseRecipientBoxController::createRow(
|
|||
? !_filter(history)
|
||||
: ((peer->isBroadcast() && !Data::CanSendAnything(peer))
|
||||
|| peer->isRepliesChat()
|
||||
|| peer->isVerifyCodes()
|
||||
|| (peer->isUser() && (_premiumRequiredError
|
||||
? !peer->asUser()->canSendIgnoreRequirePremium()
|
||||
: !Data::CanSendAnything(peer))));
|
||||
|
|
|
@ -269,7 +269,8 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
|||
const auto peer = this->peer();
|
||||
const auto saved = peer->isSelf();
|
||||
const auto replies = peer->isRepliesChat();
|
||||
auto userpic = (saved || replies)
|
||||
const auto verifyCodes = peer->isVerifyCodes();
|
||||
auto userpic = (saved || replies || verifyCodes)
|
||||
? Ui::PeerUserpicView()
|
||||
: ensureUserpicView();
|
||||
auto paint = [=](
|
||||
|
@ -302,6 +303,7 @@ PaintRoundImageCallback ForbiddenRow::generatePaintUserpicCallback(
|
|||
repaint = (_paletteVersion != style::PaletteVersion())
|
||||
|| (!saved
|
||||
&& !replies
|
||||
&& !verifyCodes
|
||||
&& (_userpicKey != peer->userpicUniqueKey(userpic)));
|
||||
}
|
||||
if (repaint) {
|
||||
|
|
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "data/components/credits.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_peer.h"
|
||||
|
@ -1591,6 +1592,9 @@ void Controller::fillBotBalanceButton() {
|
|||
|
||||
auto &lifetime = _controls.buttonsLayout->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
if (const auto balance = _peer->session().credits().balance(_peer->id)) {
|
||||
state->balance = QString::number(balance);
|
||||
}
|
||||
|
||||
const auto wrap = _controls.buttonsLayout->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
|
@ -1604,7 +1608,7 @@ void Controller::fillBotBalanceButton() {
|
|||
},
|
||||
st::manageGroupButton,
|
||||
{})));
|
||||
wrap->toggle(false, anim::type::instant);
|
||||
wrap->toggle(!state->balance.current().isEmpty(), anim::type::instant);
|
||||
|
||||
const auto button = wrap->entity();
|
||||
{
|
||||
|
|
|
@ -1131,11 +1131,14 @@ void ShowEditPeerPermissionsBox(
|
|||
disabledByAdminRights,
|
||||
tr::lng_rights_permission_cant_edit(tr::now));
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
if (channel->isPublic()
|
||||
|| (channel->isMegagroup() && channel->linkedChat())) {
|
||||
if (channel->isPublic()) {
|
||||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_unavailable(tr::now));
|
||||
} else if (channel->isMegagroup() && channel->linkedChat()) {
|
||||
result.emplace(
|
||||
Flag::ChangeInfo | Flag::PinMessages,
|
||||
tr::lng_rights_permission_in_discuss(tr::now));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -8,23 +8,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/report_messages_box.h"
|
||||
|
||||
#include "api/api_report.h"
|
||||
#include "core/application.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/report_box.h"
|
||||
#include "ui/boxes/report_box_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> Report(
|
||||
not_null<PeerData*> peer,
|
||||
std::variant<
|
||||
v::null_t,
|
||||
MessageIdsList,
|
||||
not_null<PhotoData*>,
|
||||
StoryId> data,
|
||||
std::variant<v::null_t, not_null<PhotoData*>> data,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto source = v::match(data, [](const MessageIdsList &ids) {
|
||||
return Ui::ReportSource::Message;
|
||||
|
@ -62,64 +67,151 @@ namespace {
|
|||
|
||||
} // namespace
|
||||
|
||||
object_ptr<Ui::BoxContent> ReportItemsBox(
|
||||
not_null<PeerData*> peer,
|
||||
MessageIdsList ids) {
|
||||
return Report(peer, ids, nullptr);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo) {
|
||||
return Report(peer, photo, nullptr);
|
||||
}
|
||||
|
||||
void ShowReportPeerBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer) {
|
||||
struct State {
|
||||
QPointer<Ui::BoxContent> reasonBox;
|
||||
QPointer<Ui::BoxContent> detailsBox;
|
||||
MessageIdsList ids;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto chosen = [=](Ui::ReportReason reason) {
|
||||
const auto send = [=](const QString &text) {
|
||||
window->clearChooseReportMessages();
|
||||
Api::SendReport(
|
||||
window->uiShow(),
|
||||
peer,
|
||||
reason,
|
||||
text,
|
||||
std::move(state->ids));
|
||||
if (const auto strong = state->reasonBox.data()) {
|
||||
strong->closeBox();
|
||||
void ShowReportMessageBox(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<MsgId> &ids,
|
||||
const std::vector<StoryId> &stories,
|
||||
const style::ReportBox *stOverride) {
|
||||
const auto report = Api::CreateReportMessagesOrStoriesCallback(
|
||||
show,
|
||||
peer);
|
||||
|
||||
auto performRequest = [=](
|
||||
const auto &repeatRequest,
|
||||
Data::ReportInput reportInput) -> void {
|
||||
constexpr auto kToastDuration = crl::time(4000);
|
||||
report(reportInput, [=](const Api::ReportResult &result) {
|
||||
if (!result.error.isEmpty()) {
|
||||
if (result.error == u"MESSAGE_ID_REQUIRED"_q) {
|
||||
const auto widget = show->toastParent();
|
||||
const auto window = Core::App().findWindow(widget);
|
||||
const auto controller = window
|
||||
? window->sessionController()
|
||||
: nullptr;
|
||||
if (controller) {
|
||||
const auto callback = [=](std::vector<MsgId> ids) {
|
||||
auto copy = reportInput;
|
||||
copy.ids = std::move(ids);
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
};
|
||||
controller->showChooseReportMessages(
|
||||
peer,
|
||||
reportInput,
|
||||
std::move(callback));
|
||||
}
|
||||
} else {
|
||||
show->showToast(result.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (const auto strong = state->detailsBox.data()) {
|
||||
strong->closeBox();
|
||||
if (!result.options.empty() || result.commentOption) {
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(
|
||||
rpl::single(
|
||||
result.title.isEmpty()
|
||||
? reportInput.optionText
|
||||
: result.title));
|
||||
|
||||
for (const auto &option : result.options) {
|
||||
const auto button = Ui::AddReportOptionButton(
|
||||
box->verticalLayout(),
|
||||
option.text,
|
||||
stOverride);
|
||||
button->setClickedCallback([=] {
|
||||
auto copy = reportInput;
|
||||
copy.optionId = option.id;
|
||||
copy.optionText = option.text;
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
});
|
||||
}
|
||||
if (const auto commentOption = result.commentOption) {
|
||||
constexpr auto kReportReasonLengthMax = 512;
|
||||
const auto &st = stOverride
|
||||
? stOverride
|
||||
: &st::defaultReportBox;
|
||||
Ui::AddReportDetailsIconButton(box);
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
const auto details = box->addRow(
|
||||
object_ptr<Ui::InputField>(
|
||||
box,
|
||||
st->field,
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
commentOption->optional
|
||||
? tr::lng_report_details_optional()
|
||||
: tr::lng_report_details_non_optional(),
|
||||
QString()));
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
Ui::AddSkip(box->verticalLayout());
|
||||
{
|
||||
const auto container = box->verticalLayout();
|
||||
auto label = object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
tr::lng_report_details_message_about(),
|
||||
st::boxDividerLabel);
|
||||
label->setTextColorOverride(st->dividerFg->c);
|
||||
using namespace Ui;
|
||||
const auto widget = container->add(
|
||||
object_ptr<PaddingWrap<>>(
|
||||
container,
|
||||
std::move(label),
|
||||
st::defaultBoxDividerLabelPadding));
|
||||
const auto background
|
||||
= CreateChild<BoxContentDivider>(
|
||||
widget,
|
||||
st::boxDividerHeight,
|
||||
st->dividerBg,
|
||||
RectPart::Top | RectPart::Bottom);
|
||||
background->lower();
|
||||
widget->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
background->resize(s);
|
||||
}, background->lifetime());
|
||||
}
|
||||
details->setMaxLength(kReportReasonLengthMax);
|
||||
box->setFocusCallback([=] {
|
||||
details->setFocusFast();
|
||||
});
|
||||
const auto submit = [=] {
|
||||
if (!commentOption->optional
|
||||
&& details->empty()) {
|
||||
details->showError();
|
||||
details->setFocus();
|
||||
return;
|
||||
}
|
||||
auto copy = reportInput;
|
||||
copy.optionId = commentOption->id;
|
||||
copy.comment = details->getLastText();
|
||||
repeatRequest(repeatRequest, std::move(copy));
|
||||
};
|
||||
details->submits(
|
||||
) | rpl::start_with_next(submit, details->lifetime());
|
||||
box->addButton(tr::lng_report_button(), submit);
|
||||
} else {
|
||||
box->addButton(
|
||||
tr::lng_close(),
|
||||
[=] { show->hideLayer(); });
|
||||
}
|
||||
if (!reportInput.optionId.isNull()) {
|
||||
box->addLeftButton(
|
||||
tr::lng_create_group_back(),
|
||||
[=] { box->closeBox(); });
|
||||
}
|
||||
}));
|
||||
} else if (result.successful) {
|
||||
show->showToast(
|
||||
tr::lng_report_thanks(tr::now),
|
||||
kToastDuration);
|
||||
show->hideLayer();
|
||||
}
|
||||
};
|
||||
if (reason == Ui::ReportReason::Fake
|
||||
|| reason == Ui::ReportReason::Other) {
|
||||
state->ids = {};
|
||||
state->detailsBox = window->show(
|
||||
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
|
||||
return;
|
||||
}
|
||||
window->showChooseReportMessages(peer, reason, [=](
|
||||
MessageIdsList ids) {
|
||||
state->ids = std::move(ids);
|
||||
state->detailsBox = window->show(
|
||||
Box(Ui::ReportDetailsBox, st::defaultReportBox, send));
|
||||
});
|
||||
};
|
||||
state->reasonBox = window->show(Box(
|
||||
Ui::ReportReasonBox,
|
||||
st::defaultReportBox,
|
||||
(peer->isBroadcast()
|
||||
? Ui::ReportSource::Channel
|
||||
: peer->isUser()
|
||||
? Ui::ReportSource::Bot
|
||||
: Ui::ReportSource::Group),
|
||||
chosen));
|
||||
performRequest(performRequest, { .ids = ids, .stories = stories });
|
||||
}
|
||||
|
|
|
@ -12,20 +12,22 @@ class object_ptr;
|
|||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Main
|
||||
namespace style {
|
||||
struct ReportBox;
|
||||
} // namespace style
|
||||
|
||||
class PeerData;
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportItemsBox(
|
||||
not_null<PeerData*> peer,
|
||||
MessageIdsList ids);
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> ReportProfilePhotoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<PhotoData*> photo);
|
||||
void ShowReportPeerBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void ShowReportMessageBox(
|
||||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<MsgId> &ids,
|
||||
const std::vector<StoryId> &stories,
|
||||
const style::ReportBox *stOverride = nullptr);
|
||||
|
|
|
@ -272,10 +272,13 @@ void SendCreditsBox(
|
|||
state->confirmButtonBusy = true;
|
||||
session->api().request(
|
||||
MTPpayments_SendStarsForm(
|
||||
MTP_flags(0),
|
||||
MTP_long(form->formId),
|
||||
form->inputInvoice)
|
||||
).done([=](auto result) {
|
||||
).done([=](const MTPpayments_PaymentResult &result) {
|
||||
result.match([&](const MTPDpayments_paymentResult &data) {
|
||||
session->api().applyUpdates(data.vupdates());
|
||||
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
|
||||
});
|
||||
if (weak) {
|
||||
state->confirmButtonBusy = false;
|
||||
box->closeBox();
|
||||
|
@ -311,41 +314,22 @@ void SendCreditsBox(
|
|||
AddChildToWidgetCenter(button.data(), loadingAnimation);
|
||||
loadingAnimation->showOn(state->confirmButtonBusy.value());
|
||||
}
|
||||
{
|
||||
auto buttonText = tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue);
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st::creditsBoxButtonLabel);
|
||||
std::move(
|
||||
buttonText
|
||||
) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = [=] { buttonLabel->update(); },
|
||||
});
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setTextColorOverride(
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
state->confirmButtonBusy.value(
|
||||
) | rpl::start_with_next([=](bool busy) {
|
||||
buttonLabel->setVisible(!busy);
|
||||
}, buttonLabel->lifetime());
|
||||
}
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
rpl::combine(
|
||||
tr::lng_credits_box_out_confirm(
|
||||
lt_count,
|
||||
rpl::single(form->invoice.amount) | tr::to_count(),
|
||||
lt_emoji,
|
||||
rpl::single(CreditsEmojiSmall(session)),
|
||||
Ui::Text::RichLangValue),
|
||||
state->confirmButtonBusy.value()
|
||||
) | rpl::map([](TextWithEntities &&text, bool busy) {
|
||||
return busy ? TextWithEntities() : std::move(text);
|
||||
}),
|
||||
session,
|
||||
st::creditsBoxButtonLabel,
|
||||
box->getDelegate()->style().button.textFg->c);
|
||||
|
||||
const auto buttonWidth = st::boxWidth
|
||||
- rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding);
|
||||
|
@ -405,4 +389,73 @@ TextWithEntities CreditsEmojiSmall(not_null<Main::Session*> session) {
|
|||
QString(QChar(0x2B50)));
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
const auto buttonLabel = Ui::CreateChild<Ui::FlatLabel>(
|
||||
button,
|
||||
rpl::single(QString()),
|
||||
st);
|
||||
rpl::duplicate(
|
||||
text
|
||||
) | rpl::filter([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}) | rpl::start_with_next([=](const TextWithEntities &text) {
|
||||
buttonLabel->setMarkedText(
|
||||
text,
|
||||
context([=] { buttonLabel->update(); }));
|
||||
}, buttonLabel->lifetime());
|
||||
if (textFg) {
|
||||
buttonLabel->setTextColorOverride(textFg);
|
||||
}
|
||||
button->sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
buttonLabel->moveToLeft(
|
||||
(size.width() - buttonLabel->width()) / 2,
|
||||
(size.height() - buttonLabel->height()) / 2);
|
||||
}, buttonLabel->lifetime());
|
||||
buttonLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
buttonLabel->showOn(std::move(
|
||||
text
|
||||
) | rpl::map([=](const TextWithEntities &text) {
|
||||
return !text.text.isEmpty();
|
||||
}));
|
||||
return buttonLabel;
|
||||
}
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg) {
|
||||
return SetButtonMarkedLabel(button, text, [=](Fn<void()> update) {
|
||||
return Core::MarkedTextContext{
|
||||
.session = session,
|
||||
.customEmojiRepaint = update,
|
||||
};
|
||||
}, st, textFg);
|
||||
}
|
||||
|
||||
void SendStarGift(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done) {
|
||||
session->api().request(MTPpayments_SendStarsForm(
|
||||
MTP_long(data->formId),
|
||||
data->inputInvoice
|
||||
)).done([=](const MTPpayments_PaymentResult &result) {
|
||||
result.match([&](const MTPDpayments_paymentResult &data) {
|
||||
session->api().applyUpdates(data.vupdates());
|
||||
}, [](const MTPDpayments_paymentVerificationNeeded &data) {
|
||||
});
|
||||
done(std::nullopt);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
done(error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
class HistoryItem;
|
||||
|
||||
namespace style {
|
||||
struct FlatLabel;
|
||||
} // namespace style
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -19,7 +23,9 @@ struct CreditsFormData;
|
|||
|
||||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class GenericBox;
|
||||
class FlatLabel;
|
||||
|
||||
void SendCreditsBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
|
@ -32,4 +38,23 @@ void SendCreditsBox(
|
|||
[[nodiscard]] TextWithEntities CreditsEmojiSmall(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
Fn<std::any(Fn<void()> update)> context,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
|
||||
not_null<FlatLabel*> SetButtonMarkedLabel(
|
||||
not_null<RpWidget*> button,
|
||||
rpl::producer<TextWithEntities> text,
|
||||
not_null<Main::Session*> session,
|
||||
const style::FlatLabel &st,
|
||||
std::optional<QColor> textFg = {});
|
||||
|
||||
void SendStarGift(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Payments::CreditsFormData> data,
|
||||
Fn<void(std::optional<QString>)> done);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/message_field.h"
|
||||
#include "menu/menu_send.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
|
@ -1304,13 +1305,17 @@ void SendFilesBox::setupCaption() {
|
|||
: (_limits & SendFilesAllow::EmojiWithoutPremium);
|
||||
};
|
||||
const auto show = _show;
|
||||
InitMessageFieldHandlers(
|
||||
&show->session(),
|
||||
show,
|
||||
_caption.data(),
|
||||
[=] { return show->paused(Window::GifPauseReason::Layer); },
|
||||
allow,
|
||||
&_st.files.caption);
|
||||
InitMessageFieldHandlers({
|
||||
.session = &show->session(),
|
||||
.show = show,
|
||||
.field = _caption.data(),
|
||||
.customEmojiPaused = [=] {
|
||||
return show->paused(Window::GifPauseReason::Layer);
|
||||
},
|
||||
.allowPremiumEmoji = allow,
|
||||
.fieldStyle = &_st.files.caption,
|
||||
});
|
||||
setupCaptionAutocomplete();
|
||||
Ui::Emoji::SuggestionsController::Init(
|
||||
getDelegate()->outerContainer(),
|
||||
_caption,
|
||||
|
@ -1370,6 +1375,59 @@ void SendFilesBox::setupCaption() {
|
|||
}, _caption->lifetime());
|
||||
}
|
||||
|
||||
void SendFilesBox::setupCaptionAutocomplete() {
|
||||
if (!_captionToPeer || !_caption) {
|
||||
return;
|
||||
}
|
||||
const auto parent = getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(_autocomplete, {
|
||||
.parent = parent,
|
||||
.show = _show,
|
||||
.field = _caption.data(),
|
||||
.peer = _captionToPeer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
.sendMenuDetails = _sendMenuDetails,
|
||||
});
|
||||
const auto raw = _autocomplete.get();
|
||||
const auto scheduled = std::make_shared<bool>();
|
||||
const auto recountPostponed = [=] {
|
||||
if (*scheduled) {
|
||||
return;
|
||||
}
|
||||
*scheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
*scheduled = false;
|
||||
|
||||
auto field = Ui::MapFrom(parent, this, _caption->geometry());
|
||||
_autocomplete->setBoundings(QRect(
|
||||
field.x() - _caption->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
width(),
|
||||
(field.y()
|
||||
+ _st.files.caption.textMargins.top()
|
||||
+ _st.files.caption.placeholderShift
|
||||
+ _st.files.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)_caption.data(); w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == parent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::checkCharsLimitation() {
|
||||
const auto limits = Data::PremiumLimits(&_show->session());
|
||||
const auto caption = (_caption && !_caption->isHidden())
|
||||
|
@ -1685,6 +1743,14 @@ void SendFilesBox::updateControlsGeometry() {
|
|||
_scroll->move(0, _titleHeight.current());
|
||||
}
|
||||
|
||||
void SendFilesBox::showFinished() {
|
||||
if (const auto raw = _autocomplete.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::setInnerFocus() {
|
||||
if (_caption && !_caption->isHidden()) {
|
||||
_caption->setFocusFast();
|
||||
|
|
|
@ -28,6 +28,7 @@ enum class SendType;
|
|||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
class Show;
|
||||
class FieldAutocomplete;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
|
@ -126,6 +127,8 @@ public:
|
|||
_cancelledCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void showFinished() override;
|
||||
|
||||
~SendFilesBox();
|
||||
|
||||
protected:
|
||||
|
@ -206,6 +209,7 @@ private:
|
|||
void refreshControls(bool initial = false);
|
||||
void setupSendWayControls();
|
||||
void setupCaption();
|
||||
void setupCaptionAutocomplete();
|
||||
|
||||
void setupEmojiPanel();
|
||||
void updateSendWayControls();
|
||||
|
@ -257,6 +261,7 @@ private:
|
|||
SendFilesLimits _limits = {};
|
||||
Fn<MenuDetails()> _sendMenuDetails;
|
||||
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
|
||||
|
||||
PeerData *_captionToPeer = nullptr;
|
||||
SendFilesCheck _check;
|
||||
SendFilesConfirmed _confirmedCallback;
|
||||
|
@ -268,6 +273,7 @@ private:
|
|||
bool _invertCaption = false;
|
||||
|
||||
object_ptr<Ui::InputField> _caption = { nullptr };
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> _autocomplete;
|
||||
TextWithTags _prefilledCaptionText;
|
||||
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
|
||||
|
|
|
@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "boxes/send_gif_with_caption_box.h"
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/field_autocomplete.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
|
@ -30,9 +32,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/controls/emoji_button.h"
|
||||
#include "ui/controls/emoji_button_factory.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/rect.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
@ -226,6 +229,7 @@ namespace {
|
|||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> done) {
|
||||
const auto window = Core::App().findWindow(box);
|
||||
|
@ -255,6 +259,61 @@ void SendGifWithCaptionBox(
|
|||
return true;
|
||||
});
|
||||
|
||||
const auto sendMenuDetails = [=] { return details; };
|
||||
struct Autocomplete {
|
||||
std::unique_ptr<ChatHelpers::FieldAutocomplete> dropdown;
|
||||
bool geometryUpdateScheduled = false;
|
||||
};
|
||||
const auto autocomplete = box->lifetime().make_state<Autocomplete>();
|
||||
const auto outer = box->getDelegate()->outerContainer();
|
||||
ChatHelpers::InitFieldAutocomplete(autocomplete->dropdown, {
|
||||
.parent = outer,
|
||||
.show = controller->uiShow(),
|
||||
.field = input,
|
||||
.peer = peer,
|
||||
.features = [=] {
|
||||
auto result = ChatHelpers::ComposeFeatures();
|
||||
result.autocompleteCommands = false;
|
||||
result.suggestStickersByEmoji = false;
|
||||
return result;
|
||||
},
|
||||
.sendMenuDetails = sendMenuDetails,
|
||||
});
|
||||
const auto raw = autocomplete->dropdown.get();
|
||||
const auto recountPostponed = [=] {
|
||||
if (autocomplete->geometryUpdateScheduled) {
|
||||
return;
|
||||
}
|
||||
autocomplete->geometryUpdateScheduled = true;
|
||||
Ui::PostponeCall(raw, [=] {
|
||||
autocomplete->geometryUpdateScheduled = false;
|
||||
|
||||
const auto from = input->parentWidget();
|
||||
auto field = Ui::MapFrom(outer, from, input->geometry());
|
||||
const auto &st = st::defaultComposeFiles;
|
||||
autocomplete->dropdown->setBoundings(QRect(
|
||||
field.x() - input->x(),
|
||||
st::defaultBox.margin.top(),
|
||||
input->width(),
|
||||
(field.y()
|
||||
+ st.caption.textMargins.top()
|
||||
+ st.caption.placeholderShift
|
||||
+ st.caption.placeholderFont->height
|
||||
- st::defaultBox.margin.top())));
|
||||
});
|
||||
};
|
||||
for (auto w = (QWidget*)input; w; w = w->parentWidget()) {
|
||||
base::install_event_filter(raw, w, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Move || e->type() == QEvent::Resize) {
|
||||
recountPostponed();
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
if (w == outer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto send = [=](Api::SendOptions options) {
|
||||
done(std::move(options), input->getTextWithTags());
|
||||
};
|
||||
|
@ -264,8 +323,15 @@ void SendGifWithCaptionBox(
|
|||
SendMenu::SetupMenuAndShortcuts(
|
||||
confirm,
|
||||
controller->uiShow(),
|
||||
[=] { return details; },
|
||||
sendMenuDetails,
|
||||
SendMenu::DefaultCallback(controller->uiShow(), send));
|
||||
box->setShowFinishedCallback([=] {
|
||||
if (const auto raw = autocomplete->dropdown.get()) {
|
||||
InvokeQueued(raw, [=] {
|
||||
raw->raise();
|
||||
});
|
||||
}
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
class PeerData;
|
||||
class DocumentData;
|
||||
|
||||
namespace Api {
|
||||
|
@ -24,6 +25,7 @@ class GenericBox;
|
|||
void SendGifWithCaptionBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<DocumentData*> document,
|
||||
not_null<PeerData*> peer,
|
||||
const SendMenu::Details &details,
|
||||
Fn<void(Api::SendOptions, TextWithTags)> done);
|
||||
|
||||
|
|
|
@ -245,13 +245,12 @@ void ShareBox::prepareCommentField() {
|
|||
}, field->lifetime());
|
||||
|
||||
if (const auto show = uiShow(); show->valid()) {
|
||||
InitMessageFieldHandlers(
|
||||
_descriptor.session,
|
||||
Main::MakeSessionShow(show, _descriptor.session),
|
||||
field,
|
||||
nullptr,
|
||||
nullptr,
|
||||
_descriptor.stLabel);
|
||||
InitMessageFieldHandlers({
|
||||
.session = _descriptor.session,
|
||||
.show = Main::MakeSessionShow(show, _descriptor.session),
|
||||
.field = field,
|
||||
.fieldStyle = _descriptor.stLabel,
|
||||
});
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
|
||||
|
@ -843,6 +842,8 @@ void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
|
|||
? tr::lng_saved_messages(tr::now)
|
||||
: peer->isRepliesChat()
|
||||
? tr::lng_replies_messages(tr::now)
|
||||
: peer->isVerifyCodes()
|
||||
? tr::lng_verification_codes(tr::now)
|
||||
: peer->name();
|
||||
chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
|
1341
Telegram/SourceFiles/boxes/star_gift_box.cpp
Normal file
23
Telegram/SourceFiles/boxes/star_gift_box.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void ChooseStarGiftRecipient(
|
||||
not_null<Window::SessionController*> controller);
|
||||
|
||||
void ShowStarGiftBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Ui
|
|
@ -89,7 +89,8 @@ using TLStickerSet = MTPmessages_StickerSet;
|
|||
|
||||
[[nodiscard]] std::optional<QColor> ComputeImageColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
if (frame.isNull()
|
||||
|| frame.format() != QImage::Format_ARGB32_Premultiplied) {
|
||||
return {};
|
||||
|
@ -98,13 +99,29 @@ using TLStickerSet = MTPmessages_StickerSet;
|
|||
auto sg = int64();
|
||||
auto sb = int64();
|
||||
auto sa = int64();
|
||||
const auto factor = frame.devicePixelRatio();
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto size = lockIcon.size() * factor;
|
||||
const auto width = std::min(frame.width(), size.width());
|
||||
const auto height = std::min(frame.height(), size.height());
|
||||
const auto skipx = (frame.width() - width) / 2;
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto skipy = std::max(frame.height() - height - radius, 0);
|
||||
const auto skipx = (part == RectPart::TopLeft
|
||||
|| part == RectPart::Left
|
||||
|| part == RectPart::BottomLeft)
|
||||
? 0
|
||||
: (part == RectPart::Top
|
||||
|| part == RectPart::Center
|
||||
|| part == RectPart::Bottom)
|
||||
? (frame.width() - width) / 2
|
||||
: std::max(frame.width() - width - radius, 0);
|
||||
const auto skipy = (part == RectPart::TopLeft
|
||||
|| part == RectPart::Top
|
||||
|| part == RectPart::TopRight)
|
||||
? 0
|
||||
: (part == RectPart::Left
|
||||
|| part == RectPart::Center
|
||||
|| part == RectPart::Right)
|
||||
? (frame.height() - height) / 2
|
||||
: std::max(frame.height() - height - radius, 0);
|
||||
const auto perline = frame.bytesPerLine();
|
||||
const auto addperline = perline - (width * 4);
|
||||
auto bits = static_cast<const uchar*>(frame.bits())
|
||||
|
@ -128,17 +145,20 @@ using TLStickerSet = MTPmessages_StickerSet;
|
|||
|
||||
[[nodiscard]] QColor ComputeLockColor(
|
||||
const style::icon &lockIcon,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
return ComputeImageColor(
|
||||
lockIcon,
|
||||
frame
|
||||
frame,
|
||||
part
|
||||
).value_or(st::windowSubTextFg->c);
|
||||
}
|
||||
|
||||
void ValidatePremiumLockBg(
|
||||
const style::icon &lockIcon,
|
||||
QImage &image,
|
||||
const QImage &frame) {
|
||||
const QImage &frame,
|
||||
RectPart part) {
|
||||
if (!image.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
@ -149,7 +169,7 @@ void ValidatePremiumLockBg(
|
|||
QImage::Format_ARGB32_Premultiplied);
|
||||
image.setDevicePixelRatio(factor);
|
||||
auto p = QPainter(&image);
|
||||
const auto color = ComputeLockColor(lockIcon, frame);
|
||||
const auto color = ComputeLockColor(lockIcon, frame, part);
|
||||
p.fillRect(
|
||||
QRect(QPoint(), size),
|
||||
anim::color(color, st::windowSubTextFg, kGrayLockOpacity));
|
||||
|
@ -202,8 +222,10 @@ void ValidatePremiumStarFg(const style::icon &lockIcon, QImage &image) {
|
|||
|
||||
StickerPremiumMark::StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon)
|
||||
: _lockIcon(lockIcon) {
|
||||
const style::icon &lockIcon,
|
||||
RectPart part)
|
||||
: _lockIcon(lockIcon)
|
||||
, _part(part) {
|
||||
style::PaletteChanged(
|
||||
) | rpl::start_with_next([=] {
|
||||
_lockGray = QImage();
|
||||
|
@ -228,11 +250,15 @@ void StickerPremiumMark::paint(
|
|||
const auto &bg = frame.isNull() ? _lockGray : backCache;
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
const auto radius = st::roundRadiusSmall;
|
||||
const auto point = position + QPoint(
|
||||
(singleSize.width() - (bg.width() / factor) - radius),
|
||||
singleSize.height() - (bg.height() / factor) - radius);
|
||||
const auto shiftx = (_part == RectPart::Center)
|
||||
? (singleSize.width() - (bg.width() / factor)) / 2
|
||||
: (singleSize.width() - (bg.width() / factor) - radius);
|
||||
const auto shifty = (_part == RectPart::Center)
|
||||
? (singleSize.height() - (bg.height() / factor)) / 2
|
||||
: (singleSize.height() - (bg.height() / factor) - radius);
|
||||
const auto point = position + QPoint(shiftx, shifty);
|
||||
p.drawImage(point, bg);
|
||||
if (_premium) {
|
||||
if (_premium && _part != RectPart::Center) {
|
||||
validateStar();
|
||||
p.drawImage(point, _star);
|
||||
} else {
|
||||
|
@ -244,7 +270,7 @@ void StickerPremiumMark::validateLock(
|
|||
const QImage &frame,
|
||||
QImage &backCache) {
|
||||
auto &image = frame.isNull() ? _lockGray : backCache;
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame);
|
||||
ValidatePremiumLockBg(_lockIcon, image, frame, _part);
|
||||
}
|
||||
|
||||
void StickerPremiumMark::validateStar() {
|
||||
|
@ -1447,9 +1473,13 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
|||
const auto send = crl::guard(this, [=](Api::SendOptions options) {
|
||||
chosen(index, document, options);
|
||||
});
|
||||
|
||||
// In case we're adding items after FillSendMenu we have
|
||||
// to pass nullptr for showForEffect and attach selector later.
|
||||
// Otherwise added items widths won't be respected in menu geometry.
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_show,
|
||||
nullptr, // showForEffect
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
|
@ -1483,6 +1513,12 @@ void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
|||
.isAttention = true,
|
||||
});
|
||||
}
|
||||
|
||||
SendMenu::AttachSendMenuEffect(
|
||||
_menu.get(),
|
||||
_show,
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
}
|
||||
if (_menu->empty()) {
|
||||
_menu = nullptr;
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/layers/box_content.h"
|
||||
#include "base/timer.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "ui/rect_part.h"
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
|
@ -32,7 +33,8 @@ class StickerPremiumMark final {
|
|||
public:
|
||||
StickerPremiumMark(
|
||||
not_null<Main::Session*> session,
|
||||
const style::icon &lockIcon);
|
||||
const style::icon &lockIcon,
|
||||
RectPart part = RectPart::Bottom);
|
||||
|
||||
void paint(
|
||||
QPainter &p,
|
||||
|
@ -49,6 +51,7 @@ private:
|
|||
const style::icon &_lockIcon;
|
||||
QImage _lockGray;
|
||||
QImage _star;
|
||||
RectPart _part = RectPart::Bottom;
|
||||
bool _premium = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
|
|
@ -1139,6 +1139,7 @@ groupCallTitle: WindowTitle(defaultWindowTitle) {
|
|||
bgActive: groupCallBg;
|
||||
fg: transparent;
|
||||
fgActive: transparent;
|
||||
oneSideControls: true;
|
||||
minimize: IconButton(groupCallTitleButton) {
|
||||
icon: groupCallTitleMinimizeIcon;
|
||||
iconOver: groupCallTitleMinimizeIconOver;
|
||||
|
|
|
@ -198,7 +198,9 @@ void Panel::initWindow() {
|
|||
return Flag::None | Flag(0);
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
if (_controls->controls.geometry().contains(widgetPoint)) {
|
||||
using Result = Ui::Platform::HitTestResult;
|
||||
const auto windowPoint = widget()->mapTo(window(), widgetPoint);
|
||||
if (_controls->controls.hitTest(windowPoint) != Result::None) {
|
||||
return Flag::None | Flag(0);
|
||||
}
|
||||
#endif // !Q_OS_MAC
|
||||
|
|
|
@ -23,7 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceManager.h>
|
||||
#include <tgcalls/desktop_capturer/DesktopCaptureSourceHelper.h>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Calls::Group::Ui::DesktopCapture {
|
||||
|
@ -585,13 +584,7 @@ void ChooseSourceProcess::setupSourcesGeometry() {
|
|||
|
||||
void ChooseSourceProcess::setupGeometryWithParent(
|
||||
not_null<QWidget*> parent) {
|
||||
const auto parentScreen = [&] {
|
||||
if (const auto screen = QGuiApplication::screenAt(
|
||||
parent->geometry().center())) {
|
||||
return screen;
|
||||
}
|
||||
return parent->screen();
|
||||
}();
|
||||
const auto parentScreen = parent->screen();
|
||||
const auto myScreen = _window->screen();
|
||||
if (parentScreen && myScreen != parentScreen) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
|
|
|
@ -218,8 +218,11 @@ ComposeControls {
|
|||
|
||||
ReportBox {
|
||||
button: SettingsButton;
|
||||
noIconButton: SettingsButton;
|
||||
label: FlatLabel;
|
||||
field: InputField;
|
||||
dividerBg: color;
|
||||
dividerFg: color;
|
||||
spam: icon;
|
||||
fake: icon;
|
||||
violence: icon;
|
||||
|
@ -1360,8 +1363,13 @@ reportReasonButton: SettingsButton(defaultSettingsButton) {
|
|||
|
||||
defaultReportBox: ReportBox {
|
||||
button: reportReasonButton;
|
||||
noIconButton: SettingsButton(reportReasonButton) {
|
||||
padding: margins(22px, 7px, 8px, 7px);
|
||||
}
|
||||
label: boxLabel;
|
||||
field: newGroupDescription;
|
||||
dividerBg: boxDividerBg;
|
||||
dividerFg: windowSubTextFg;
|
||||
spam: menuIconDelete;
|
||||
fake: menuIconFake;
|
||||
violence: menuIconViolence;
|
||||
|
|
|
@ -10,20 +10,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace ChatHelpers {
|
||||
|
||||
struct ComposeFeatures {
|
||||
bool likes = false;
|
||||
bool sendAs = true;
|
||||
bool ttlInfo = true;
|
||||
bool botCommandSend = true;
|
||||
bool silentBroadcastToggle = true;
|
||||
bool attachBotsMenu = true;
|
||||
bool inlineBots = true;
|
||||
bool megagroupSet = true;
|
||||
bool stickersSettings = true;
|
||||
bool openStickerSets = true;
|
||||
bool autocompleteHashtags = true;
|
||||
bool autocompleteMentions = true;
|
||||
bool autocompleteCommands = true;
|
||||
bool commonTabbedPanel = true;
|
||||
bool likes : 1 = false;
|
||||
bool sendAs : 1 = true;
|
||||
bool ttlInfo : 1 = true;
|
||||
bool botCommandSend : 1 = true;
|
||||
bool silentBroadcastToggle : 1 = true;
|
||||
bool attachBotsMenu : 1 = true;
|
||||
bool inlineBots : 1 = true;
|
||||
bool megagroupSet : 1 = true;
|
||||
bool stickersSettings : 1 = true;
|
||||
bool openStickerSets : 1 = true;
|
||||
bool autocompleteHashtags : 1 = true;
|
||||
bool autocompleteMentions : 1 = true;
|
||||
bool autocompleteCommands : 1 = true;
|
||||
bool suggestStickersByEmoji : 1 = true;
|
||||
bool commonTabbedPanel : 1 = true;
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -41,9 +41,9 @@ inline auto PreviewPath(int i) {
|
|||
|
||||
const auto kSets = {
|
||||
Set{ { 0, 0, 0, "Mac" }, PreviewPath(0) },
|
||||
Set{ { 1, 1804, 8'115'639, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 1805, 5'481'197, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 1806, 7'047'594, "JoyPixels" }, PreviewPath(3) },
|
||||
Set{ { 1, 2290, 8'306'943, "Android" }, PreviewPath(1) },
|
||||
Set{ { 2, 2291, 5'694'303, "Twemoji" }, PreviewPath(2) },
|
||||
Set{ { 3, 2292, 7'261'223, "JoyPixels" }, PreviewPath(3) },
|
||||
};
|
||||
|
||||
using Loading = MTP::DedicatedLoader::Progress;
|
||||
|
|
|
@ -713,11 +713,13 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
|
|||
}
|
||||
|
||||
SuggestionsController::SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> session,
|
||||
const Options &options)
|
||||
: _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
||||
: QObject(parent)
|
||||
, _st(options.st ? *options.st : st::defaultEmojiSuggestions)
|
||||
, _field(field)
|
||||
, _session(session)
|
||||
, _showExactTimer([=] { showWithQuery(getEmojiQuery()); })
|
||||
|
|
|
@ -37,7 +37,7 @@ class SuggestionsWidget;
|
|||
|
||||
using SuggestionsQuery = std::variant<QString, EmojiPtr>;
|
||||
|
||||
class SuggestionsController {
|
||||
class SuggestionsController final : public QObject {
|
||||
public:
|
||||
struct Options {
|
||||
bool suggestExactFirstWord = true;
|
||||
|
@ -47,6 +47,7 @@ public:
|
|||
};
|
||||
|
||||
SuggestionsController(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<QWidget*> outer,
|
||||
not_null<QTextEdit*> field,
|
||||
not_null<Main::Session*> session,
|
||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -53,6 +54,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace ChatHelpers {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QString PrimaryUsername(not_null<UserData*> user) {
|
||||
|
@ -60,6 +62,18 @@ namespace {
|
|||
return usernames.empty() ? user->username() : usernames.front();
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last)
|
||||
; i != e
|
||||
; ++i) {
|
||||
if (i->user == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class FieldAutocomplete::Inner final : public Ui::RpWidget {
|
||||
|
@ -70,7 +84,7 @@ public:
|
|||
};
|
||||
|
||||
Inner(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan &st,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
|
@ -127,7 +141,7 @@ private:
|
|||
Media::Clip::Notification notification,
|
||||
not_null<DocumentData*> document);
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
const style::EmojiPan &_st;
|
||||
const not_null<FieldAutocomplete*> _parent;
|
||||
|
@ -191,13 +205,7 @@ struct FieldAutocomplete::BotCommandRow {
|
|||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller)
|
||||
: FieldAutocomplete(parent, controller->uiShow()) {
|
||||
}
|
||||
|
||||
FieldAutocomplete::FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan *stOverride)
|
||||
: RpWidget(parent)
|
||||
, _show(std::move(show))
|
||||
|
@ -235,10 +243,26 @@ FieldAutocomplete::FieldAutocomplete(
|
|||
}), lifetime());
|
||||
}
|
||||
|
||||
std::shared_ptr<ChatHelpers::Show> FieldAutocomplete::uiShow() const {
|
||||
std::shared_ptr<Show> FieldAutocomplete::uiShow() const {
|
||||
return _show;
|
||||
}
|
||||
|
||||
void FieldAutocomplete::requestRefresh() {
|
||||
_refreshRequests.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> FieldAutocomplete::refreshRequests() const {
|
||||
return _refreshRequests.events();
|
||||
}
|
||||
|
||||
void FieldAutocomplete::requestStickersUpdate() {
|
||||
_stickersUpdateRequests.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> FieldAutocomplete::stickersUpdateRequests() const {
|
||||
return _stickersUpdateRequests.events();
|
||||
}
|
||||
|
||||
auto FieldAutocomplete::mentionChosen() const
|
||||
-> rpl::producer<FieldAutocomplete::MentionChosen> {
|
||||
return _inner->mentionChosen();
|
||||
|
@ -365,6 +389,10 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
|
|||
updateFiltered(resetScroll);
|
||||
}
|
||||
|
||||
EmojiPtr FieldAutocomplete::stickersEmoji() const {
|
||||
return _emoji;
|
||||
}
|
||||
|
||||
bool FieldAutocomplete::clearFilteredBotCommands() {
|
||||
if (_brows.empty()) {
|
||||
return false;
|
||||
|
@ -373,18 +401,6 @@ bool FieldAutocomplete::clearFilteredBotCommands() {
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + std::max(int(v.size()), last); i != e; ++i) {
|
||||
if (i->user == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FieldAutocomplete::StickerRows FieldAutocomplete::getStickerSuggestions() {
|
||||
const auto data = &_session->data().stickers();
|
||||
const auto list = data->getListByEmoji({ _emoji }, _stickersSeed);
|
||||
|
@ -871,7 +887,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
|
|||
}
|
||||
|
||||
FieldAutocomplete::Inner::Inner(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan &st,
|
||||
not_null<FieldAutocomplete*> parent,
|
||||
not_null<MentionRows*> mrows,
|
||||
|
@ -963,8 +979,8 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
|||
|
||||
media->checkStickerSmall();
|
||||
const auto paused = _show->paused(
|
||||
ChatHelpers::PauseReason::TabbedPanel);
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
PauseReason::TabbedPanel);
|
||||
const auto size = ComputeStickerSize(
|
||||
document,
|
||||
stickerBoundingBox());
|
||||
const auto ppos = pos + QPoint(
|
||||
|
@ -989,7 +1005,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) {
|
|||
} else if (const auto image = media->getStickerSmall()) {
|
||||
p.drawPixmapLeft(ppos, width(), image->pix(size));
|
||||
} else {
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
PaintStickerThumbnailPath(
|
||||
p,
|
||||
media.get(),
|
||||
QRect(ppos, size),
|
||||
|
@ -1250,7 +1266,7 @@ bool FieldAutocomplete::Inner::chooseAtIndex(
|
|||
const auto bounding = selectedRect(index);
|
||||
auto contentRect = QRect(
|
||||
QPoint(),
|
||||
ChatHelpers::ComputeStickerSize(
|
||||
ComputeStickerSize(
|
||||
document,
|
||||
stickerBoundingBox()));
|
||||
contentRect.moveCenter(bounding.center());
|
||||
|
@ -1464,9 +1480,9 @@ auto FieldAutocomplete::Inner::getLottieRenderer()
|
|||
|
||||
void FieldAutocomplete::Inner::setupLottie(StickerSuggestion &suggestion) {
|
||||
const auto document = suggestion.document;
|
||||
suggestion.lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
suggestion.lottie = LottiePlayerFromDocument(
|
||||
suggestion.documentMedia.get(),
|
||||
ChatHelpers::StickerLottieSize::InlineResults,
|
||||
StickerLottieSize::InlineResults,
|
||||
stickerBoundingBox() * style::DevicePixelRatio(),
|
||||
Lottie::Quality::Default,
|
||||
getLottieRenderer());
|
||||
|
@ -1534,7 +1550,7 @@ void FieldAutocomplete::Inner::clipCallback(
|
|||
} else if (i->webm->state() == State::Error) {
|
||||
i->webm.setBad();
|
||||
} else if (i->webm->ready() && !i->webm->started()) {
|
||||
const auto size = ChatHelpers::ComputeStickerSize(
|
||||
const auto size = ComputeStickerSize(
|
||||
i->document,
|
||||
stickerBoundingBox());
|
||||
i->webm->start({ .frame = size, .keepAlpha = true });
|
||||
|
@ -1632,3 +1648,171 @@ auto FieldAutocomplete::Inner::scrollToRequested() const
|
|||
-> rpl::producer<ScrollTo> {
|
||||
return _scrollToRequested.events();
|
||||
}
|
||||
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor) {
|
||||
Expects(!autocomplete);
|
||||
|
||||
autocomplete = std::make_unique<FieldAutocomplete>(
|
||||
descriptor.parent,
|
||||
descriptor.show,
|
||||
descriptor.stOverride);
|
||||
const auto raw = autocomplete.get();
|
||||
const auto field = descriptor.field;
|
||||
|
||||
field->rawTextEdit()->installEventFilter(raw);
|
||||
field->customTab(true);
|
||||
|
||||
raw->mentionChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::MentionChosen data) {
|
||||
const auto user = data.user;
|
||||
if (data.mention.isEmpty()) {
|
||||
field->insertTag(
|
||||
user->firstName.isEmpty() ? user->name() : user->firstName,
|
||||
PrepareMentionTag(user));
|
||||
} else {
|
||||
field->insertTag('@' + data.mention);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto sendCommand = descriptor.sendBotCommand;
|
||||
const auto setText = descriptor.setText;
|
||||
|
||||
raw->hashtagChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::HashtagChosen data) {
|
||||
field->insertTag(data.hashtag);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto peer = descriptor.peer;
|
||||
const auto features = descriptor.features;
|
||||
const auto processShortcut = descriptor.processShortcut;
|
||||
const auto shortcutMessages = (processShortcut != nullptr)
|
||||
? &peer->owner().shortcutMessages()
|
||||
: nullptr;
|
||||
raw->botCommandChosen(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::BotCommandChosen data) {
|
||||
if (!features().autocompleteCommands) {
|
||||
return;
|
||||
}
|
||||
using Method = FieldAutocompleteChooseMethod;
|
||||
const auto byTab = (data.method == Method::ByTab);
|
||||
const auto shortcut = data.user->isSelf();
|
||||
|
||||
// Send bot command at once, if it was not inserted by pressing Tab.
|
||||
if (byTab && data.command.size() > 1) {
|
||||
field->insertTag(data.command);
|
||||
} else if (!shortcut) {
|
||||
sendCommand(data.command);
|
||||
setText(
|
||||
field->getTextWithTagsPart(field->textCursor().position()));
|
||||
} else if (processShortcut) {
|
||||
processShortcut(data.command.mid(1));
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->setModerateKeyActivateCallback(std::move(descriptor.moderateKeyActivateCallback));
|
||||
|
||||
if (const auto stickerChoosing = descriptor.stickerChoosing) {
|
||||
raw->choosingProcesses(
|
||||
) | rpl::start_with_next([=](FieldAutocomplete::Type type) {
|
||||
if (type == FieldAutocomplete::Type::Stickers) {
|
||||
stickerChoosing();
|
||||
}
|
||||
}, raw->lifetime());
|
||||
}
|
||||
if (const auto chosen = descriptor.stickerChosen) {
|
||||
raw->stickerChosen(
|
||||
) | rpl::start_with_next(chosen, raw->lifetime());
|
||||
}
|
||||
|
||||
field->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!raw->isHidden()) {
|
||||
raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto check = [=] {
|
||||
auto parsed = ParseMentionHashtagBotCommandQuery(field, features());
|
||||
if (parsed.query.isEmpty()) {
|
||||
} else if (parsed.query[0] == '#'
|
||||
&& cRecentWriteHashtags().isEmpty()
|
||||
&& cRecentSearchHashtags().isEmpty()) {
|
||||
peer->session().local().readRecentHashtagsAndBots();
|
||||
} else if (parsed.query[0] == '@'
|
||||
&& cRecentInlineBots().isEmpty()) {
|
||||
peer->session().local().readRecentHashtagsAndBots();
|
||||
} else if (parsed.query[0] == '/'
|
||||
&& peer->isUser()
|
||||
&& !peer->asUser()->isBot()
|
||||
&& (!shortcutMessages
|
||||
|| shortcutMessages->shortcuts().list.empty())) {
|
||||
parsed = {};
|
||||
}
|
||||
raw->showFiltered(peer, parsed.query, parsed.fromStart);
|
||||
};
|
||||
|
||||
const auto updateStickersByEmoji = [=] {
|
||||
const auto errorForStickers = Data::RestrictionError(
|
||||
peer,
|
||||
ChatRestriction::SendStickers);
|
||||
if (features().suggestStickersByEmoji && !errorForStickers) {
|
||||
const auto &text = field->getTextWithTags().text;
|
||||
auto length = 0;
|
||||
if (const auto emoji = Ui::Emoji::Find(text, &length)) {
|
||||
if (text.size() <= length) {
|
||||
raw->showStickers(emoji);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
raw->showStickers(nullptr);
|
||||
};
|
||||
|
||||
raw->refreshRequests(
|
||||
) | rpl::start_with_next(check, raw->lifetime());
|
||||
|
||||
raw->stickersUpdateRequests(
|
||||
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
peer->owner().botCommandsChanges(
|
||||
) | rpl::filter([=](not_null<PeerData*> changed) {
|
||||
return (peer == changed);
|
||||
}) | rpl::start_with_next([=] {
|
||||
if (raw->clearFilteredBotCommands()) {
|
||||
check();
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
peer->owner().stickers().updated(
|
||||
Data::StickersType::Stickers
|
||||
) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
QObject::connect(
|
||||
field->rawTextEdit(),
|
||||
&QTextEdit::cursorPositionChanged,
|
||||
raw,
|
||||
check,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
field->changes() | rpl::start_with_next(
|
||||
updateStickersByEmoji,
|
||||
raw->lifetime());
|
||||
|
||||
peer->session().changes().peerUpdates(
|
||||
Data::PeerUpdate::Flag::Rights
|
||||
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||
return (update.peer == peer);
|
||||
}) | rpl::start_with_next(updateStickersByEmoji, raw->lifetime());
|
||||
|
||||
if (shortcutMessages) {
|
||||
shortcutMessages->shortcutsChanged(
|
||||
) | rpl::start_with_next(check, raw->lifetime());
|
||||
}
|
||||
|
||||
raw->setSendMenuDetails(std::move(descriptor.sendMenuDetails));
|
||||
raw->hideFast();
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -46,46 +46,49 @@ struct Details;
|
|||
} // namespace SendMenu
|
||||
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct ComposeFeatures;
|
||||
struct FileChosen;
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
enum class FieldAutocompleteChooseMethod {
|
||||
ByEnter,
|
||||
ByTab,
|
||||
ByClick,
|
||||
};
|
||||
|
||||
class FieldAutocomplete final : public Ui::RpWidget {
|
||||
public:
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller);
|
||||
FieldAutocomplete(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
std::shared_ptr<Show> show,
|
||||
const style::EmojiPan *stOverride = nullptr);
|
||||
~FieldAutocomplete();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() const;
|
||||
[[nodiscard]] std::shared_ptr<Show> uiShow() const;
|
||||
|
||||
bool clearFilteredBotCommands();
|
||||
void showFiltered(
|
||||
not_null<PeerData*> peer,
|
||||
QString query,
|
||||
bool addInlineBots);
|
||||
|
||||
void showStickers(EmojiPtr emoji);
|
||||
[[nodiscard]] EmojiPtr stickersEmoji() const;
|
||||
|
||||
void setBoundings(QRect boundings);
|
||||
|
||||
const QString &filter() const;
|
||||
ChatData *chat() const;
|
||||
ChannelData *channel() const;
|
||||
UserData *user() const;
|
||||
[[nodiscard]] const QString &filter() const;
|
||||
[[nodiscard]] ChatData *chat() const;
|
||||
[[nodiscard]] ChannelData *channel() const;
|
||||
[[nodiscard]] UserData *user() const;
|
||||
|
||||
int32 innerTop();
|
||||
int32 innerBottom();
|
||||
[[nodiscard]] int32 innerTop();
|
||||
[[nodiscard]] int32 innerBottom();
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||
|
||||
enum class ChooseMethod {
|
||||
ByEnter,
|
||||
ByTab,
|
||||
ByClick,
|
||||
};
|
||||
using ChooseMethod = FieldAutocompleteChooseMethod;
|
||||
struct MentionChosen {
|
||||
not_null<UserData*> user;
|
||||
QString mention;
|
||||
|
@ -100,7 +103,7 @@ public:
|
|||
QString command;
|
||||
ChooseMethod method = ChooseMethod::ByEnter;
|
||||
};
|
||||
using StickerChosen = ChatHelpers::FileChosen;
|
||||
using StickerChosen = FileChosen;
|
||||
enum class Type {
|
||||
Mentions,
|
||||
Hashtags,
|
||||
|
@ -110,13 +113,14 @@ public:
|
|||
|
||||
bool chooseSelected(ChooseMethod method) const;
|
||||
|
||||
bool stickersShown() const {
|
||||
[[nodiscard]] bool stickersShown() const {
|
||||
return !_srows.empty();
|
||||
}
|
||||
|
||||
bool overlaps(const QRect &globalRect) {
|
||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
|
||||
|
||||
[[nodiscard]] bool overlaps(const QRect &globalRect) {
|
||||
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) {
|
||||
return false;
|
||||
}
|
||||
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
|
||||
}
|
||||
|
||||
|
@ -129,11 +133,16 @@ public:
|
|||
void showAnimated();
|
||||
void hideAnimated();
|
||||
|
||||
rpl::producer<MentionChosen> mentionChosen() const;
|
||||
rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
rpl::producer<StickerChosen> stickerChosen() const;
|
||||
rpl::producer<Type> choosingProcesses() const;
|
||||
void requestRefresh();
|
||||
[[nodiscard]] rpl::producer<> refreshRequests() const;
|
||||
void requestStickersUpdate();
|
||||
[[nodiscard]] rpl::producer<> stickersUpdateRequests() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<MentionChosen> mentionChosen() const;
|
||||
[[nodiscard]] rpl::producer<HashtagChosen> hashtagChosen() const;
|
||||
[[nodiscard]] rpl::producer<BotCommandChosen> botCommandChosen() const;
|
||||
[[nodiscard]] rpl::producer<StickerChosen> stickerChosen() const;
|
||||
[[nodiscard]] rpl::producer<Type> choosingProcesses() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
@ -157,7 +166,7 @@ private:
|
|||
void recount(bool resetScroll = false);
|
||||
StickerRows getStickerSuggestions();
|
||||
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Show> _show;
|
||||
const not_null<Main::Session*> _session;
|
||||
const style::EmojiPan &_st;
|
||||
QPixmap _cache;
|
||||
|
@ -189,7 +198,30 @@ private:
|
|||
bool _hiding = false;
|
||||
|
||||
Ui::Animations::Simple _a_opacity;
|
||||
rpl::event_stream<> _refreshRequests;
|
||||
rpl::event_stream<> _stickersUpdateRequests;
|
||||
|
||||
Fn<bool(int)> _moderateKeyActivateCallback;
|
||||
|
||||
};
|
||||
|
||||
struct FieldAutocompleteDescriptor {
|
||||
not_null<QWidget*> parent;
|
||||
std::shared_ptr<Show> show;
|
||||
not_null<Ui::InputField*> field;
|
||||
const style::EmojiPan *stOverride = nullptr;
|
||||
not_null<PeerData*> peer;
|
||||
Fn<ComposeFeatures()> features;
|
||||
Fn<SendMenu::Details()> sendMenuDetails;
|
||||
Fn<void()> stickerChoosing;
|
||||
Fn<void(FileChosen&&)> stickerChosen;
|
||||
Fn<void(TextWithTags)> setText;
|
||||
Fn<void(QString)> sendBotCommand;
|
||||
Fn<void(QString)> processShortcut;
|
||||
Fn<bool(int)> moderateKeyActivateCallback;
|
||||
};
|
||||
void InitFieldAutocomplete(
|
||||
std::unique_ptr<FieldAutocomplete> &autocomplete,
|
||||
FieldAutocompleteDescriptor &&descriptor);
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -409,24 +409,30 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
|
|||
// inline results don't have effects
|
||||
copyDetails.effectAllowed = false;
|
||||
}
|
||||
|
||||
// In case we're adding items after FillSendMenu we have
|
||||
// to pass nullptr for showForEffect and attach selector later.
|
||||
// Otherwise added items widths won't be respected in menu geometry.
|
||||
SendMenu::FillSendMenu(
|
||||
menu,
|
||||
_show,
|
||||
nullptr, // showForMenu
|
||||
copyDetails,
|
||||
SendMenu::DefaultCallback(_show, send),
|
||||
icons);
|
||||
|
||||
if (!isInlineResult) {
|
||||
if (!isInlineResult && _inlineQueryPeer) {
|
||||
auto done = crl::guard(this, [=](
|
||||
Api::SendOptions options,
|
||||
TextWithTags text) {
|
||||
selectInlineResult(selected, options, true, std::move(text));
|
||||
});
|
||||
const auto show = _show;
|
||||
const auto peer = _inlineQueryPeer;
|
||||
menu->addAction(tr::lng_send_gif_with_caption(tr::now), [=] {
|
||||
show->show(Box(
|
||||
Ui::SendGifWithCaptionBox,
|
||||
item->getDocument(),
|
||||
peer,
|
||||
copyDetails,
|
||||
std::move(done)));
|
||||
}, &st::menuIconEdit);
|
||||
|
@ -446,6 +452,13 @@ base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
|
|||
AddGifAction(std::move(callback), _show, document, icons);
|
||||
}
|
||||
}
|
||||
|
||||
SendMenu::AttachSendMenuEffect(
|
||||
menu,
|
||||
_show,
|
||||
copyDetails,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,12 @@ constexpr auto kParseLinksTimeout = crl::time(500);
|
|||
constexpr auto kTypesDuration = 4 * crl::time(1000);
|
||||
constexpr auto kCodeLanguageLimit = 32;
|
||||
|
||||
constexpr auto kLinkProtocols = {
|
||||
"http://",
|
||||
"https://",
|
||||
"tonsite://"
|
||||
};
|
||||
|
||||
// For mention / custom emoji tags save and validate selfId,
|
||||
// ignore tags for different users.
|
||||
[[nodiscard]] Fn<QString(QStringView)> FieldTagMimeProcessor(
|
||||
|
@ -152,11 +158,10 @@ void EditLinkBox(
|
|||
return startLink.trimmed();
|
||||
}
|
||||
const auto clipboard = QGuiApplication::clipboard()->text().trimmed();
|
||||
if (clipboard.startsWith("http://")
|
||||
|| clipboard.startsWith("https://")) {
|
||||
return clipboard;
|
||||
}
|
||||
return QString();
|
||||
const auto starts = [&](const auto &protocol) {
|
||||
return clipboard.startsWith(protocol);
|
||||
};
|
||||
return std::ranges::any_of(kLinkProtocols, starts) ? clipboard : QString();
|
||||
}();
|
||||
const auto url = Ui::AttachParentChild(
|
||||
content,
|
||||
|
@ -220,6 +225,9 @@ void EditLinkBox(
|
|||
if (startText.isEmpty()) {
|
||||
text->setFocusFast();
|
||||
} else {
|
||||
if (!url->empty()) {
|
||||
url->selectAll();
|
||||
}
|
||||
url->setFocusFast();
|
||||
}
|
||||
});
|
||||
|
@ -227,12 +235,31 @@ void EditLinkBox(
|
|||
url->customTab(true);
|
||||
text->customTab(true);
|
||||
|
||||
const auto clearFullSelection = [=](not_null<Ui::InputField*> input) {
|
||||
if (input->empty()) {
|
||||
return;
|
||||
}
|
||||
auto cursor = input->rawTextEdit()->textCursor();
|
||||
const auto hasFull = (!cursor.selectionStart()
|
||||
&& (cursor.selectionEnd()
|
||||
== (input->rawTextEdit()->document()->characterCount() - 1)));
|
||||
if (hasFull) {
|
||||
cursor.clearSelection();
|
||||
input->setTextCursor(cursor);
|
||||
}
|
||||
};
|
||||
|
||||
url->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearFullSelection(url);
|
||||
text->setFocus();
|
||||
}, url->lifetime());
|
||||
text->tabbed(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (!url->empty()) {
|
||||
url->selectAll();
|
||||
}
|
||||
clearFullSelection(text);
|
||||
url->setFocus();
|
||||
}, text->lifetime());
|
||||
}
|
||||
|
@ -396,18 +423,14 @@ Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
|
|||
};
|
||||
}
|
||||
|
||||
void InitMessageFieldHandlers(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<Ui::InputField*> field,
|
||||
Fn<bool()> customEmojiPaused,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji,
|
||||
const style::InputField *fieldStyle) {
|
||||
const auto paused = [customEmojiPaused] {
|
||||
return customEmojiPaused && customEmojiPaused();
|
||||
void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
||||
const auto paused = [passed = args.customEmojiPaused] {
|
||||
return passed && passed();
|
||||
};
|
||||
const auto field = args.field;
|
||||
const auto session = args.session;
|
||||
field->setTagMimeProcessor(
|
||||
FieldTagMimeProcessor(session, allowPremiumEmoji));
|
||||
FieldTagMimeProcessor(session, args.allowPremiumEmoji));
|
||||
field->setCustomTextContext([=](Fn<void()> repaint) {
|
||||
return std::any(Core::MarkedTextContext{
|
||||
.session = session,
|
||||
|
@ -421,12 +444,14 @@ void InitMessageFieldHandlers(
|
|||
field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
field->setInstantReplacesEnabled(
|
||||
Core::App().settings().replaceEmojiValue());
|
||||
field->setMarkdownReplacesEnabled(true);
|
||||
if (show) {
|
||||
field->setMarkdownReplacesEnabled(rpl::single(Ui::MarkdownEnabledState{
|
||||
Ui::MarkdownEnabled{ std::move(args.allowMarkdownTags) }
|
||||
}));
|
||||
if (const auto &show = args.show) {
|
||||
field->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(show, field, fieldStyle));
|
||||
DefaultEditLinkCallback(show, field, args.fieldStyle));
|
||||
field->setEditLanguageCallback(DefaultEditLanguageCallback(show));
|
||||
InitSpellchecker(show, field, fieldStyle != nullptr);
|
||||
InitSpellchecker(show, field, args.fieldStyle != nullptr);
|
||||
}
|
||||
const auto style = field->lifetime().make_state<Ui::ChatStyle>(
|
||||
session->colorIndicesValue());
|
||||
|
@ -526,12 +551,15 @@ void InitMessageFieldHandlers(
|
|||
not_null<Ui::InputField*> field,
|
||||
ChatHelpers::PauseReason pauseReasonLevel,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji) {
|
||||
InitMessageFieldHandlers(
|
||||
&controller->session(),
|
||||
controller->uiShow(),
|
||||
field,
|
||||
[=] { return controller->isGifPausedAtLeastFor(pauseReasonLevel); },
|
||||
allowPremiumEmoji);
|
||||
InitMessageFieldHandlers({
|
||||
.session = &controller->session(),
|
||||
.show = controller->uiShow(),
|
||||
.field = field,
|
||||
.customEmojiPaused = [=] {
|
||||
return controller->isGifPausedAtLeastFor(pauseReasonLevel);
|
||||
},
|
||||
.allowPremiumEmoji = std::move(allowPremiumEmoji),
|
||||
});
|
||||
}
|
||||
|
||||
void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
|
||||
|
@ -547,14 +575,16 @@ void InitMessageField(
|
|||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::InputField*> field,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji) {
|
||||
InitMessageFieldHandlers(
|
||||
&show->session(),
|
||||
show,
|
||||
field,
|
||||
[=] { return show->paused(ChatHelpers::PauseReason::Any); },
|
||||
std::move(allowPremiumEmoji));
|
||||
InitMessageFieldHandlers({
|
||||
.session = &show->session(),
|
||||
.show = show,
|
||||
.field = field,
|
||||
.customEmojiPaused = [=] {
|
||||
return show->paused(ChatHelpers::PauseReason::Any);
|
||||
},
|
||||
.allowPremiumEmoji = std::move(allowPremiumEmoji),
|
||||
});
|
||||
InitMessageFieldGeometry(field);
|
||||
field->customTab(true);
|
||||
}
|
||||
|
||||
void InitMessageField(
|
||||
|
|
|
@ -54,13 +54,18 @@ Fn<bool(
|
|||
const style::InputField *fieldStyle = nullptr);
|
||||
Fn<void(QString now, Fn<void(QString)> save)> DefaultEditLanguageCallback(
|
||||
std::shared_ptr<Ui::Show> show);
|
||||
void InitMessageFieldHandlers(
|
||||
not_null<Main::Session*> session,
|
||||
std::shared_ptr<Main::SessionShow> show, // may be null
|
||||
not_null<Ui::InputField*> field,
|
||||
Fn<bool()> customEmojiPaused,
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji = nullptr,
|
||||
const style::InputField *fieldStyle = nullptr);
|
||||
|
||||
struct MessageFieldHandlersArgs {
|
||||
not_null<Main::Session*> session;
|
||||
std::shared_ptr<Main::SessionShow> show; // may be null
|
||||
not_null<Ui::InputField*> field;
|
||||
Fn<bool()> customEmojiPaused;
|
||||
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji;
|
||||
const style::InputField *fieldStyle = nullptr;
|
||||
base::flat_set<QString> allowMarkdownTags;
|
||||
};
|
||||
void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args);
|
||||
|
||||
void InitMessageFieldHandlers(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Ui::InputField*> field,
|
||||
|
|
|
@ -22,6 +22,10 @@ GiftBoxPack::GiftBoxPack(not_null<Main::Session*> session)
|
|||
|
||||
GiftBoxPack::~GiftBoxPack() = default;
|
||||
|
||||
rpl::producer<> GiftBoxPack::updated() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
||||
int GiftBoxPack::monthsForStars(int stars) const {
|
||||
if (stars <= 1000) {
|
||||
return 3;
|
||||
|
@ -112,6 +116,7 @@ void GiftBoxPack::applySet(const MTPDmessages_stickerSet &data) {
|
|||
}
|
||||
});
|
||||
}
|
||||
_updated.fire({});
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
[[nodiscard]] int monthsForStars(int stars) const;
|
||||
[[nodiscard]] DocumentData *lookup(int months) const;
|
||||
[[nodiscard]] Data::FileOrigin origin() const;
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
|
||||
private:
|
||||
using SetId = uint64;
|
||||
|
@ -36,6 +37,7 @@ private:
|
|||
const not_null<Main::Session*> _session;
|
||||
const std::vector<int> _localMonths;
|
||||
|
||||
rpl::event_stream<> _updated;
|
||||
std::vector<DocumentData*> _documents;
|
||||
SetId _setId = 0;
|
||||
uint64 _accessHash = 0;
|
||||
|
|
|
@ -1800,9 +1800,13 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
|
|||
});
|
||||
});
|
||||
const auto icons = &st().icons;
|
||||
|
||||
// In case we're adding items after FillSendMenu we have
|
||||
// to pass nullptr for showForEffect and attach selector later.
|
||||
// Otherwise added items widths won't be respected in menu geometry.
|
||||
SendMenu::FillSendMenu(
|
||||
menu,
|
||||
_show,
|
||||
nullptr, // showForEffect
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send),
|
||||
icons);
|
||||
|
@ -1836,6 +1840,13 @@ base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
|
|||
false);
|
||||
}, &icons->menuRecentRemove);
|
||||
}
|
||||
|
||||
SendMenu::AttachSendMenuEffect(
|
||||
menu,
|
||||
_show,
|
||||
details,
|
||||
SendMenu::DefaultCallback(_show, send));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
|
|
@ -1170,7 +1170,14 @@ bool Application::openCustomUrl(
|
|||
|| passcodeLocked()) {
|
||||
return false;
|
||||
}
|
||||
const auto command = base::StringViewMid(urlTrimmed, protocol.size(), 8192);
|
||||
static const auto kTagExp = QRegularExpression(
|
||||
u"^\\~[a-zA-Z0-9_\\-]+\\~:"_q);
|
||||
auto skip = protocol.size();
|
||||
const auto match = kTagExp.match(base::StringViewMid(urlTrimmed, skip));
|
||||
if (match.hasMatch()) {
|
||||
skip += match.capturedLength();
|
||||
}
|
||||
const auto command = base::StringViewMid(urlTrimmed, skip, 8192);
|
||||
const auto my = context.value<ClickHandlerContext>();
|
||||
const auto controller = my.sessionWindow.get()
|
||||
? my.sessionWindow.get()
|
||||
|
|
|
@ -367,17 +367,6 @@ void MonospaceClickHandler::onClick(ClickContext context) const {
|
|||
}
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto controller = my.sessionWindow.get()) {
|
||||
auto &data = controller->session().data();
|
||||
const auto item = data.message(my.itemId);
|
||||
const auto hasCopyRestriction = item
|
||||
&& (!item->history()->peer->allowsForwarding()
|
||||
|| item->forbidsForward());
|
||||
if (hasCopyRestriction) {
|
||||
controller->showToast(item->history()->peer->isBroadcast()
|
||||
? tr::lng_error_nocopy_channel(tr::now)
|
||||
: tr::lng_error_nocopy_group(tr::now));
|
||||
return;
|
||||
}
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
}
|
||||
TextUtilities::SetClipboardText(TextForMimeData::Simple(_text.trimmed()));
|
||||
|
|
|
@ -225,7 +225,8 @@ QByteArray Settings::serialize() const {
|
|||
+ Serialize::stringSize(noWarningExtensions)
|
||||
+ Serialize::stringSize(_customFontFamily)
|
||||
+ sizeof(qint32) * 3
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken);
|
||||
+ Serialize::bytearraySize(_tonsiteStorageToken)
|
||||
+ sizeof(qint32) * 2;
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
|
@ -379,7 +380,9 @@ QByteArray Settings::serialize() const {
|
|||
1000000))
|
||||
<< qint32(_systemUnlockEnabled ? 1 : 0)
|
||||
<< qint32(!_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2)
|
||||
<< _tonsiteStorageToken;
|
||||
<< _tonsiteStorageToken
|
||||
<< qint32(_includeMutedCounterFolders ? 1 : 0)
|
||||
<< qint32(_ivZoom.current());
|
||||
}
|
||||
|
||||
Ensures(result.size() == size);
|
||||
|
@ -429,6 +432,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
qint32 sendFilesWay = _sendFilesWay.serialize();
|
||||
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay.current());
|
||||
qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0;
|
||||
qint32 includeMutedCounterFolders = _includeMutedCounterFolders ? 1 : 0;
|
||||
qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0;
|
||||
std::optional<QString> noWarningExtensions;
|
||||
qint32 legacyExeLaunchWarning = 1;
|
||||
|
@ -504,6 +508,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
qint32 systemUnlockEnabled = _systemUnlockEnabled ? 1 : 0;
|
||||
qint32 weatherInCelsius = !_weatherInCelsius ? 0 : *_weatherInCelsius ? 1 : 2;
|
||||
QByteArray tonsiteStorageToken = _tonsiteStorageToken;
|
||||
qint32 ivZoom = _ivZoom.current();
|
||||
|
||||
stream >> themesAccentColors;
|
||||
if (!stream.atEnd()) {
|
||||
|
@ -810,6 +815,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
if (!stream.atEnd()) {
|
||||
stream >> tonsiteStorageToken;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> includeMutedCounterFolders;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> ivZoom;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("App Error: "
|
||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||
|
@ -851,6 +862,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
case ScreenCorner::TopCenter: _notificationsCorner = uncheckedNotificationsCorner; break;
|
||||
}
|
||||
_includeMutedCounter = (includeMutedCounter == 1);
|
||||
_includeMutedCounterFolders = (includeMutedCounterFolders == 1);
|
||||
_countUnreadMessages = (countUnreadMessages == 1);
|
||||
_notifyAboutPinned = (notifyAboutPinned == 1);
|
||||
_autoLock = autoLock;
|
||||
|
@ -871,8 +883,6 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
case Ui::InputSubmitSettings::Enter:
|
||||
case Ui::InputSubmitSettings::CtrlEnter: _sendSubmitWay = uncheckedSendSubmitWay; break;
|
||||
}
|
||||
_includeMutedCounter = (includeMutedCounter == 1);
|
||||
_countUnreadMessages = (countUnreadMessages == 1);
|
||||
if (noWarningExtensions) {
|
||||
const auto list = noWarningExtensions->mid(0, 10240)
|
||||
.split(' ', Qt::SkipEmptyParts)
|
||||
|
@ -1023,6 +1033,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
? std::optional<bool>()
|
||||
: (weatherInCelsius == 1);
|
||||
_tonsiteStorageToken = tonsiteStorageToken;
|
||||
_ivZoom = ivZoom;
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
@ -1352,6 +1363,7 @@ void Settings::resetOnLastLogout() {
|
|||
//_notificationsCount = 3;
|
||||
//_notificationsCorner = ScreenCorner::BottomRight;
|
||||
_includeMutedCounter = true;
|
||||
_includeMutedCounterFolders = true;
|
||||
_countUnreadMessages = true;
|
||||
_notifyAboutPinned = true;
|
||||
//_autoLock = 3600;
|
||||
|
@ -1409,6 +1421,7 @@ void Settings::resetOnLastLogout() {
|
|||
_hiddenGroupCallTooltips = 0;
|
||||
_storiesClickTooltipHidden = false;
|
||||
_ttlVoiceClickTooltipHidden = false;
|
||||
_ivZoom = 100;
|
||||
|
||||
_recentEmojiPreload.clear();
|
||||
_recentEmoji.clear();
|
||||
|
@ -1548,4 +1561,16 @@ bool Settings::rememberedDeleteMessageOnlyForYou() const {
|
|||
return _rememberedDeleteMessageOnlyForYou;
|
||||
}
|
||||
|
||||
int Settings::ivZoom() const {
|
||||
return _ivZoom.current();
|
||||
}
|
||||
rpl::producer<int> Settings::ivZoomValue() const {
|
||||
return _ivZoom.value();
|
||||
}
|
||||
void Settings::setIvZoom(int value) {
|
||||
constexpr auto kMin = 30;
|
||||
constexpr auto kMax = 200;
|
||||
_ivZoom = std::clamp(value, kMin, kMax);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -244,6 +244,12 @@ public:
|
|||
void setIncludeMutedCounter(bool value) {
|
||||
_includeMutedCounter = value;
|
||||
}
|
||||
[[nodiscard]] bool includeMutedCounterFolders() const {
|
||||
return _includeMutedCounterFolders;
|
||||
}
|
||||
void setIncludeMutedCounterFolders(bool value) {
|
||||
_includeMutedCounterFolders = value;
|
||||
}
|
||||
[[nodiscard]] bool countUnreadMessages() const {
|
||||
return _countUnreadMessages;
|
||||
}
|
||||
|
@ -915,6 +921,10 @@ public:
|
|||
_tonsiteStorageToken = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] int ivZoom() const;
|
||||
[[nodiscard]] rpl::producer<int> ivZoomValue() const;
|
||||
void setIvZoom(int value);
|
||||
|
||||
[[nodiscard]] static bool ThirdColumnByDefault();
|
||||
[[nodiscard]] static float64 DefaultDialogsWidthRatio();
|
||||
|
||||
|
@ -957,6 +967,7 @@ private:
|
|||
int _notificationsCount = 3;
|
||||
ScreenCorner _notificationsCorner = ScreenCorner::BottomRight;
|
||||
bool _includeMutedCounter = true;
|
||||
bool _includeMutedCounterFolders = true;
|
||||
bool _countUnreadMessages = true;
|
||||
rpl::variable<bool> _notifyAboutPinned = true;
|
||||
int _autoLock = 3600;
|
||||
|
@ -1049,6 +1060,7 @@ private:
|
|||
bool _systemUnlockEnabled = false;
|
||||
std::optional<bool> _weatherInCelsius;
|
||||
QByteArray _tonsiteStorageToken;
|
||||
rpl::variable<int> _ivZoom = 100;
|
||||
|
||||
bool _tabbedReplacedWithInfo = false; // per-window
|
||||
rpl::event_stream<bool> _tabbedReplacedWithInfoValue; // per-window
|
||||
|
|
|
@ -27,10 +27,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "payments/payments_non_panel_process.h"
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/connection_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/edit_privacy_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/sessions_box.h"
|
||||
#include "boxes/star_gift_box.h"
|
||||
#include "boxes/language_box.h"
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
@ -787,9 +789,9 @@ bool CopyPeerId(
|
|||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
TextUtilities::SetClipboardText(TextForMimeData{ match->captured(1) });
|
||||
TextUtilities::SetClipboardText({ match->captured(1) });
|
||||
if (controller) {
|
||||
controller->showToast(tr::lng_text_copied(tr::now));
|
||||
controller->showToast(u"ID copied to clipboard."_q);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -928,6 +930,34 @@ bool ShowCollectibleUsername(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CopyUsernameLink(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto username = match->captured(1);
|
||||
TextUtilities::SetClipboardText({
|
||||
controller->session().createInternalLinkFull(username)
|
||||
});
|
||||
controller->showToast(tr::lng_username_copied(tr::now));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CopyUsername(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto username = match->captured(1);
|
||||
TextUtilities::SetClipboardText({ '@' + username });
|
||||
controller->showToast(tr::lng_username_text_copied(tr::now));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowStarsExamples(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
|
@ -1135,10 +1165,7 @@ bool ResolvePremiumMultigift(
|
|||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1).mid(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
controller->showGiftPremiumsBox(params.value(u"ref"_q, u"gift_url"_q));
|
||||
Ui::ChooseStarGiftRecipient(controller);
|
||||
controller->window().activate();
|
||||
return true;
|
||||
}
|
||||
|
@ -1404,6 +1431,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
|
|||
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
ShowCollectibleUsername,
|
||||
},
|
||||
{
|
||||
u"^username_link/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
CopyUsernameLink,
|
||||
},
|
||||
{
|
||||
u"^username_regular/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
|
||||
CopyUsername,
|
||||
},
|
||||
{
|
||||
u"^stars_examples$"_q,
|
||||
ShowStarsExamples,
|
||||
|
|
|
@ -13,7 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/application.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "data/data_session.h"
|
||||
#include "iv/iv_instance.h"
|
||||
|
@ -304,16 +303,6 @@ Fn<void()> UiIntegration::createSpoilerRepaint(const std::any &context) {
|
|||
return my ? my->customEmojiRepaint : nullptr;
|
||||
}
|
||||
|
||||
bool UiIntegration::allowClickHandlerActivation(
|
||||
const std::shared_ptr<ClickHandler> &handler,
|
||||
const ClickContext &context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto window = my.sessionWindow.get()) {
|
||||
window->session().sponsoredMessages().clicked(my.itemId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
|
||||
return Core::App().passcodeLockChanges() | rpl::to_empty;
|
||||
}
|
||||
|
|
|
@ -61,9 +61,6 @@ public:
|
|||
QStringView data,
|
||||
const std::any &context) override;
|
||||
Fn<void()> createSpoilerRepaint(const std::any &context) override;
|
||||
bool allowClickHandlerActivation(
|
||||
const std::shared_ptr<ClickHandler> &handler,
|
||||
const ClickContext &context) override;
|
||||
|
||||
QString phraseContextCopyText() override;
|
||||
QString phraseContextCopyEmail() override;
|
||||
|
|
|
@ -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 = 5005005;
|
||||
constexpr auto AppVersionStr = "5.5.5";
|
||||
constexpr auto AppVersion = 5006003;
|
||||
constexpr auto AppVersionStr = "5.6.3";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -69,6 +69,11 @@ uint64 Credits::balance() const {
|
|||
return _nonLockedBalance.current();
|
||||
}
|
||||
|
||||
uint64 Credits::balance(PeerId peerId) const {
|
||||
const auto it = _cachedPeerBalances.find(peerId);
|
||||
return (it != _cachedPeerBalances.end()) ? it->second : 0;
|
||||
}
|
||||
|
||||
rpl::producer<uint64> Credits::balanceValue() const {
|
||||
return _nonLockedBalance.value();
|
||||
}
|
||||
|
@ -119,4 +124,8 @@ void Credits::apply(uint64 balance) {
|
|||
}
|
||||
}
|
||||
|
||||
void Credits::apply(PeerId peerId, uint64 balance) {
|
||||
_cachedPeerBalances[peerId] = balance;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -24,11 +24,13 @@ public:
|
|||
|
||||
void load(bool force = false);
|
||||
void apply(uint64 balance);
|
||||
void apply(PeerId peerId, uint64 balance);
|
||||
|
||||
[[nodiscard]] bool loaded() const;
|
||||
[[nodiscard]] rpl::producer<bool> loadedValue() const;
|
||||
|
||||
[[nodiscard]] uint64 balance() const;
|
||||
[[nodiscard]] uint64 balance(PeerId peerId) const;
|
||||
[[nodiscard]] rpl::producer<uint64> balanceValue() const;
|
||||
[[nodiscard]] rpl::producer<float64> rateValue(
|
||||
not_null<PeerData*> ownedBotOrChannel);
|
||||
|
@ -47,6 +49,8 @@ private:
|
|||
|
||||
std::unique_ptr<Api::CreditsStatus> _loader;
|
||||
|
||||
base::flat_map<PeerId, uint64> _cachedPeerBalances;
|
||||
|
||||
uint64 _balance = 0;
|
||||
uint64 _locked = 0;
|
||||
rpl::variable<uint64> _nonLockedBalance;
|
||||
|
|
|
@ -95,6 +95,7 @@ QByteArray RecentPeers::serialize() const {
|
|||
void RecentPeers::applyLocal(QByteArray serialized) {
|
||||
_list.clear();
|
||||
if (serialized.isEmpty()) {
|
||||
DEBUG_LOG(("Suggestions: Bad RecentPeers local, empty."));
|
||||
return;
|
||||
}
|
||||
auto stream = Serialize::ByteArrayReader(serialized);
|
||||
|
@ -102,8 +103,13 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
|||
auto count = quint32();
|
||||
stream >> streamAppVersion >> count;
|
||||
if (!stream.ok()) {
|
||||
DEBUG_LOG(("Suggestions: Bad RecentPeers local, not ok."));
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("Suggestions: "
|
||||
"Start RecentPeers read, count: %1, version: %2."
|
||||
).arg(count
|
||||
).arg(streamAppVersion));
|
||||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
const auto peer = Serialize::readPeer(
|
||||
|
@ -114,9 +120,15 @@ void RecentPeers::applyLocal(QByteArray serialized) {
|
|||
_list.push_back(peer);
|
||||
} else {
|
||||
_list.clear();
|
||||
DEBUG_LOG(("Suggestions: Failed RecentPeers reading %1 / %2."
|
||||
).arg(i + 1
|
||||
).arg(count));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
DEBUG_LOG(
|
||||
("Suggestions: RecentPeers read OK, count: %1").arg(_list.size()));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/click_handler_types.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_media_preload.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -73,16 +75,17 @@ void SponsoredMessages::clearOldRequests() {
|
|||
}
|
||||
}
|
||||
|
||||
bool SponsoredMessages::append(not_null<History*> history) {
|
||||
SponsoredMessages::AppendResult SponsoredMessages::append(
|
||||
not_null<History*> history) {
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
auto &list = it->second;
|
||||
if (list.showedAll
|
||||
|| !TooEarlyForRequest(list.received)
|
||||
|| list.postsBetween) {
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
}
|
||||
|
||||
const auto entryIt = ranges::find_if(list.entries, [](const Entry &e) {
|
||||
|
@ -90,19 +93,16 @@ bool SponsoredMessages::append(not_null<History*> history) {
|
|||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
list.showedAll = true;
|
||||
return false;
|
||||
return SponsoredMessages::AppendResult::None;
|
||||
} else if (entryIt->preload) {
|
||||
return SponsoredMessages::AppendResult::MediaLoading;
|
||||
}
|
||||
// SponsoredMessages::Details can be requested within
|
||||
// the constructor of HistoryItem, so itemFullId is used as a key.
|
||||
entryIt->itemFullId = FullMsgId(
|
||||
history->peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
entryIt->item.reset(history->addSponsoredMessage(
|
||||
entryIt->itemFullId.msg,
|
||||
entryIt->sponsored.from,
|
||||
entryIt->sponsored.textWithEntities));
|
||||
|
||||
return true;
|
||||
return SponsoredMessages::AppendResult::Appended;
|
||||
}
|
||||
|
||||
void SponsoredMessages::inject(
|
||||
|
@ -227,8 +227,7 @@ void SponsoredMessages::request(not_null<History*> history, Fn<void()> done) {
|
|||
const auto channel = history->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(
|
||||
channel->inputChannel)
|
||||
MTPchannels_GetSponsoredMessages(channel->inputChannel)
|
||||
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
if (done) {
|
||||
|
@ -276,15 +275,14 @@ void SponsoredMessages::append(
|
|||
const MTPSponsoredMessage &message) {
|
||||
const auto &data = message.data();
|
||||
const auto randomId = data.vrandom_id().v;
|
||||
auto mediaPhotoId = PhotoId(0);
|
||||
auto mediaDocumentId = DocumentId(0);
|
||||
auto mediaPhoto = (PhotoData*)nullptr;
|
||||
auto mediaDocument = (DocumentData*)nullptr;
|
||||
{
|
||||
if (data.vmedia()) {
|
||||
data.vmedia()->match([&](const MTPDmessageMediaPhoto &media) {
|
||||
if (const auto tlPhoto = media.vphoto()) {
|
||||
tlPhoto->match([&](const MTPDphoto &data) {
|
||||
const auto p = history->owner().processPhoto(data);
|
||||
mediaPhotoId = p->id;
|
||||
mediaPhoto = history->owner().processPhoto(data);
|
||||
}, [](const MTPDphotoEmpty &) {
|
||||
});
|
||||
}
|
||||
|
@ -296,7 +294,7 @@ void SponsoredMessages::append(
|
|||
|| d->isSilentVideo()
|
||||
|| d->isAnimation()
|
||||
|| d->isGifv()) {
|
||||
mediaDocumentId = d->id;
|
||||
mediaDocument = d;
|
||||
}
|
||||
}, [](const MTPDdocumentEmpty &) {
|
||||
});
|
||||
|
@ -312,8 +310,8 @@ void SponsoredMessages::append(
|
|||
.photoId = data.vphoto()
|
||||
? history->session().data().processPhoto(*data.vphoto())->id
|
||||
: PhotoId(0),
|
||||
.mediaPhotoId = mediaPhotoId,
|
||||
.mediaDocumentId = mediaDocumentId,
|
||||
.mediaPhotoId = (mediaPhoto ? mediaPhoto->id : 0),
|
||||
.mediaDocumentId = (mediaDocument ? mediaDocument->id : 0),
|
||||
.backgroundEmojiId = data.vcolor().has_value()
|
||||
? data.vcolor()->data().vbackground_emoji_id().value_or_empty()
|
||||
: uint64(0),
|
||||
|
@ -347,7 +345,56 @@ void SponsoredMessages::append(
|
|||
.sponsorInfo = std::move(sponsorInfo),
|
||||
.additionalInfo = std::move(additionalInfo),
|
||||
};
|
||||
list.entries.push_back({ nullptr, {}, std::move(sharedMessage) });
|
||||
list.entries.push_back({
|
||||
.sponsored = std::move(sharedMessage),
|
||||
});
|
||||
auto &entry = list.entries.back();
|
||||
const auto itemId = entry.itemFullId = FullMsgId(
|
||||
history->peer->id,
|
||||
_session->data().nextLocalMessageId());
|
||||
const auto fileOrigin = FileOrigin(); // No way to refresh in ads.
|
||||
|
||||
static const auto kFlaggedPreload = ((MediaPreload*)quintptr(0x01));
|
||||
const auto preloaded = [=] {
|
||||
const auto i = _data.find(history);
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
auto &entries = i->second.entries;
|
||||
const auto j = ranges::find(entries, itemId, &Entry::itemFullId);
|
||||
if (j == end(entries)) {
|
||||
return;
|
||||
}
|
||||
auto &entry = *j;
|
||||
if (entry.preload.get() == kFlaggedPreload) {
|
||||
entry.preload.release();
|
||||
} else {
|
||||
entry.preload = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
auto preload = std::unique_ptr<MediaPreload>();
|
||||
entry.preload.reset(kFlaggedPreload);
|
||||
if (mediaPhoto) {
|
||||
preload = std::make_unique<PhotoPreload>(
|
||||
mediaPhoto,
|
||||
fileOrigin,
|
||||
preloaded);
|
||||
} else if (mediaDocument && VideoPreload::Can(mediaDocument)) {
|
||||
preload = std::make_unique<VideoPreload>(
|
||||
mediaDocument,
|
||||
fileOrigin,
|
||||
preloaded);
|
||||
}
|
||||
// Preload constructor may have called preloaded(), which zero-ed
|
||||
// entry.preload, that way we're ready and don't need to save it.
|
||||
// Otherwise we're preloading and need to save the task.
|
||||
if (entry.preload.get() == kFlaggedPreload) {
|
||||
entry.preload.release();
|
||||
if (preload) {
|
||||
entry.preload = std::move(preload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
|
@ -439,7 +486,10 @@ SponsoredMessages::Details SponsoredMessages::lookupDetails(
|
|||
};
|
||||
}
|
||||
|
||||
void SponsoredMessages::clicked(const FullMsgId &fullId) {
|
||||
void SponsoredMessages::clicked(
|
||||
const FullMsgId &fullId,
|
||||
bool isMedia,
|
||||
bool isFullscreen) {
|
||||
const auto entryPtr = find(fullId);
|
||||
if (!entryPtr) {
|
||||
return;
|
||||
|
@ -447,7 +497,11 @@ void SponsoredMessages::clicked(const FullMsgId &fullId) {
|
|||
const auto randomId = entryPtr->sponsored.randomId;
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
using Flag = MTPchannels_ClickSponsoredMessage::Flag;
|
||||
_session->api().request(MTPchannels_ClickSponsoredMessage(
|
||||
MTP_flags(Flag(0)
|
||||
| (isMedia ? Flag::f_media : Flag(0))
|
||||
| (isFullscreen ? Flag::f_fullscreen : Flag(0))),
|
||||
channel->inputChannel,
|
||||
MTP_bytes(randomId)
|
||||
)).send();
|
||||
|
|
|
@ -20,6 +20,8 @@ class Session;
|
|||
|
||||
namespace Data {
|
||||
|
||||
class MediaPreload;
|
||||
|
||||
struct SponsoredReportResult final {
|
||||
using Id = QByteArray;
|
||||
struct Option final {
|
||||
|
@ -65,6 +67,11 @@ struct SponsoredMessage {
|
|||
|
||||
class SponsoredMessages final {
|
||||
public:
|
||||
enum class AppendResult {
|
||||
None,
|
||||
Appended,
|
||||
MediaLoading,
|
||||
};
|
||||
enum class State {
|
||||
None,
|
||||
AppendToEnd,
|
||||
|
@ -90,9 +97,9 @@ public:
|
|||
void request(not_null<History*> history, Fn<void()> done);
|
||||
void clearItems(not_null<History*> history);
|
||||
[[nodiscard]] Details lookupDetails(const FullMsgId &fullId) const;
|
||||
void clicked(const FullMsgId &fullId);
|
||||
void clicked(const FullMsgId &fullId, bool isMedia, bool isFullscreen);
|
||||
|
||||
[[nodiscard]] bool append(not_null<History*> history);
|
||||
[[nodiscard]] AppendResult append(not_null<History*> history);
|
||||
void inject(
|
||||
not_null<History*> history,
|
||||
MsgId injectAfterMsgId,
|
||||
|
@ -114,6 +121,7 @@ private:
|
|||
OwnedItem item;
|
||||
FullMsgId itemFullId;
|
||||
SponsoredMessage sponsored;
|
||||
std::unique_ptr<MediaPreload> preload;
|
||||
};
|
||||
struct List {
|
||||
std::vector<Entry> entries;
|
||||
|
|
|
@ -274,11 +274,13 @@ QByteArray TopPeers::serialize() const {
|
|||
|
||||
void TopPeers::applyLocal(QByteArray serialized) {
|
||||
if (_lastReceived) {
|
||||
DEBUG_LOG(("Suggestions: Skipping TopPeers local, got already."));
|
||||
return;
|
||||
}
|
||||
_list.clear();
|
||||
_disabled = false;
|
||||
if (serialized.isEmpty()) {
|
||||
DEBUG_LOG(("Suggestions: Bad TopPeers local, empty."));
|
||||
return;
|
||||
}
|
||||
auto stream = Serialize::ByteArrayReader(serialized);
|
||||
|
@ -287,8 +289,14 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
|||
auto count = quint32();
|
||||
stream >> streamAppVersion >> disabled >> count;
|
||||
if (!stream.ok()) {
|
||||
DEBUG_LOG(("Suggestions: Bad TopPeers local, not ok."));
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("Suggestions: "
|
||||
"Start TopPeers read, count: %1, version: %2, disabled: %3."
|
||||
).arg(count
|
||||
).arg(streamAppVersion
|
||||
).arg(disabled));
|
||||
_list.reserve(count);
|
||||
for (auto i = 0; i != int(count); ++i) {
|
||||
auto rating = quint64();
|
||||
|
@ -303,11 +311,15 @@ void TopPeers::applyLocal(QByteArray serialized) {
|
|||
.rating = DeserializeRating(rating),
|
||||
});
|
||||
} else {
|
||||
DEBUG_LOG(("Suggestions: "
|
||||
"Failed TopPeers reading %1 / %2.").arg(i + 1).arg(count));
|
||||
_list.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_disabled = (disabled == 1);
|
||||
DEBUG_LOG(
|
||||
("Suggestions: TopPeers read OK, count: %1").arg(_list.size()));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -84,34 +84,35 @@ struct PeerUpdate {
|
|||
BotCanBeInvited = (1ULL << 22),
|
||||
BotStartToken = (1ULL << 23),
|
||||
CommonChats = (1ULL << 24),
|
||||
HasCalls = (1ULL << 25),
|
||||
SupportInfo = (1ULL << 26),
|
||||
IsBot = (1ULL << 27),
|
||||
EmojiStatus = (1ULL << 28),
|
||||
BusinessDetails = (1ULL << 29),
|
||||
Birthday = (1ULL << 30),
|
||||
PersonalChannel = (1ULL << 31),
|
||||
PeerGifts = (1ULL << 25),
|
||||
HasCalls = (1ULL << 26),
|
||||
SupportInfo = (1ULL << 27),
|
||||
IsBot = (1ULL << 28),
|
||||
EmojiStatus = (1ULL << 29),
|
||||
BusinessDetails = (1ULL << 30),
|
||||
Birthday = (1ULL << 31),
|
||||
PersonalChannel = (1ULL << 32),
|
||||
|
||||
// For chats and channels
|
||||
InviteLinks = (1ULL << 32),
|
||||
Members = (1ULL << 33),
|
||||
Admins = (1ULL << 34),
|
||||
BannedUsers = (1ULL << 35),
|
||||
Rights = (1ULL << 36),
|
||||
PendingRequests = (1ULL << 37),
|
||||
Reactions = (1ULL << 38),
|
||||
InviteLinks = (1ULL << 33),
|
||||
Members = (1ULL << 34),
|
||||
Admins = (1ULL << 35),
|
||||
BannedUsers = (1ULL << 36),
|
||||
Rights = (1ULL << 37),
|
||||
PendingRequests = (1ULL << 38),
|
||||
Reactions = (1ULL << 39),
|
||||
|
||||
// For channels
|
||||
ChannelAmIn = (1ULL << 39),
|
||||
StickersSet = (1ULL << 40),
|
||||
EmojiSet = (1ULL << 41),
|
||||
ChannelLinkedChat = (1ULL << 42),
|
||||
ChannelLocation = (1ULL << 43),
|
||||
Slowmode = (1ULL << 44),
|
||||
GroupCall = (1ULL << 45),
|
||||
ChannelAmIn = (1ULL << 40),
|
||||
StickersSet = (1ULL << 41),
|
||||
EmojiSet = (1ULL << 42),
|
||||
ChannelLinkedChat = (1ULL << 43),
|
||||
ChannelLocation = (1ULL << 44),
|
||||
Slowmode = (1ULL << 45),
|
||||
GroupCall = (1ULL << 46),
|
||||
|
||||
// For iteration
|
||||
LastUsedBit = (1ULL << 45),
|
||||
LastUsedBit = (1ULL << 46),
|
||||
};
|
||||
using Flags = base::flags<Flag>;
|
||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||
|
|
|
@ -112,7 +112,9 @@ bool CanSendAnyOf(
|
|||
ChatRestrictions rights,
|
||||
bool forbidInForums) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->isInaccessible() || user->isRepliesChat()) {
|
||||
if (user->isInaccessible()
|
||||
|| user->isRepliesChat()
|
||||
|| user->isVerifyCodes()) {
|
||||
return false;
|
||||
} else if (user->meRequiresPremiumToWrite()
|
||||
&& !user->session().premium()) {
|
||||
|
|
|
@ -50,7 +50,7 @@ struct CreditsHistoryEntry final {
|
|||
|
||||
QString id;
|
||||
QString title;
|
||||
QString description;
|
||||
TextWithEntities description;
|
||||
QDateTime date;
|
||||
PhotoId photoId = 0;
|
||||
std::vector<CreditsHistoryMedia> extended;
|
||||
|
@ -58,10 +58,18 @@ struct CreditsHistoryEntry final {
|
|||
uint64 bareMsgId = 0;
|
||||
uint64 barePeerId = 0;
|
||||
uint64 bareGiveawayMsgId = 0;
|
||||
uint64 bareGiftStickerId = 0;
|
||||
PeerType peerType;
|
||||
QDateTime subscriptionUntil;
|
||||
QDateTime successDate;
|
||||
QString successLink;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int convertStars = 0;
|
||||
bool converted = false;
|
||||
bool anonymous = false;
|
||||
bool savedToProfile = false;
|
||||
bool fromGiftsList = false;
|
||||
bool reaction = false;
|
||||
bool refunded = false;
|
||||
bool pending = false;
|
||||
|
|
|
@ -936,14 +936,14 @@ void DocumentData::setFileName(const QString &remoteFileName) {
|
|||
// in filenames, because they introduce a security issue, when
|
||||
// an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg".
|
||||
QChar controls[] = {
|
||||
0x200E, // LTR Mark
|
||||
0x200F, // RTL Mark
|
||||
0x202A, // LTR Embedding
|
||||
0x202B, // RTL Embedding
|
||||
0x202D, // LTR Override
|
||||
0x202E, // RTL Override
|
||||
0x2066, // LTR Isolate
|
||||
0x2067, // RTL Isolate
|
||||
QChar(0x200E), // LTR Mark
|
||||
QChar(0x200F), // RTL Mark
|
||||
QChar(0x202A), // LTR Embedding
|
||||
QChar(0x202B), // RTL Embedding
|
||||
QChar(0x202D), // LTR Override
|
||||
QChar(0x202E), // RTL Override
|
||||
QChar(0x2066), // LTR Isolate
|
||||
QChar(0x2067), // RTL Isolate
|
||||
};
|
||||
for (const auto &ch : controls) {
|
||||
_filename = std::move(_filename).replace(ch, "_");
|
||||
|
|
|
@ -42,6 +42,8 @@ namespace {
|
|||
base::options::toggle OptionExternalVideoPlayer({
|
||||
.id = kOptionExternalVideoPlayer,
|
||||
.name = "External video player",
|
||||
.description = "Use system video player instead of the internal one. "
|
||||
"This disabes video playback in messages.",
|
||||
});
|
||||
|
||||
void ConfirmDontWarnBox(
|
||||
|
|
209
Telegram/SourceFiles/data/data_media_preload.cpp
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_media_preload.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "media/streaming/media_streaming_reader.h"
|
||||
#include "storage/file_download.h" // kMaxFileInMemory.
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDefaultPreloadPrefix = 4 * 1024 * 1024;
|
||||
|
||||
[[nodiscard]] int64 ChoosePreloadPrefix(not_null<DocumentData*> video) {
|
||||
const auto result = video->videoPreloadPrefix();
|
||||
return result
|
||||
? result
|
||||
: std::min(int64(kDefaultPreloadPrefix), video->size);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MediaPreload::MediaPreload(Fn<void()> done)
|
||||
: _done(std::move(done)) {
|
||||
}
|
||||
|
||||
void MediaPreload::callDone() {
|
||||
if (const auto onstack = _done) {
|
||||
onstack();
|
||||
}
|
||||
}
|
||||
|
||||
PhotoPreload::PhotoPreload(
|
||||
not_null<PhotoData*> photo,
|
||||
FileOrigin origin,
|
||||
Fn<void()> done)
|
||||
: MediaPreload(std::move(done))
|
||||
, _photo(photo->createMediaView()) {
|
||||
start(origin);
|
||||
}
|
||||
|
||||
PhotoPreload::~PhotoPreload() {
|
||||
if (_photo) {
|
||||
base::take(_photo)->owner()->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
bool PhotoPreload::Should(
|
||||
not_null<PhotoData*> photo,
|
||||
not_null<PeerData*> context) {
|
||||
return !photo->cancelled()
|
||||
&& AutoDownload::Should(
|
||||
photo->session().settings().autoDownload(),
|
||||
context,
|
||||
photo);
|
||||
}
|
||||
|
||||
void PhotoPreload::start(FileOrigin origin) {
|
||||
if (_photo->loaded()) {
|
||||
callDone();
|
||||
} else {
|
||||
_photo->owner()->load(origin, LoadFromCloudOrLocal, true);
|
||||
_photo->owner()->session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
return _photo->loaded();
|
||||
}) | rpl::start_with_next([=] { callDone(); }, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
VideoPreload::VideoPreload(
|
||||
not_null<DocumentData*> video,
|
||||
FileOrigin origin,
|
||||
Fn<void()> done)
|
||||
: MediaPreload(std::move(done))
|
||||
, DownloadMtprotoTask(
|
||||
&video->session().downloader(),
|
||||
video->videoPreloadLocation(),
|
||||
origin)
|
||||
, _video(video)
|
||||
, _full(video->size) {
|
||||
if (Can(video)) {
|
||||
check();
|
||||
} else {
|
||||
callDone();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoPreload::check() {
|
||||
const auto key = _video->bigFileBaseCacheKey();
|
||||
const auto weak = base::make_weak(static_cast<has_weak_ptr*>(this));
|
||||
_video->owner().cacheBigFile().get(key, [weak](
|
||||
const QByteArray &result) {
|
||||
if (!result.isEmpty()) {
|
||||
crl::on_main([weak] {
|
||||
if (const auto strong = weak.get()) {
|
||||
static_cast<VideoPreload*>(strong)->callDone();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
crl::on_main([weak] {
|
||||
if (const auto strong = weak.get()) {
|
||||
static_cast<VideoPreload*>(strong)->load();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VideoPreload::load() {
|
||||
if (!Can(_video)) {
|
||||
callDone();
|
||||
return;
|
||||
}
|
||||
const auto prefix = ChoosePreloadPrefix(_video);
|
||||
Assert(prefix > 0 && prefix <= _video->size);
|
||||
const auto part = Storage::kDownloadPartSize;
|
||||
const auto parts = (prefix + part - 1) / part;
|
||||
for (auto i = 0; i != parts; ++i) {
|
||||
_parts.emplace(i * part, QByteArray());
|
||||
}
|
||||
addToQueue();
|
||||
}
|
||||
|
||||
void VideoPreload::done(QByteArray result) {
|
||||
const auto key = _video->bigFileBaseCacheKey();
|
||||
if (!result.isEmpty() && key) {
|
||||
Assert(result.size() < Storage::kMaxFileInMemory);
|
||||
_video->owner().cacheBigFile().putIfEmpty(
|
||||
key,
|
||||
Storage::Cache::Database::TaggedValue(std::move(result), 0));
|
||||
}
|
||||
callDone();
|
||||
}
|
||||
|
||||
VideoPreload::~VideoPreload() {
|
||||
if (!_finished && !_failed) {
|
||||
cancelAllRequests();
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoPreload::Can(not_null<DocumentData*> video) {
|
||||
return video->canBeStreamed(nullptr)
|
||||
&& video->videoPreloadLocation().valid()
|
||||
&& video->bigFileBaseCacheKey();
|
||||
}
|
||||
|
||||
bool VideoPreload::readyToRequest() const {
|
||||
const auto part = Storage::kDownloadPartSize;
|
||||
return !_failed && (_nextRequestOffset < _parts.size() * part);
|
||||
}
|
||||
|
||||
int64 VideoPreload::takeNextRequestOffset() {
|
||||
Expects(readyToRequest());
|
||||
|
||||
_requestedOffsets.emplace(_nextRequestOffset);
|
||||
_nextRequestOffset += Storage::kDownloadPartSize;
|
||||
return _requestedOffsets.back();
|
||||
}
|
||||
|
||||
bool VideoPreload::feedPart(
|
||||
int64 offset,
|
||||
const QByteArray &bytes) {
|
||||
Expects(offset < _parts.size() * Storage::kDownloadPartSize);
|
||||
Expects(_requestedOffsets.contains(int(offset)));
|
||||
Expects(bytes.size() <= Storage::kDownloadPartSize);
|
||||
|
||||
const auto part = Storage::kDownloadPartSize;
|
||||
_requestedOffsets.remove(int(offset));
|
||||
_parts[offset] = bytes;
|
||||
if ((_nextRequestOffset + part >= _parts.size() * part)
|
||||
&& _requestedOffsets.empty()) {
|
||||
_finished = true;
|
||||
removeFromQueue();
|
||||
auto result = ::Media::Streaming::SerializeComplexPartsMap(_parts);
|
||||
if (result.size() == _full) {
|
||||
// Make sure it is parsed as a complex map.
|
||||
result.push_back(char(0));
|
||||
}
|
||||
done(std::move(result));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoPreload::cancelOnFail() {
|
||||
_failed = true;
|
||||
cancelAllRequests();
|
||||
done({});
|
||||
}
|
||||
|
||||
bool VideoPreload::setWebFileSizeHook(int64 size) {
|
||||
_failed = true;
|
||||
cancelAllRequests();
|
||||
done({});
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Data
|
83
Telegram/SourceFiles/data/data_media_preload.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
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
|
||||
|
||||
#include "storage/download_manager_mtproto.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
class PhotoMedia;
|
||||
struct FileOrigin;
|
||||
|
||||
class MediaPreload {
|
||||
public:
|
||||
explicit MediaPreload(Fn<void()> done);
|
||||
virtual ~MediaPreload() = default;
|
||||
|
||||
protected:
|
||||
void callDone();
|
||||
|
||||
private:
|
||||
Fn<void()> _done;
|
||||
|
||||
};
|
||||
|
||||
class PhotoPreload final : public MediaPreload {
|
||||
public:
|
||||
[[nodiscard]] static bool Should(
|
||||
not_null<PhotoData*> photo,
|
||||
not_null<PeerData*> context);
|
||||
|
||||
PhotoPreload(
|
||||
not_null<PhotoData*> data,
|
||||
FileOrigin origin,
|
||||
Fn<void()> done);
|
||||
~PhotoPreload();
|
||||
|
||||
private:
|
||||
void start(FileOrigin origin);
|
||||
|
||||
std::shared_ptr<PhotoMedia> _photo;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class VideoPreload final
|
||||
: public MediaPreload
|
||||
, private Storage::DownloadMtprotoTask {
|
||||
public:
|
||||
[[nodiscard]] static bool Can(not_null<DocumentData*> video);
|
||||
|
||||
VideoPreload(
|
||||
not_null<DocumentData*> video,
|
||||
FileOrigin origin,
|
||||
Fn<void()> done);
|
||||
~VideoPreload();
|
||||
|
||||
private:
|
||||
void check();
|
||||
void load();
|
||||
void done(QByteArray result);
|
||||
|
||||
bool readyToRequest() const override;
|
||||
int64 takeNextRequestOffset() override;
|
||||
bool feedPart(int64 offset, const QByteArray &bytes) override;
|
||||
void cancelOnFail() override;
|
||||
bool setWebFileSizeHook(int64 size) override;
|
||||
|
||||
const not_null<DocumentData*> _video;
|
||||
base::flat_map<uint32, QByteArray> _parts;
|
||||
base::flat_set<int> _requestedOffsets;
|
||||
int64 _full = 0;
|
||||
int _nextRequestOffset = 0;
|
||||
bool _finished = false;
|
||||
bool _failed = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -579,6 +579,10 @@ const Invoice *Media::invoice() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const GiftCode *Media::gift() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CloudImage *Media::location() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -2331,8 +2335,8 @@ not_null<PeerData*> MediaGiftBox::from() const {
|
|||
return _from;
|
||||
}
|
||||
|
||||
const GiftCode &MediaGiftBox::data() const {
|
||||
return _data;
|
||||
const GiftCode *MediaGiftBox::gift() const {
|
||||
return &_data;
|
||||
}
|
||||
|
||||
TextWithEntities MediaGiftBox::notificationText() const {
|
||||
|
|
|
@ -130,16 +130,25 @@ struct GiveawayResults {
|
|||
enum class GiftType : uchar {
|
||||
Premium, // count - months
|
||||
Credits, // count - credits
|
||||
StarGift, // count - stars
|
||||
};
|
||||
|
||||
struct GiftCode {
|
||||
QString slug;
|
||||
DocumentData *document = nullptr;
|
||||
TextWithEntities message;
|
||||
ChannelData *channel = nullptr;
|
||||
MsgId giveawayMsgId = 0;
|
||||
int convertStars = 0;
|
||||
int limitedCount = 0;
|
||||
int limitedLeft = 0;
|
||||
int count = 0;
|
||||
int giveawayMsgId = 0;
|
||||
GiftType type = GiftType::Premium;
|
||||
bool viaGiveaway = false;
|
||||
bool unclaimed = false;
|
||||
bool viaGiveaway : 1 = false;
|
||||
bool unclaimed : 1 = false;
|
||||
bool anonymous : 1 = false;
|
||||
bool converted : 1 = false;
|
||||
bool saved : 1 = false;
|
||||
};
|
||||
|
||||
class Media {
|
||||
|
@ -163,6 +172,7 @@ public:
|
|||
virtual const Call *call() const;
|
||||
virtual GameData *game() const;
|
||||
virtual const Invoice *invoice() const;
|
||||
virtual const GiftCode *gift() const;
|
||||
virtual CloudImage *location() const;
|
||||
virtual PollData *poll() const;
|
||||
virtual const WallPaper *paper() const;
|
||||
|
@ -612,7 +622,7 @@ public:
|
|||
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
|
||||
|
||||
[[nodiscard]] not_null<PeerData*> from() const;
|
||||
[[nodiscard]] const GiftCode &data() const;
|
||||
[[nodiscard]] const GiftCode *gift() const override;
|
||||
|
||||
TextWithEntities notificationText() const override;
|
||||
QString pinnedTextSubstring() const override;
|
||||
|
|
|
@ -394,7 +394,8 @@ void PeerData::paintUserpic(
|
|||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const {
|
||||
int size,
|
||||
bool forceCircle) const {
|
||||
const auto cloud = userpicCloudImage(view);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
Ui::ValidateUserpicCache(
|
||||
|
@ -402,7 +403,7 @@ void PeerData::paintUserpic(
|
|||
cloud,
|
||||
cloud ? nullptr : ensureEmptyUserpic().get(),
|
||||
size * ratio,
|
||||
isForum());
|
||||
!forceCircle && isForum());
|
||||
p.drawImage(QRect(x, y, size, size), view.cached);
|
||||
}
|
||||
|
||||
|
@ -627,7 +628,8 @@ bool PeerData::canCreatePolls() const {
|
|||
if (const auto user = asUser()) {
|
||||
return user->isBot()
|
||||
&& !user->isSupport()
|
||||
&& !user->isRepliesChat();
|
||||
&& !user->isRepliesChat()
|
||||
&& !user->isVerifyCodes();
|
||||
}
|
||||
return Data::CanSend(this, ChatRestriction::SendPolls);
|
||||
}
|
||||
|
@ -663,7 +665,7 @@ bool PeerData::canEditMessagesIndefinitely() const {
|
|||
}
|
||||
|
||||
bool PeerData::canExportChatHistory() const {
|
||||
if (isRepliesChat() || !allowsForwarding()) {
|
||||
if (isRepliesChat() || isVerifyCodes() || !allowsForwarding()) {
|
||||
return false;
|
||||
} else if (const auto channel = asChannel()) {
|
||||
if (!channel->amIn() && channel->invitePeekExpires()) {
|
||||
|
@ -864,6 +866,13 @@ void PeerData::fillNames() {
|
|||
if (localized != english) {
|
||||
appendToIndex(localized);
|
||||
}
|
||||
} else if (isVerifyCodes()) {
|
||||
const auto english = u"Verification Codes"_q;
|
||||
const auto localized = tr::lng_verification_codes(tr::now);
|
||||
appendToIndex(english);
|
||||
if (localized != english) {
|
||||
appendToIndex(localized);
|
||||
}
|
||||
}
|
||||
} else if (const auto channel = asChannel()) {
|
||||
appendToIndex(channel->username());
|
||||
|
@ -1201,6 +1210,11 @@ bool PeerData::isRepliesChat() const {
|
|||
: kTestId) == id;
|
||||
}
|
||||
|
||||
bool PeerData::isVerifyCodes() const {
|
||||
constexpr auto kVerifyCodesId = peerFromUser(489000);
|
||||
return (id == kVerifyCodesId);
|
||||
}
|
||||
|
||||
bool PeerData::sharedMediaInfo() const {
|
||||
return isSelf() || isRepliesChat();
|
||||
}
|
||||
|
|
|
@ -227,6 +227,7 @@ public:
|
|||
[[nodiscard]] bool isForum() const;
|
||||
[[nodiscard]] bool isGigagroup() const;
|
||||
[[nodiscard]] bool isRepliesChat() const;
|
||||
[[nodiscard]] bool isVerifyCodes() const;
|
||||
[[nodiscard]] bool sharedMediaInfo() const;
|
||||
[[nodiscard]] bool savedSublistsInfo() const;
|
||||
[[nodiscard]] bool hasStoriesHidden() const;
|
||||
|
@ -317,15 +318,23 @@ public:
|
|||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int size) const;
|
||||
int size,
|
||||
bool forceCircle = false) const;
|
||||
void paintUserpicLeft(
|
||||
Painter &p,
|
||||
Ui::PeerUserpicView &view,
|
||||
int x,
|
||||
int y,
|
||||
int w,
|
||||
int size) const {
|
||||
paintUserpic(p, view, rtl() ? (w - x - size) : x, y, size);
|
||||
int size,
|
||||
bool forceCircle = false) const {
|
||||
paintUserpic(
|
||||
p,
|
||||
view,
|
||||
rtl() ? (w - x - size) : x,
|
||||
y,
|
||||
size,
|
||||
forceCircle);
|
||||
}
|
||||
void loadUserpic();
|
||||
[[nodiscard]] bool hasUserpic() const;
|
||||
|
|
|
@ -220,7 +220,7 @@ inline auto DefaultRestrictionValue(
|
|||
ChatRestrictions rights,
|
||||
bool forbidInForums) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
if (user->isRepliesChat()) {
|
||||
if (user->isRepliesChat() || user->isVerifyCodes()) {
|
||||
return rpl::single(false);
|
||||
}
|
||||
using namespace rpl::mappers;
|
||||
|
|
24
Telegram/SourceFiles/data/data_report.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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 {
|
||||
|
||||
struct ReportInput final {
|
||||
QByteArray optionId;
|
||||
QString optionText;
|
||||
QString comment;
|
||||
std::vector<MsgId> ids;
|
||||
std::vector<StoryId> stories;
|
||||
|
||||
inline bool operator==(const ReportInput &other) const {
|
||||
return optionId == other.optionId && comment == other.comment;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -1668,6 +1668,14 @@ rpl::producer<not_null<HistoryItem*>> Session::newItemAdded() const {
|
|||
return _newItemAdded.events();
|
||||
}
|
||||
|
||||
void Session::notifyGiftUpdate(GiftUpdate &&update) {
|
||||
_giftUpdates.fire(std::move(update));
|
||||
}
|
||||
|
||||
rpl::producer<GiftUpdate> Session::giftUpdates() const {
|
||||
return _giftUpdates.events();
|
||||
}
|
||||
|
||||
HistoryItem *Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) {
|
||||
const auto list = messagesListForInsert(peerId);
|
||||
const auto i = list->find(wasId);
|
||||
|
|
|
@ -77,6 +77,18 @@ struct RepliesReadTillUpdate {
|
|||
bool out = false;
|
||||
};
|
||||
|
||||
struct GiftUpdate {
|
||||
enum class Action : uchar {
|
||||
Save,
|
||||
Unsave,
|
||||
Convert,
|
||||
Delete,
|
||||
};
|
||||
|
||||
FullMsgId itemId;
|
||||
Action action = {};
|
||||
};
|
||||
|
||||
class Session final {
|
||||
public:
|
||||
using ViewElement = HistoryView::Element;
|
||||
|
@ -281,6 +293,8 @@ public:
|
|||
[[nodiscard]] rpl::producer<not_null<const ViewElement*>> viewLayoutChanged() const;
|
||||
void notifyNewItemAdded(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<HistoryItem*>> newItemAdded() const;
|
||||
void notifyGiftUpdate(GiftUpdate &&update);
|
||||
[[nodiscard]] rpl::producer<GiftUpdate> giftUpdates() const;
|
||||
void requestItemRepaint(not_null<const HistoryItem*> item);
|
||||
[[nodiscard]] rpl::producer<not_null<const HistoryItem*>> itemRepaintRequest() const;
|
||||
void requestViewRepaint(not_null<const ViewElement*> view);
|
||||
|
@ -924,6 +938,7 @@ private:
|
|||
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
|
||||
rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges;
|
||||
rpl::event_stream<not_null<HistoryItem*>> _newItemAdded;
|
||||
rpl::event_stream<GiftUpdate> _giftUpdates;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
|
||||
rpl::event_stream<not_null<const ViewElement*>> _viewRepaintRequest;
|
||||
rpl::event_stream<not_null<const HistoryItem*>> _itemResizeRequest;
|
||||
|
|