Merge tag 'v5.10.7' into dev
|
@ -1096,8 +1096,8 @@ PRIVATE
|
|||
info/saved/info_saved_sublists_widget.h
|
||||
info/settings/info_settings_widget.cpp
|
||||
info/settings/info_settings_widget.h
|
||||
info/similar_channels/info_similar_channels_widget.cpp
|
||||
info/similar_channels/info_similar_channels_widget.h
|
||||
info/similar_peers/info_similar_peers_widget.cpp
|
||||
info/similar_peers/info_similar_peers_widget.h
|
||||
info/statistics/info_statistics_common.h
|
||||
info/statistics/info_statistics_inner_widget.cpp
|
||||
info/statistics/info_statistics_inner_widget.h
|
||||
|
@ -1217,6 +1217,8 @@ PRIVATE
|
|||
media/audio/media_audio_loader.h
|
||||
media/audio/media_audio_loaders.cpp
|
||||
media/audio/media_audio_loaders.h
|
||||
media/audio/media_audio_local_cache.cpp
|
||||
media/audio/media_audio_local_cache.h
|
||||
media/audio/media_audio_track.cpp
|
||||
media/audio/media_audio_track.h
|
||||
media/audio/media_child_ffmpeg_loader.cpp
|
||||
|
|
BIN
Telegram/Resources/icons/chat/markup_webview.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
Telegram/Resources/icons/chat/markup_webview@2x.png
Normal file
After Width: | Height: | Size: 761 B |
BIN
Telegram/Resources/icons/chat/markup_webview@3x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/nft_takeoff.png
Normal file
After Width: | Height: | Size: 730 B |
BIN
Telegram/Resources/icons/menu/nft_takeoff@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/nft_takeoff@3x.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
Telegram/Resources/icons/menu/nft_wear.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
Telegram/Resources/icons/menu/nft_wear@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/menu/nft_wear@3x.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
|
@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||
"lng_settings_window_close" = "When window closed";
|
||||
"lng_settings_run_in_background" = "Run in the background";
|
||||
"lng_settings_quit_on_close" = "Quit the application";
|
||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||
"lng_settings_window_system" = "Window title bar";
|
||||
|
@ -1346,6 +1349,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_common_groups#other" = "{count} groups in common";
|
||||
"lng_profile_similar_channels#one" = "{count} similar channel";
|
||||
"lng_profile_similar_channels#other" = "{count} similar channels";
|
||||
"lng_profile_similar_bots#one" = "{count} similar bot";
|
||||
"lng_profile_similar_bots#other" = "{count} similar bots";
|
||||
"lng_profile_saved_messages#one" = "{count} saved message";
|
||||
"lng_profile_saved_messages#other" = "{count} saved messages";
|
||||
"lng_profile_peer_gifts#one" = "{count} gift";
|
||||
|
@ -2020,16 +2025,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_gift_sent" = "You sent a gift for {cost}";
|
||||
"lng_action_gift_unique_sent" = "You sent a unique collectible item";
|
||||
"lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible";
|
||||
"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible";
|
||||
"lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible";
|
||||
"lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible";
|
||||
"lng_action_gift_transferred" = "{user} transferred you a gift";
|
||||
"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_unknown" = "Someone transferred you a gift";
|
||||
"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_self" = "You transferred a unique collectible";
|
||||
"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}";
|
||||
"lng_action_gift_transferred_mine" = "You transferred a gift to {user}";
|
||||
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}";
|
||||
"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}";
|
||||
"lng_action_gift_self_bought" = "You bought a gift for {cost}";
|
||||
"lng_action_gift_self_subtitle" = "Saved Gift";
|
||||
"lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star.";
|
||||
"lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others.";
|
||||
"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star.";
|
||||
"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars.";
|
||||
"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible.";
|
||||
"lng_action_gift_for_stars#one" = "{count} Star";
|
||||
"lng_action_gift_for_stars#other" = "{count} Stars";
|
||||
"lng_action_gift_got_subtitle" = "Gift from {user}";
|
||||
|
@ -2038,6 +2055,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible.";
|
||||
"lng_action_gift_got_gift_text" = "You can keep this gift on your page.";
|
||||
"lng_action_gift_can_remove_text" = "You can remove this gift from your page.";
|
||||
"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts.";
|
||||
"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts.";
|
||||
"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.";
|
||||
|
@ -2108,9 +2127,23 @@ 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_similar_bots_title" = "Similar bots";
|
||||
"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot.";
|
||||
"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots.";
|
||||
"lng_similar_bots_show_more" = "Show more bots";
|
||||
|
||||
"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_peer_gifts_notify" = "Notify About New Gifts";
|
||||
"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift.";
|
||||
"lng_peer_gifts_filter_by_value" = "Sort by Value";
|
||||
"lng_peer_gifts_filter_by_date" = "Sort by Date";
|
||||
"lng_peer_gifts_filter_unlimited" = "Unlimited";
|
||||
"lng_peer_gifts_filter_limited" = "Limited";
|
||||
"lng_peer_gifts_filter_unique" = "Unique";
|
||||
"lng_peer_gifts_filter_saved" = "Displayed";
|
||||
"lng_peer_gifts_filter_unsaved" = "Hidden";
|
||||
|
||||
"lng_premium_gift_duration_months#one" = "for {count} month";
|
||||
"lng_premium_gift_duration_months#other" = "for {count} months";
|
||||
|
@ -2373,6 +2406,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_group_stickers_add" = "Choose sticker set";
|
||||
"lng_group_emoji" = "Select Emoji Pack";
|
||||
"lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group.";
|
||||
"lng_collectible_emoji" = "Collectibles";
|
||||
|
||||
"lng_premium" = "Premium";
|
||||
"lng_premium_free" = "Free";
|
||||
|
@ -2960,6 +2994,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
"lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack.";
|
||||
|
||||
"lng_boost_channel_title_wear" = "Wear Item";
|
||||
"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles.";
|
||||
|
||||
"lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:";
|
||||
"lng_boost_channel_ask_button" = "Copy Link";
|
||||
"lng_boost_channel_or" = "or";
|
||||
|
@ -3239,10 +3277,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_send_anonymous" = "Hide My Name";
|
||||
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile.";
|
||||
"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_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins.";
|
||||
"lng_gift_send_unique" = "Make Unique for {price}";
|
||||
"lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}";
|
||||
"lng_gift_send_unique_link" = "Learn More >";
|
||||
"lng_gift_send_premium_about" = "Only {user} will see your message.";
|
||||
"lng_gift_send_limited_sold#one" = "{count} sold";
|
||||
"lng_gift_send_limited_sold#other" = "{count} sold";
|
||||
"lng_gift_send_limited_left#one" = "{count} left";
|
||||
"lng_gift_send_limited_left#other" = "{count} left";
|
||||
"lng_gift_send_button" = "Send a Gift for {cost}";
|
||||
"lng_gift_send_button_self" = "Buy a Gift for {cost}";
|
||||
"lng_gift_sent_title" = "Gift Sent!";
|
||||
|
@ -3254,20 +3298,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_price_unique" = "Unique";
|
||||
"lng_gift_view_unpack" = "Unpack";
|
||||
"lng_gift_anonymous_hint" = "Only you can see the sender's name.";
|
||||
"lng_gift_anonymous_hint_channel" = "Only admins of this channel 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_hidden_unique" = "This gift is not displayed on your page.";
|
||||
"lng_gift_visible_hint" = "This gift is visible on your page.";
|
||||
"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel.";
|
||||
"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts.";
|
||||
"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}";
|
||||
"lng_gift_in_blockchain_link" = "View >";
|
||||
"lng_gift_visible_hide" = "Hide >";
|
||||
"lng_gift_show_on_page" = "Display on my Page";
|
||||
"lng_gift_show_on_channel" = "Display in channel's Gifts";
|
||||
"lng_gift_availability" = "Availability";
|
||||
"lng_gift_from_hidden" = "Hidden User";
|
||||
"lng_gift_visibility" = "Visibility";
|
||||
"lng_gift_visibility_shown" = "Visible on your page";
|
||||
"lng_gift_visibility_hidden" = "Not visible on your page";
|
||||
"lng_gift_visibility_show" = "show";
|
||||
"lng_gift_visibility_hide" = "hide";
|
||||
"lng_gift_self_status" = "buy yourself a gift";
|
||||
"lng_gift_self_title" = "Buy a Gift";
|
||||
"lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
|
||||
"lng_gift_channel_title" = "Send a Gift";
|
||||
"lng_gift_channel_about" = "Select a gift to show appreciation for {name}.";
|
||||
"lng_gift_unique_owner" = "Owner";
|
||||
"lng_gift_unique_owner_change" = "change";
|
||||
"lng_gift_unique_address_copied" = "Address copied to clipboard.";
|
||||
"lng_gift_unique_status" = "Status";
|
||||
"lng_gift_unique_status_non" = "Non-Unique";
|
||||
"lng_gift_unique_status_upgrade" = "upgrade";
|
||||
|
@ -3291,14 +3341,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_convert_sure_title" = "Convert Gift to Stars";
|
||||
"lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?";
|
||||
"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?";
|
||||
"lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**.";
|
||||
"lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**.";
|
||||
"lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"lng_gift_convert_sure" = "Convert";
|
||||
"lng_gift_display_done" = "The gift is now shown on your profile page.";
|
||||
"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts.";
|
||||
"lng_gift_display_done_hide" = "The gift is now hidden from your profile page.";
|
||||
"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts.";
|
||||
"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_channel_got#one" = "Channel got **{count} Star** for this gift.";
|
||||
"lng_gift_channel_got#other" = "Channel 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.";
|
||||
|
@ -3309,6 +3365,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction.";
|
||||
"lng_gift_upgrade_preview_title" = "Make Unique";
|
||||
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible.";
|
||||
"lng_gift_upgrade_unique_title" = "Unique";
|
||||
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
|
||||
"lng_gift_upgrade_transferable_title" = "Transferable";
|
||||
|
@ -3328,6 +3385,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}.";
|
||||
"lng_gift_transfer_title" = "Transfer {name}";
|
||||
"lng_gift_transfer_via_blockchain" = "Send via Blockchain";
|
||||
"lng_gift_transfer_password_title" = "Two-step verification";
|
||||
"lng_gift_transfer_password_description" = "Please enter your password to transfer.";
|
||||
"lng_gift_transfer_password_about" = "You can withdraw only if you have:";
|
||||
"lng_gift_transfer_confirm_title" = "Manage with Fragment";
|
||||
"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment.";
|
||||
"lng_gift_transfer_confirm_button" = "Open Fragment";
|
||||
"lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day";
|
||||
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
|
||||
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour";
|
||||
|
@ -3344,6 +3407,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
|
||||
"lng_gift_transfer_button" = "Transfer";
|
||||
"lng_gift_transfer_button_for" = "Transfer for {price}";
|
||||
"lng_gift_transfer_wear" = "Wear";
|
||||
"lng_gift_transfer_take_off" = "Take Off";
|
||||
"lng_gift_wear_title" = "Wear {name}";
|
||||
"lng_gift_wear_about" = "and get these benefits:";
|
||||
"lng_gift_wear_badge_title" = "Radiant Badge";
|
||||
"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name.";
|
||||
"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name.";
|
||||
"lng_gift_wear_proof_title" = "Proof of Ownership";
|
||||
"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner.";
|
||||
"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner.";
|
||||
"lng_gift_wear_start" = "Start Wearing";
|
||||
"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles.";
|
||||
"lng_gift_wear_start_toast" = "You put on {name}";
|
||||
"lng_gift_wear_end_toast" = "You took off {name}";
|
||||
|
||||
"lng_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
|
||||
|
@ -5520,6 +5597,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_view_button_iv" = "Instant View";
|
||||
"lng_view_button_stickerset" = "View stickers";
|
||||
"lng_view_button_emojipack" = "View emoji";
|
||||
"lng_view_button_collectible" = "View collectible";
|
||||
|
||||
"lng_sponsored_hide_ads" = "Hide";
|
||||
"lng_sponsored_title" = "What are sponsored messages?";
|
||||
|
@ -5836,6 +5914,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
|
||||
|
||||
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
|
||||
"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up.";
|
||||
"lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway";
|
||||
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
|
||||
"lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium";
|
||||
|
@ -5895,6 +5974,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
|
||||
"lng_channel_earn_chart_top_hours" = "Ad impressions";
|
||||
"lng_channel_earn_chart_revenue" = "Ad rewards";
|
||||
"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars";
|
||||
"lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON";
|
||||
"lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD";
|
||||
"lng_channel_earn_currency_history" = "TON Transactions";
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="5.10.3.0" />
|
||||
Version="5.10.7.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,10,7,0
|
||||
PRODUCTVERSION 5,10,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -62,10 +62,10 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Radolyn Labs"
|
||||
VALUE "FileDescription", "AyuGram Desktop"
|
||||
VALUE "FileVersion", "5.10.3.0"
|
||||
VALUE "FileVersion", "5.10.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.10.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 5,10,3,0
|
||||
PRODUCTVERSION 5,10,3,0
|
||||
FILEVERSION 5,10,7,0
|
||||
PRODUCTVERSION 5,10,7,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.10.3.0"
|
||||
VALUE "FileVersion", "5.10.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2025"
|
||||
VALUE "ProductName", "AyuGram Desktop"
|
||||
VALUE "ProductVersion", "5.10.3.0"
|
||||
VALUE "ProductVersion", "5.10.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -211,11 +211,10 @@ void ApplyBotsList(
|
|||
Data::PeerUpdate::Flag::FullInfo);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPmessages_Chats &chats) {
|
||||
auto result = ChatParticipants::Channels();
|
||||
std::vector<not_null<ChannelData*>>();
|
||||
auto result = ChatParticipants::Peers();
|
||||
chats.match([&](const auto &data) {
|
||||
const auto &list = data.vchats().v;
|
||||
result.list.reserve(list.size());
|
||||
|
@ -234,10 +233,29 @@ void ApplyBotsList(
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
|
||||
not_null<ChannelData*> channel,
|
||||
const MTPmessages_Chats &chats) {
|
||||
return ParseSimilar(&channel->session(), chats);
|
||||
return ParseSimilarChannels(&channel->session(), chats);
|
||||
}
|
||||
|
||||
[[nodiscard]] ChatParticipants::Peers ParseSimilarBots(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPusers_Users &users) {
|
||||
auto result = ChatParticipants::Peers();
|
||||
users.match([&](const auto &data) {
|
||||
const auto &list = data.vusers().v;
|
||||
result.list.reserve(list.size());
|
||||
for (const auto &user : list) {
|
||||
result.list.push_back(session->data().processUser(user));
|
||||
}
|
||||
if constexpr (MTPDusers_usersSlice::Is<decltype(data)>()) {
|
||||
if (session->premiumPossible()) {
|
||||
result.more = data.vcount().v - data.vusers().v.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -782,52 +800,65 @@ void ChatParticipants::unblock(
|
|||
_kickRequests.emplace(kick, requestId);
|
||||
}
|
||||
|
||||
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) {
|
||||
if (!channel->isBroadcast()) {
|
||||
return;
|
||||
} else if (const auto i = _similar.find(channel); i != end(_similar)) {
|
||||
void ChatParticipants::loadSimilarPeers(not_null<PeerData*> peer) {
|
||||
if (const auto i = _similar.find(peer); i != end(_similar)) {
|
||||
if (i->second.requestId
|
||||
|| !i->second.channels.more
|
||||
|| !channel->session().premium()) {
|
||||
|| !i->second.peers.more
|
||||
|| !peer->session().premium()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[channel].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilar(channel, result);
|
||||
if (similar.channels == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.channels = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
if (const auto channel = peer->asBroadcast()) {
|
||||
using Flag = MTPchannels_GetChannelRecommendations::Flag;
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPchannels_GetChannelRecommendations(
|
||||
MTP_flags(Flag::f_channel),
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
auto &similar = _similar[channel];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarChannels(channel, result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
similar.peers = std::move(parsed);
|
||||
if (const auto history = channel->owner().historyLoaded(channel)) {
|
||||
if (const auto item = history->joinedMessageInstance()) {
|
||||
history->owner().requestItemResize(item);
|
||||
}
|
||||
}
|
||||
_similarLoaded.fire_copy(channel);
|
||||
}).send();
|
||||
} else if (const auto bot = peer->asBot()) {
|
||||
_similar[peer].requestId = _api.request(
|
||||
MTPbots_GetBotRecommendations(bot->inputUser)
|
||||
).done([=](const MTPusers_Users &result) {
|
||||
auto &similar = _similar[peer];
|
||||
similar.requestId = 0;
|
||||
auto parsed = ParseSimilarBots(&peer->session(), result);
|
||||
if (similar.peers == parsed) {
|
||||
return;
|
||||
}
|
||||
similar.peers = std::move(parsed);
|
||||
_similarLoaded.fire_copy(peer);
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
|
||||
auto ChatParticipants::similar(not_null<ChannelData*> channel)
|
||||
-> const Channels & {
|
||||
const auto i = channel->isBroadcast()
|
||||
? _similar.find(channel)
|
||||
auto ChatParticipants::similar(not_null<PeerData*> peer)
|
||||
-> const Peers & {
|
||||
const auto i = (peer->isBroadcast() || peer->isBot())
|
||||
? _similar.find(peer)
|
||||
: end(_similar);
|
||||
if (i != end(_similar)) {
|
||||
return i->second.channels;
|
||||
return i->second.peers;
|
||||
}
|
||||
static const auto empty = Channels();
|
||||
static const auto empty = Peers();
|
||||
return empty;
|
||||
}
|
||||
|
||||
auto ChatParticipants::similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>> {
|
||||
-> rpl::producer<not_null<PeerData*>> {
|
||||
return _similarLoaded.events();
|
||||
}
|
||||
|
||||
|
@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() {
|
|||
MTP_inputChannelEmpty())
|
||||
).done([=](const MTPmessages_Chats &result) {
|
||||
_recommendations.requestId = 0;
|
||||
auto parsed = ParseSimilar(_session, result);
|
||||
_recommendations.channels = std::move(parsed);
|
||||
_recommendations.channels.more = 0;
|
||||
auto parsed = ParseSimilarChannels(_session, result);
|
||||
_recommendations.peers = std::move(parsed);
|
||||
_recommendations.peers.more = 0;
|
||||
_recommendationsLoaded = true;
|
||||
}).send();
|
||||
}
|
||||
|
||||
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
|
||||
return _recommendations.channels;
|
||||
const ChatParticipants::Peers &ChatParticipants::recommendations() const {
|
||||
return _recommendations.peers;
|
||||
}
|
||||
|
||||
rpl::producer<> ChatParticipants::recommendationsLoaded() const {
|
||||
|
|
|
@ -138,27 +138,27 @@ public:
|
|||
not_null<ChannelData*> channel,
|
||||
not_null<PeerData*> participant);
|
||||
|
||||
void loadSimilarChannels(not_null<ChannelData*> channel);
|
||||
void loadSimilarPeers(not_null<PeerData*> peer);
|
||||
|
||||
struct Channels {
|
||||
std::vector<not_null<ChannelData*>> list;
|
||||
struct Peers {
|
||||
std::vector<not_null<PeerData*>> list;
|
||||
int more = 0;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Channels &,
|
||||
const Channels &) = default;
|
||||
const Peers &,
|
||||
const Peers &) = default;
|
||||
};
|
||||
[[nodiscard]] const Channels &similar(not_null<ChannelData*> channel);
|
||||
[[nodiscard]] const Peers &similar(not_null<PeerData*> peer);
|
||||
[[nodiscard]] auto similarLoaded() const
|
||||
-> rpl::producer<not_null<ChannelData*>>;
|
||||
-> rpl::producer<not_null<PeerData*>>;
|
||||
|
||||
void loadRecommendations();
|
||||
[[nodiscard]] const Channels &recommendations() const;
|
||||
[[nodiscard]] const Peers &recommendations() const;
|
||||
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
|
||||
|
||||
private:
|
||||
struct SimilarChannels {
|
||||
Channels channels;
|
||||
struct SimilarPeers {
|
||||
Peers peers;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
|
@ -186,10 +186,10 @@ private:
|
|||
not_null<PeerData*>>;
|
||||
base::flat_map<KickRequest, mtpRequestId> _kickRequests;
|
||||
|
||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||
base::flat_map<not_null<PeerData*>, SimilarPeers> _similar;
|
||||
rpl::event_stream<not_null<PeerData*>> _similarLoaded;
|
||||
|
||||
SimilarChannels _recommendations;
|
||||
SimilarPeers _recommendations;
|
||||
rpl::variable<bool> _recommendationsLoaded = false;
|
||||
|
||||
};
|
||||
|
|
|
@ -513,4 +513,12 @@ void EditCreditsSubscription(
|
|||
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send();
|
||||
}
|
||||
|
||||
MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) {
|
||||
return id.isUser()
|
||||
? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare))
|
||||
: MTP_inputSavedStarGiftChat(
|
||||
id.chat()->input,
|
||||
MTP_long(id.chatSavedId()));
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_credits_earn.h"
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
namespace Data {
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
@ -116,4 +120,7 @@ void EditCreditsSubscription(
|
|||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
|
||||
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
|
||||
const Data::SavedStarGiftId &id);
|
||||
|
||||
} // namespace Api
|
||||
|
|
|
@ -283,7 +283,9 @@ mtpRequestId EditTextMessage(
|
|||
return MTP_inputMediaDocument(
|
||||
MTP_flags(flags),
|
||||
document->mtpInput(),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTP_int(media->ttlSeconds()),
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
};
|
||||
takeFileReference = [=] { return document->fileReference(); };
|
||||
|
|
|
@ -121,6 +121,8 @@ MTPInputMedia PrepareUploadedDocument(
|
|||
ComposeSendingDocumentAttributes(document),
|
||||
MTP_vector<MTPInputDocument>(
|
||||
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTP_int(0), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() {
|
|||
_requestId = _history->session().api().request(MTPmessages_Search(
|
||||
MTP_flags((fromPeer ? Flag::f_from_id : Flag())
|
||||
| (savedPeer ? Flag::f_saved_peer_id : Flag())
|
||||
| (_request.topMsgId ? Flag::f_top_msg_id : Flag())
|
||||
| (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
|
||||
_history->peer->input,
|
||||
MTP_string(_request.query),
|
||||
|
@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() {
|
|||
MTP_vector_from_range(_request.tags | ranges::views::transform(
|
||||
Data::ReactionToMTP
|
||||
)),
|
||||
MTPint(), // top_msg_id
|
||||
MTP_int(_request.topMsgId), // top_msg_id
|
||||
MTP_inputMessagesFilterEmpty(),
|
||||
MTP_int(0), // min_date
|
||||
MTP_int(0), // max_date
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
QString query;
|
||||
PeerData *from = nullptr;
|
||||
std::vector<Data::ReactionId> tags;
|
||||
MsgId topMsgId;
|
||||
|
||||
friend inline bool operator==(
|
||||
const Request &,
|
||||
|
|
|
@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
|
|||
}
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::disableMigrated() {
|
||||
_migratedSearch = std::nullopt;
|
||||
}
|
||||
|
||||
void MessagesSearchMerged::addFound(const FoundMessages &data) {
|
||||
for (const auto &message : data.messages) {
|
||||
_concatedFound.messages.push_back(message);
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
void clear();
|
||||
void search(const Request &search);
|
||||
void searchMore();
|
||||
void disableMigrated();
|
||||
|
||||
[[nodiscard]] const FoundMessages &messages() const;
|
||||
[[nodiscard]] const Request &request() const;
|
||||
|
|
|
@ -805,8 +805,14 @@ std::optional<Data::StarGift> FromTL(
|
|||
auto result = Data::StarGift{
|
||||
.id = uint64(data.vid().v),
|
||||
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
|
||||
.id = data.vid().v,
|
||||
.slug = qs(data.vslug()),
|
||||
.title = qs(data.vtitle()),
|
||||
.ownerId = peerFromUser(UserId(data.vowner_id().v)),
|
||||
.ownerAddress = qs(data.vowner_address().value_or_empty()),
|
||||
.ownerName = qs(data.vowner_name().value_or_empty()),
|
||||
.ownerId = (data.vowner_id()
|
||||
? peerFromMTP(*data.vowner_id())
|
||||
: PeerId()),
|
||||
.number = data.vnum().v,
|
||||
.model = *model,
|
||||
.pattern = *pattern,
|
||||
|
@ -829,9 +835,9 @@ std::optional<Data::StarGift> FromTL(
|
|||
});
|
||||
}
|
||||
|
||||
std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift) {
|
||||
std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift) {
|
||||
const auto session = &to->session();
|
||||
const auto &data = gift.data();
|
||||
auto parsed = FromTL(session, data.vgift());
|
||||
|
@ -841,8 +847,12 @@ std::optional<Data::UserStarGift> FromTL(
|
|||
unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
|
||||
unique->exportAt = data.vcan_export_at().value_or_empty();
|
||||
}
|
||||
return Data::UserStarGift{
|
||||
using Id = Data::SavedStarGiftId;
|
||||
return Data::SavedStarGift{
|
||||
.info = std::move(*parsed),
|
||||
.manageId = (to->isUser()
|
||||
? Id::User(data.vmsg_id().value_or_empty())
|
||||
: Id::Chat(to, data.vsaved_id().value_or_empty())),
|
||||
.message = (data.vmessage()
|
||||
? TextWithEntities{
|
||||
.text = qs(data.vmessage()->data().vtext()),
|
||||
|
@ -855,9 +865,8 @@ std::optional<Data::UserStarGift> FromTL(
|
|||
.starsUpgradedBySender = int64(
|
||||
data.vupgrade_stars().value_or_empty()),
|
||||
.fromId = (data.vfrom_id()
|
||||
? peerFromUser(data.vfrom_id()->v)
|
||||
? peerFromMTP(*data.vfrom_id())
|
||||
: PeerId()),
|
||||
.messageId = data.vmsg_id().value_or_empty(),
|
||||
.date = data.vdate().v,
|
||||
.upgradable = data.is_can_upgrade(),
|
||||
.anonymous = data.is_name_hidden(),
|
||||
|
@ -910,11 +919,9 @@ Data::UniqueGiftOriginalDetails FromTL(
|
|||
auto result = Data::UniqueGiftOriginalDetails();
|
||||
result.date = data.vdate().v;
|
||||
result.senderId = data.vsender_id()
|
||||
? peerFromUser(
|
||||
UserId(data.vsender_id().value_or_empty()))
|
||||
? peerFromMTP(*data.vsender_id())
|
||||
: PeerId();
|
||||
result.recipientId = peerFromUser(
|
||||
UserId(data.vrecipient_id().v));
|
||||
result.recipientId = peerFromMTP(data.vrecipient_id());
|
||||
result.message = data.vmessage()
|
||||
? ParseTextWithEntities(session, *data.vmessage())
|
||||
: TextWithEntities();
|
||||
|
|
|
@ -259,9 +259,9 @@ enum class RequirePremiumState {
|
|||
[[nodiscard]] std::optional<Data::StarGift> FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
const MTPstarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::UserStarGift> FromTL(
|
||||
not_null<UserData*> to,
|
||||
const MTPuserStarGift &gift);
|
||||
[[nodiscard]] std::optional<Data::SavedStarGift> FromTL(
|
||||
not_null<PeerData*> to,
|
||||
const MTPsavedStarGift &gift);
|
||||
|
||||
[[nodiscard]] Data::UniqueGiftModel FromTL(
|
||||
not_null<Main::Session*> session,
|
||||
|
|
|
@ -272,7 +272,9 @@ void SendExistingDocument(
|
|||
return MTP_inputMediaDocument(
|
||||
MTP_flags(0),
|
||||
document->mtpInput(),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTPint(), // ttl_seconds
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
};
|
||||
SendExistingMedia(
|
||||
|
@ -550,6 +552,8 @@ void SendConfirmedFile(
|
|||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
MTPint(), // video_timestamp
|
||||
MTPint());
|
||||
} else if (file->type == SendMediaType::Audio) {
|
||||
const auto ttlSeconds = file->to.options.ttlSeconds;
|
||||
|
@ -560,6 +564,8 @@ void SendConfirmedFile(
|
|||
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else if (file->type == SendMediaType::Round) {
|
||||
using Flag = MTPDmessageMediaDocument::Flag;
|
||||
|
@ -571,6 +577,8 @@ void SendConfirmedFile(
|
|||
| (file->spoiler ? Flag::f_spoiler : Flag())),
|
||||
file->document,
|
||||
MTPVector<MTPDocument>(), // alt_documents
|
||||
MTPPhoto(), // video_cover
|
||||
MTPint(), // video_timestamp
|
||||
MTP_int(ttlSeconds));
|
||||
} else {
|
||||
Unexpected("Type in sendFilesConfirmed.");
|
||||
|
|
|
@ -1812,7 +1812,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
|
|||
_channelAmInRequests.emplace(channel, requestId);
|
||||
|
||||
using Flag = ChannelDataFlag;
|
||||
chatParticipants().loadSimilarChannels(channel);
|
||||
chatParticipants().loadSimilarPeers(channel);
|
||||
|
||||
const auto settings = &AyuSettings::getInstance();
|
||||
if (!settings->collapseSimilarChannels) {
|
||||
|
@ -3422,7 +3422,8 @@ void ApiWrap::forwardMessages(
|
|||
MTP_int(topMsgId),
|
||||
MTP_int(action.options.scheduled),
|
||||
(sendAs ? sendAs->input : MTP_inputPeerEmpty()),
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId)
|
||||
Data::ShortcutIdToMTP(_session, action.options.shortcutId),
|
||||
MTPint() // video_timestamp
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
if (!scheduled) {
|
||||
this->updates().checkForSentToScheduled(result);
|
||||
|
@ -4218,7 +4219,9 @@ void ApiWrap::uploadAlbumMedia(
|
|||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference()),
|
||||
MTPInputPhoto(), // video_cover
|
||||
MTP_int(data.vttl_seconds().value_or_empty()),
|
||||
MTPint(), // video_timestamp
|
||||
MTPstring()); // query
|
||||
sendAlbumWithUploaded(item, groupId, media);
|
||||
} break;
|
||||
|
|
|
@ -13,6 +13,10 @@ namespace Api {
|
|||
struct GiftCode;
|
||||
} // namespace Api
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct Boost;
|
||||
struct CreditsHistoryEntry;
|
||||
|
@ -21,6 +25,14 @@ struct GiveawayResults;
|
|||
struct SubscriptionEntry;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Settings {
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
|
@ -54,29 +66,37 @@ void ResolveGiveawayInfo(
|
|||
std::optional<Data::GiveawayStart> start,
|
||||
std::optional<Data::GiveawayResults> results);
|
||||
|
||||
[[nodiscard]] QString TonAddressUrl(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &address);
|
||||
|
||||
void AddStarGiftTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry,
|
||||
Fn<void(bool)> toggleVisibility,
|
||||
Fn<void()> convertToStars,
|
||||
Fn<void()> startUpgrade);
|
||||
void AddCreditsHistoryEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::CreditsHistoryEntry &entry);
|
||||
|
||||
void AddSubscriptionEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::SubscriptionEntry &s);
|
||||
void AddSubscriberEntryTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
not_null<PeerData*> peer,
|
||||
TimeId date);
|
||||
|
||||
void AddCreditsBoostTable(
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
const Data::Boost &boost);
|
||||
|
|
|
@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
|
|||
if (peer != item->history()->peer) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
const auto author = item->author();
|
||||
if (author == peer) {
|
||||
return {};
|
||||
} else if (const auto channel = author->asChannel()) {
|
||||
if (channel->linkedChat() == peer) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!item->suggestBanReport()) {
|
||||
result.allCanBan = false;
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ struct IconSelector {
|
|||
const auto manager = &controller->session().data().customEmojiManager();
|
||||
|
||||
auto factory = [=](DocumentId id, Fn<void()> repaint)
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
-> std::unique_ptr<Ui::Text::CustomEmoji> {
|
||||
const auto tag = Data::CustomEmojiManager::SizeTag::Large;
|
||||
if (id == kDefaultIconId) {
|
||||
return std::make_unique<DefaultIconEmoji>(
|
||||
|
@ -288,7 +288,7 @@ struct IconSelector {
|
|||
.show = controller->uiShow(),
|
||||
.mode = EmojiListWidget::Mode::TopicIcon,
|
||||
.paused = Window::PausedIn(controller, PauseReason::Layer),
|
||||
.customRecentList = recent(),
|
||||
.customRecentList = DocumentListToRecent(recent()),
|
||||
.customRecentFactory = std::move(factory),
|
||||
.st = &st::reactPanelEmojiPan,
|
||||
}),
|
||||
|
@ -297,7 +297,7 @@ struct IconSelector {
|
|||
icons->requestDefaultIfUnknown();
|
||||
icons->defaultUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
selector->provideRecent(recent());
|
||||
selector->provideRecent(DocumentListToRecent(recent()));
|
||||
}, selector->lifetime());
|
||||
|
||||
placeFooter(selector->createFooter());
|
||||
|
|
|
@ -313,6 +313,7 @@ PreviewWrap::PreviewWrap(
|
|||
WebPageCollage(),
|
||||
nullptr, // iv
|
||||
nullptr, // stickerSet
|
||||
nullptr, // uniqueGift
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
|
@ -525,7 +526,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) {
|
|||
struct SetValues {
|
||||
uint8 colorIndex = 0;
|
||||
DocumentId backgroundEmojiId = 0;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
};
|
||||
|
@ -807,7 +808,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
emojiIdChosen(chosen.id);
|
||||
emojiIdChosen(chosen.id.documentId);
|
||||
}, raw->lifetime());
|
||||
|
||||
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
|
||||
|
@ -871,13 +872,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
const auto customTextColor = [=] {
|
||||
return style->coloredValues(false, state->index).name;
|
||||
};
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->emojiId,
|
||||
.ensureAddedEmojiId = { state->emojiId },
|
||||
.customTextColor = customTextColor,
|
||||
.backgroundEmojiMode = true,
|
||||
});
|
||||
|
@ -901,8 +901,8 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<ChannelData*> channel,
|
||||
rpl::producer<DocumentId> statusIdValue,
|
||||
Fn<void(DocumentId,TimeId)> statusIdChosen,
|
||||
rpl::producer<EmojiStatusId> statusIdValue,
|
||||
Fn<void(EmojiStatusId,TimeId)> statusIdChosen,
|
||||
bool group) {
|
||||
const auto button = ButtonStyleWithRightEmoji(
|
||||
parent,
|
||||
|
@ -924,20 +924,20 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
struct State {
|
||||
EmojiStatusPanel panel;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
|
||||
DocumentId statusId = 0;
|
||||
EmojiStatusId statusId;
|
||||
};
|
||||
const auto state = right->lifetime().make_state<State>();
|
||||
state->panel.someCustomChosen(
|
||||
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
|
||||
statusIdChosen(chosen.id, chosen.until);
|
||||
statusIdChosen({ chosen.id }, chosen.until);
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto session = &show->session();
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
|
||||
std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) {
|
||||
state->statusId = id;
|
||||
state->emoji = id
|
||||
? session->data().customEmojiManager().create(
|
||||
id,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
[=] { right->update(); })
|
||||
: nullptr;
|
||||
right->resize(
|
||||
|
@ -985,13 +985,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
|
|||
}, right->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto controller = show->resolveWindow();
|
||||
if (controller) {
|
||||
state->panel.show({
|
||||
.controller = controller,
|
||||
.button = right,
|
||||
.ensureAddedEmojiId = state->statusId,
|
||||
.ensureAddedEmojiId = { state->statusId },
|
||||
.channelStatusMode = true,
|
||||
});
|
||||
}
|
||||
|
@ -1183,7 +1182,7 @@ void EditPeerColorBox(
|
|||
struct State {
|
||||
rpl::variable<uint8> index;
|
||||
rpl::variable<DocumentId> emojiId;
|
||||
rpl::variable<DocumentId> statusId;
|
||||
rpl::variable<EmojiStatusId> statusId;
|
||||
TimeId statusUntil = 0;
|
||||
bool statusChanged = false;
|
||||
bool changing = false;
|
||||
|
@ -1263,8 +1262,7 @@ void EditPeerColorBox(
|
|||
{ &st::menuBlueIconWallpaper }
|
||||
);
|
||||
button->setClickedCallback([=] {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto strong = show->resolveWindow(usage)) {
|
||||
if (const auto strong = show->resolveWindow()) {
|
||||
show->show(Box<BackgroundBox>(strong, channel));
|
||||
}
|
||||
});
|
||||
|
@ -1321,7 +1319,7 @@ void EditPeerColorBox(
|
|||
show,
|
||||
channel,
|
||||
state->statusId.value(),
|
||||
[=](DocumentId id, TimeId until) {
|
||||
[=](EmojiStatusId id, TimeId until) {
|
||||
state->statusId = id;
|
||||
state->statusUntil = until;
|
||||
state->statusChanged = true;
|
||||
|
@ -1471,8 +1469,7 @@ void CheckBoostLevel(
|
|||
return;
|
||||
}
|
||||
const auto openStatistics = [=] {
|
||||
if (const auto controller = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo)) {
|
||||
if (const auto controller = show->resolveWindow()) {
|
||||
controller->showSection(Info::Boosts::Make(peer));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1242,7 +1242,9 @@ void Controller::fillManageSection() {
|
|||
? channel->canViewMembers()
|
||||
: chat->amIn();
|
||||
const auto canViewKicked = isChannel
|
||||
&& (channel->isBroadcast() || channel->isGigagroup());
|
||||
&& (channel->isMegagroup()
|
||||
? (channel->isBroadcast() || channel->isGigagroup())
|
||||
: true);
|
||||
const auto hasRecentActions = isChannel
|
||||
&& (channel->hasAdminRights() || channel->amCreator());
|
||||
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)
|
||||
|
|
|
@ -1019,7 +1019,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
|
|||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
|
||||
const auto show = controller->uiShow();
|
||||
AddSubscriberEntryTable(show, content, {}, row->peer(), data.date);
|
||||
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
|
|
@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mtproto/mtproto_config.h" // megagroupSizeMax
|
||||
#include "apiwrap.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
@ -49,7 +50,6 @@ namespace {
|
|||
|
||||
constexpr auto kSlowmodeValues = 7;
|
||||
constexpr auto kBoostsUnrestrictValues = 5;
|
||||
constexpr auto kSuggestGigagroupThreshold = 199000;
|
||||
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
|
||||
|
@ -1191,8 +1191,11 @@ void ShowEditPeerPermissionsBox(
|
|||
});
|
||||
|
||||
if (const auto channel = peer->asChannel()) {
|
||||
constexpr auto kThresholdOffset = int(1000);
|
||||
const auto threshold = -kThresholdOffset
|
||||
+ channel->session().serverConfig().megagroupSizeMax;
|
||||
if (channel->amCreator()
|
||||
&& channel->membersCount() >= kSuggestGigagroupThreshold) {
|
||||
&& channel->membersCount() >= threshold) {
|
||||
AddSuggestGigagroup(
|
||||
inner,
|
||||
AboutGigagroupCallback(
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "chat_helpers/emoji_list_widget.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
|
@ -491,7 +492,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
|
|||
panelList.erase(
|
||||
ranges::remove(panelList, paid->selectAnimation->id),
|
||||
end(panelList));
|
||||
panel->selector()->provideRecentEmoji(panelList);
|
||||
panel->selector()->provideRecentEmoji(
|
||||
ChatHelpers::DocumentListToRecent(panelList));
|
||||
panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
|
|
|
@ -486,20 +486,23 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
|||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
const auto open = [=] { navigation->showPeerHistory(peer); };
|
||||
const auto open = [=] {
|
||||
if (const auto window = show->resolveWindow()) {
|
||||
window->showPeerHistory(peer);
|
||||
}
|
||||
};
|
||||
const auto videoIsPaused = [=] {
|
||||
return navigation->parentController()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
return show->paused(Window::GifPauseReason::Layer);
|
||||
};
|
||||
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
|
||||
const auto controller = navigation->parentController();
|
||||
const auto peerSeparateId = Window::SeparateId(peer);
|
||||
if (controller->windowId() != peerSeparateId) {
|
||||
const auto window = show->resolveWindow();
|
||||
if (window && window->windowId() != peerSeparateId) {
|
||||
addAction(tr::lng_context_new_window(tr::now), [=] {
|
||||
Ui::PreventDelayedActivation();
|
||||
controller->showInNewWindow(peer);
|
||||
window->showInNewWindow(peer);
|
||||
}, &st::menuIconNewWindow);
|
||||
}
|
||||
};
|
||||
|
@ -511,6 +514,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
|||
stOverride);
|
||||
}
|
||||
|
||||
object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
const style::ShortInfoBox *stOverride) {
|
||||
return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride);
|
||||
}
|
||||
|
||||
rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
|
||||
return StatusValue(peer);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,10 @@ struct ShortInfoCover;
|
|||
struct ShortInfoBox;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui::Menu {
|
||||
struct MenuCallback;
|
||||
} // namespace Ui::Menu
|
||||
|
@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic {
|
|||
Fn<void(Ui::Menu::MenuCallback)> menuFiller,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const style::ShortInfoBox *stOverride = nullptr);
|
||||
|
||||
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionNavigation*> navigation,
|
||||
|
|
|
@ -1139,8 +1139,7 @@ void PreviewBox(
|
|||
button->resizeToWidth(width);
|
||||
if (!descriptor.fromSettings) {
|
||||
button->setClickedCallback([=] {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto window = show->resolveWindow();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_session.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "styles/style_calls.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
|
@ -202,16 +203,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
|
|||
, _api(&_descriptor.session->mtp())
|
||||
, _select(
|
||||
this,
|
||||
(_descriptor.stMultiSelect
|
||||
? *_descriptor.stMultiSelect
|
||||
(_descriptor.st.multiSelect
|
||||
? *_descriptor.st.multiSelect
|
||||
: st::defaultMultiSelect),
|
||||
tr::lng_participant_filter())
|
||||
, _comment(
|
||||
this,
|
||||
object_ptr<Ui::InputField>(
|
||||
this,
|
||||
(_descriptor.stComment
|
||||
? *_descriptor.stComment
|
||||
(_descriptor.st.comment
|
||||
? *_descriptor.st.comment
|
||||
: st::shareComment),
|
||||
Ui::InputField::Mode::MultiLine,
|
||||
tr::lng_photos_comment()),
|
||||
|
@ -256,7 +257,7 @@ void ShareBox::prepareCommentField() {
|
|||
.session = _descriptor.session,
|
||||
.show = Main::MakeSessionShow(show, _descriptor.session),
|
||||
.field = field,
|
||||
.fieldStyle = _descriptor.stLabel,
|
||||
.fieldStyle = _descriptor.st.label,
|
||||
});
|
||||
}
|
||||
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
|
||||
|
@ -566,6 +567,9 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
|||
submit(action.options);
|
||||
return;
|
||||
}
|
||||
const auto st = _descriptor.st.scheduleBox
|
||||
? *_descriptor.st.scheduleBox
|
||||
: HistoryView::ScheduleBoxStyleArgs();
|
||||
uiShow()->showBox(
|
||||
HistoryView::PrepareScheduleBox(
|
||||
this,
|
||||
|
@ -574,7 +578,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
|
|||
[=](Api::SendOptions options) { submit(options); },
|
||||
action.options,
|
||||
HistoryView::DefaultScheduleTime(),
|
||||
_descriptor.scheduleBoxStyle));
|
||||
st));
|
||||
});
|
||||
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
|
||||
const auto result = FillSendMenu(
|
||||
|
@ -709,7 +713,9 @@ ShareBox::Inner::Inner(
|
|||
: RpWidget(parent)
|
||||
, _descriptor(descriptor)
|
||||
, _show(std::move(show))
|
||||
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList)
|
||||
, _st(_descriptor.st.peerList
|
||||
? *_descriptor.st.peerList
|
||||
: st::shareBoxList)
|
||||
, _defaultChatsIndexed(
|
||||
std::make_unique<Dialogs::IndexedList>(
|
||||
Dialogs::SortMode::Add))
|
||||
|
@ -1586,7 +1592,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
MTP_int(topMsgId),
|
||||
MTP_int(options.scheduled),
|
||||
MTP_inputPeerEmpty(), // send_as
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId)
|
||||
Data::ShortcutIdToMTP(session, options.shortcutId),
|
||||
MTPint() // video_timestamp
|
||||
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
|
||||
threadHistory->session().api().applyUpdates(updates);
|
||||
state->requests.remove(reqId);
|
||||
|
@ -1624,9 +1631,37 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
|
|||
};
|
||||
}
|
||||
|
||||
ShareBoxStyleOverrides DarkShareBoxStyle() {
|
||||
using namespace HistoryView;
|
||||
|
||||
const auto schedule = [&] {
|
||||
auto date = Ui::ChooseDateTimeStyleArgs();
|
||||
date.labelStyle = &st::groupCallBoxLabel;
|
||||
date.dateFieldStyle = &st::groupCallScheduleDateField;
|
||||
date.timeFieldStyle = &st::groupCallScheduleTimeField;
|
||||
date.separatorStyle = &st::callMuteButtonLabel;
|
||||
date.atStyle = &st::callMuteButtonLabel;
|
||||
date.calendarStyle = &st::groupCallCalendarColors;
|
||||
|
||||
auto st = ScheduleBoxStyleArgs();
|
||||
st.topButtonStyle = &st::groupCallMenuToggle;
|
||||
st.popupMenuStyle = &st::groupCallPopupMenu;
|
||||
st.chooseDateTimeArgs = std::move(date);
|
||||
return st;
|
||||
};
|
||||
return {
|
||||
.multiSelect = &st::groupCallMultiSelect,
|
||||
.comment = &st::groupCallShareBoxComment,
|
||||
.peerList = &st::groupCallShareBoxList,
|
||||
.label = &st::groupCallField,
|
||||
.scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
|
||||
};
|
||||
}
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item) {
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto history = item->history();
|
||||
const auto owner = &history->owner();
|
||||
const auto session = &history->session();
|
||||
|
@ -1695,6 +1730,7 @@ void FastShareMessage(
|
|||
history,
|
||||
msgIds),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.forwardOptions = {
|
||||
.sendersCount = ItemsForwardSendersCount(items),
|
||||
.captionsCount = ItemsForwardCaptionsCount(items),
|
||||
|
@ -1706,19 +1742,22 @@ void FastShareMessage(
|
|||
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
FastShareMessage(controller->uiShow(), item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareMessage(controller->uiShow(), item, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url) {
|
||||
FastShareLink(controller->uiShow(), url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
FastShareLink(controller->uiShow(), url, st);
|
||||
}
|
||||
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url) {
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st) {
|
||||
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||
const auto sending = std::make_shared<bool>();
|
||||
auto copyCallback = [=] {
|
||||
|
@ -1782,6 +1821,7 @@ void FastShareLink(
|
|||
.copyCallback = std::move(copyCallback),
|
||||
.submitCallback = std::move(submitCallback),
|
||||
.filterCallback = std::move(filterCallback),
|
||||
.st = st,
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
}),
|
||||
Ui::LayerOption::KeepOther,
|
||||
|
|
|
@ -61,18 +61,31 @@ class PopupMenu;
|
|||
|
||||
class ShareBox;
|
||||
|
||||
struct ShareBoxStyleOverrides {
|
||||
const style::MultiSelect *multiSelect = nullptr;
|
||||
const style::InputField *comment = nullptr;
|
||||
const style::PeerList *peerList = nullptr;
|
||||
const style::InputField *label = nullptr;
|
||||
std::shared_ptr<HistoryView::ScheduleBoxStyleArgs> scheduleBox;
|
||||
};
|
||||
[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle();
|
||||
|
||||
void FastShareMessage(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
not_null<HistoryItem*> item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareMessage(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
not_null<HistoryItem*> item,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
void FastShareLink(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &url);
|
||||
const QString &url,
|
||||
ShareBoxStyleOverrides st = {});
|
||||
|
||||
struct RecipientPremiumRequiredError;
|
||||
[[nodiscard]] auto SharePremiumRequiredError()
|
||||
|
@ -100,16 +113,12 @@ public:
|
|||
FilterCallback filterCallback;
|
||||
object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
|
||||
rpl::producer<QString> copyLinkText;
|
||||
const style::MultiSelect *stMultiSelect = nullptr;
|
||||
const style::InputField *stComment = nullptr;
|
||||
const style::PeerList *st = nullptr;
|
||||
const style::InputField *stLabel = nullptr;
|
||||
ShareBoxStyleOverrides st;
|
||||
struct {
|
||||
int sendersCount = 0;
|
||||
int captionsCount = 0;
|
||||
bool show = false;
|
||||
} forwardOptions;
|
||||
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
|
||||
|
||||
using PremiumRequiredError = RecipientPremiumRequiredError;
|
||||
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;
|
||||
|
|
|
@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/star_gift_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "boxes/peers/edit_peer_color_box.h"
|
||||
#include "boxes/gift_premium_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/send_credits_box.h"
|
||||
#include "chat_helpers/emoji_suggestions_widget.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
|
@ -24,10 +27,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "core/ui_integration.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_credits.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_premium_limits.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
|
@ -51,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "settings/settings_credits.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/controls/emoji_button.h"
|
||||
|
@ -70,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
|
@ -82,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_premium.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
|
@ -206,8 +216,12 @@ auto GenerateGiftMedia(
|
|||
Element *replacing,
|
||||
not_null<PeerData*> recipient,
|
||||
const GiftDetails &data)
|
||||
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
|
||||
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
|
||||
-> Fn<void(
|
||||
not_null<MediaGeneric*>,
|
||||
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
|
||||
return [=](
|
||||
not_null<MediaGeneric*> media,
|
||||
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
|
||||
const auto &descriptor = data.descriptor;
|
||||
auto pushText = [&](
|
||||
TextWithEntities text,
|
||||
|
@ -241,18 +255,26 @@ auto GenerateGiftMedia(
|
|||
replacing,
|
||||
sticker,
|
||||
st::giftBoxPreviewStickerPadding));
|
||||
const auto title = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
auto title = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
return tr::lng_action_gift_premium_months(
|
||||
tr::now,
|
||||
lt_count,
|
||||
gift.months);
|
||||
gift.months,
|
||||
Text::Bold);
|
||||
}, [&](const GiftTypeStars &gift) {
|
||||
return recipient->isSelf()
|
||||
? tr::lng_action_gift_self_subtitle(tr::now)
|
||||
? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold)
|
||||
: tr::lng_action_gift_got_subtitle(
|
||||
tr::now,
|
||||
lt_user,
|
||||
recipient->session().user()->shortName());
|
||||
TextWithEntities()
|
||||
.append(Text::SingleCustomEmoji(
|
||||
recipient->owner().customEmojiManager(
|
||||
).peerUserpicEmojiData(
|
||||
recipient->session().user())))
|
||||
.append(' ')
|
||||
.append(recipient->session().user()->shortName()),
|
||||
Text::Bold);
|
||||
});
|
||||
auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
|
||||
return tr::lng_action_gift_premium_about(
|
||||
|
@ -267,8 +289,14 @@ auto GenerateGiftMedia(
|
|||
? tr::lng_action_gift_self_about_unique(
|
||||
tr::now,
|
||||
Text::RichLangValue)
|
||||
: (recipient->isBroadcast() && gift.info.starsToUpgrade)
|
||||
? tr::lng_action_gift_channel_about_unique(
|
||||
tr::now,
|
||||
Text::RichLangValue)
|
||||
: (recipient->isSelf()
|
||||
? tr::lng_action_gift_self_about
|
||||
: recipient->isBroadcast()
|
||||
? tr::lng_action_gift_channel_about
|
||||
: tr::lng_action_gift_got_stars_text)(
|
||||
tr::now,
|
||||
lt_count,
|
||||
|
@ -278,15 +306,20 @@ auto GenerateGiftMedia(
|
|||
auto description = data.text.empty()
|
||||
? std::move(textFallback)
|
||||
: data.text;
|
||||
pushText(Text::Bold(title), st::giftBoxPreviewTitlePadding);
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &parent->history()->session(),
|
||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
||||
};
|
||||
pushText(
|
||||
std::move(title),
|
||||
st::giftBoxPreviewTitlePadding,
|
||||
{},
|
||||
context);
|
||||
pushText(
|
||||
std::move(description),
|
||||
st::giftBoxPreviewTextPadding,
|
||||
{},
|
||||
Core::MarkedTextContext{
|
||||
.session = &parent->history()->session(),
|
||||
.customEmojiRepaint = [parent] { parent->repaint(); },
|
||||
});
|
||||
context);
|
||||
|
||||
push(HistoryView::MakeGenericButtonPart(
|
||||
(data.upgraded
|
||||
|
@ -475,6 +508,15 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
|
|||
? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
|
||||
: _recipient->isSelf()
|
||||
? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost)
|
||||
: _recipient->isBroadcast()
|
||||
? tr::lng_action_gift_sent_channel(
|
||||
tr::now,
|
||||
lt_user,
|
||||
name,
|
||||
lt_name,
|
||||
_recipient->name(),
|
||||
lt_cost,
|
||||
cost)
|
||||
: tr::lng_action_gift_received(
|
||||
tr::now,
|
||||
lt_user,
|
||||
|
@ -652,6 +694,21 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
|||
for (auto &gift : gifts) {
|
||||
list.push_back({ .info = gift });
|
||||
}
|
||||
ranges::sort(list, [](
|
||||
const GiftTypeStars &a,
|
||||
const GiftTypeStars &b) {
|
||||
if (!a.info.limitedCount && !b.info.limitedCount) {
|
||||
return a.info.stars <= b.info.stars;
|
||||
} else if (!a.info.limitedCount) {
|
||||
return true;
|
||||
} else if (!b.info.limitedCount) {
|
||||
return false;
|
||||
} else if (a.info.limitedLeft != b.info.limitedLeft) {
|
||||
return a.info.limitedLeft > b.info.limitedLeft;
|
||||
}
|
||||
return a.info.stars <= b.info.stars;
|
||||
});
|
||||
|
||||
auto &map = Map[session];
|
||||
if (map.last != list) {
|
||||
map.last = list;
|
||||
|
@ -1060,7 +1117,7 @@ void SendGift(
|
|||
.giftId = gift.info.id,
|
||||
.randomId = details.randomId,
|
||||
.message = details.text,
|
||||
.user = peer->asUser(),
|
||||
.recipient = peer,
|
||||
.limitedCount = gift.info.limitedCount,
|
||||
.anonymous = details.anonymous,
|
||||
.upgraded = details.upgraded,
|
||||
|
@ -1145,7 +1202,7 @@ void SendStarsFormRequest(
|
|||
|
||||
void UpgradeGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
MsgId messageId,
|
||||
Data::SavedStarGiftId savedId,
|
||||
bool keepDetails,
|
||||
int stars,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
|
@ -1165,7 +1222,7 @@ void UpgradeGift(
|
|||
using Flag = MTPpayments_UpgradeStarGift::Flag;
|
||||
session->api().request(MTPpayments_UpgradeStarGift(
|
||||
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
|
||||
MTP_int(messageId.bare)
|
||||
Api::InputSavedStarGiftId(savedId)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
|
@ -1182,7 +1239,7 @@ void UpgradeGift(
|
|||
window,
|
||||
MTP_inputInvoiceStarGiftUpgrade(
|
||||
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
|
||||
MTP_int(messageId.bare)),
|
||||
Api::InputSavedStarGiftId(savedId)),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
|
@ -1211,7 +1268,7 @@ void AddUpgradeButton(
|
|||
not_null<Ui::VerticalLayout*> container,
|
||||
not_null<Main::Session*> session,
|
||||
int cost,
|
||||
QString name,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<void(bool)> toggled,
|
||||
Fn<void()> preview) {
|
||||
const auto button = container->add(
|
||||
|
@ -1258,25 +1315,105 @@ void AddUpgradeButton(
|
|||
AddSkip(container);
|
||||
const auto about = AddDividerText(
|
||||
container,
|
||||
tr::lng_gift_send_unique_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ name }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities));
|
||||
(peer->isBroadcast()
|
||||
? tr::lng_gift_send_unique_about_channel(
|
||||
lt_name,
|
||||
rpl::single(TextWithEntities{ peer->name() }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities)
|
||||
: tr::lng_gift_send_unique_about(
|
||||
lt_user,
|
||||
rpl::single(TextWithEntities{ peer->shortName() }),
|
||||
lt_link,
|
||||
tr::lng_gift_send_unique_link() | Text::ToLink(),
|
||||
Text::WithEntities)));
|
||||
about->setClickHandlerFilter([=](const auto &...) {
|
||||
preview();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void AddSoldLeftSlider(
|
||||
not_null<RoundButton*> button,
|
||||
const GiftTypeStars &gift) {
|
||||
const auto still = gift.info.limitedLeft;
|
||||
const auto total = gift.info.limitedCount;
|
||||
const auto slider = CreateChild<RpWidget>(button->parentWidget());
|
||||
struct State {
|
||||
Text::String still;
|
||||
Text::String sold;
|
||||
int height = 0;
|
||||
};
|
||||
const auto state = slider->lifetime().make_state<State>();
|
||||
const auto sold = total - still;
|
||||
state->still.setText(
|
||||
st::semiboldTextStyle,
|
||||
tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still));
|
||||
state->sold.setText(
|
||||
st::semiboldTextStyle,
|
||||
tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold));
|
||||
state->height = st::giftLimitedPadding.top()
|
||||
+ st::semiboldFont->height
|
||||
+ st::giftLimitedPadding.bottom();
|
||||
button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
|
||||
const auto space = st::giftLimitedBox.buttonPadding.top();
|
||||
const auto skip = (space - state->height) / 2;
|
||||
slider->setGeometry(
|
||||
geometry.x(),
|
||||
geometry.y() - skip - state->height,
|
||||
geometry.width(),
|
||||
state->height);
|
||||
}, slider->lifetime());
|
||||
slider->paintRequest() | rpl::start_with_next([=] {
|
||||
const auto &padding = st::giftLimitedPadding;
|
||||
const auto left = (padding.left() * 2) + state->still.maxWidth();
|
||||
const auto right = (padding.right() * 2) + state->sold.maxWidth();
|
||||
const auto space = slider->width() - left - right;
|
||||
if (space <= 0) {
|
||||
return;
|
||||
}
|
||||
const auto edge = left + ((space * still) / total);
|
||||
|
||||
const auto radius = st::buttonRadius;
|
||||
auto p = QPainter(slider);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::windowBgOver);
|
||||
p.drawRoundedRect(
|
||||
edge - (radius * 3),
|
||||
0,
|
||||
slider->width() - (edge - (radius * 3)),
|
||||
state->height,
|
||||
radius,
|
||||
radius);
|
||||
p.setBrush(st::windowBgActive);
|
||||
p.drawRoundedRect(0, 0, edge, state->height, radius, radius);
|
||||
|
||||
p.setPen(st::windowFgActive);
|
||||
state->still.draw(p, {
|
||||
.position = { padding.left(), padding.top() },
|
||||
.availableWidth = left,
|
||||
});
|
||||
p.setPen(st::windowSubTextFg);
|
||||
state->sold.draw(p, {
|
||||
.position = { left + space + padding.right(), padding.top() },
|
||||
.availableWidth = right,
|
||||
});
|
||||
}, slider->lifetime());
|
||||
}
|
||||
|
||||
void SendGiftBox(
|
||||
not_null<GenericBox*> box,
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
|
||||
const GiftDescriptor &descriptor) {
|
||||
box->setStyle(st::giftBox);
|
||||
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
|
||||
const auto limited = stars
|
||||
&& (stars->info.limitedCount > stars->info.limitedLeft)
|
||||
&& (stars->info.limitedLeft > 0);
|
||||
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setTitle(tr::lng_gift_send_title());
|
||||
box->addTopButton(st::boxTitleClose, [=] {
|
||||
|
@ -1373,18 +1510,14 @@ void SendGiftBox(
|
|||
session,
|
||||
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
|
||||
|
||||
if (const auto stars = std::get_if<GiftTypeStars>(&descriptor)) {
|
||||
if (stars) {
|
||||
const auto cost = stars->info.starsToUpgrade;
|
||||
if (cost > 0 && !peer->isSelf()) {
|
||||
const auto user = peer->asUser();
|
||||
Assert(user != nullptr);
|
||||
|
||||
const auto id = stars->info.id;
|
||||
const auto name = user->shortName();
|
||||
const auto showing = std::make_shared<bool>();
|
||||
AddDivider(container);
|
||||
AddSkip(container);
|
||||
AddUpgradeButton(container, session, cost, name, [=](bool on) {
|
||||
AddUpgradeButton(container, session, cost, peer, [=](bool on) {
|
||||
auto now = state->details.current();
|
||||
now.upgraded = on;
|
||||
state->details = std::move(now);
|
||||
|
@ -1397,7 +1530,7 @@ void SendGiftBox(
|
|||
.controller = window,
|
||||
.stargiftId = id,
|
||||
.ready = [=](bool) { *showing = false; },
|
||||
.user = user,
|
||||
.peer = peer,
|
||||
.cost = int(cost),
|
||||
});
|
||||
});
|
||||
|
@ -1425,6 +1558,8 @@ void SendGiftBox(
|
|||
}, [&](const GiftTypeStars &) {
|
||||
AddDividerText(container, peer->isSelf()
|
||||
? tr::lng_gift_send_anonymous_self()
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_send_anonymous_about_channel()
|
||||
: tr::lng_gift_send_anonymous_about(
|
||||
lt_user,
|
||||
rpl::single(peer->shortName()),
|
||||
|
@ -1454,6 +1589,9 @@ void SendGiftBox(
|
|||
};
|
||||
SendGift(window, peer, api, details, done);
|
||||
});
|
||||
if (limited) {
|
||||
AddSoldLeftSlider(button, *stars);
|
||||
}
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
(peer->isSelf()
|
||||
|
@ -1704,7 +1842,7 @@ void GiftBox(
|
|||
window->showSettings(Settings::CreditsId());
|
||||
return false;
|
||||
};
|
||||
if (!peer->isSelf()) {
|
||||
if (peer->isUser() && !peer->isSelf()) {
|
||||
const auto premiumClickHandlerFilter = [=](const auto &...) {
|
||||
Settings::ShowPremium(window, u"gift_send"_q);
|
||||
return false;
|
||||
|
@ -1724,9 +1862,16 @@ void GiftBox(
|
|||
AddBlock(content, window, {
|
||||
.subtitle = (peer->isSelf()
|
||||
? tr::lng_gift_self_title()
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_channel_title()
|
||||
: tr::lng_gift_stars_subtitle()),
|
||||
.about = (peer->isSelf()
|
||||
? tr::lng_gift_self_about(Text::WithEntities)
|
||||
: peer->isBroadcast()
|
||||
? tr::lng_gift_channel_about(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(peer->name())),
|
||||
Text::WithEntities)
|
||||
: tr::lng_gift_stars_about(
|
||||
lt_name,
|
||||
rpl::single(Text::Bold(peer->shortName())),
|
||||
|
@ -2112,6 +2257,242 @@ void AddUniqueGiftCover(
|
|||
}, cover->lifetime());
|
||||
}
|
||||
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto cover = container->add(object_ptr<RpWidget>(container));
|
||||
|
||||
const auto title = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
rpl::single(peer->name()),
|
||||
st::uniqueGiftTitle);
|
||||
title->setTextColorOverride(QColor(255, 255, 255));
|
||||
const auto subtitle = CreateChild<FlatLabel>(
|
||||
cover,
|
||||
(peer->isChannel()
|
||||
? tr::lng_chat_status_subscribers(
|
||||
lt_count,
|
||||
rpl::single(peer->asChannel()->membersCount() * 1.))
|
||||
: tr::lng_status_online()),
|
||||
st::uniqueGiftSubtitle);
|
||||
subtitle->setTextColorOverride(data.backdrop.textColor);
|
||||
|
||||
struct State {
|
||||
QImage gradient;
|
||||
Data::UniqueGift gift;
|
||||
Ui::PeerUserpicView view;
|
||||
std::unique_ptr<Text::CustomEmoji> emoji;
|
||||
base::flat_map<float64, QImage> emojis;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = cover->lifetime().make_state<State>(State{
|
||||
.gift = data,
|
||||
});
|
||||
state->emoji = peer->owner().customEmojiManager().create(
|
||||
state->gift.pattern.document,
|
||||
[=] { cover->update(); },
|
||||
Data::CustomEmojiSizeTag::Large);
|
||||
|
||||
cover->widthValue() | rpl::start_with_next([=](int width) {
|
||||
const auto skip = st::uniqueGiftBottom;
|
||||
if (width <= 3 * skip) {
|
||||
return;
|
||||
}
|
||||
const auto available = width - 2 * skip;
|
||||
title->resizeToWidth(available);
|
||||
title->moveToLeft(skip, st::uniqueGiftTitleTop);
|
||||
|
||||
subtitle->resizeToWidth(available);
|
||||
subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
|
||||
|
||||
cover->resize(width, subtitle->y() + subtitle->height() + skip);
|
||||
}, cover->lifetime());
|
||||
|
||||
cover->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = Painter(cover);
|
||||
|
||||
const auto width = cover->width();
|
||||
const auto pointsHeight = st::uniqueGiftSubtitleTop;
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (state->gradient.size() != cover->size() * ratio) {
|
||||
state->gradient = CreateGradient(cover->size(), state->gift);
|
||||
}
|
||||
p.drawImage(0, 0, state->gradient);
|
||||
|
||||
PaintPoints(
|
||||
p,
|
||||
PatternPoints(),
|
||||
state->emojis,
|
||||
state->emoji.get(),
|
||||
state->gift,
|
||||
QRect(0, 0, width, pointsHeight),
|
||||
1.);
|
||||
|
||||
peer->paintUserpic(
|
||||
p,
|
||||
state->view,
|
||||
(width - st::uniqueGiftUserpicSize) / 2,
|
||||
st::uniqueGiftUserpicTop,
|
||||
st::uniqueGiftUserpicSize);
|
||||
}, cover->lifetime());
|
||||
}
|
||||
|
||||
void ShowUniqueGiftWearBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::UniqueGift &gift,
|
||||
Settings::GiftWearBoxStyleOverride st) {
|
||||
show->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setNoContentMargin(true);
|
||||
|
||||
box->setWidth((st::boxWidth + st::boxWideWidth) / 2); // =)
|
||||
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
|
||||
|
||||
const auto channel = peer->isChannel();
|
||||
const auto content = box->verticalLayout();
|
||||
AddWearGiftCover(content, gift, peer);
|
||||
|
||||
AddSkip(content, st::defaultVerticalListSkip * 2);
|
||||
|
||||
const auto infoRow = [&](
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> text,
|
||||
not_null<const style::icon*> icon) {
|
||||
auto raw = content->add(
|
||||
object_ptr<Ui::VerticalLayout>(content));
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title) | Ui::Text::ToBold(),
|
||||
st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(text),
|
||||
st.infoAbout ? *st.infoAbout : st::boxDividerLabel),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
object_ptr<Info::Profile::FloatingIcon>(
|
||||
raw,
|
||||
*icon,
|
||||
st::starrefInfoIconPosition);
|
||||
};
|
||||
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_gift_wear_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(gift))),
|
||||
st.title ? *st.title : st::uniqueGiftTitle),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
tr::lng_gift_wear_about(),
|
||||
st.subtitle ? *st.subtitle : st::uniqueGiftSubtitle),
|
||||
st::settingsPremiumRowAboutPadding);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_badge_title(),
|
||||
(channel
|
||||
? tr::lng_gift_wear_badge_about_channel()
|
||||
: tr::lng_gift_wear_badge_about()),
|
||||
st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
|
||||
//infoRow(
|
||||
// tr::lng_gift_wear_design_title(),
|
||||
// tr::lng_gift_wear_design_about(),
|
||||
// &st::menuIconUniqueProfile);
|
||||
infoRow(
|
||||
tr::lng_gift_wear_proof_title(),
|
||||
(channel
|
||||
? tr::lng_gift_wear_proof_about_channel()
|
||||
: tr::lng_gift_wear_proof_about()),
|
||||
st.proofIcon ? st.proofIcon : &st::menuIconFactcheck);
|
||||
|
||||
const auto session = &show->session();
|
||||
const auto checking = std::make_shared<bool>();
|
||||
const auto button = box->addButton(rpl::single(QString()), [=] {
|
||||
const auto emojiStatuses = &session->data().emojiStatuses();
|
||||
const auto id = emojiStatuses->fromUniqueGift(gift);
|
||||
if (!peer->isSelf()) {
|
||||
if (*checking) {
|
||||
return;
|
||||
}
|
||||
*checking = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
CheckBoostLevel(show, peer, [=](int level) {
|
||||
const auto limits = Data::LevelLimits(&peer->session());
|
||||
const auto wanted = limits.channelEmojiStatusLevelMin();
|
||||
if (level >= wanted) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
emojiStatuses->set(peer, id);
|
||||
return std::optional<Ui::AskBoostReason>();
|
||||
}
|
||||
const auto reason = [&]() -> Ui::AskBoostReason {
|
||||
return { Ui::AskBoostWearCollectible{
|
||||
wanted
|
||||
} };
|
||||
}();
|
||||
return std::make_optional(reason);
|
||||
}, [=] { *checking = false; });
|
||||
} else if (session->premium()) {
|
||||
box->closeBox();
|
||||
emojiStatuses->set(peer, id);
|
||||
} else {
|
||||
const auto link = Ui::Text::Bold(
|
||||
tr::lng_send_as_premium_required_link(tr::now));
|
||||
Settings::ShowPremiumPromoToast(
|
||||
show,
|
||||
tr::lng_gift_wear_subscribe(
|
||||
tr::now,
|
||||
lt_link,
|
||||
Ui::Text::Link(link),
|
||||
Ui::Text::WithEntities),
|
||||
u"wear_collectibles"_q);
|
||||
}
|
||||
});
|
||||
const auto lock = Ui::Text::SingleCustomEmoji(
|
||||
session->data().customEmojiManager().registerInternalEmoji(
|
||||
st::historySendDisabledIcon,
|
||||
st::giftBoxLockMargins,
|
||||
true));
|
||||
auto label = rpl::combine(
|
||||
tr::lng_gift_wear_start(),
|
||||
Data::AmPremiumValue(&show->session())
|
||||
) | rpl::map([=](const QString &text, bool premium) {
|
||||
auto result = TextWithEntities();
|
||||
if (!premium && peer->isSelf()) {
|
||||
result.append(lock);
|
||||
}
|
||||
result.append(text);
|
||||
return result;
|
||||
});
|
||||
SetButtonMarkedLabel(
|
||||
button,
|
||||
std::move(label),
|
||||
session,
|
||||
st::creditsBoxButtonLabel,
|
||||
&st::giftBox.button.textFg);
|
||||
|
||||
rpl::combine(
|
||||
box->widthValue(),
|
||||
button->widthValue()
|
||||
) | rpl::start_with_next([=](int outer, int inner) {
|
||||
const auto padding = st::giftBox.buttonPadding;
|
||||
const auto wanted = outer - padding.left() - padding.right();
|
||||
if (inner != wanted) {
|
||||
button->resizeToWidth(wanted);
|
||||
button->moveToLeft(padding.left(), padding.top());
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
AddUniqueCloseButton(box, {});
|
||||
}));
|
||||
}
|
||||
|
||||
struct UpgradeArgs : StarGiftUpgradeArgs {
|
||||
std::vector<Data::UniqueGiftModel> models;
|
||||
std::vector<Data::UniqueGiftPattern> patterns;
|
||||
|
@ -2162,7 +2543,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
|
|||
auto &patterns = state->data.patterns;
|
||||
auto &backdrops = state->data.backdrops;
|
||||
consumer.put_next(Data::UniqueGift{
|
||||
.title = (state->data.itemId
|
||||
.title = (state->data.savedId
|
||||
? tr::lng_gift_upgrade_title(tr::now)
|
||||
: tr::lng_gift_upgrade_preview_title(tr::now)),
|
||||
.model = models[index(state->modelIndices, models)],
|
||||
|
@ -2186,11 +2567,13 @@ void AddUpgradeGiftCover(
|
|||
AddUniqueGiftCover(
|
||||
container,
|
||||
MakeUpgradeGiftStream(args),
|
||||
(args.itemId
|
||||
(args.savedId
|
||||
? tr::lng_gift_upgrade_about()
|
||||
: tr::lng_gift_upgrade_preview_about(
|
||||
lt_name,
|
||||
rpl::single(args.user->shortName()))));
|
||||
: (args.peer->isBroadcast()
|
||||
? tr::lng_gift_upgrade_preview_about_channel
|
||||
: tr::lng_gift_upgrade_preview_about)(
|
||||
lt_name,
|
||||
rpl::single(args.peer->shortName()))));
|
||||
}
|
||||
|
||||
void UpgradeBox(
|
||||
|
@ -2207,26 +2590,15 @@ void UpgradeBox(
|
|||
const auto infoRow = [&](
|
||||
rpl::producer<QString> title,
|
||||
rpl::producer<QString> text,
|
||||
not_null<const style::icon*> icon,
|
||||
bool newBadge = false) {
|
||||
not_null<const style::icon*> icon) {
|
||||
auto raw = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container));
|
||||
const auto widget = raw->add(
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
std::move(title) | Ui::Text::ToBold(),
|
||||
st::defaultFlatLabel),
|
||||
st::settingsPremiumRowTitlePadding);
|
||||
if (newBadge) {
|
||||
const auto badge = NewBadge::CreateNewBadge(
|
||||
raw,
|
||||
tr::lng_soon_badge(Ui::Text::Upper));
|
||||
widget->geometryValue(
|
||||
) | rpl::start_with_next([=](QRect geometry) {
|
||||
badge->move(st::settingsPremiumNewBadgePosition
|
||||
+ QPoint(widget->x() + widget->width(), widget->y()));
|
||||
}, badge->lifetime());
|
||||
}
|
||||
raw->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
raw,
|
||||
|
@ -2250,15 +2622,14 @@ void UpgradeBox(
|
|||
infoRow(
|
||||
tr::lng_gift_upgrade_tradable_title(),
|
||||
tr::lng_gift_upgrade_tradable_about(),
|
||||
&st::menuIconTradable,
|
||||
true);
|
||||
&st::menuIconTradable);
|
||||
|
||||
struct State {
|
||||
bool sent = false;
|
||||
bool preserveDetails = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto preview = !args.itemId;
|
||||
const auto preview = !args.savedId;
|
||||
|
||||
if (!preview) {
|
||||
const auto skip = st::defaultVerticalListSkip;
|
||||
|
@ -2303,13 +2674,13 @@ void UpgradeBox(
|
|||
if (result != Payments::CheckoutResult::Paid) {
|
||||
state->sent = false;
|
||||
} else {
|
||||
controller->showPeerHistory(args.user);
|
||||
controller->showPeerHistory(args.peer);
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
UpgradeGift(controller, args.itemId, keepDetails, cost, done);
|
||||
UpgradeGift(controller, args.savedId, keepDetails, cost, done);
|
||||
});
|
||||
if (!preview) {
|
||||
auto star = session->data().customEmojiManager().creditsEmoji();
|
||||
|
@ -2339,7 +2710,7 @@ void UpgradeBox(
|
|||
}
|
||||
}, box->lifetime());
|
||||
|
||||
AddUniqueCloseButton(box);
|
||||
AddUniqueCloseButton(box, {});
|
||||
}
|
||||
|
||||
const std::vector<PatternPoint> &PatternPoints() {
|
||||
|
@ -2458,7 +2829,7 @@ void PaintPoints(
|
|||
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
|
||||
const auto weak = base::make_weak(args.controller);
|
||||
const auto session = &args.user->session();
|
||||
const auto session = &args.peer->session();
|
||||
session->api().request(MTPpayments_GetStarGiftUpgradePreview(
|
||||
MTP_long(args.stargiftId)
|
||||
)).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
|
||||
|
@ -2494,19 +2865,51 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box) {
|
||||
const auto button = Ui::CreateChild<IconButton>(
|
||||
void AddUniqueCloseButton(
|
||||
not_null<GenericBox*> box,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
|
||||
const auto close = Ui::CreateChild<IconButton>(
|
||||
box,
|
||||
st::uniqueCloseButton);
|
||||
button->show();
|
||||
button->raise();
|
||||
const auto menu = fillMenu
|
||||
? Ui::CreateChild<IconButton>(box, st::uniqueMenuButton)
|
||||
: nullptr;
|
||||
close->show();
|
||||
close->raise();
|
||||
if (menu) {
|
||||
menu->show();
|
||||
menu->raise();
|
||||
}
|
||||
box->widthValue() | rpl::start_with_next([=](int width) {
|
||||
button->moveToRight(0, 0, width);
|
||||
button->raise();
|
||||
}, button->lifetime());
|
||||
button->setClickedCallback([=] {
|
||||
close->moveToRight(0, 0, width);
|
||||
close->raise();
|
||||
if (menu) {
|
||||
menu->moveToRight(close->width(), 0, width);
|
||||
menu->raise();
|
||||
}
|
||||
}, close->lifetime());
|
||||
close->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
if (menu) {
|
||||
const auto state = menu->lifetime().make_state<
|
||||
base::unique_qptr<Ui::PopupMenu>
|
||||
>();
|
||||
menu->setClickedCallback([=] {
|
||||
if (*state) {
|
||||
*state = nullptr;
|
||||
return;
|
||||
}
|
||||
*state = base::make_unique_q<Ui::PopupMenu>(
|
||||
menu,
|
||||
st.menu ? *st.menu : st::popupMenuWithIcons);
|
||||
fillMenu(state->get());
|
||||
if (!(*state)->empty()) {
|
||||
(*state)->popup(QCursor::pos());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
|
@ -2539,25 +2942,29 @@ void RequestStarsFormAndSubmit(
|
|||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(error.type());
|
||||
const auto type = error.type();
|
||||
if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) {
|
||||
done(Payments::CheckoutResult::Cancelled, nullptr);
|
||||
} else {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(type);
|
||||
}
|
||||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
}
|
||||
done(Payments::CheckoutResult::Failed, nullptr);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result) {
|
||||
const auto gift = FindUniqueGift(&to->session(), result);
|
||||
if (const auto strong = gift ? weak.get() : nullptr) {
|
||||
const Data::UniqueGift &gift) {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast({
|
||||
.title = tr::lng_gift_transferred_title(tr::now),
|
||||
.text = tr::lng_gift_transferred_about(
|
||||
tr::now,
|
||||
lt_name,
|
||||
Text::Bold(Data::UniqueGiftName(*gift)),
|
||||
Text::Bold(Data::UniqueGiftName(gift)),
|
||||
lt_recipient,
|
||||
Text::Bold(to->shortName()),
|
||||
Ui::Text::WithEntities),
|
||||
|
|
|
@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "data/data_star_gift.h"
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
struct GiftCode;
|
||||
|
@ -17,6 +23,11 @@ namespace Payments {
|
|||
enum class CheckoutResult;
|
||||
} // namespace Payments
|
||||
|
||||
namespace Settings {
|
||||
struct GiftWearBoxStyleOverride;
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
@ -27,6 +38,7 @@ class CustomEmoji;
|
|||
|
||||
namespace Ui {
|
||||
|
||||
class PopupMenu;
|
||||
class GenericBox;
|
||||
class VerticalLayout;
|
||||
|
||||
|
@ -41,6 +53,16 @@ void AddUniqueGiftCover(
|
|||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<Data::UniqueGift> data,
|
||||
rpl::producer<QString> subtitleOverride = nullptr);
|
||||
void AddWearGiftCover(
|
||||
not_null<VerticalLayout*> container,
|
||||
const Data::UniqueGift &data,
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
void ShowUniqueGiftWearBox(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
not_null<PeerData*> peer,
|
||||
const Data::UniqueGift &gift,
|
||||
Settings::GiftWearBoxStyleOverride st);
|
||||
|
||||
struct PatternPoint {
|
||||
QPointF position;
|
||||
|
@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs {
|
|||
not_null<Window::SessionController*> controller;
|
||||
base::required<uint64> stargiftId;
|
||||
Fn<void(bool)> ready;
|
||||
not_null<UserData*> user;
|
||||
MsgId itemId = 0;
|
||||
not_null<PeerData*> peer;
|
||||
Data::SavedStarGiftId savedId;
|
||||
int cost = 0;
|
||||
bool canAddSender = false;
|
||||
bool canAddComment = false;
|
||||
|
@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs {
|
|||
};
|
||||
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args);
|
||||
|
||||
void AddUniqueCloseButton(not_null<GenericBox*> box);
|
||||
void AddUniqueCloseButton(
|
||||
not_null<GenericBox*> box,
|
||||
Settings::CreditsEntryBoxStyleOverrides st,
|
||||
Fn<void(not_null<PopupMenu*>)> fillMenu = nullptr);
|
||||
|
||||
void RequestStarsFormAndSubmit(
|
||||
not_null<Window::SessionController*> window,
|
||||
|
@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit(
|
|||
void ShowGiftTransferredToast(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
not_null<PeerData*> to,
|
||||
const MTPUpdates &result);
|
||||
const Data::UniqueGift &gift);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -817,9 +817,7 @@ void StickerSetBox::updateButtons() {
|
|||
- st.buttonPadding.left()
|
||||
- st.buttonPadding.left());
|
||||
button->setClickedCallback([=] {
|
||||
using namespace ChatHelpers;
|
||||
const auto usage = WindowUsage::PremiumPromo;
|
||||
if (const auto window = _show->resolveWindow(usage)) {
|
||||
if (const auto window = _show->resolveWindow()) {
|
||||
Settings::ShowPremium(window, u"animated_emoji"_q);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/transfer_gift_box.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_credits.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "data/data_user.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
|
||||
|
@ -21,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/basic_click_handlers.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h" // peerListSingleRow.
|
||||
#include "styles/style_dialogs.h" // recentPeersSpecialName.
|
||||
#include "styles/style_layers.h" // boxLabel.
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -41,13 +47,15 @@ public:
|
|||
Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose);
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void(not_null<PeerData*>, Fn<void()>)> choose);
|
||||
|
||||
void init(not_null<PeerListBox*> box);
|
||||
|
||||
void noSearchSubmit();
|
||||
|
||||
private:
|
||||
void prepareViewHook() override;
|
||||
void setupExportOption();
|
||||
|
||||
bool overrideKeyboardNavigation(
|
||||
int direction,
|
||||
|
@ -58,34 +66,150 @@ private:
|
|||
not_null<UserData*> user) override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
const std::shared_ptr<Data::UniqueGift> _gift;
|
||||
const Fn<void(not_null<PeerData*>)> _choose;
|
||||
const Data::SavedStarGiftId _giftId;
|
||||
const Fn<void(not_null<PeerData*>, Fn<void()>)> _choose;
|
||||
ExportOption _exportOption;
|
||||
QPointer<PeerListBox> _box;
|
||||
|
||||
};
|
||||
|
||||
void ConfirmExportBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(Fn<void()> close)> confirmed) {
|
||||
box->setTitle(tr::lng_gift_transfer_confirm_title());
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_gift_transfer_confirm_text(
|
||||
lt_name,
|
||||
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxLabel));
|
||||
box->addButton(tr::lng_gift_transfer_confirm_button(), [=] {
|
||||
confirmed([weak = Ui::MakeWeak(box)] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
}
|
||||
|
||||
void ExportOnBlockchain(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId giftId,
|
||||
Fn<void()> boxShown,
|
||||
Fn<void()> wentToUrl) {
|
||||
struct State {
|
||||
bool loading = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto session = &window->session();
|
||||
const auto show = window->uiShow();
|
||||
session->api().cloudPassword().reload();
|
||||
session->api().request(
|
||||
MTPpayments_GetStarGiftWithdrawalUrl(
|
||||
Api::InputSavedStarGiftId(giftId),
|
||||
MTP_inputCheckPasswordEmpty())
|
||||
).fail([=](const MTP::Error &error) {
|
||||
auto box = PrePasswordErrorBox(
|
||||
error.type(),
|
||||
session,
|
||||
TextWithEntities{
|
||||
tr::lng_gift_transfer_password_about(tr::now),
|
||||
});
|
||||
if (box) {
|
||||
show->show(std::move(box));
|
||||
boxShown();
|
||||
return;
|
||||
}
|
||||
state->lifetime = session->api().cloudPassword().state(
|
||||
) | rpl::take(
|
||||
1
|
||||
) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
|
||||
auto fields = PasscodeBox::CloudFields::From(pass);
|
||||
fields.customTitle = tr::lng_gift_transfer_password_title();
|
||||
fields.customDescription
|
||||
= tr::lng_gift_transfer_password_description(tr::now);
|
||||
fields.customSubmitButton = tr::lng_passcode_submit();
|
||||
fields.customCheckCallback = crl::guard(parent, [=](
|
||||
const Core::CloudPasswordResult &result,
|
||||
QPointer<PasscodeBox> box) {
|
||||
using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
|
||||
session->api().request(
|
||||
MTPpayments_GetStarGiftWithdrawalUrl(
|
||||
Api::InputSavedStarGiftId(giftId),
|
||||
result.result)
|
||||
).done([=](const ExportUrl &result) {
|
||||
UrlClickHandler::Open(qs(result.data().vurl()));
|
||||
wentToUrl();
|
||||
if (box) {
|
||||
box->closeBox();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto message = error.type();
|
||||
if (box && !box->handleCustomCheckError(message)) {
|
||||
show->showToast(message);
|
||||
}
|
||||
}).send();
|
||||
});
|
||||
show->show(Box<PasscodeBox>(session, fields));
|
||||
boxShown();
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
[[nodiscard]] ExportOption MakeExportOption(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerListBox*> box,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Data::SavedStarGiftId giftId,
|
||||
TimeId when) {
|
||||
struct State {
|
||||
bool exporting = false;
|
||||
};
|
||||
const auto state = std::make_shared<State>();
|
||||
const auto activate = [=] {
|
||||
const auto now = base::unixtime::now();
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto left = (when > now) ? (when - now) : 0;
|
||||
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
|
||||
if (!hours) {
|
||||
window->show(Box(ConfirmExportBox, gift, [=](Fn<void()> close) {
|
||||
if (state->exporting) {
|
||||
return;
|
||||
}
|
||||
state->exporting = true;
|
||||
ExportOnBlockchain(window, box, gift, giftId, [=] {
|
||||
state->exporting = false;
|
||||
close();
|
||||
}, [=] {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
close();
|
||||
});
|
||||
}));
|
||||
return;
|
||||
}
|
||||
window->show(Ui::MakeInformBox({
|
||||
.text = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_about()
|
||||
: tr::lng_gift_transfer_unlocks_about(
|
||||
lt_when,
|
||||
((hours >= 24)
|
||||
? tr::lng_gift_transfer_unlocks_when_days(
|
||||
lt_count,
|
||||
rpl::single((hours / 24) * 1.))
|
||||
: tr::lng_gift_transfer_unlocks_when_hours(
|
||||
lt_count,
|
||||
rpl::single(hours * 1.))))),
|
||||
.title = (!hours
|
||||
? tr::lng_gift_transfer_unlocks_update_title()
|
||||
: tr::lng_gift_transfer_unlocks_title()),
|
||||
.text = tr::lng_gift_transfer_unlocks_about(
|
||||
lt_when,
|
||||
((hours >= 24)
|
||||
? tr::lng_gift_transfer_unlocks_when_days(
|
||||
lt_count,
|
||||
rpl::single((hours / 24) * 1.))
|
||||
: tr::lng_gift_transfer_unlocks_when_hours(
|
||||
lt_count,
|
||||
rpl::single(hours * 1.)))),
|
||||
.title = tr::lng_gift_transfer_unlocks_title(),
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -130,10 +254,14 @@ private:
|
|||
|
||||
const style::PeerListItem &computeSt(
|
||||
const style::PeerListItem &st) const override {
|
||||
return _available ? st::recentPeersSpecialName : st;
|
||||
_st = st;
|
||||
_st.namePosition.setY(
|
||||
st::recentPeersSpecialName.namePosition.y());
|
||||
return _available ? _st : st;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable style::PeerListItem _st;
|
||||
bool _available = false;
|
||||
|
||||
};
|
||||
|
@ -228,18 +356,27 @@ private:
|
|||
Controller::Controller(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
Fn<void(not_null<PeerData*>)> choose)
|
||||
Data::SavedStarGiftId giftId,
|
||||
Fn<void(not_null<PeerData*>, Fn<void()>)> choose)
|
||||
: ContactsBoxController(&window->session())
|
||||
, _window(window)
|
||||
, _gift(std::move(gift))
|
||||
, _giftId(giftId)
|
||||
, _choose(std::move(choose)) {
|
||||
if (const auto when = _gift->exportAt) {
|
||||
_exportOption = MakeExportOption(window, when);
|
||||
}
|
||||
if (_exportOption.content) {
|
||||
if (_gift->exportAt) {
|
||||
setStyleOverrides(&st::peerListSmallSkips);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::init(not_null<PeerListBox*> box) {
|
||||
_box = box;
|
||||
if (const auto when = _gift->exportAt) {
|
||||
_exportOption = MakeExportOption(_window, box, _gift, _giftId, when);
|
||||
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
|
||||
delegate()->peerListRefreshRows();
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::noSearchSubmit() {
|
||||
if (const auto onstack = _exportOption.activate) {
|
||||
onstack();
|
||||
|
@ -258,11 +395,6 @@ void Controller::prepareViewHook() {
|
|||
delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
|
||||
lt_name,
|
||||
rpl::single(UniqueGiftName(*_gift))));
|
||||
setupExportOption();
|
||||
}
|
||||
|
||||
void Controller::setupExportOption() {
|
||||
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerListRow> Controller::createRow(
|
||||
|
@ -277,14 +409,18 @@ std::unique_ptr<PeerListRow> Controller::createRow(
|
|||
}
|
||||
|
||||
void Controller::rowClicked(not_null<PeerListRow*> row) {
|
||||
_choose(row->peer());
|
||||
_choose(row->peer(), [parentBox = _box] {
|
||||
if (const auto strong = parentBox.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TransferGift(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<PeerData*> to,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId messageId,
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void(Payments::CheckoutResult)> done) {
|
||||
Expects(to->isUser());
|
||||
|
||||
|
@ -293,33 +429,37 @@ void TransferGift(
|
|||
auto formDone = [=](
|
||||
Payments::CheckoutResult result,
|
||||
const MTPUpdates *updates) {
|
||||
if (result == Payments::CheckoutResult::Paid && updates) {
|
||||
done(result);
|
||||
if (result == Payments::CheckoutResult::Paid) {
|
||||
if (const auto strong = weak.get()) {
|
||||
Ui::ShowGiftTransferredToast(strong, to, *updates);
|
||||
strong->session().data().notifyGiftUpdate({
|
||||
.id = savedId,
|
||||
.action = Data::GiftUpdate::Action::Transfer,
|
||||
});
|
||||
Ui::ShowGiftTransferredToast(strong, to, *gift);
|
||||
}
|
||||
}
|
||||
done(result);
|
||||
};
|
||||
if (gift->starsForTransfer <= 0) {
|
||||
session->api().request(MTPpayments_TransferStarGift(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
to->input
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
session->api().applyUpdates(result);
|
||||
formDone(Payments::CheckoutResult::Paid, &result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->showToast(error.type());
|
||||
}
|
||||
formDone(Payments::CheckoutResult::Failed, nullptr);
|
||||
}).send();
|
||||
return;
|
||||
}
|
||||
Ui::RequestStarsFormAndSubmit(
|
||||
window,
|
||||
MTP_inputInvoiceStarGiftTransfer(
|
||||
MTP_int(messageId.bare),
|
||||
to->asUser()->inputUser),
|
||||
Api::InputSavedStarGiftId(savedId),
|
||||
to->input),
|
||||
std::move(formDone));
|
||||
}
|
||||
|
||||
|
@ -327,7 +467,8 @@ void ShowTransferToBox(
|
|||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
Data::SavedStarGiftId savedId,
|
||||
Fn<void()> closeParentBox) {
|
||||
const auto stars = gift->starsForTransfer;
|
||||
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
box->setTitle(tr::lng_gift_transfer_title(
|
||||
|
@ -353,16 +494,24 @@ void ShowTransferToBox(
|
|||
state->sent = true;
|
||||
const auto weak = Ui::MakeWeak(box);
|
||||
const auto done = [=](Payments::CheckoutResult result) {
|
||||
if (result != Payments::CheckoutResult::Paid) {
|
||||
if (result == Payments::CheckoutResult::Cancelled) {
|
||||
closeParentBox();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
} else if (result != Payments::CheckoutResult::Paid) {
|
||||
state->sent = false;
|
||||
} else {
|
||||
controller->showPeerHistory(peer);
|
||||
if (savedId.isUser()) {
|
||||
controller->showPeerHistory(peer);
|
||||
}
|
||||
closeParentBox();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
}
|
||||
};
|
||||
TransferGift(controller, peer, gift, msgId, done);
|
||||
TransferGift(controller, peer, gift, savedId, done);
|
||||
};
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
|
@ -395,15 +544,18 @@ void ShowTransferToBox(
|
|||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId) {
|
||||
Data::SavedStarGiftId savedId) {
|
||||
auto controller = std::make_unique<Controller>(
|
||||
window,
|
||||
gift,
|
||||
[=](not_null<PeerData*> peer) {
|
||||
ShowTransferToBox(window, peer, gift, msgId);
|
||||
savedId,
|
||||
[=](not_null<PeerData*> peer, Fn<void()> done) {
|
||||
ShowTransferToBox(window, peer, gift, savedId, done);
|
||||
});
|
||||
const auto controllerRaw = controller.get();
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
controllerRaw->init(box);
|
||||
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
|
||||
box->noSearchSubmits() | rpl::start_with_next([=] {
|
||||
|
|
|
@ -13,9 +13,10 @@ class SessionController;
|
|||
|
||||
namespace Data {
|
||||
struct UniqueGift;
|
||||
class SavedStarGiftId;
|
||||
} // namespace Data
|
||||
|
||||
void ShowTransferGiftBox(
|
||||
not_null<Window::SessionController*> window,
|
||||
std::shared_ptr<Data::UniqueGift> gift,
|
||||
MsgId msgId);
|
||||
Data::SavedStarGiftId savedId);
|
||||
|
|
|
@ -1441,6 +1441,19 @@ groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
|
|||
placeholderFont: font(14px);
|
||||
}
|
||||
|
||||
groupCallVolumeSettings: Menu(groupCallPopupVolumeMenu) {
|
||||
widthMin: 210px;
|
||||
itemBg: groupCallMembersBg;
|
||||
itemBgOver: groupCallMembersBgOver;
|
||||
}
|
||||
groupCallVolumeSettingsPadding: margins(24px, 8px, 24px, 6px);
|
||||
groupCallVolumeSettingsSlider: MediaSlider(groupCallMenuVolumeSlider) {
|
||||
activeFg: groupCallMenuBg;
|
||||
inactiveFg: groupCallMenuBg;
|
||||
activeFgOver: groupCallMenuBg;
|
||||
inactiveFgOver: groupCallMenuBg;
|
||||
}
|
||||
|
||||
//
|
||||
groupCallCalendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", groupCallMemberNotJoinedStatus }};
|
||||
groupCallCalendarNextDisabled: icon {{ "calendar_down", groupCallMemberNotJoinedStatus }};
|
||||
|
|
|
@ -1431,10 +1431,12 @@ void Members::Controller::addMuteActionsToContextMenu(
|
|||
auto volumeItem = base::make_unique_q<MenuVolumeItem>(
|
||||
menu->menu(),
|
||||
st::groupCallPopupVolumeMenu,
|
||||
st::groupCallMenuVolumeSlider,
|
||||
otherParticipantStateValue,
|
||||
_call->rtmp() ? _call->rtmpVolume() : row->volume(),
|
||||
Group::kMaxVolume,
|
||||
muted);
|
||||
muted,
|
||||
st::groupCallMenuVolumePadding);
|
||||
|
||||
mutesFromVolume = volumeItem->toggleMuteRequests();
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "calls/group/calls_group_menu.h" // LeaveBox.
|
||||
#include "calls/group/calls_group_common.h"
|
||||
#include "calls/group/calls_choose_join_as.h"
|
||||
#include "calls/group/calls_volume_item.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "ui/widgets/level_meter.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
|
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "webrtc/webrtc_audio_input_tester.h"
|
||||
#include "webrtc/webrtc_device_resolver.h"
|
||||
#include "settings/settings_calls.h"
|
||||
#include "settings/settings_credits_graphics.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_invite_links.h"
|
||||
|
@ -183,22 +185,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
return Data::CanSend(thread, ChatRestriction::SendOther);
|
||||
};
|
||||
|
||||
const auto scheduleStyle = [&] {
|
||||
auto date = Ui::ChooseDateTimeStyleArgs();
|
||||
date.labelStyle = &st::groupCallBoxLabel;
|
||||
date.dateFieldStyle = &st::groupCallScheduleDateField;
|
||||
date.timeFieldStyle = &st::groupCallScheduleTimeField;
|
||||
date.separatorStyle = &st::callMuteButtonLabel;
|
||||
date.atStyle = &st::callMuteButtonLabel;
|
||||
date.calendarStyle = &st::groupCallCalendarColors;
|
||||
|
||||
auto st = HistoryView::ScheduleBoxStyleArgs();
|
||||
st.topButtonStyle = &st::groupCallMenuToggle;
|
||||
st.popupMenuStyle = &st::groupCallPopupMenu;
|
||||
st.chooseDateTimeArgs = std::move(date);
|
||||
return st;
|
||||
};
|
||||
|
||||
const auto st = ::Settings::DarkCreditsEntryBoxStyle();
|
||||
auto result = Box<ShareBox>(ShareBox::Descriptor{
|
||||
.session = &peer->session(),
|
||||
.copyCallback = std::move(copyCallback),
|
||||
|
@ -211,11 +198,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
|
|||
: rpl::single(false)),
|
||||
tr::lng_group_call_copy_speaker_link(),
|
||||
tr::lng_group_call_copy_listener_link()),
|
||||
.stMultiSelect = &st::groupCallMultiSelect,
|
||||
.stComment = &st::groupCallShareBoxComment,
|
||||
.st = &st::groupCallShareBoxList,
|
||||
.stLabel = &st::groupCallField,
|
||||
.scheduleBoxStyle = scheduleStyle(),
|
||||
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
|
||||
.premiumRequiredError = SharePremiumRequiredError(),
|
||||
});
|
||||
*box = result.data();
|
||||
|
@ -731,6 +714,50 @@ void SettingsBox(
|
|||
addDivider();
|
||||
Ui::AddSkip(layout);
|
||||
}
|
||||
if (rtmp) {
|
||||
const auto volumeItem = layout->add(
|
||||
object_ptr<MenuVolumeItem>(
|
||||
layout,
|
||||
st::groupCallVolumeSettings,
|
||||
st::groupCallVolumeSettingsSlider,
|
||||
call->otherParticipantStateValue(
|
||||
) | rpl::filter([=](const Group::ParticipantState &data) {
|
||||
return data.peer == peer;
|
||||
}),
|
||||
call->rtmpVolume(),
|
||||
Group::kMaxVolume,
|
||||
false,
|
||||
st::groupCallVolumeSettingsPadding));
|
||||
|
||||
const auto toggleMute = crl::guard(layout, [=](bool m, bool local) {
|
||||
if (call) {
|
||||
call->toggleMute({
|
||||
.peer = peer,
|
||||
.mute = m,
|
||||
.locallyOnly = local,
|
||||
});
|
||||
}
|
||||
});
|
||||
const auto changeVolume = crl::guard(layout, [=](int v, bool local) {
|
||||
if (call) {
|
||||
call->changeVolume({
|
||||
.peer = peer,
|
||||
.volume = std::clamp(v, 1, Group::kMaxVolume),
|
||||
.locallyOnly = local,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
volumeItem->toggleMuteLocallyRequests(
|
||||
) | rpl::start_with_next([=](bool muted) {
|
||||
toggleMute(muted, true);
|
||||
}, volumeItem->lifetime());
|
||||
|
||||
volumeItem->changeVolumeLocallyRequests(
|
||||
) | rpl::start_with_next([=](int volume) {
|
||||
changeVolume(volume, true);
|
||||
}, volumeItem->lifetime());
|
||||
}
|
||||
|
||||
if (peer->canManageGroupCall()) {
|
||||
layout->add(object_ptr<Ui::SettingsButton>(
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/effects/animation_value.h"
|
||||
#include "ui/effects/cross_line.h"
|
||||
#include "ui/widgets/continuous_sliders.h"
|
||||
#include "ui/rect.h"
|
||||
#include "styles/style_calls.h"
|
||||
|
||||
#include "ui/paint/arcs.h"
|
||||
|
@ -41,20 +42,21 @@ constexpr auto kVolumeStickedValues
|
|||
MenuVolumeItem::MenuVolumeItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MediaSlider &stSlider,
|
||||
rpl::producer<Group::ParticipantState> participantState,
|
||||
int startVolume,
|
||||
int maxVolume,
|
||||
bool muted)
|
||||
bool muted,
|
||||
const QMargins &padding)
|
||||
: Ui::Menu::ItemBase(parent, st)
|
||||
, _maxVolume(maxVolume)
|
||||
, _cloudMuted(muted)
|
||||
, _localMuted(muted)
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(
|
||||
this,
|
||||
st::groupCallMenuVolumeSlider))
|
||||
, _slider(base::make_unique_q<Ui::MediaSlider>(this, stSlider))
|
||||
, _dummyAction(new QAction(parent))
|
||||
, _st(st)
|
||||
, _stCross(st::groupCallMuteCrossLine)
|
||||
, _padding(padding)
|
||||
, _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
|
||||
, _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
|
||||
st::groupCallSpeakerArcsAnimation,
|
||||
|
@ -71,7 +73,7 @@ MenuVolumeItem::MenuVolumeItem(
|
|||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &size) {
|
||||
const auto geometry = QRect(QPoint(), size);
|
||||
_itemRect = geometry - st::groupCallMenuVolumePadding;
|
||||
_itemRect = geometry - _padding;
|
||||
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
|
||||
_arcPosition = _speakerRect.center()
|
||||
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip);
|
||||
|
@ -280,9 +282,7 @@ bool MenuVolumeItem::isEnabled() const {
|
|||
}
|
||||
|
||||
int MenuVolumeItem::contentHeight() const {
|
||||
return st::groupCallMenuVolumePadding.top()
|
||||
+ st::groupCallMenuVolumePadding.bottom()
|
||||
+ _stCross.icon.height();
|
||||
return rect::m::sum::v(_padding) + _stCross.icon.height();
|
||||
}
|
||||
|
||||
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {
|
||||
|
|
|
@ -31,10 +31,12 @@ public:
|
|||
MenuVolumeItem(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MediaSlider &stSlider,
|
||||
rpl::producer<Group::ParticipantState> participantState,
|
||||
int startVolume,
|
||||
int maxVolume,
|
||||
bool muted);
|
||||
bool muted,
|
||||
const QMargins &padding);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
@ -71,6 +73,7 @@ private:
|
|||
const not_null<QAction*> _dummyAction;
|
||||
const style::Menu &_st;
|
||||
const style::CrossLineAnimation &_stCross;
|
||||
const QMargins &_padding;
|
||||
|
||||
const std::unique_ptr<Ui::CrossLineAnimation> _crossLineMute;
|
||||
Ui::Animations::Simple _crossLineAnimation;
|
||||
|
|
|
@ -40,6 +40,7 @@ TabbedSearch {
|
|||
|
||||
ComposeIcons {
|
||||
settings: icon;
|
||||
collectibles: icon;
|
||||
|
||||
recent: icon;
|
||||
recentActive: icon;
|
||||
|
@ -587,6 +588,7 @@ sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roun
|
|||
|
||||
defaultComposeIcons: ComposeIcons {
|
||||
settings: icon {{ "emoji/emoji_settings", emojiIconFg }};
|
||||
collectibles: icon {{ "menu/unique", emojiIconFg }};
|
||||
|
||||
recent: icon {{ "emoji/emoji_recent", emojiIconFg }};
|
||||
recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }};
|
||||
|
|
|
@ -18,6 +18,7 @@ struct ComposeFeatures {
|
|||
bool attachBotsMenu : 1 = true;
|
||||
bool inlineBots : 1 = true;
|
||||
bool megagroupSet : 1 = true;
|
||||
bool collectibleStatus : 1 = false;
|
||||
bool stickersSettings : 1 = true;
|
||||
bool openStickerSets : 1 = true;
|
||||
bool autocompleteHashtags : 1 = true;
|
||||
|
|
|
@ -19,7 +19,7 @@ rpl::producer<bool> Show::adjustShadowLeft() const {
|
|||
}
|
||||
|
||||
ResolveWindow ResolveWindowDefault() {
|
||||
return [](not_null<Main::Session*> session, WindowUsage usage)
|
||||
return [](not_null<Main::Session*> session)
|
||||
-> Window::SessionController* {
|
||||
const auto check = [&](Window::Controller *window) {
|
||||
if (const auto controller = window->sessionController()) {
|
||||
|
@ -45,8 +45,8 @@ ResolveWindow ResolveWindowDefault() {
|
|||
};
|
||||
}
|
||||
|
||||
Window::SessionController *Show::resolveWindow(WindowUsage usage) const {
|
||||
return ResolveWindowDefault()(&session(), usage);
|
||||
Window::SessionController *Show::resolveWindow() const {
|
||||
return ResolveWindowDefault()(&session());
|
||||
}
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -40,13 +40,8 @@ enum class PauseReason {
|
|||
using PauseReasons = base::flags<PauseReason>;
|
||||
inline constexpr bool is_flag_type(PauseReason) { return true; };
|
||||
|
||||
enum class WindowUsage {
|
||||
PremiumPromo,
|
||||
};
|
||||
|
||||
using ResolveWindow = Fn<Window::SessionController*(
|
||||
not_null<Main::Session*>,
|
||||
WindowUsage)>;
|
||||
not_null<Main::Session*>)>;
|
||||
[[nodiscard]] ResolveWindow ResolveWindowDefault();
|
||||
|
||||
class Show : public Main::SessionShow {
|
||||
|
@ -68,8 +63,7 @@ public:
|
|||
|
||||
virtual void processChosenSticker(FileChosen &&chosen) const = 0;
|
||||
|
||||
[[nodiscard]] virtual Window::SessionController *resolveWindow(
|
||||
WindowUsage) const;
|
||||
[[nodiscard]] virtual Window::SessionController *resolveWindow() const;
|
||||
};
|
||||
|
||||
} // namespace ChatHelpers
|
||||
|
|
|
@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/sticker_set_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "layout/layout_position.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_channel.h"
|
||||
|
@ -134,6 +135,7 @@ struct EmojiListWidget::CustomEmojiInstance {
|
|||
};
|
||||
|
||||
struct EmojiListWidget::RecentOne {
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
Ui::Text::CustomEmoji *custom = nullptr;
|
||||
RecentEmojiId id;
|
||||
mutable QImage premiumLock;
|
||||
|
@ -447,6 +449,13 @@ void EmojiColorPicker::drawVariant(QPainter &p, int variant) {
|
|||
w.y() + _innerPosition.y());
|
||||
}
|
||||
|
||||
std::vector<EmojiStatusId> DocumentListToRecent(
|
||||
const std::vector<DocumentId> &documents) {
|
||||
return documents | ranges::views::transform([](DocumentId id) {
|
||||
return EmojiStatusId{ .documentId = id };
|
||||
}) | ranges::to_vector;
|
||||
}
|
||||
|
||||
EmojiListWidget::EmojiListWidget(
|
||||
QWidget *parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
|
@ -510,6 +519,11 @@ EmojiListWidget::EmojiListWidget(
|
|||
refreshCustom();
|
||||
}
|
||||
}, lifetime());
|
||||
} else if (_mode == Mode::EmojiStatus && _features.collectibleStatus) {
|
||||
session().data().emojiStatuses().collectiblesUpdates(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshCustom();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
_customSingleSize = Data::FrameSizeFromTag(
|
||||
|
@ -656,7 +670,8 @@ void EmojiListWidget::applyNextSearchQuery() {
|
|||
void EmojiListWidget::showPreview() {
|
||||
if (const auto over = std::get_if<OverEmoji>(&_pressed)) {
|
||||
if (const auto custom = lookupCustomEmoji(over)) {
|
||||
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
|
||||
const auto document = custom.document;
|
||||
_show->showMediaPreview(document->stickerSetOrigin(), document);
|
||||
_previewShown = true;
|
||||
}
|
||||
}
|
||||
|
@ -706,7 +721,7 @@ void EmojiListWidget::appendPremiumSearchResults() {
|
|||
}
|
||||
|
||||
void EmojiListWidget::provideRecent(
|
||||
const std::vector<DocumentId> &customRecentList) {
|
||||
const std::vector<EmojiStatusId> &customRecentList) {
|
||||
clearSelection();
|
||||
fillRecentFrom(customRecentList);
|
||||
resizeToWidth(width());
|
||||
|
@ -1094,7 +1109,8 @@ void EmojiListWidget::fillRecent() {
|
|||
}
|
||||
}
|
||||
|
||||
void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
||||
void EmojiListWidget::fillRecentFrom(
|
||||
const std::vector<EmojiStatusId> &list) {
|
||||
const auto test = session().isTestMode();
|
||||
_recent.clear();
|
||||
_recent.reserve(list.size());
|
||||
|
@ -1114,10 +1130,15 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
|
|||
_recentCustomIds.emplace(fakeId);
|
||||
} else {
|
||||
_recent.push_back({
|
||||
.collectible = id.collectible,
|
||||
.custom = resolveCustomRecent(id),
|
||||
.id = { RecentEmojiDocument{ .id = id, .test = test } },
|
||||
.id = {
|
||||
RecentEmojiDocument{ .id = id.documentId, .test = test },
|
||||
},
|
||||
});
|
||||
_recentCustomIds.emplace(id);
|
||||
_recentCustomIds.emplace(id.collectible
|
||||
? id.collectible->documentId
|
||||
: id.documentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1158,8 +1179,12 @@ void EmojiListWidget::fillRecentMenu(
|
|||
const auto over = OverEmoji{ section, index };
|
||||
const auto emoji = lookupOverEmoji(&over);
|
||||
const auto custom = lookupCustomEmoji(&over);
|
||||
if (custom && custom->sticker()) {
|
||||
const auto sticker = custom->sticker();
|
||||
if (custom.collectible) {
|
||||
return;
|
||||
}
|
||||
const auto document = custom.document;
|
||||
if (document && document->sticker()) {
|
||||
const auto sticker = document->sticker();
|
||||
const auto emoji = sticker->alt;
|
||||
const auto setId = sticker->set.id;
|
||||
if (!emoji.isEmpty()) {
|
||||
|
@ -1168,7 +1193,7 @@ void EmojiListWidget::fillRecentMenu(
|
|||
EntityType::CustomEmoji,
|
||||
0,
|
||||
int(emoji.size()),
|
||||
Data::SerializeCustomEmojiId(custom)
|
||||
Data::SerializeCustomEmojiId(document)
|
||||
});
|
||||
addAction(tr::lng_emoji_copy(tr::now), [=] {
|
||||
TextUtilities::SetClipboardText(data);
|
||||
|
@ -1192,8 +1217,8 @@ void EmojiListWidget::fillRecentMenu(
|
|||
auto id = RecentEmojiId{ emoji };
|
||||
if (custom) {
|
||||
id.data = RecentEmojiDocument{
|
||||
.id = custom->id,
|
||||
.test = custom->session().isTestMode(),
|
||||
.id = custom.document->id,
|
||||
.test = custom.document->session().isTestMode(),
|
||||
};
|
||||
}
|
||||
addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] {
|
||||
|
@ -1229,7 +1254,7 @@ void EmojiListWidget::fillEmojiStatusMenu(
|
|||
int section,
|
||||
int index) {
|
||||
const auto chosen = lookupCustomEmoji(index, section);
|
||||
if (!chosen) {
|
||||
if (!chosen || chosen.collectible) {
|
||||
return;
|
||||
}
|
||||
const auto selectWith = [=](TimeId scheduled) {
|
||||
|
@ -1573,12 +1598,14 @@ bool EmojiListWidget::checkPickerHide() {
|
|||
return false;
|
||||
}
|
||||
|
||||
DocumentData *EmojiListWidget::lookupCustomEmoji(
|
||||
EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
|
||||
const OverEmoji *over) const {
|
||||
return over ? lookupCustomEmoji(over->index, over->section) : nullptr;
|
||||
return over
|
||||
? lookupCustomEmoji(over->index, over->section)
|
||||
: ResolvedCustom();
|
||||
}
|
||||
|
||||
DocumentData *EmojiListWidget::lookupCustomEmoji(
|
||||
EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
|
||||
int index,
|
||||
int section) const {
|
||||
if (_searchMode) {
|
||||
|
@ -1586,22 +1613,30 @@ DocumentData *EmojiListWidget::lookupCustomEmoji(
|
|||
const auto document = std::get_if<RecentEmojiDocument>(
|
||||
&_searchResults[index].id.data);
|
||||
if (document) {
|
||||
return session().data().document(document->id);
|
||||
return { session().data().document(document->id) };
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
} else if (section == int(Section::Recent) && index < _recent.size()) {
|
||||
const auto &recent = _recent[index];
|
||||
if (recent.collectible) {
|
||||
return {
|
||||
session().data().document(recent.collectible->documentId),
|
||||
recent.collectible,
|
||||
};
|
||||
}
|
||||
const auto document = std::get_if<RecentEmojiDocument>(
|
||||
&_recent[index].id.data);
|
||||
&recent.id.data);
|
||||
if (document) {
|
||||
return session().data().document(document->id);
|
||||
return { session().data().document(document->id) };
|
||||
}
|
||||
} else if (section >= _staticCount
|
||||
&& index < _custom[section - _staticCount].list.size()) {
|
||||
auto &set = _custom[section - _staticCount];
|
||||
return set.list[index].document;
|
||||
auto &entry = set.list[index];
|
||||
return { entry.document, entry.collectible };
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
|
||||
|
@ -1643,9 +1678,11 @@ EmojiChosen EmojiListWidget::lookupChosen(
|
|||
}
|
||||
|
||||
FileChosen EmojiListWidget::lookupChosen(
|
||||
not_null<DocumentData*> custom,
|
||||
ResolvedCustom custom,
|
||||
const OverEmoji *over,
|
||||
Api::SendOptions options) {
|
||||
Expects(custom.document != nullptr);
|
||||
|
||||
_grabbingChosen = true;
|
||||
const auto guard = gsl::finally([&] { _grabbingChosen = false; });
|
||||
const auto rect = over ? emojiRect(over->section, over->index) : QRect();
|
||||
|
@ -1655,13 +1692,14 @@ FileChosen EmojiListWidget::lookupChosen(
|
|||
) : QRect();
|
||||
|
||||
return {
|
||||
.document = custom,
|
||||
.document = custom.document,
|
||||
.options = options,
|
||||
.messageSendingFrom = {
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Emoji,
|
||||
.globalStartGeometry = over ? mapToGlobal(emoji) : QRect(),
|
||||
.frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(),
|
||||
},
|
||||
.collectible = custom.collectible,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1751,7 +1789,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
const auto id = hasColorButton(button->section)
|
||||
? 0
|
||||
: _custom[button->section - _staticCount].id;
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (hasColorButton(button->section)) {
|
||||
_pickerSelected = pressed;
|
||||
showPicker();
|
||||
|
@ -1759,7 +1796,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
removeSet(id);
|
||||
} else if (hasAddButton(button->section)) {
|
||||
_localSetsManager->install(id);
|
||||
} else if (const auto resolved = _show->resolveWindow(usage)) {
|
||||
} else if (const auto resolved = _show->resolveWindow()) {
|
||||
_jumpedToPremium.fire({});
|
||||
switch (_mode) {
|
||||
case Mode::Full:
|
||||
|
@ -1792,6 +1829,8 @@ void EmojiListWidget::displaySet(uint64 setId) {
|
|||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (setId == Data::Stickers::CollectibleSetId) {
|
||||
return;
|
||||
}
|
||||
const auto &sets = session().data().stickers().sets();
|
||||
auto it = sets.find(setId);
|
||||
|
@ -1830,6 +1869,7 @@ void EmojiListWidget::removeSet(uint64 setId) {
|
|||
Assert(i != end(_custom));
|
||||
const auto removeLocally = !_megagroupSet->canEditEmoji();
|
||||
removeMegagroupSet(removeLocally);
|
||||
} else if (setId == Data::Stickers::CollectibleSetId) {
|
||||
} else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
|
||||
checkHideWithBox(std::move(box));
|
||||
}
|
||||
|
@ -1933,6 +1973,8 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
|
|||
return true;
|
||||
}
|
||||
return !set.list.empty() && _megagroupSet->canEditEmoji();
|
||||
} else if (set.id == Data::Stickers::CollectibleSetId) {
|
||||
return false;
|
||||
}
|
||||
return set.canRemove && !set.premiumRequired;
|
||||
}
|
||||
|
@ -1962,7 +2004,8 @@ bool EmojiListWidget::hasAddButton(int index) const {
|
|||
const auto &set = _custom[index - _staticCount];
|
||||
return !set.canRemove
|
||||
&& !set.premiumRequired
|
||||
&& set.id != Data::Stickers::MegagroupSetId;
|
||||
&& set.id != Data::Stickers::MegagroupSetId
|
||||
&& set.id != Data::Stickers::CollectibleSetId;
|
||||
}
|
||||
|
||||
QRect EmojiListWidget::addButtonRect(int index) const {
|
||||
|
@ -1991,8 +2034,9 @@ bool EmojiListWidget::hasButton(int index) const {
|
|||
} else if (index >= _staticCount
|
||||
&& index < _staticCount + _custom.size()) {
|
||||
const auto &custom = _custom[index - _staticCount];
|
||||
return (custom.id != Data::Stickers::MegagroupSetId)
|
||||
|| custom.canRemove;
|
||||
return (custom.id != Data::Stickers::CollectibleSetId)
|
||||
&& ((custom.id != Data::Stickers::MegagroupSetId)
|
||||
|| custom.canRemove);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -2020,6 +2064,7 @@ QRect EmojiListWidget::buttonRect(
|
|||
auto EmojiListWidget::rightButton(int index) const -> const RightButton & {
|
||||
Expects(index >= _staticCount
|
||||
&& index < _staticCount + _custom.size());
|
||||
|
||||
return hasAddButton(index)
|
||||
? _add
|
||||
: _custom[index - _staticCount].canRemove
|
||||
|
@ -2279,11 +2324,12 @@ void EmojiListWidget::refreshCustom() {
|
|||
auto set = std::vector<CustomOne>();
|
||||
set.reserve(list.size());
|
||||
for (const auto document : list) {
|
||||
if (_restrictedCustomList.contains(document->id)) {
|
||||
const auto id = EmojiStatusId{ document->id };
|
||||
if (_restrictedCustomList.contains(id.documentId)) {
|
||||
continue;
|
||||
} else if (const auto sticker = document->sticker()) {
|
||||
set.push_back({
|
||||
.custom = resolveCustomEmoji(document, lookupId),
|
||||
.custom = resolveCustomEmoji(id, document, lookupId),
|
||||
.document = document,
|
||||
.emoji = Ui::Emoji::Find(sticker->alt),
|
||||
});
|
||||
|
@ -2305,6 +2351,7 @@ void EmojiListWidget::refreshCustom() {
|
|||
.premiumRequired = premium && premiumMayBeBought,
|
||||
});
|
||||
};
|
||||
refreshEmojiStatusCollectibles();
|
||||
refreshMegagroupStickers(push, GroupStickersPlace::Visible);
|
||||
for (const auto setId : owner->stickers().emojiSetsOrder()) {
|
||||
push(setId, true);
|
||||
|
@ -2337,18 +2384,17 @@ Fn<void()> EmojiListWidget::repaintCallback(
|
|||
}
|
||||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
|
||||
EmojiStatusId id,
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId) {
|
||||
Expects(document->sticker() != nullptr);
|
||||
|
||||
const auto documentId = document->id;
|
||||
const auto i = _customEmoji.find(documentId);
|
||||
const auto i = _customEmoji.find(id);
|
||||
const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly;
|
||||
if (i != end(_customEmoji) && !recentOnly) {
|
||||
return i->second.emoji.get();
|
||||
}
|
||||
auto instance = document->owner().customEmojiManager().create(
|
||||
document,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
repaintCallback(documentId, setId),
|
||||
Data::CustomEmojiManager::SizeTag::Large);
|
||||
if (recentOnly) {
|
||||
|
@ -2362,7 +2408,7 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
|
|||
return i->second.emoji.get();
|
||||
}
|
||||
return _customEmoji.emplace(
|
||||
documentId,
|
||||
id,
|
||||
CustomEmojiInstance{ .emoji = std::move(instance) }
|
||||
).first->second.emoji.get();
|
||||
}
|
||||
|
@ -2380,31 +2426,78 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent(
|
|||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
|
||||
DocumentId documentId) {
|
||||
const auto i = _customRecent.find(documentId);
|
||||
return resolveCustomRecent(EmojiStatusId{ documentId });
|
||||
}
|
||||
|
||||
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
|
||||
EmojiStatusId id) {
|
||||
const auto i = id.collectible
|
||||
? end(_customRecent)
|
||||
: _customRecent.find(id.documentId);
|
||||
if (i != end(_customRecent)) {
|
||||
return i->second.get();
|
||||
}
|
||||
const auto j = _customEmoji.find(documentId);
|
||||
const auto j = _customEmoji.find(id);
|
||||
if (j != end(_customEmoji)) {
|
||||
return j->second.emoji.get();
|
||||
}
|
||||
const auto documentId = id.collectible
|
||||
? id.collectible->documentId
|
||||
: id.documentId;
|
||||
auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId());
|
||||
if (_customRecentFactory) {
|
||||
if (_customRecentFactory && !id.collectible) {
|
||||
return _customRecent.emplace(
|
||||
documentId,
|
||||
_customRecentFactory(documentId, std::move(repaint))
|
||||
id.documentId,
|
||||
_customRecentFactory(id.documentId, std::move(repaint))
|
||||
).first->second.get();
|
||||
}
|
||||
auto custom = session().data().customEmojiManager().create(
|
||||
documentId,
|
||||
Data::EmojiStatusCustomId(id),
|
||||
std::move(repaint),
|
||||
Data::CustomEmojiManager::SizeTag::Large);
|
||||
return _customEmoji.emplace(
|
||||
documentId,
|
||||
id,
|
||||
CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true }
|
||||
).first->second.emoji.get();
|
||||
}
|
||||
|
||||
void EmojiListWidget::refreshEmojiStatusCollectibles() {
|
||||
if (_mode != Mode::EmojiStatus || !_features.collectibleStatus) {
|
||||
return;
|
||||
}
|
||||
const auto type = Data::EmojiStatuses::Type::Collectibles;
|
||||
const auto &list = session().data().emojiStatuses().list(type);
|
||||
const auto setId = Data::Stickers::CollectibleSetId;
|
||||
auto set = std::vector<CustomOne>();
|
||||
set.reserve(list.size());
|
||||
for (const auto &status : list) {
|
||||
const auto documentId = status.collectible
|
||||
? status.collectible->documentId
|
||||
: status.documentId;
|
||||
const auto document = session().data().document(documentId);
|
||||
const auto sticker = document->sticker();
|
||||
set.push_back({
|
||||
.collectible = status.collectible,
|
||||
.custom = resolveCustomEmoji(status, document, setId),
|
||||
.document = document,
|
||||
.emoji = sticker ? Ui::Emoji::Find(sticker->alt) : nullptr,
|
||||
});
|
||||
}
|
||||
if (set.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto collectibles = session().data().stickers().collectibleSet();
|
||||
_custom.push_back({
|
||||
.id = setId,
|
||||
.set = collectibles,
|
||||
.thumbnailDocument = nullptr,
|
||||
.title = collectibles->title,
|
||||
.list = std::move(set),
|
||||
.canRemove = false,
|
||||
.premiumRequired = !session().premium(),
|
||||
});
|
||||
}
|
||||
|
||||
void EmojiListWidget::refreshMegagroupStickers(
|
||||
Fn<void(uint64 setId, bool installed)> push,
|
||||
GroupStickersPlace place) {
|
||||
|
@ -2639,8 +2732,11 @@ void EmojiListWidget::setSelected(OverState newSelected) {
|
|||
} else if (_previewShown && _pressed != _selected) {
|
||||
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
|
||||
if (const auto custom = lookupCustomEmoji(over)) {
|
||||
const auto document = custom.document;
|
||||
_pressed = _selected;
|
||||
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
|
||||
_show->showMediaPreview(
|
||||
document->stickerSetOrigin(),
|
||||
document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,12 +83,15 @@ enum class EmojiListMode {
|
|||
MessageEffects,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<EmojiStatusId> DocumentListToRecent(
|
||||
const std::vector<DocumentId> &documents);
|
||||
|
||||
struct EmojiListDescriptor {
|
||||
std::shared_ptr<Show> show;
|
||||
EmojiListMode mode = EmojiListMode::Full;
|
||||
Fn<QColor()> customTextColor;
|
||||
Fn<bool()> paused;
|
||||
std::vector<DocumentId> customRecentList;
|
||||
std::vector<EmojiStatusId> customRecentList;
|
||||
Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
|
||||
DocumentId,
|
||||
Fn<void()>)> customRecentFactory;
|
||||
|
@ -137,7 +140,7 @@ public:
|
|||
[[nodiscard]] rpl::producer<> jumpedToPremium() const;
|
||||
[[nodiscard]] rpl::producer<> escapes() const;
|
||||
|
||||
void provideRecent(const std::vector<DocumentId> &customRecentList);
|
||||
void provideRecent(const std::vector<EmojiStatusId> &customRecentList);
|
||||
|
||||
void prepareExpanding();
|
||||
void paintExpanding(
|
||||
|
@ -186,6 +189,7 @@ private:
|
|||
bool collapsed = false;
|
||||
};
|
||||
struct CustomOne {
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
not_null<Ui::Text::CustomEmoji*> custom;
|
||||
not_null<DocumentData*> document;
|
||||
EmojiPtr emoji = nullptr;
|
||||
|
@ -253,6 +257,14 @@ private:
|
|||
int finalHeight = 0;
|
||||
bool expanding = false;
|
||||
};
|
||||
struct ResolvedCustom {
|
||||
DocumentData *document = nullptr;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
|
||||
explicit operator bool() const {
|
||||
return document != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
bool enumerateSections(Callback callback) const;
|
||||
|
@ -271,6 +283,7 @@ private:
|
|||
Visible,
|
||||
Hidden,
|
||||
};
|
||||
void refreshEmojiStatusCollectibles();
|
||||
void refreshMegagroupStickers(
|
||||
Fn<void(uint64 setId, bool installed)> push,
|
||||
GroupStickersPlace place);
|
||||
|
@ -296,16 +309,16 @@ private:
|
|||
int index);
|
||||
|
||||
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
|
||||
[[nodiscard]] DocumentData *lookupCustomEmoji(
|
||||
[[nodiscard]] ResolvedCustom lookupCustomEmoji(
|
||||
const OverEmoji *over) const;
|
||||
[[nodiscard]] DocumentData *lookupCustomEmoji(
|
||||
[[nodiscard]] ResolvedCustom lookupCustomEmoji(
|
||||
int index,
|
||||
int section) const;
|
||||
[[nodiscard]] EmojiChosen lookupChosen(
|
||||
EmojiPtr emoji,
|
||||
not_null<const OverEmoji*> over);
|
||||
[[nodiscard]] FileChosen lookupChosen(
|
||||
not_null<DocumentData*> custom,
|
||||
ResolvedCustom custom,
|
||||
const OverEmoji *over,
|
||||
Api::SendOptions options = Api::SendOptions());
|
||||
void selectEmoji(EmojiChosen data);
|
||||
|
@ -370,14 +383,17 @@ private:
|
|||
void repaintCustom(uint64 setId);
|
||||
|
||||
void fillRecent();
|
||||
void fillRecentFrom(const std::vector<DocumentId> &list);
|
||||
void fillRecentFrom(const std::vector<EmojiStatusId> &list);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
|
||||
EmojiStatusId id,
|
||||
not_null<DocumentData*> document,
|
||||
uint64 setId);
|
||||
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent(
|
||||
Core::RecentEmojiId customId);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
|
||||
DocumentId documentId);
|
||||
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
|
||||
EmojiStatusId id);
|
||||
[[nodiscard]] Fn<void()> repaintCallback(
|
||||
DocumentId documentId,
|
||||
uint64 setId);
|
||||
|
@ -415,7 +431,7 @@ private:
|
|||
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
|
||||
std::vector<CustomSet> _custom;
|
||||
base::flat_set<DocumentId> _restrictedCustomList;
|
||||
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<EmojiStatusId, CustomEmojiInstance> _customEmoji;
|
||||
base::flat_map<
|
||||
DocumentId,
|
||||
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;
|
||||
|
|
|
@ -123,9 +123,9 @@ constexpr auto kLinkProtocols = {
|
|||
void EditLinkBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
const QString &startText,
|
||||
const TextWithTags &startText,
|
||||
const QString &startLink,
|
||||
Fn<void(QString, QString)> callback,
|
||||
Fn<void(TextWithTags, QString)> callback,
|
||||
const style::InputField *fieldStyle,
|
||||
Fn<QString(QString)> validate) {
|
||||
Expects(callback != nullptr);
|
||||
|
@ -137,6 +137,7 @@ void EditLinkBox(
|
|||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
fieldSt,
|
||||
Ui::InputField::Mode::SingleLine,
|
||||
tr::lng_formatting_link_text(),
|
||||
startText),
|
||||
st::markdownLinkFieldPadding);
|
||||
|
@ -181,9 +182,9 @@ void EditLinkBox(
|
|||
url->move(placeholder->pos());
|
||||
|
||||
const auto submit = [=] {
|
||||
const auto linkText = text->getLastText();
|
||||
const auto linkText = text->getTextWithTags();
|
||||
const auto linkUrl = validate(url->getLastText());
|
||||
if (linkText.isEmpty()) {
|
||||
if (linkText.text.isEmpty()) {
|
||||
text->showError();
|
||||
return;
|
||||
} else if (linkUrl.isEmpty()) {
|
||||
|
@ -222,7 +223,7 @@ void EditLinkBox(
|
|||
box->setWidth(st::boxWidth);
|
||||
|
||||
box->setFocusCallback([=] {
|
||||
if (startText.isEmpty()) {
|
||||
if (startText.text.isEmpty()) {
|
||||
text->setFocusFast();
|
||||
} else {
|
||||
if (!url->empty()) {
|
||||
|
@ -383,7 +384,7 @@ bool EditTextChanged(
|
|||
|
||||
Fn<bool(
|
||||
Ui::InputField::EditLinkSelection selection,
|
||||
QString text,
|
||||
TextWithTags text,
|
||||
QString link,
|
||||
EditLinkAction action)> DefaultEditLinkCallback(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
|
@ -392,14 +393,14 @@ Fn<bool(
|
|||
const auto weak = Ui::MakeWeak(field);
|
||||
return [=](
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
TextWithTags text,
|
||||
QString link,
|
||||
EditLinkAction action) {
|
||||
if (action == EditLinkAction::Check) {
|
||||
return Ui::InputField::IsValidMarkdownLink(link)
|
||||
&& !TextUtilities::IsMentionLink(link);
|
||||
}
|
||||
auto callback = [=](const QString &text, const QString &link) {
|
||||
auto callback = [=](const TextWithTags &text, const QString &link) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->commitMarkdownLinkEdit(selection, text, link);
|
||||
}
|
||||
|
@ -470,7 +471,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
|||
|
||||
[[nodiscard]] Fn<bool(
|
||||
Ui::InputField::EditLinkSelection selection,
|
||||
QString text,
|
||||
TextWithTags text,
|
||||
QString link,
|
||||
EditLinkAction action)> FactcheckEditLinkCallback(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
|
@ -478,7 +479,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
|||
const auto weak = Ui::MakeWeak(field);
|
||||
return [=](
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
TextWithTags text,
|
||||
QString link,
|
||||
EditLinkAction action) {
|
||||
const auto validate = [=](QString url) {
|
||||
|
@ -493,7 +494,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
|
|||
if (action == EditLinkAction::Check) {
|
||||
return IsGoodFactcheckUrl(link);
|
||||
}
|
||||
auto callback = [=](const QString &text, const QString &link) {
|
||||
auto callback = [=](const TextWithTags &text, const QString &link) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->commitMarkdownLinkEdit(selection, text, link);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class Show;
|
|||
|
||||
Fn<bool(
|
||||
Ui::InputField::EditLinkSelection selection,
|
||||
QString text,
|
||||
TextWithTags text,
|
||||
QString link,
|
||||
Ui::InputField::EditLinkAction action)> DefaultEditLinkCallback(
|
||||
std::shared_ptr<Main::SessionShow> show,
|
||||
|
|
|
@ -1467,6 +1467,8 @@ void StickersListFooter::paintSetIconToCache(
|
|||
return &st().icons.people;
|
||||
} else if (const auto section = SetIdEmojiSection(icon.setId)) {
|
||||
return sectionIcon(*section, selected);
|
||||
} else if (icon.setId == Data::Stickers::CollectibleSetId) {
|
||||
return &st().icons.collectibles;
|
||||
}
|
||||
return sectionIcon(Section::Recent, selected);
|
||||
}());
|
||||
|
|
|
@ -245,8 +245,7 @@ StickersListWidget::StickersListWidget(
|
|||
}
|
||||
|
||||
_settings->addClickHandler([=] {
|
||||
if (const auto window = _show->resolveWindow(
|
||||
WindowUsage::PremiumPromo)) {
|
||||
if (const auto window = _show->resolveWindow()) {
|
||||
// While media viewer can't show StickersBox.
|
||||
using Section = StickersBox::Section;
|
||||
window->show(
|
||||
|
|
|
@ -294,7 +294,11 @@ void TabbedPanel::otherLeave() {
|
|||
if (_a_opacity.animating()) {
|
||||
hideByTimerOrLeave();
|
||||
} else {
|
||||
_hideTimer.callOnce(0);
|
||||
// In case of animations disabled add some delay before hiding.
|
||||
// Otherwise if emoji suggestions panel is shown in between
|
||||
// (z-order wise) the emoji toggle button and tabbed panel,
|
||||
// we won't be able to move cursor from the button to the panel.
|
||||
_hideTimer.callOnce(anim::Disabled() ? kHideTimeoutMs : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1018,7 +1018,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
|
|||
}
|
||||
|
||||
void TabbedSelector::provideRecentEmoji(
|
||||
const std::vector<DocumentId> &customRecentList) {
|
||||
const std::vector<EmojiStatusId> &customRecentList) {
|
||||
for (const auto &tab : _tabs) {
|
||||
if (tab.type() == SelectorTab::Emoji) {
|
||||
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
|
||||
|
@ -1062,8 +1062,7 @@ void TabbedSelector::checkRestrictedPeer() {
|
|||
st::stickersRestrictedLabel);
|
||||
const auto lifting = error.boostsToLift;
|
||||
_restrictedLabel->setClickHandlerFilter([=](auto...) {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto window = show->resolveWindow();
|
||||
window->resolveBoostState(peer->asChannel(), lifting);
|
||||
return false;
|
||||
});
|
||||
|
|
|
@ -62,6 +62,7 @@ struct FileChosen {
|
|||
not_null<DocumentData*> document;
|
||||
Api::SendOptions options;
|
||||
Ui::MessageSendingAnimationFrom messageSendingFrom;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
TextWithTags caption;
|
||||
};
|
||||
|
||||
|
@ -154,7 +155,7 @@ public:
|
|||
void refreshStickers();
|
||||
void setCurrentPeer(PeerData *peer);
|
||||
void provideRecentEmoji(
|
||||
const std::vector<DocumentId> &customRecentList);
|
||||
const std::vector<EmojiStatusId> &customRecentList);
|
||||
|
||||
void hideFinished();
|
||||
void showStarted();
|
||||
|
|
|
@ -341,7 +341,7 @@ QByteArray Settings::serialize() const {
|
|||
<< _photoEditorBrush
|
||||
<< qint32(_groupCallNoiseSuppression ? 1 : 0)
|
||||
<< qint32(SerializePlaybackSpeed(_voicePlaybackSpeed))
|
||||
<< qint32(_closeToTaskbar.current() ? 1 : 0)
|
||||
<< qint32(_closeBehavior)
|
||||
<< _customDeviceModel.current()
|
||||
<< qint32(_playerRepeatMode.current())
|
||||
<< qint32(_playerOrderMode.current())
|
||||
|
@ -499,7 +499,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
QByteArray proxy;
|
||||
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
|
||||
QByteArray photoEditorBrush = _photoEditorBrush;
|
||||
qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0;
|
||||
qint32 closeBehavior = qint32(_closeBehavior);
|
||||
QString customDeviceModel = _customDeviceModel.current();
|
||||
qint32 playerRepeatMode = static_cast<qint32>(_playerRepeatMode.current());
|
||||
qint32 playerOrderMode = static_cast<qint32>(_playerOrderMode.current());
|
||||
|
@ -695,7 +695,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
stream >> voicePlaybackSpeed;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> closeToTaskbar;
|
||||
stream >> closeBehavior;
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
stream >> customDeviceModel;
|
||||
|
@ -1005,7 +1005,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
|||
: Tooltip(0));
|
||||
}();
|
||||
_photoEditorBrush = photoEditorBrush;
|
||||
_closeToTaskbar = (closeToTaskbar == 1);
|
||||
const auto uncheckedCloseBehavior = static_cast<CloseBehavior>(closeBehavior);
|
||||
switch (uncheckedCloseBehavior) {
|
||||
case CloseBehavior::CloseToTaskbar:
|
||||
case CloseBehavior::RunInBackground:
|
||||
case CloseBehavior::Quit: _closeBehavior = uncheckedCloseBehavior; break;
|
||||
}
|
||||
_customDeviceModel = customDeviceModel;
|
||||
_accountsOrder = accountsOrder;
|
||||
const auto uncheckedPlayerRepeatMode = static_cast<Media::RepeatMode>(playerRepeatMode);
|
||||
|
|
|
@ -110,6 +110,11 @@ public:
|
|||
TrayOnly = 1,
|
||||
WindowOnly = 2,
|
||||
};
|
||||
enum class CloseBehavior {
|
||||
Quit = 0,
|
||||
CloseToTaskbar = 1,
|
||||
RunInBackground = 2,
|
||||
};
|
||||
|
||||
static constexpr auto kDefaultVolume = 0.9;
|
||||
|
||||
|
@ -751,17 +756,11 @@ public:
|
|||
_hiddenGroupCallTooltips |= value;
|
||||
}
|
||||
|
||||
void setCloseToTaskbar(bool value) {
|
||||
_closeToTaskbar = value;
|
||||
void setCloseBehavior(CloseBehavior value) {
|
||||
_closeBehavior = value;
|
||||
}
|
||||
[[nodiscard]] bool closeToTaskbar() const {
|
||||
return _closeToTaskbar.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarValue() const {
|
||||
return _closeToTaskbar.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
||||
return _closeToTaskbar.changes();
|
||||
[[nodiscard]] CloseBehavior closeBehavior() const {
|
||||
return _closeBehavior;
|
||||
}
|
||||
void setTrayIconMonochrome(bool value) {
|
||||
_trayIconMonochrome = value;
|
||||
|
@ -1048,7 +1047,7 @@ private:
|
|||
bool _disableOpenGL = false;
|
||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
rpl::variable<bool> _closeToTaskbar = false;
|
||||
CloseBehavior _closeBehavior = CloseBehavior::Quit;
|
||||
rpl::variable<bool> _trayIconMonochrome = true;
|
||||
rpl::variable<QString> _customDeviceModel;
|
||||
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
||||
|
|
|
@ -1348,6 +1348,21 @@ bool ResolveChatLink(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ResolveUniqueGift(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto slug = match->captured(1);
|
||||
if (slug.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
ResolveAndShowUniqueGift(controller->uiShow(), slug);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
|
@ -1440,6 +1455,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
|||
u"^stars_topup/?\\?(.+)(#|$)"_q,
|
||||
ResolveTopUp
|
||||
},
|
||||
{
|
||||
u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
|
||||
ResolveUniqueGift
|
||||
},
|
||||
{
|
||||
u"^user\\?(.+)(#|$)"_q,
|
||||
AyuUrlHandlers::ResolveUser
|
||||
|
@ -1597,6 +1616,9 @@ QString TryConvertUrlToLocal(QString url) {
|
|||
} else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
|
||||
const auto slug = chatlinkMatch->captured(1);
|
||||
return u"tg://message?slug="_q + slug;
|
||||
} else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
|
||||
const auto slug = nftMatch->captured(1);
|
||||
return u"tg://nft?slug="_q + slug;
|
||||
} else if (const auto privateMatch = regex_match(u"^"
|
||||
"c/(\\-?\\d+)"
|
||||
"("
|
||||
|
@ -1690,4 +1712,51 @@ bool StartUrlRequiresActivate(const QString &url) {
|
|||
: !InternalPassportLink(url);
|
||||
}
|
||||
|
||||
void ResolveAndShowUniqueGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const QString &slug,
|
||||
::Settings::CreditsEntryBoxStyleOverrides st) {
|
||||
struct Request {
|
||||
base::weak_ptr<Main::Session> weak;
|
||||
QString slug;
|
||||
mtpRequestId id = 0;
|
||||
};
|
||||
static auto request = Request();
|
||||
|
||||
const auto session = &show->session();
|
||||
if (request.weak.get() == session && request.slug == slug) {
|
||||
return;
|
||||
} else if (const auto strong = request.weak.get()) {
|
||||
strong->api().request(request.id).cancel();
|
||||
}
|
||||
request.weak = session;
|
||||
request.slug = slug;
|
||||
const auto clear = [=] {
|
||||
if (request.weak.get() == session && request.slug == slug) {
|
||||
request = {};
|
||||
}
|
||||
};
|
||||
request.id = session->api().request(
|
||||
MTPpayments_GetUniqueStarGift(MTP_string(slug))
|
||||
).done([=](const MTPpayments_UniqueStarGift &result) {
|
||||
clear();
|
||||
|
||||
const auto &data = result.data();
|
||||
session->data().processUsers(data.vusers());
|
||||
if (const auto gift = Api::FromTL(session, data.vgift())) {
|
||||
using namespace ::Settings;
|
||||
show->show(Box(GlobalStarGiftBox, show, *gift, st));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
clear();
|
||||
show->showToast(u"Error: "_q + error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ResolveAndShowUniqueGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const QString &slug) {
|
||||
ResolveAndShowUniqueGift(std::move(show), slug, {});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -11,6 +11,14 @@ namespace qthelp {
|
|||
class RegularExpressionMatch;
|
||||
} // namespace qthelp
|
||||
|
||||
namespace ChatHelpers {
|
||||
class Show;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Settings {
|
||||
struct CreditsEntryBoxStyleOverrides;
|
||||
} // namespace Settings
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
@ -34,4 +42,12 @@ struct LocalUrlHandler {
|
|||
|
||||
[[nodiscard]] bool StartUrlRequiresActivate(const QString &url);
|
||||
|
||||
void ResolveAndShowUniqueGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const QString &slug,
|
||||
::Settings::CreditsEntryBoxStyleOverrides st);
|
||||
void ResolveAndShowUniqueGift(
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
const QString &slug);
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -100,6 +100,8 @@ const auto CommandByName = base::flat_map<QString, Command>{
|
|||
|
||||
{ u"read_chat"_q , Command::ReadChat },
|
||||
|
||||
{ u"show_chat_menu"_q , Command::ShowChatMenu },
|
||||
|
||||
// Shortcuts that have no default values.
|
||||
{ u"message"_q , Command::JustSendMessage },
|
||||
{ u"message_silently"_q , Command::SendSilentMessage },
|
||||
|
@ -147,6 +149,8 @@ const auto CommandNames = base::flat_map<Command, QString>{
|
|||
{ Command::ShowContacts , u"show_contacts"_q },
|
||||
|
||||
{ Command::ReadChat , u"read_chat"_q },
|
||||
|
||||
{ Command::ShowChatMenu , u"show_chat_menu"_q },
|
||||
};
|
||||
|
||||
[[maybe_unused]] constexpr auto kNoValue = {
|
||||
|
@ -435,6 +439,8 @@ void Manager::fillDefaults() {
|
|||
set(u"ctrl+j"_q, Command::ShowContacts);
|
||||
|
||||
set(u"ctrl+r"_q, Command::ReadChat);
|
||||
|
||||
set(u"ctrl+="_q, Command::ShowChatMenu);
|
||||
}
|
||||
|
||||
void Manager::writeDefaultFile() {
|
||||
|
|
|
@ -71,6 +71,8 @@ enum class Command {
|
|||
|
||||
MediaViewerFullscreen,
|
||||
|
||||
ShowChatMenu,
|
||||
|
||||
SupportReloadTemplates,
|
||||
SupportToggleMuted,
|
||||
SupportScrollToCurrent,
|
||||
|
|
|
@ -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 = 5010003;
|
||||
constexpr auto AppVersionStr = "5.10.3";
|
||||
constexpr auto AppVersion = 5010007;
|
||||
constexpr auto AppVersionStr = "5.10.7";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
|
|
@ -859,6 +859,17 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
|
|||
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
|
||||
}
|
||||
|
||||
int ChannelData::peerGiftsCount() const {
|
||||
return _peerGiftsCount;
|
||||
}
|
||||
|
||||
void ChannelData::setPeerGiftsCount(int count) {
|
||||
if (_peerGiftsCount != count) {
|
||||
_peerGiftsCount = count;
|
||||
session().changes().peerUpdated(this, UpdateFlag::PeerGifts);
|
||||
}
|
||||
}
|
||||
|
||||
int ChannelData::boostsApplied() const {
|
||||
if (const auto info = mgInfo.get()) {
|
||||
return info->boostsApplied;
|
||||
|
@ -1138,7 +1149,8 @@ void ApplyChannelUpdate(
|
|||
| Flag::ViewAsMessages
|
||||
| Flag::CanViewRevenue
|
||||
| Flag::PaidMediaAllowed
|
||||
| Flag::CanViewCreditsRevenue;
|
||||
| Flag::CanViewCreditsRevenue
|
||||
| Flag::StargiftsAvailable;
|
||||
channel->setFlags((channel->flags() & ~mask)
|
||||
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
|
||||
| (update.is_can_view_participants()
|
||||
|
@ -1159,6 +1171,9 @@ void ApplyChannelUpdate(
|
|||
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())
|
||||
| (update.is_can_view_stars_revenue()
|
||||
? Flag::CanViewCreditsRevenue
|
||||
: Flag())
|
||||
| (update.is_stargifts_available()
|
||||
? Flag::StargiftsAvailable
|
||||
: Flag()));
|
||||
channel->setUserpicPhoto(update.vchat_photo());
|
||||
if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
|
||||
|
@ -1172,6 +1187,7 @@ void ApplyChannelUpdate(
|
|||
channel->setRestrictedCount(update.vbanned_count().value_or_empty());
|
||||
channel->setKickedCount(update.vkicked_count().value_or_empty());
|
||||
channel->setSlowmodeSeconds(update.vslowmode_seconds().value_or_empty());
|
||||
channel->setPeerGiftsCount(update.vstargifts_count().value_or_empty());
|
||||
if (const auto next = update.vslowmode_next_send_date()) {
|
||||
channel->growSlowmodeLastMessage(
|
||||
next->v - channel->slowmodeSeconds());
|
||||
|
|
|
@ -69,6 +69,7 @@ enum class ChannelDataFlag : uint64 {
|
|||
PaidMediaAllowed = (1ULL << 33),
|
||||
CanViewCreditsRevenue = (1ULL << 34),
|
||||
SignatureProfiles = (1ULL << 35),
|
||||
StargiftsAvailable = (1ULL << 36),
|
||||
};
|
||||
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
|
||||
using ChannelDataFlags = base::flags<ChannelDataFlag>;
|
||||
|
@ -253,6 +254,9 @@ public:
|
|||
[[nodiscard]] bool viewForumAsMessages() const {
|
||||
return flags() & Flag::ViewAsMessages;
|
||||
}
|
||||
[[nodiscard]] bool stargiftsAvailable() const {
|
||||
return flags() & Flag::StargiftsAvailable;
|
||||
}
|
||||
|
||||
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
|
||||
not_null<PeerData*> participant);
|
||||
|
@ -452,6 +456,9 @@ public:
|
|||
[[nodiscard]] TimeId slowmodeLastMessage() const;
|
||||
void growSlowmodeLastMessage(TimeId when);
|
||||
|
||||
[[nodiscard]] int peerGiftsCount() const;
|
||||
void setPeerGiftsCount(int count);
|
||||
|
||||
[[nodiscard]] int boostsApplied() const;
|
||||
[[nodiscard]] int boostsUnrestrict() const;
|
||||
[[nodiscard]] bool unrestrictedByBoosts() const;
|
||||
|
@ -522,6 +529,7 @@ private:
|
|||
std::vector<Data::UnavailableReason> &&reasons) override;
|
||||
|
||||
Flags _flags = ChannelDataFlags(Flag::Forbidden);
|
||||
int _peerGiftsCount = 0;
|
||||
|
||||
PtsWaiter _ptsWaiter;
|
||||
|
||||
|
|
|
@ -416,8 +416,7 @@ void ShowSendErrorToast(
|
|||
return;
|
||||
}
|
||||
const auto boost = [=] {
|
||||
const auto window = show->resolveWindow(
|
||||
ChatHelpers::WindowUsage::PremiumPromo);
|
||||
const auto window = show->resolveWindow();
|
||||
window->resolveBoostState(peer->asChannel(), error.boostsToLift);
|
||||
};
|
||||
show->showToast({
|
||||
|
|
|
@ -66,6 +66,8 @@ struct CreditsHistoryEntry final {
|
|||
uint64 bareGiftStickerId = 0;
|
||||
uint64 bareGiftOwnerId = 0;
|
||||
uint64 bareActorId = 0;
|
||||
uint64 bareGiftListPeerId = 0;
|
||||
uint64 giftSavedId = 0;
|
||||
uint64 stargiftId = 0;
|
||||
std::shared_ptr<UniqueGift> uniqueGift;
|
||||
StarsAmount starrefAmount;
|
||||
|
@ -89,6 +91,7 @@ struct CreditsHistoryEntry final {
|
|||
bool giftUpgraded : 1 = false;
|
||||
bool savedToProfile : 1 = false;
|
||||
bool fromGiftsList : 1 = false;
|
||||
bool fromGiftSlug : 1 = false;
|
||||
bool soldOutInfo : 1 = false;
|
||||
bool canUpgradeGift : 1 = false;
|
||||
bool hasGiftComment : 1 = false;
|
||||
|
|
|
@ -11,7 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_wall_paper.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/timer_rpl.h"
|
||||
|
@ -26,20 +28,19 @@ constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000);
|
|||
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
|
||||
constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] std::vector<DocumentId> ListFromMTP(
|
||||
const MTPDaccount_emojiStatuses &data) {
|
||||
const auto &list = data.vstatuses().v;
|
||||
auto result = std::vector<DocumentId>();
|
||||
result.reserve(list.size());
|
||||
for (const auto &status : list) {
|
||||
const auto parsed = ParseEmojiStatus(status);
|
||||
if (!parsed.id) {
|
||||
LOG(("API Error: emojiStatusEmpty in account.emojiStatuses."));
|
||||
} else {
|
||||
result.push_back(parsed.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
[[nodiscard]] EmojiStatusCollectible ParseEmojiStatusCollectible(
|
||||
const MTPDemojiStatusCollectible &data) {
|
||||
return EmojiStatusCollectible{
|
||||
.id = data.vcollectible_id().v,
|
||||
.documentId = data.vdocument_id().v,
|
||||
.title = qs(data.vtitle()),
|
||||
.slug = qs(data.vslug()),
|
||||
.patternDocumentId = data.vpattern_document_id().v,
|
||||
.centerColor = Ui::ColorFromSerialized(data.vcenter_color()),
|
||||
.edgeColor = Ui::ColorFromSerialized(data.vedge_color()),
|
||||
.patternColor = Ui::ColorFromSerialized(data.vpattern_color()),
|
||||
.textColor = Ui::ColorFromSerialized(data.vtext_color()),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -84,6 +85,10 @@ void EmojiStatuses::refreshChannelColored() {
|
|||
requestChannelColored();
|
||||
}
|
||||
|
||||
void EmojiStatuses::refreshCollectibles() {
|
||||
requestCollectibles();
|
||||
}
|
||||
|
||||
void EmojiStatuses::refreshRecentDelayed() {
|
||||
if (_recentRequestId || _recentRequestScheduled) {
|
||||
return;
|
||||
|
@ -96,17 +101,42 @@ void EmojiStatuses::refreshRecentDelayed() {
|
|||
});
|
||||
}
|
||||
|
||||
const std::vector<DocumentId> &EmojiStatuses::list(Type type) const {
|
||||
const std::vector<EmojiStatusId> &EmojiStatuses::list(Type type) const {
|
||||
switch (type) {
|
||||
case Type::Recent: return _recent;
|
||||
case Type::Default: return _default;
|
||||
case Type::Colored: return _colored;
|
||||
case Type::ChannelDefault: return _channelDefault;
|
||||
case Type::ChannelColored: return _channelColored;
|
||||
case Type::Collectibles: return _collectibles;
|
||||
}
|
||||
Unexpected("Type in EmojiStatuses::list.");
|
||||
}
|
||||
|
||||
EmojiStatusData EmojiStatuses::parse(const MTPEmojiStatus &status) {
|
||||
return status.match([](const MTPDemojiStatus &data) {
|
||||
return EmojiStatusData{
|
||||
.id = { .documentId = data.vdocument_id().v },
|
||||
.until = data.vuntil().value_or_empty(),
|
||||
};
|
||||
}, [&](const MTPDemojiStatusCollectible &data) {
|
||||
const auto collectibleId = data.vcollectible_id().v;
|
||||
auto &collectible = _collectibleData[collectibleId];
|
||||
if (!collectible) {
|
||||
collectible = std::make_shared<EmojiStatusCollectible>(
|
||||
ParseEmojiStatusCollectible(data));
|
||||
}
|
||||
return EmojiStatusData{
|
||||
.id = { .collectible = collectible },
|
||||
.until = data.vuntil().value_or_empty(),
|
||||
};
|
||||
}, [](const MTPDinputEmojiStatusCollectible &) {
|
||||
return EmojiStatusData();
|
||||
}, [](const MTPDemojiStatusEmpty &) {
|
||||
return EmojiStatusData();
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<> EmojiStatuses::recentUpdates() const {
|
||||
return _recentUpdated.events();
|
||||
}
|
||||
|
@ -119,6 +149,10 @@ rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
|
|||
return _channelDefaultUpdated.events();
|
||||
}
|
||||
|
||||
rpl::producer<> EmojiStatuses::collectiblesUpdates() const {
|
||||
return _collectiblesUpdated.events();
|
||||
}
|
||||
|
||||
void EmojiStatuses::registerAutomaticClear(
|
||||
not_null<PeerData*> peer,
|
||||
TimeId until) {
|
||||
|
@ -253,7 +287,7 @@ void EmojiStatuses::processClearing() {
|
|||
}
|
||||
++i;
|
||||
} else {
|
||||
i->first->setEmojiStatus(0, 0);
|
||||
i->first->setEmojiStatus(EmojiStatusId());
|
||||
i = clearing.erase(i);
|
||||
}
|
||||
}
|
||||
|
@ -271,6 +305,22 @@ void EmojiStatuses::processClearing() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<EmojiStatusId> EmojiStatuses::parse(
|
||||
const MTPDaccount_emojiStatuses &data) {
|
||||
const auto &list = data.vstatuses().v;
|
||||
auto result = std::vector<EmojiStatusId>();
|
||||
result.reserve(list.size());
|
||||
for (const auto &status : list) {
|
||||
const auto parsed = parse(status);
|
||||
if (!parsed.id) {
|
||||
LOG(("API Error: empty status in account.emojiStatuses."));
|
||||
} else {
|
||||
result.push_back(parsed.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void EmojiStatuses::processClearingIn(TimeId wait) {
|
||||
const auto waitms = wait * crl::time(1000);
|
||||
_clearingTimer.callOnce(std::min(waitms, kMaxTimeout));
|
||||
|
@ -327,6 +377,7 @@ void EmojiStatuses::requestColored() {
|
|||
_coloredRequestId = 0;
|
||||
result.match([&](const MTPDmessages_stickerSet &data) {
|
||||
updateColored(data);
|
||||
refreshCollectibles();
|
||||
}, [](const MTPDmessages_stickerSetNotModified &) {
|
||||
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
|
||||
});
|
||||
|
@ -374,15 +425,34 @@ void EmojiStatuses::requestChannelColored() {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void EmojiStatuses::requestCollectibles() {
|
||||
if (_collectiblesRequestId) {
|
||||
return;
|
||||
}
|
||||
auto &api = _owner->session().api();
|
||||
_collectiblesRequestId = api.request(
|
||||
MTPaccount_GetCollectibleEmojiStatuses(MTP_long(_collectiblesHash))
|
||||
).done([=](const MTPaccount_EmojiStatuses &result) {
|
||||
_collectiblesRequestId = 0;
|
||||
result.match([&](const MTPDaccount_emojiStatuses &data) {
|
||||
updateCollectibles(data);
|
||||
}, [&](const MTPDaccount_emojiStatusesNotModified &) {
|
||||
});
|
||||
}).fail([=] {
|
||||
_collectiblesRequestId = 0;
|
||||
_collectiblesHash = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
|
||||
_recentHash = data.vhash().v;
|
||||
_recent = ListFromMTP(data);
|
||||
_recent = parse(data);
|
||||
_recentUpdated.fire({});
|
||||
}
|
||||
|
||||
void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) {
|
||||
_defaultHash = data.vhash().v;
|
||||
_default = ListFromMTP(data);
|
||||
_default = parse(data);
|
||||
_defaultUpdated.fire({});
|
||||
}
|
||||
|
||||
|
@ -391,7 +461,9 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
|
|||
_colored.clear();
|
||||
_colored.reserve(list.size());
|
||||
for (const auto &sticker : data.vdocuments().v) {
|
||||
_colored.push_back(_owner->processDocument(sticker)->id);
|
||||
_colored.push_back({
|
||||
.documentId = _owner->processDocument(sticker)->id,
|
||||
});
|
||||
}
|
||||
_coloredUpdated.fire({});
|
||||
}
|
||||
|
@ -399,7 +471,7 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
|
|||
void EmojiStatuses::updateChannelDefault(
|
||||
const MTPDaccount_emojiStatuses &data) {
|
||||
_channelDefaultHash = data.vhash().v;
|
||||
_channelDefault = ListFromMTP(data);
|
||||
_channelDefault = parse(data);
|
||||
_channelDefaultUpdated.fire({});
|
||||
}
|
||||
|
||||
|
@ -409,18 +481,27 @@ void EmojiStatuses::updateChannelColored(
|
|||
_channelColored.clear();
|
||||
_channelColored.reserve(list.size());
|
||||
for (const auto &sticker : data.vdocuments().v) {
|
||||
_channelColored.push_back(_owner->processDocument(sticker)->id);
|
||||
_channelColored.push_back({
|
||||
.documentId = _owner->processDocument(sticker)->id,
|
||||
});
|
||||
}
|
||||
_channelColoredUpdated.fire({});
|
||||
}
|
||||
|
||||
void EmojiStatuses::set(DocumentId id, TimeId until) {
|
||||
void EmojiStatuses::updateCollectibles(
|
||||
const MTPDaccount_emojiStatuses &data) {
|
||||
_collectiblesHash = data.vhash().v;
|
||||
_collectibles = parse(data);
|
||||
_collectiblesUpdated.fire({});
|
||||
}
|
||||
|
||||
void EmojiStatuses::set(EmojiStatusId id, TimeId until) {
|
||||
set(_owner->session().user(), id, until);
|
||||
}
|
||||
|
||||
void EmojiStatuses::set(
|
||||
not_null<PeerData*> peer,
|
||||
DocumentId id,
|
||||
EmojiStatusId id,
|
||||
TimeId until) {
|
||||
auto &api = _owner->session().api();
|
||||
auto &requestId = _sentRequests[peer];
|
||||
|
@ -437,11 +518,19 @@ void EmojiStatuses::set(
|
|||
_sentRequests.remove(peer);
|
||||
}).send();
|
||||
};
|
||||
using EFlag = MTPDemojiStatus::Flag;
|
||||
using CFlag = MTPDinputEmojiStatusCollectible::Flag;
|
||||
const auto status = !id
|
||||
? MTP_emojiStatusEmpty()
|
||||
: !until
|
||||
? MTP_emojiStatus(MTP_long(id))
|
||||
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until));
|
||||
: id.collectible
|
||||
? MTP_inputEmojiStatusCollectible(
|
||||
MTP_flags(until ? CFlag::f_until : CFlag()),
|
||||
MTP_long(id.collectible->id),
|
||||
MTP_int(until))
|
||||
: MTP_emojiStatus(
|
||||
MTP_flags(until ? EFlag::f_until : EFlag()),
|
||||
MTP_long(id.documentId),
|
||||
MTP_int(until));
|
||||
if (peer->isSelf()) {
|
||||
send(MTPaccount_UpdateEmojiStatus(status));
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
|
@ -449,14 +538,30 @@ void EmojiStatuses::set(
|
|||
}
|
||||
}
|
||||
|
||||
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) {
|
||||
return status.match([](const MTPDemojiStatus &data) {
|
||||
return EmojiStatusData{ data.vdocument_id().v };
|
||||
}, [](const MTPDemojiStatusUntil &data) {
|
||||
return EmojiStatusData{ data.vdocument_id().v, data.vuntil().v };
|
||||
}, [](const MTPDemojiStatusEmpty &) {
|
||||
return EmojiStatusData();
|
||||
});
|
||||
EmojiStatusId EmojiStatuses::fromUniqueGift(
|
||||
const Data::UniqueGift &gift) {
|
||||
const auto collectibleId = gift.id;
|
||||
auto &collectible = _collectibleData[collectibleId];
|
||||
if (!collectible) {
|
||||
collectible = std::make_shared<EmojiStatusCollectible>(
|
||||
EmojiStatusCollectible{
|
||||
.id = gift.id,
|
||||
.documentId = gift.model.document->id,
|
||||
.title = Data::UniqueGiftName(gift),
|
||||
.slug = gift.slug,
|
||||
.patternDocumentId = gift.pattern.document->id,
|
||||
.centerColor = gift.backdrop.centerColor,
|
||||
.edgeColor = gift.backdrop.edgeColor,
|
||||
.patternColor = gift.backdrop.patternColor,
|
||||
.textColor = gift.backdrop.textColor,
|
||||
});
|
||||
}
|
||||
return { .collectible = collectible };
|
||||
}
|
||||
|
||||
EmojiStatusCollectible *EmojiStatuses::collectibleInfo(CollectibleId id) {
|
||||
const auto i = _collectibleData.find(id);
|
||||
return (i != end(_collectibleData)) ? i->second.get() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -21,6 +21,27 @@ namespace Data {
|
|||
|
||||
class DocumentMedia;
|
||||
class Session;
|
||||
struct UniqueGift;
|
||||
|
||||
struct EmojiStatusCollectible {
|
||||
CollectibleId id = 0;
|
||||
DocumentId documentId = 0;
|
||||
QString title;
|
||||
QString slug;
|
||||
DocumentId patternDocumentId = 0;
|
||||
QColor centerColor;
|
||||
QColor edgeColor;
|
||||
QColor patternColor;
|
||||
QColor textColor;
|
||||
|
||||
explicit operator bool() const {
|
||||
return id != 0;
|
||||
}
|
||||
};
|
||||
struct EmojiStatusData {
|
||||
EmojiStatusId id;
|
||||
TimeId until = 0;
|
||||
};
|
||||
|
||||
class EmojiStatuses final {
|
||||
public:
|
||||
|
@ -38,6 +59,7 @@ public:
|
|||
void refreshColored();
|
||||
void refreshChannelDefault();
|
||||
void refreshChannelColored();
|
||||
void refreshCollectibles();
|
||||
|
||||
enum class Type {
|
||||
Recent,
|
||||
|
@ -45,15 +67,21 @@ public:
|
|||
Colored,
|
||||
ChannelDefault,
|
||||
ChannelColored,
|
||||
Collectibles,
|
||||
};
|
||||
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
|
||||
[[nodiscard]] const std::vector<EmojiStatusId> &list(Type type) const;
|
||||
|
||||
[[nodiscard]] EmojiStatusData parse(const MTPEmojiStatus &status);
|
||||
|
||||
[[nodiscard]] rpl::producer<> recentUpdates() const;
|
||||
[[nodiscard]] rpl::producer<> defaultUpdates() const;
|
||||
[[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
|
||||
[[nodiscard]] rpl::producer<> collectiblesUpdates() const;
|
||||
|
||||
void set(DocumentId id, TimeId until = 0);
|
||||
void set(not_null<PeerData*> peer, DocumentId id, TimeId until = 0);
|
||||
void set(EmojiStatusId id, TimeId until = 0);
|
||||
void set(not_null<PeerData*> peer, EmojiStatusId id, TimeId until = 0);
|
||||
[[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift);
|
||||
[[nodiscard]] EmojiStatusCollectible *collectibleInfo(CollectibleId id);
|
||||
|
||||
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
|
||||
|
||||
|
@ -79,31 +107,42 @@ private:
|
|||
void requestColored();
|
||||
void requestChannelDefault();
|
||||
void requestChannelColored();
|
||||
void requestCollectibles();
|
||||
|
||||
void updateRecent(const MTPDaccount_emojiStatuses &data);
|
||||
void updateDefault(const MTPDaccount_emojiStatuses &data);
|
||||
void updateColored(const MTPDmessages_stickerSet &data);
|
||||
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
|
||||
void updateChannelColored(const MTPDmessages_stickerSet &data);
|
||||
void updateCollectibles(const MTPDaccount_emojiStatuses &data);
|
||||
|
||||
void processClearingIn(TimeId wait);
|
||||
void processClearing();
|
||||
|
||||
[[nodiscard]] std::vector<EmojiStatusId> parse(
|
||||
const MTPDaccount_emojiStatuses &data);
|
||||
|
||||
template <typename Request>
|
||||
void requestGroups(not_null<GroupsType*> type, Request &&request);
|
||||
|
||||
const not_null<Session*> _owner;
|
||||
|
||||
std::vector<DocumentId> _recent;
|
||||
std::vector<DocumentId> _default;
|
||||
std::vector<DocumentId> _colored;
|
||||
std::vector<DocumentId> _channelDefault;
|
||||
std::vector<DocumentId> _channelColored;
|
||||
std::vector<EmojiStatusId> _recent;
|
||||
std::vector<EmojiStatusId> _default;
|
||||
std::vector<EmojiStatusId> _colored;
|
||||
std::vector<EmojiStatusId> _channelDefault;
|
||||
std::vector<EmojiStatusId> _channelColored;
|
||||
std::vector<EmojiStatusId> _collectibles;
|
||||
rpl::event_stream<> _recentUpdated;
|
||||
rpl::event_stream<> _defaultUpdated;
|
||||
rpl::event_stream<> _coloredUpdated;
|
||||
rpl::event_stream<> _channelDefaultUpdated;
|
||||
rpl::event_stream<> _channelColoredUpdated;
|
||||
rpl::event_stream<> _collectiblesUpdated;
|
||||
|
||||
base::flat_map<
|
||||
CollectibleId,
|
||||
std::shared_ptr<EmojiStatusCollectible>> _collectibleData;
|
||||
|
||||
mtpRequestId _recentRequestId = 0;
|
||||
bool _recentRequestScheduled = false;
|
||||
|
@ -119,6 +158,9 @@ private:
|
|||
|
||||
mtpRequestId _channelColoredRequestId = 0;
|
||||
|
||||
mtpRequestId _collectiblesRequestId = 0;
|
||||
uint64 _collectiblesHash = 0;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
|
||||
|
||||
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
|
||||
|
@ -133,10 +175,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
struct EmojiStatusData {
|
||||
DocumentId id = 0;
|
||||
TimeId until = 0;
|
||||
};
|
||||
[[nodiscard]] EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status);
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -59,6 +59,24 @@ struct FileReferenceAccumulator {
|
|||
push(data.vdocuments());
|
||||
}, [&](const MTPDwebPageAttributeStickerSet &data) {
|
||||
push(data.vstickers());
|
||||
}, [&](const MTPDwebPageAttributeUniqueStarGift &data) {
|
||||
push(data.vgift());
|
||||
});
|
||||
}
|
||||
void push(const MTPStarGift &data) {
|
||||
data.match([&](const MTPDstarGift &data) {
|
||||
push(data.vsticker());
|
||||
}, [&](const MTPDstarGiftUnique &data) {
|
||||
push(data.vattributes());
|
||||
});
|
||||
}
|
||||
void push(const MTPStarGiftAttribute &data) {
|
||||
data.match([&](const MTPDstarGiftAttributeModel &data) {
|
||||
push(data.vdocument());
|
||||
}, [&](const MTPDstarGiftAttributePattern &data) {
|
||||
push(data.vdocument());
|
||||
}, [&](const MTPDstarGiftAttributeBackdrop &data) {
|
||||
}, [&](const MTPDstarGiftAttributeOriginalDetails &data) {
|
||||
});
|
||||
}
|
||||
void push(const MTPWebPage &data) {
|
||||
|
|
|
@ -897,12 +897,10 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
|
|||
const auto muted = this->muted();
|
||||
result.messages = count;
|
||||
result.chats = count ? 1 : 0;
|
||||
result.chatsTopic = count ? 1 : 0;
|
||||
result.mentions = unreadMentions().has() ? 1 : 0;
|
||||
result.reactions = unreadReactions().has() ? 1 : 0;
|
||||
result.messagesMuted = muted ? result.messages : 0;
|
||||
result.chatsMuted = muted ? result.chats : 0;
|
||||
result.chatsTopicMuted = muted ? result.chats : 0;
|
||||
result.reactionsMuted = muted ? result.reactions : 0;
|
||||
result.known = known;
|
||||
return result;
|
||||
|
|
|
@ -2375,13 +2375,13 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
|
|||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
if (const auto raw = _data.unique.get()) {
|
||||
if (const auto &unique = _data.unique) {
|
||||
return std::make_unique<HistoryView::MediaGeneric>(
|
||||
message,
|
||||
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw),
|
||||
HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
|
||||
HistoryView::MediaGenericDescriptor{
|
||||
.maxWidth = st::msgServiceGiftBoxSize.width(),
|
||||
.paintBg = HistoryView::UniqueGiftBg(message, raw),
|
||||
.paintBg = HistoryView::UniqueGiftBg(message, unique),
|
||||
.service = true,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -141,6 +141,8 @@ struct GiftCode {
|
|||
std::shared_ptr<UniqueGift> unique;
|
||||
TextWithEntities message;
|
||||
ChannelData *channel = nullptr;
|
||||
PeerData *channelFrom = nullptr;
|
||||
uint64 channelSavedId = 0;
|
||||
MsgId giveawayMsgId = 0;
|
||||
MsgId upgradeMsgId = 0;
|
||||
int starsConverted = 0;
|
||||
|
|
|
@ -652,6 +652,20 @@ bool PeerData::canManageTopics() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::canManageGifts() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->canPostMessages();
|
||||
}
|
||||
return isSelf();
|
||||
}
|
||||
|
||||
bool PeerData::canTransferGifts() const {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->amCreator();
|
||||
}
|
||||
return isSelf();
|
||||
}
|
||||
|
||||
bool PeerData::canEditMessagesIndefinitely() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->isSelf();
|
||||
|
@ -911,6 +925,16 @@ void PeerData::fullUpdated() {
|
|||
setLoadedStatus(LoadedStatus::Full);
|
||||
}
|
||||
|
||||
UserData *PeerData::asBot() {
|
||||
return isBot() ? static_cast<UserData*>(this) : nullptr;
|
||||
}
|
||||
|
||||
const UserData *PeerData::asBot() const {
|
||||
return isBot()
|
||||
? static_cast<const UserData*>(this)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
UserData *PeerData::asUser() {
|
||||
return isUser() ? static_cast<UserData*>(this) : nullptr;
|
||||
}
|
||||
|
@ -1115,11 +1139,11 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
|
|||
}
|
||||
|
||||
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
|
||||
const auto parsed = Data::ParseEmojiStatus(status);
|
||||
const auto parsed = owner().emojiStatuses().parse(status);
|
||||
setEmojiStatus(parsed.id, parsed.until);
|
||||
}
|
||||
|
||||
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
|
||||
void PeerData::setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until) {
|
||||
if (_emojiStatusId != emojiStatusId) {
|
||||
_emojiStatusId = emojiStatusId;
|
||||
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
|
||||
|
@ -1127,10 +1151,17 @@ void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
|
|||
owner().emojiStatuses().registerAutomaticClear(this, until);
|
||||
}
|
||||
|
||||
DocumentId PeerData::emojiStatusId() const {
|
||||
EmojiStatusId PeerData::emojiStatusId() const {
|
||||
return _emojiStatusId;
|
||||
}
|
||||
|
||||
bool PeerData::isBot() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->isBot();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeerData::isSelf() const {
|
||||
if (const auto user = asUser()) {
|
||||
return (user->flags() & UserDataFlag::Self);
|
||||
|
@ -1515,6 +1546,15 @@ void PeerData::setStoriesState(StoriesState state) {
|
|||
}
|
||||
}
|
||||
|
||||
int PeerData::peerGiftsCount() const {
|
||||
if (const auto user = asUser()) {
|
||||
return user->peerGiftsCount();
|
||||
} else if (const auto channel = asChannel()) {
|
||||
return channel->peerGiftsCount();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PeerData::setIsBlocked(bool is) {
|
||||
const auto status = is
|
||||
? BlockStatus::Blocked
|
||||
|
|
|
@ -206,8 +206,8 @@ public:
|
|||
bool changeBackgroundEmojiId(DocumentId id);
|
||||
|
||||
void setEmojiStatus(const MTPEmojiStatus &status);
|
||||
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0);
|
||||
[[nodiscard]] DocumentId emojiStatusId() const;
|
||||
void setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until = 0);
|
||||
[[nodiscard]] EmojiStatusId emojiStatusId() const;
|
||||
|
||||
[[nodiscard]] bool isUser() const {
|
||||
return peerIsUser(id);
|
||||
|
@ -218,6 +218,7 @@ public:
|
|||
[[nodiscard]] bool isChannel() const {
|
||||
return peerIsChannel(id);
|
||||
}
|
||||
[[nodiscard]] bool isBot() const;
|
||||
[[nodiscard]] bool isSelf() const;
|
||||
[[nodiscard]] bool isVerified() const;
|
||||
[[nodiscard]] bool isPremium() const;
|
||||
|
@ -267,6 +268,8 @@ public:
|
|||
[[nodiscard]] int slowmodeSecondsLeft() const;
|
||||
[[nodiscard]] bool canManageGroupCall() const;
|
||||
|
||||
[[nodiscard]] UserData *asBot();
|
||||
[[nodiscard]] const UserData *asBot() const;
|
||||
[[nodiscard]] UserData *asUser();
|
||||
[[nodiscard]] const UserData *asUser() const;
|
||||
[[nodiscard]] ChatData *asChat();
|
||||
|
@ -381,6 +384,8 @@ public:
|
|||
[[nodiscard]] bool canCreatePolls() const;
|
||||
[[nodiscard]] bool canCreateTopics() const;
|
||||
[[nodiscard]] bool canManageTopics() const;
|
||||
[[nodiscard]] bool canManageGifts() const;
|
||||
[[nodiscard]] bool canTransferGifts() const;
|
||||
[[nodiscard]] bool canExportChatHistory() const;
|
||||
|
||||
// Returns true if about text was changed.
|
||||
|
@ -483,6 +488,8 @@ public:
|
|||
[[nodiscard]] bool hasUnreadStories() const;
|
||||
void setStoriesState(StoriesState state);
|
||||
|
||||
[[nodiscard]] int peerGiftsCount() const;
|
||||
|
||||
const PeerId id;
|
||||
MTPinputPeer input = MTP_inputPeerEmpty();
|
||||
|
||||
|
@ -523,7 +530,7 @@ private:
|
|||
base::flat_set<QString> _nameWords; // for filtering
|
||||
base::flat_set<QChar> _nameFirstLetters;
|
||||
|
||||
DocumentId _emojiStatusId = 0;
|
||||
EmojiStatusId _emojiStatusId;
|
||||
DocumentId _backgroundEmojiId = 0;
|
||||
crl::time _lastFullUpdate = 0;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "mainwidget.h"
|
||||
#include "api/api_bot.h"
|
||||
#include "api/api_premium.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_user_names.h"
|
||||
#include "chat_helpers/stickers_lottie.h"
|
||||
|
@ -717,7 +718,7 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
|
|||
if (const auto &status = data.vemoji_status()) {
|
||||
result->setEmojiStatus(*status);
|
||||
} else {
|
||||
result->setEmojiStatus(0);
|
||||
result->setEmojiStatus(EmojiStatusId());
|
||||
}
|
||||
if (!minimal) {
|
||||
if (const auto botInfoVersion = data.vbot_info_version()) {
|
||||
|
@ -908,7 +909,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
|||
if (const auto &status = data.vemoji_status()) {
|
||||
channel->setEmojiStatus(*status);
|
||||
} else {
|
||||
channel->setEmojiStatus(0);
|
||||
channel->setEmojiStatus(EmojiStatusId());
|
||||
}
|
||||
if (minimal) {
|
||||
if (channel->input.type() == mtpc_inputPeerEmpty
|
||||
|
@ -3539,6 +3540,7 @@ not_null<WebPageData*> Session::processWebpage(
|
|||
WebPageCollage(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
false,
|
||||
|
@ -3565,6 +3567,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
WebPageCollage(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
false,
|
||||
|
@ -3584,6 +3587,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
std::unique_ptr<WebPageStickerSet> stickerSet,
|
||||
std::shared_ptr<UniqueGift> uniqueGift,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -3603,6 +3607,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
std::move(collage),
|
||||
std::move(iv),
|
||||
std::move(stickerSet),
|
||||
std::move(uniqueGift),
|
||||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
|
@ -3637,6 +3642,7 @@ void Session::webpageApplyFields(
|
|||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const auto lookupThemeDocument = [&]() -> DocumentData* {
|
||||
if (const auto attributes = data.vattributes()) {
|
||||
for (const auto &attribute : attributes->v) {
|
||||
|
@ -3647,6 +3653,8 @@ void Session::webpageApplyFields(
|
|||
return (DocumentData*)nullptr;
|
||||
}, [](const MTPDwebPageAttributeStickerSet &) {
|
||||
return (DocumentData*)nullptr;
|
||||
}, [](const MTPDwebPageAttributeUniqueStarGift &) {
|
||||
return (DocumentData*)nullptr;
|
||||
});
|
||||
if (result) {
|
||||
return result;
|
||||
|
@ -3655,6 +3663,7 @@ void Session::webpageApplyFields(
|
|||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
using WebPageStickerSetPtr = std::unique_ptr<WebPageStickerSet>;
|
||||
const auto lookupStickerSet = [&]() -> WebPageStickerSetPtr {
|
||||
if (const auto attributes = data.vattributes()) {
|
||||
|
@ -3678,6 +3687,21 @@ void Session::webpageApplyFields(
|
|||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
using UniqueGiftPtr = std::shared_ptr<UniqueGift>;
|
||||
const auto lookupUniqueGift = [&]() -> UniqueGiftPtr {
|
||||
if (const auto attributes = data.vattributes()) {
|
||||
for (const auto &attribute : attributes->v) {
|
||||
return attribute.match([&](
|
||||
const MTPDwebPageAttributeUniqueStarGift &data) {
|
||||
const auto gift = Api::FromTL(_session, data.vgift());
|
||||
return gift ? gift->unique : nullptr;
|
||||
}, [](const auto &) -> UniqueGiftPtr { return nullptr; });
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto story = (Data::Story*)nullptr;
|
||||
auto storyId = FullStoryId();
|
||||
if (const auto attributes = data.vattributes()) {
|
||||
|
@ -3770,6 +3794,7 @@ void Session::webpageApplyFields(
|
|||
WebPageCollage(this, data),
|
||||
std::move(iv),
|
||||
lookupStickerSet(),
|
||||
lookupUniqueGift(),
|
||||
data.vduration().value_or_empty(),
|
||||
qs(data.vauthor().value_or_empty()),
|
||||
data.is_has_large_media(),
|
||||
|
@ -3790,6 +3815,7 @@ void Session::webpageApplyFields(
|
|||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
std::unique_ptr<WebPageStickerSet> stickerSet,
|
||||
std::shared_ptr<UniqueGift> uniqueGift,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -3808,6 +3834,7 @@ void Session::webpageApplyFields(
|
|||
std::move(collage),
|
||||
std::move(iv),
|
||||
std::move(stickerSet),
|
||||
std::move(uniqueGift),
|
||||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
|
|
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "dialogs/dialogs_main_list.h"
|
||||
#include "data/data_groups.h"
|
||||
#include "data/data_cloud_file.h"
|
||||
#include "data/data_star_gift.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
|
@ -71,6 +72,7 @@ class BusinessInfo;
|
|||
struct ReactionId;
|
||||
struct UnavailableReason;
|
||||
struct CreditsStatusSlice;
|
||||
struct UniqueGift;
|
||||
|
||||
struct RepliesReadTillUpdate {
|
||||
FullMsgId id;
|
||||
|
@ -83,10 +85,11 @@ struct GiftUpdate {
|
|||
Save,
|
||||
Unsave,
|
||||
Convert,
|
||||
Transfer,
|
||||
Delete,
|
||||
};
|
||||
|
||||
FullMsgId itemId;
|
||||
Data::SavedStarGiftId id;
|
||||
Action action = {};
|
||||
};
|
||||
|
||||
|
@ -336,7 +339,7 @@ public:
|
|||
void notifyPinnedDialogsOrderUpdated();
|
||||
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
|
||||
|
||||
using CreditsSubsRebuilder = rpl::event_stream<Data::CreditsStatusSlice>;
|
||||
using CreditsSubsRebuilder = rpl::event_stream<CreditsStatusSlice>;
|
||||
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
|
||||
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
|
||||
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
|
||||
|
@ -422,7 +425,7 @@ public:
|
|||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
FilterId filterId) const;
|
||||
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
|
||||
not_null<Data::SavedMessages*> saved) const;
|
||||
not_null<SavedMessages*> saved) const;
|
||||
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
|
||||
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
|
||||
void clearPinnedChats(Folder *folder);
|
||||
|
@ -430,7 +433,7 @@ public:
|
|||
Folder *folder,
|
||||
const QVector<MTPDialogPeer> &list);
|
||||
void applyPinnedTopics(
|
||||
not_null<Data::Forum*> forum,
|
||||
not_null<Forum*> forum,
|
||||
const QVector<MTPint> &list);
|
||||
void reorderTwoPinnedChats(
|
||||
FilterId filterId,
|
||||
|
@ -624,6 +627,7 @@ public:
|
|||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
std::unique_ptr<WebPageStickerSet> stickerSet,
|
||||
std::shared_ptr<UniqueGift> uniqueGift,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -908,6 +912,7 @@ private:
|
|||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
std::unique_ptr<WebPageStickerSet> stickerSet,
|
||||
std::shared_ptr<UniqueGift> uniqueGift,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
|
|
@ -37,7 +37,11 @@ struct UniqueGiftOriginalDetails {
|
|||
};
|
||||
|
||||
struct UniqueGift {
|
||||
CollectibleId id = 0;
|
||||
QString slug;
|
||||
QString title;
|
||||
QString ownerAddress;
|
||||
QString ownerName;
|
||||
PeerId ownerId = 0;
|
||||
int number = 0;
|
||||
int starsForTransfer = -1;
|
||||
|
@ -71,13 +75,60 @@ struct StarGift {
|
|||
const StarGift &) = default;
|
||||
};
|
||||
|
||||
struct UserStarGift {
|
||||
class SavedStarGiftId {
|
||||
public:
|
||||
[[nodiscard]] static SavedStarGiftId User(MsgId messageId) {
|
||||
auto result = SavedStarGiftId();
|
||||
result.entityId = uint64(messageId.bare);
|
||||
return result;
|
||||
}
|
||||
[[nodiscard]] static SavedStarGiftId Chat(
|
||||
not_null<PeerData*> peer,
|
||||
uint64 savedId) {
|
||||
auto result = SavedStarGiftId();
|
||||
result.peer = peer;
|
||||
result.entityId = savedId;
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isUser() const {
|
||||
return !peer;
|
||||
}
|
||||
[[nodiscard]] bool isChat() const {
|
||||
return peer != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] MsgId userMessageId() const {
|
||||
return peer ? MsgId(0) : MsgId(entityId);
|
||||
}
|
||||
[[nodiscard]] PeerData *chat() const {
|
||||
return peer;
|
||||
}
|
||||
[[nodiscard]] uint64 chatSavedId() const {
|
||||
return peer ? entityId : 0;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return entityId != 0;
|
||||
}
|
||||
|
||||
friend inline bool operator==(
|
||||
const SavedStarGiftId &a,
|
||||
const SavedStarGiftId &b) = default;
|
||||
|
||||
private:
|
||||
PeerData *peer = nullptr;
|
||||
uint64 entityId = 0;
|
||||
|
||||
};
|
||||
|
||||
struct SavedStarGift {
|
||||
StarGift info;
|
||||
SavedStarGiftId manageId;
|
||||
TextWithEntities message;
|
||||
int64 starsConverted = 0;
|
||||
int64 starsUpgradedBySender = 0;
|
||||
PeerId fromId = 0;
|
||||
MsgId messageId = 0;
|
||||
TimeId date = 0;
|
||||
bool upgradable = false;
|
||||
bool anonymous = false;
|
||||
|
|
|
@ -87,6 +87,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDmediaAreaStarGift &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
|
@ -110,6 +111,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
}, [&](const MTPDmediaAreaChannelPost &data) {
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDmediaAreaStarGift &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
|
@ -133,6 +135,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
});
|
||||
}, [&](const MTPDmediaAreaUrl &data) {
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDmediaAreaStarGift &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
|
@ -154,6 +157,11 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
.url = qs(data.vurl()),
|
||||
});
|
||||
}, [&](const MTPDmediaAreaWeather &data) {
|
||||
}, [&](const MTPDmediaAreaStarGift &data) {
|
||||
result.emplace(UrlArea{
|
||||
.area = ParseArea(data.vcoordinates()),
|
||||
.url = u"tg://nft?slug="_q + qs(data.vslug()),
|
||||
});
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
|
@ -180,6 +188,7 @@ using UpdateFlag = StoryUpdate::Flag;
|
|||
-274.,
|
||||
1'000'000.)),
|
||||
});
|
||||
}, [&](const MTPDmediaAreaStarGift &data) {
|
||||
}, [&](const MTPDinputMediaAreaChannelPost &data) {
|
||||
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
|
||||
}, [&](const MTPDinputMediaAreaVenue &data) {
|
||||
|
|
|
@ -18,7 +18,6 @@ struct PeerSubscription final {
|
|||
}
|
||||
};
|
||||
|
||||
using PhotoId = uint64;
|
||||
struct SubscriptionEntry final {
|
||||
explicit operator bool() const {
|
||||
return !id.isEmpty();
|
||||
|
@ -31,7 +30,7 @@ struct SubscriptionEntry final {
|
|||
QDateTime until;
|
||||
PeerSubscription subscription;
|
||||
uint64 barePeerId = 0;
|
||||
PhotoId photoId = PhotoId(0);
|
||||
uint64 photoId = 0;
|
||||
bool cancelled = false;
|
||||
bool cancelledByBot = false;
|
||||
bool expired = false;
|
||||
|
|
|
@ -37,6 +37,7 @@ using Options = base::flags<Option>;
|
|||
namespace Data {
|
||||
|
||||
struct FileOrigin;
|
||||
struct EmojiStatusCollectible;
|
||||
|
||||
struct UploadState {
|
||||
explicit UploadState(int64 size) : size(size) {
|
||||
|
@ -139,6 +140,23 @@ using WallPaperId = uint64;
|
|||
using CallId = uint64;
|
||||
using BotAppId = uint64;
|
||||
using EffectId = uint64;
|
||||
using CollectibleId = uint64;
|
||||
|
||||
struct EmojiStatusId {
|
||||
DocumentId documentId = 0;
|
||||
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
|
||||
|
||||
explicit operator bool() const {
|
||||
return documentId || collectible;
|
||||
}
|
||||
|
||||
friend inline auto operator<=>(
|
||||
const EmojiStatusId &,
|
||||
const EmojiStatusId &) = default;
|
||||
friend inline bool operator==(
|
||||
const EmojiStatusId &,
|
||||
const EmojiStatusId &) = default;
|
||||
};
|
||||
|
||||
constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL);
|
||||
|
||||
|
|
|
@ -224,6 +224,7 @@ bool WebPageData::applyChanges(
|
|||
WebPageCollage &&newCollage,
|
||||
std::unique_ptr<Iv::Data> newIv,
|
||||
std::unique_ptr<WebPageStickerSet> newStickerSet,
|
||||
std::shared_ptr<Data::UniqueGift> newUniqueGift,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
|
@ -278,6 +279,7 @@ bool WebPageData::applyChanges(
|
|||
&& (!iv == !newIv)
|
||||
&& (!iv || iv->partial() == newIv->partial())
|
||||
&& (!stickerSet == !newStickerSet)
|
||||
&& (!uniqueGift == !newUniqueGift)
|
||||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||
|
@ -300,6 +302,7 @@ bool WebPageData::applyChanges(
|
|||
collage = std::move(newCollage);
|
||||
iv = std::move(newIv);
|
||||
stickerSet = std::move(newStickerSet);
|
||||
uniqueGift = std::move(newUniqueGift);
|
||||
duration = newDuration;
|
||||
author = resultAuthor;
|
||||
pendingTill = newPendingTill;
|
||||
|
|
|
@ -15,6 +15,7 @@ class ChannelData;
|
|||
|
||||
namespace Data {
|
||||
class Session;
|
||||
struct UniqueGift;
|
||||
} // namespace Data
|
||||
|
||||
namespace Iv {
|
||||
|
@ -101,6 +102,7 @@ struct WebPageData {
|
|||
WebPageCollage &&newCollage,
|
||||
std::unique_ptr<Iv::Data> newIv,
|
||||
std::unique_ptr<WebPageStickerSet> newStickerSet,
|
||||
std::shared_ptr<Data::UniqueGift> newUniqueGift,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
|
@ -129,6 +131,7 @@ struct WebPageData {
|
|||
WebPageCollage collage;
|
||||
std::unique_ptr<Iv::Data> iv;
|
||||
std::unique_ptr<WebPageStickerSet> stickerSet;
|
||||
std::shared_ptr<Data::UniqueGift> uniqueGift;
|
||||
int duration = 0;
|
||||
TimeId pendingTill = 0;
|
||||
uint32 version : 30 = 0;
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity.
|
||||
#include "data/data_peer.h"
|
||||
|
@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "chat_helpers/stickers_lottie.h"
|
||||
#include "storage/file_download.h" // kMaxFileInMemory
|
||||
#include "ui/chat/chats_filter_tag.h"
|
||||
#include "ui/effects/premium_stars_colored.h"
|
||||
#include "ui/effects/credits_graphics.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/text/custom_emoji_instance.h"
|
||||
|
@ -117,6 +119,10 @@ private:
|
|||
return u"force-static:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString CollectiblePrefix() {
|
||||
return u"collectible:"_q;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString InternalPadding(QMargins value) {
|
||||
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
|
||||
).arg(value.left()
|
||||
|
@ -568,6 +574,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
|
|||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto size = EmojiSizeFromTag(tag) / ratio;
|
||||
return userpic(data, std::move(update), size);
|
||||
} else if (data.startsWith(CollectiblePrefix())) {
|
||||
const auto id = data.mid(CollectiblePrefix().size()).toULongLong();
|
||||
const auto emojiStatuses = &session().data().emojiStatuses();
|
||||
auto info = emojiStatuses->collectibleInfo(id);
|
||||
Assert(info != nullptr);
|
||||
const auto documentId = info->documentId;
|
||||
auto inner = create(documentId, base::duplicate(update), tag);
|
||||
return Ui::Premium::MakeCollectibleEmoji(
|
||||
data,
|
||||
info->centerColor,
|
||||
info->edgeColor,
|
||||
std::move(inner),
|
||||
std::move(update),
|
||||
FrameSizeFromTag(tag) / style::DevicePixelRatio());
|
||||
} else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
|
||||
return MakeTopicIconEmoji(parsed, std::move(update), tag);
|
||||
}
|
||||
|
@ -1147,4 +1167,14 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory(
|
|||
};
|
||||
}
|
||||
|
||||
QString CollectibleCustomEmojiId(Data::EmojiStatusCollectible &data) {
|
||||
return CollectiblePrefix() + QString::number(data.id);
|
||||
}
|
||||
|
||||
QString EmojiStatusCustomId(const EmojiStatusId &id) {
|
||||
return id.collectible
|
||||
? CollectibleCustomEmojiId(*id.collectible)
|
||||
: SerializeCustomEmojiId(id.documentId);
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -223,4 +223,8 @@ void InsertCustomEmoji(
|
|||
[[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory(
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
[[nodiscard]] QString CollectibleCustomEmojiId(
|
||||
Data::EmojiStatusCollectible &data);
|
||||
[[nodiscard]] QString EmojiStatusCustomId(const EmojiStatusId &id);
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -88,8 +88,7 @@ void MaybeShowPremiumToast(
|
|||
return;
|
||||
}
|
||||
const auto filter = [=](const auto ...) {
|
||||
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
|
||||
if (const auto controller = show->resolveWindow(usage)) {
|
||||
if (const auto controller = show->resolveWindow()) {
|
||||
Settings::ShowPremium(controller, ref);
|
||||
}
|
||||
return false;
|
||||
|
@ -815,6 +814,25 @@ void Stickers::setPackAndEmoji(
|
|||
}
|
||||
}
|
||||
|
||||
not_null<StickersSet*> Stickers::collectibleSet() {
|
||||
const auto setId = CollectibleSetId;
|
||||
auto &sets = setsRef();
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.emplace(setId, std::make_unique<StickersSet>(
|
||||
&owner(),
|
||||
setId,
|
||||
uint64(0), // accessHash
|
||||
uint64(0), // hash
|
||||
tr::lng_collectible_emoji(tr::now),
|
||||
QString(),
|
||||
0, // count
|
||||
SetFlag::Special,
|
||||
TimeId(0))).first;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void Stickers::specialSetReceived(
|
||||
uint64 setId,
|
||||
const QString &setTitle,
|
||||
|
|
|
@ -65,6 +65,9 @@ public:
|
|||
// For setting up megagroup sticker set.
|
||||
static constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL;
|
||||
|
||||
// For collectible emoji statuses.
|
||||
static constexpr auto CollectibleSetId = 0xFFFFFFFFFFFFFFF8ULL;
|
||||
|
||||
void notifyUpdated(StickersType type);
|
||||
[[nodiscard]] rpl::producer<StickersType> updated() const;
|
||||
[[nodiscard]] rpl::producer<> updated(StickersType type) const;
|
||||
|
@ -244,6 +247,8 @@ public:
|
|||
[[nodiscard]] auto getEmojiListFromSet(not_null<DocumentData*> document)
|
||||
-> std::optional<std::vector<not_null<EmojiPtr>>>;
|
||||
|
||||
[[nodiscard]] not_null<StickersSet*> collectibleSet();
|
||||
|
||||
not_null<StickersSet*> feedSet(const MTPStickerSet &data);
|
||||
not_null<StickersSet*> feedSet(const MTPStickerSetCovered &data);
|
||||
not_null<StickersSet*> feedSetFull(const MTPDmessages_stickerSet &data);
|
||||
|
|
|
@ -32,14 +32,10 @@ struct UnreadState {
|
|||
int messagesMuted = 0;
|
||||
int chats = 0;
|
||||
int chatsMuted = 0;
|
||||
int chatsTopic = 0;
|
||||
int chatsTopicMuted = 0;
|
||||
int marks = 0;
|
||||
int marksMuted = 0;
|
||||
int reactions = 0;
|
||||
int reactionsMuted = 0;
|
||||
int forums = 0;
|
||||
int forumsMuted = 0;
|
||||
int mentions = 0;
|
||||
bool known = false;
|
||||
|
||||
|
@ -48,14 +44,10 @@ struct UnreadState {
|
|||
messagesMuted += other.messagesMuted;
|
||||
chats += other.chats;
|
||||
chatsMuted += other.chatsMuted;
|
||||
chatsTopic += other.chatsTopic;
|
||||
chatsTopicMuted += other.chatsTopicMuted;
|
||||
marks += other.marks;
|
||||
marksMuted += other.marksMuted;
|
||||
reactions += other.reactions;
|
||||
reactionsMuted += other.reactionsMuted;
|
||||
forums += other.forums;
|
||||
forumsMuted += other.forumsMuted;
|
||||
mentions += other.mentions;
|
||||
return *this;
|
||||
}
|
||||
|
@ -64,14 +56,10 @@ struct UnreadState {
|
|||
messagesMuted -= other.messagesMuted;
|
||||
chats -= other.chats;
|
||||
chatsMuted -= other.chatsMuted;
|
||||
chatsTopic -= other.chatsTopic;
|
||||
chatsTopicMuted -= other.chatsTopicMuted;
|
||||
marks -= other.marks;
|
||||
marksMuted -= other.marksMuted;
|
||||
reactions -= other.reactions;
|
||||
reactionsMuted -= other.reactionsMuted;
|
||||
forums -= other.forums;
|
||||
forumsMuted -= other.forumsMuted;
|
||||
mentions -= other.mentions;
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -1199,6 +1199,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
|||
p.translate(0, (_previewResults.size() - to) * _st->height);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_searchResults.empty()) {
|
||||
const auto text = showUnreadInSearchResults
|
||||
? u"Search results"_q
|
||||
|
@ -1215,7 +1216,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
|
|||
const auto filterFont = filterOver
|
||||
? st::searchedBarFont->underline()
|
||||
: st::searchedBarFont;
|
||||
if (_searchState.tab == ChatSearchTab::MyMessages) {
|
||||
if (hasChatTypeFilter()) {
|
||||
const auto text = ChatTypeFilterLabel(_searchState.filter);
|
||||
if (!_chatTypeFilterWidth) {
|
||||
_chatTypeFilterWidth = filterFont->width(text);
|
||||
|
@ -1522,6 +1523,21 @@ void InnerWidget::paintSearchTags(
|
|||
_searchTags->paint(p, position, context.now, context.paused);
|
||||
}
|
||||
|
||||
void InnerWidget::showPeerMenu() {
|
||||
if (!_selected) {
|
||||
return;
|
||||
}
|
||||
const auto &padding = st::defaultDialogRow.padding;
|
||||
const auto pos = QPoint(
|
||||
width() - padding.right(),
|
||||
_selected->top() + _selected->height() + padding.bottom());
|
||||
auto event = QContextMenuEvent(
|
||||
QContextMenuEvent::Keyboard,
|
||||
pos,
|
||||
mapToGlobal(pos));
|
||||
InnerWidget::contextMenuEvent(&event);
|
||||
}
|
||||
|
||||
void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
|
||||
return;
|
||||
|
@ -1747,7 +1763,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
|
|||
}
|
||||
auto selectedChatTypeFilter = false;
|
||||
const auto from = skip - st::searchedBarHeight;
|
||||
if (mouseY <= skip && mouseY >= from) {
|
||||
if (hasChatTypeFilter() && mouseY <= skip && mouseY >= from) {
|
||||
const auto left = width()
|
||||
- _chatTypeFilterWidth
|
||||
- 2 * st::searchedBarPosition.x();
|
||||
|
@ -2834,7 +2850,9 @@ bool InnerWidget::scheduleChatPreview(QPoint positionOverride) {
|
|||
void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||
_menu = nullptr;
|
||||
|
||||
if (e->reason() == QContextMenuEvent::Mouse) {
|
||||
const auto fromMouse = e->reason() == QContextMenuEvent::Mouse;
|
||||
|
||||
if (fromMouse) {
|
||||
selectByMouse(e->globalPos());
|
||||
}
|
||||
|
||||
|
@ -2897,6 +2915,9 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
|||
if (_menuRow.key) {
|
||||
updateDialogRow(base::take(_menuRow));
|
||||
}
|
||||
if (!fromMouse) {
|
||||
return;
|
||||
}
|
||||
const auto globalPosition = QCursor::pos();
|
||||
if (rect().contains(mapFromGlobal(globalPosition))) {
|
||||
setMouseTracking(true);
|
||||
|
@ -2998,6 +3019,11 @@ void InnerWidget::dragPinnedFromTouch() {
|
|||
updateReorderPinned(now);
|
||||
}
|
||||
|
||||
bool InnerWidget::hasChatTypeFilter() const {
|
||||
return !_searchResults.empty()
|
||||
&& (_searchState.tab == ChatSearchTab::MyMessages);
|
||||
}
|
||||
|
||||
void InnerWidget::searchRequested(bool loading) {
|
||||
_searchWaiting = false;
|
||||
_searchLoading = loading;
|
||||
|
|
|
@ -141,6 +141,8 @@ public:
|
|||
void refreshEmpty();
|
||||
void resizeEmpty();
|
||||
|
||||
void showPeerMenu();
|
||||
|
||||
[[nodiscard]] bool isUserpicPress() const;
|
||||
[[nodiscard]] bool isUserpicPressOnWide() const;
|
||||
void cancelChatPreview();
|
||||
|
@ -461,6 +463,7 @@ private:
|
|||
void handleChatListEntryRefreshes();
|
||||
void moveSearchIn();
|
||||
void dragPinnedFromTouch();
|
||||
[[nodiscard]] bool hasChatTypeFilter() const;
|
||||
|
||||
void saveChatsFilterScrollState(FilterId filterId);
|
||||
void restoreChatsFilterScrollState(FilterId filterId);
|
||||
|
|
|
@ -1231,6 +1231,12 @@ void Widget::setupShortcuts() {
|
|||
}
|
||||
return false;
|
||||
});
|
||||
request->check(Command::ShowChatMenu, 1) && request->handle([=] {
|
||||
if (_inner) {
|
||||
_inner->showPeerMenu();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
|