Merge tag 'v5.10.7' into dev

This commit is contained in:
AlexeyZavar 2025-01-29 01:48:14 +03:00
commit ab3a61794b
251 changed files with 6508 additions and 2322 deletions

View file

@ -1096,8 +1096,8 @@ PRIVATE
info/saved/info_saved_sublists_widget.h info/saved/info_saved_sublists_widget.h
info/settings/info_settings_widget.cpp info/settings/info_settings_widget.cpp
info/settings/info_settings_widget.h info/settings/info_settings_widget.h
info/similar_channels/info_similar_channels_widget.cpp info/similar_peers/info_similar_peers_widget.cpp
info/similar_channels/info_similar_channels_widget.h info/similar_peers/info_similar_peers_widget.h
info/statistics/info_statistics_common.h info/statistics/info_statistics_common.h
info/statistics/info_statistics_inner_widget.cpp info/statistics/info_statistics_inner_widget.cpp
info/statistics/info_statistics_inner_widget.h info/statistics/info_statistics_inner_widget.h
@ -1217,6 +1217,8 @@ PRIVATE
media/audio/media_audio_loader.h media/audio/media_audio_loader.h
media/audio/media_audio_loaders.cpp media/audio/media_audio_loaders.cpp
media/audio/media_audio_loaders.h 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.cpp
media/audio/media_audio_track.h media/audio/media_audio_track.h
media/audio/media_child_ffmpeg_loader.cpp media/audio/media_child_ffmpeg_loader.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_update_fail" = "Update check failed :("; "lng_settings_update_fail" = "Update check failed :(";
"lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_tray" = "Show tray icon";
"lng_settings_workmode_window" = "Show taskbar 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_close_to_taskbar" = "Close to taskbar";
"lng_settings_monochrome_icon" = "Use monochrome icon"; "lng_settings_monochrome_icon" = "Use monochrome icon";
"lng_settings_window_system" = "Window title bar"; "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_common_groups#other" = "{count} groups in common";
"lng_profile_similar_channels#one" = "{count} similar channel"; "lng_profile_similar_channels#one" = "{count} similar channel";
"lng_profile_similar_channels#other" = "{count} similar channels"; "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#one" = "{count} saved message";
"lng_profile_saved_messages#other" = "{count} saved messages"; "lng_profile_saved_messages#other" = "{count} saved messages";
"lng_profile_peer_gifts#one" = "{count} gift"; "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_sent" = "You sent a gift for {cost}";
"lng_action_gift_unique_sent" = "You sent a unique collectible item"; "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" = "{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_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_upgraded_self" = "You turned this gift into a unique collectible";
"lng_action_gift_transferred" = "{user} transferred you a gift"; "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_transferred_mine" = "You transferred a gift to {user}";
"lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; "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_bought" = "You bought a gift for {cost}";
"lng_action_gift_self_subtitle" = "Saved Gift"; "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#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#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_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#one" = "{count} Star";
"lng_action_gift_for_stars#other" = "{count} Stars"; "lng_action_gift_for_stars#other" = "{count} Stars";
"lng_action_gift_got_subtitle" = "Gift from {user}"; "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_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_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_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_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#one" = "{user} can display this gift on their page or convert it to {count} Star.";
"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars."; "lng_action_gift_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_premium_all_link" = "Telegram Premium";
"lng_similar_channels_show_more" = "Show more channels"; "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_title" = "Gifts";
"lng_peer_gifts_about" = "These gifts were sent to {user} by other users."; "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_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#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months"; "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_stickers_add" = "Choose sticker set";
"lng_group_emoji" = "Select Emoji Pack"; "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_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" = "Premium";
"lng_premium_free" = "Free"; "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#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_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" = "Ask your **Premium** subscribers to boost your channel with this link:";
"lng_boost_channel_ask_button" = "Copy Link"; "lng_boost_channel_ask_button" = "Copy Link";
"lng_boost_channel_or" = "or"; "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" = "Hide My Name";
"lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "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" = "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" = "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" = "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_unique_link" = "Learn More >";
"lng_gift_send_premium_about" = "Only {user} will see your message."; "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" = "Send a Gift for {cost}";
"lng_gift_send_button_self" = "Buy a Gift for {cost}"; "lng_gift_send_button_self" = "Buy a Gift for {cost}";
"lng_gift_sent_title" = "Gift Sent!"; "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_price_unique" = "Unique";
"lng_gift_view_unpack" = "Unpack"; "lng_gift_view_unpack" = "Unpack";
"lng_gift_anonymous_hint" = "Only you can see the sender's name."; "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_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_availability" = "Availability";
"lng_gift_from_hidden" = "Hidden User"; "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_status" = "buy yourself a gift";
"lng_gift_self_title" = "Buy 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_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" = "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" = "Status";
"lng_gift_unique_status_non" = "Non-Unique"; "lng_gift_unique_status_non" = "Non-Unique";
"lng_gift_unique_status_upgrade" = "upgrade"; "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_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#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#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#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_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_caution" = "This action cannot be undone. This will permanently destroy the gift.";
"lng_gift_convert_sure" = "Convert"; "lng_gift_convert_sure" = "Convert";
"lng_gift_display_done" = "The gift is now shown on your profile page."; "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" = "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#one" = "You got **{count} Star** for this gift.";
"lng_gift_got_stars#other" = "You got **{count} Stars** 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_title" = "Sold Out!";
"lng_gift_sold_out_text#one" = "All {count} gift was already sold."; "lng_gift_sold_out_text#one" = "All {count} gift was already sold.";
"lng_gift_sold_out_text#other" = "All {count} gifts were already sold."; "lng_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_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_title" = "Make Unique";
"lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible."; "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_title" = "Unique";
"lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift."; "lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift.";
"lng_gift_upgrade_transferable_title" = "Transferable"; "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_transferred_about" = "{name} was successfully transferred to {recipient}.";
"lng_gift_transfer_title" = "Transfer {name}"; "lng_gift_transfer_title" = "Transfer {name}";
"lng_gift_transfer_via_blockchain" = "Send via Blockchain"; "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#one" = "unlocks in {count} day";
"lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days"; "lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days";
"lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour"; "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_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?";
"lng_gift_transfer_button" = "Transfer"; "lng_gift_transfer_button" = "Transfer";
"lng_gift_transfer_button_for" = "Transfer for {price}"; "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_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; "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_iv" = "Instant View";
"lng_view_button_stickerset" = "View stickers"; "lng_view_button_stickerset" = "View stickers";
"lng_view_button_emojipack" = "View emoji"; "lng_view_button_emojipack" = "View emoji";
"lng_view_button_collectible" = "View collectible";
"lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_hide_ads" = "Hide";
"lng_sponsored_title" = "What are sponsored messages?"; "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_list_tab_gifts#other" = "{count} Gifts";
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways"; "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_single" = "Prepaid giveaway";
"lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium"; "lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium";
"lng_boosts_prepaid_giveaway_quantity#other" = "{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_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
"lng_channel_earn_chart_top_hours" = "Ad impressions"; "lng_channel_earn_chart_top_hours" = "Ad impressions";
"lng_channel_earn_chart_revenue" = "Ad rewards"; "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_currency" = "Rewards in TON";
"lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD"; "lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD";
"lng_channel_earn_currency_history" = "TON Transactions"; "lng_channel_earn_currency_history" = "TON Transactions";

View file

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

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,3,0 FILEVERSION 5,10,7,0
PRODUCTVERSION 5,10,3,0 PRODUCTVERSION 5,10,7,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "5.10.3.0" VALUE "FileVersion", "5.10.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.10.3.0" VALUE "ProductVersion", "5.10.7.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 5,10,3,0 FILEVERSION 5,10,7,0
PRODUCTVERSION 5,10,3,0 PRODUCTVERSION 5,10,7,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "5.10.3.0" VALUE "FileVersion", "5.10.7.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "LegalCopyright", "Copyright (C) 2014-2025"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "5.10.3.0" VALUE "ProductVersion", "5.10.7.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -211,11 +211,10 @@ void ApplyBotsList(
Data::PeerUpdate::Flag::FullInfo); Data::PeerUpdate::Flag::FullInfo);
} }
[[nodiscard]] ChatParticipants::Channels ParseSimilar( [[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPmessages_Chats &chats) { const MTPmessages_Chats &chats) {
auto result = ChatParticipants::Channels(); auto result = ChatParticipants::Peers();
std::vector<not_null<ChannelData*>>();
chats.match([&](const auto &data) { chats.match([&](const auto &data) {
const auto &list = data.vchats().v; const auto &list = data.vchats().v;
result.list.reserve(list.size()); result.list.reserve(list.size());
@ -234,10 +233,29 @@ void ApplyBotsList(
return result; return result;
} }
[[nodiscard]] ChatParticipants::Channels ParseSimilar( [[nodiscard]] ChatParticipants::Peers ParseSimilarChannels(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
const MTPmessages_Chats &chats) { 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 } // namespace
@ -782,52 +800,65 @@ void ChatParticipants::unblock(
_kickRequests.emplace(kick, requestId); _kickRequests.emplace(kick, requestId);
} }
void ChatParticipants::loadSimilarChannels(not_null<ChannelData*> channel) { void ChatParticipants::loadSimilarPeers(not_null<PeerData*> peer) {
if (!channel->isBroadcast()) { if (const auto i = _similar.find(peer); i != end(_similar)) {
return;
} else if (const auto i = _similar.find(channel); i != end(_similar)) {
if (i->second.requestId if (i->second.requestId
|| !i->second.channels.more || !i->second.peers.more
|| !channel->session().premium()) { || !peer->session().premium()) {
return; return;
} }
} }
using Flag = MTPchannels_GetChannelRecommendations::Flag; if (const auto channel = peer->asBroadcast()) {
_similar[channel].requestId = _api.request( using Flag = MTPchannels_GetChannelRecommendations::Flag;
MTPchannels_GetChannelRecommendations( _similar[peer].requestId = _api.request(
MTP_flags(Flag::f_channel), MTPchannels_GetChannelRecommendations(
channel->inputChannel) MTP_flags(Flag::f_channel),
).done([=](const MTPmessages_Chats &result) { channel->inputChannel)
auto &similar = _similar[channel]; ).done([=](const MTPmessages_Chats &result) {
similar.requestId = 0; auto &similar = _similar[channel];
auto parsed = ParseSimilar(channel, result); similar.requestId = 0;
if (similar.channels == parsed) { auto parsed = ParseSimilarChannels(channel, result);
return; if (similar.peers == 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);
} }
} similar.peers = std::move(parsed);
_similarLoaded.fire_copy(channel); if (const auto history = channel->owner().historyLoaded(channel)) {
}).send(); 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) auto ChatParticipants::similar(not_null<PeerData*> peer)
-> const Channels & { -> const Peers & {
const auto i = channel->isBroadcast() const auto i = (peer->isBroadcast() || peer->isBot())
? _similar.find(channel) ? _similar.find(peer)
: end(_similar); : end(_similar);
if (i != 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; return empty;
} }
auto ChatParticipants::similarLoaded() const auto ChatParticipants::similarLoaded() const
-> rpl::producer<not_null<ChannelData*>> { -> rpl::producer<not_null<PeerData*>> {
return _similarLoaded.events(); return _similarLoaded.events();
} }
@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() {
MTP_inputChannelEmpty()) MTP_inputChannelEmpty())
).done([=](const MTPmessages_Chats &result) { ).done([=](const MTPmessages_Chats &result) {
_recommendations.requestId = 0; _recommendations.requestId = 0;
auto parsed = ParseSimilar(_session, result); auto parsed = ParseSimilarChannels(_session, result);
_recommendations.channels = std::move(parsed); _recommendations.peers = std::move(parsed);
_recommendations.channels.more = 0; _recommendations.peers.more = 0;
_recommendationsLoaded = true; _recommendationsLoaded = true;
}).send(); }).send();
} }
const ChatParticipants::Channels &ChatParticipants::recommendations() const { const ChatParticipants::Peers &ChatParticipants::recommendations() const {
return _recommendations.channels; return _recommendations.peers;
} }
rpl::producer<> ChatParticipants::recommendationsLoaded() const { rpl::producer<> ChatParticipants::recommendationsLoaded() const {

View file

@ -138,27 +138,27 @@ public:
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
not_null<PeerData*> participant); not_null<PeerData*> participant);
void loadSimilarChannels(not_null<ChannelData*> channel); void loadSimilarPeers(not_null<PeerData*> peer);
struct Channels { struct Peers {
std::vector<not_null<ChannelData*>> list; std::vector<not_null<PeerData*>> list;
int more = 0; int more = 0;
friend inline bool operator==( friend inline bool operator==(
const Channels &, const Peers &,
const Channels &) = default; const Peers &) = default;
}; };
[[nodiscard]] const Channels &similar(not_null<ChannelData*> channel); [[nodiscard]] const Peers &similar(not_null<PeerData*> peer);
[[nodiscard]] auto similarLoaded() const [[nodiscard]] auto similarLoaded() const
-> rpl::producer<not_null<ChannelData*>>; -> rpl::producer<not_null<PeerData*>>;
void loadRecommendations(); void loadRecommendations();
[[nodiscard]] const Channels &recommendations() const; [[nodiscard]] const Peers &recommendations() const;
[[nodiscard]] rpl::producer<> recommendationsLoaded() const; [[nodiscard]] rpl::producer<> recommendationsLoaded() const;
private: private:
struct SimilarChannels { struct SimilarPeers {
Channels channels; Peers peers;
mtpRequestId requestId = 0; mtpRequestId requestId = 0;
}; };
@ -186,10 +186,10 @@ private:
not_null<PeerData*>>; not_null<PeerData*>>;
base::flat_map<KickRequest, mtpRequestId> _kickRequests; base::flat_map<KickRequest, mtpRequestId> _kickRequests;
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar; base::flat_map<not_null<PeerData*>, SimilarPeers> _similar;
rpl::event_stream<not_null<ChannelData*>> _similarLoaded; rpl::event_stream<not_null<PeerData*>> _similarLoaded;
SimilarChannels _recommendations; SimilarPeers _recommendations;
rpl::variable<bool> _recommendationsLoaded = false; rpl::variable<bool> _recommendationsLoaded = false;
}; };

View file

@ -513,4 +513,12 @@ void EditCreditsSubscription(
)).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); )).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 } // namespace Api

View file

@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_credits_earn.h" #include "data/data_credits_earn.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
namespace Data {
class SavedStarGiftId;
} // namespace Data
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -116,4 +120,7 @@ void EditCreditsSubscription(
Fn<void()> done, Fn<void()> done,
Fn<void(QString)> fail); Fn<void(QString)> fail);
[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId(
const Data::SavedStarGiftId &id);
} // namespace Api } // namespace Api

View file

@ -283,7 +283,9 @@ mtpRequestId EditTextMessage(
return MTP_inputMediaDocument( return MTP_inputMediaDocument(
MTP_flags(flags), MTP_flags(flags),
document->mtpInput(), document->mtpInput(),
MTPInputPhoto(), // video_cover
MTP_int(media->ttlSeconds()), MTP_int(media->ttlSeconds()),
MTPint(), // video_timestamp
MTPstring()); // query MTPstring()); // query
}; };
takeFileReference = [=] { return document->fileReference(); }; takeFileReference = [=] { return document->fileReference(); };

View file

@ -121,6 +121,8 @@ MTPInputMedia PrepareUploadedDocument(
ComposeSendingDocumentAttributes(document), ComposeSendingDocumentAttributes(document),
MTP_vector<MTPInputDocument>( MTP_vector<MTPInputDocument>(
ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)), ranges::to<QVector<MTPInputDocument>>(info.attachedStickers)),
MTPInputPhoto(), // video_cover
MTP_int(0), // video_timestamp
MTP_int(ttlSeconds)); MTP_int(ttlSeconds));
} }

View file

@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() {
_requestId = _history->session().api().request(MTPmessages_Search( _requestId = _history->session().api().request(MTPmessages_Search(
MTP_flags((fromPeer ? Flag::f_from_id : Flag()) MTP_flags((fromPeer ? Flag::f_from_id : Flag())
| (savedPeer ? Flag::f_saved_peer_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)), | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)),
_history->peer->input, _history->peer->input,
MTP_string(_request.query), MTP_string(_request.query),
@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() {
MTP_vector_from_range(_request.tags | ranges::views::transform( MTP_vector_from_range(_request.tags | ranges::views::transform(
Data::ReactionToMTP Data::ReactionToMTP
)), )),
MTPint(), // top_msg_id MTP_int(_request.topMsgId), // top_msg_id
MTP_inputMessagesFilterEmpty(), MTP_inputMessagesFilterEmpty(),
MTP_int(0), // min_date MTP_int(0), // min_date
MTP_int(0), // max_date MTP_int(0), // max_date

View file

@ -32,6 +32,7 @@ public:
QString query; QString query;
PeerData *from = nullptr; PeerData *from = nullptr;
std::vector<Data::ReactionId> tags; std::vector<Data::ReactionId> tags;
MsgId topMsgId;
friend inline bool operator==( friend inline bool operator==(
const Request &, const Request &,

View file

@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null<History*> history)
} }
} }
void MessagesSearchMerged::disableMigrated() {
_migratedSearch = std::nullopt;
}
void MessagesSearchMerged::addFound(const FoundMessages &data) { void MessagesSearchMerged::addFound(const FoundMessages &data) {
for (const auto &message : data.messages) { for (const auto &message : data.messages) {
_concatedFound.messages.push_back(message); _concatedFound.messages.push_back(message);

View file

@ -29,6 +29,7 @@ public:
void clear(); void clear();
void search(const Request &search); void search(const Request &search);
void searchMore(); void searchMore();
void disableMigrated();
[[nodiscard]] const FoundMessages &messages() const; [[nodiscard]] const FoundMessages &messages() const;
[[nodiscard]] const Request &request() const; [[nodiscard]] const Request &request() const;

View file

@ -805,8 +805,14 @@ std::optional<Data::StarGift> FromTL(
auto result = Data::StarGift{ auto result = Data::StarGift{
.id = uint64(data.vid().v), .id = uint64(data.vid().v),
.unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{ .unique = std::make_shared<Data::UniqueGift>(Data::UniqueGift{
.id = data.vid().v,
.slug = qs(data.vslug()),
.title = qs(data.vtitle()), .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, .number = data.vnum().v,
.model = *model, .model = *model,
.pattern = *pattern, .pattern = *pattern,
@ -829,9 +835,9 @@ std::optional<Data::StarGift> FromTL(
}); });
} }
std::optional<Data::UserStarGift> FromTL( std::optional<Data::SavedStarGift> FromTL(
not_null<UserData*> to, not_null<PeerData*> to,
const MTPuserStarGift &gift) { const MTPsavedStarGift &gift) {
const auto session = &to->session(); const auto session = &to->session();
const auto &data = gift.data(); const auto &data = gift.data();
auto parsed = FromTL(session, data.vgift()); auto parsed = FromTL(session, data.vgift());
@ -841,8 +847,12 @@ std::optional<Data::UserStarGift> FromTL(
unique->starsForTransfer = data.vtransfer_stars().value_or(-1); unique->starsForTransfer = data.vtransfer_stars().value_or(-1);
unique->exportAt = data.vcan_export_at().value_or_empty(); unique->exportAt = data.vcan_export_at().value_or_empty();
} }
return Data::UserStarGift{ using Id = Data::SavedStarGiftId;
return Data::SavedStarGift{
.info = std::move(*parsed), .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() .message = (data.vmessage()
? TextWithEntities{ ? TextWithEntities{
.text = qs(data.vmessage()->data().vtext()), .text = qs(data.vmessage()->data().vtext()),
@ -855,9 +865,8 @@ std::optional<Data::UserStarGift> FromTL(
.starsUpgradedBySender = int64( .starsUpgradedBySender = int64(
data.vupgrade_stars().value_or_empty()), data.vupgrade_stars().value_or_empty()),
.fromId = (data.vfrom_id() .fromId = (data.vfrom_id()
? peerFromUser(data.vfrom_id()->v) ? peerFromMTP(*data.vfrom_id())
: PeerId()), : PeerId()),
.messageId = data.vmsg_id().value_or_empty(),
.date = data.vdate().v, .date = data.vdate().v,
.upgradable = data.is_can_upgrade(), .upgradable = data.is_can_upgrade(),
.anonymous = data.is_name_hidden(), .anonymous = data.is_name_hidden(),
@ -910,11 +919,9 @@ Data::UniqueGiftOriginalDetails FromTL(
auto result = Data::UniqueGiftOriginalDetails(); auto result = Data::UniqueGiftOriginalDetails();
result.date = data.vdate().v; result.date = data.vdate().v;
result.senderId = data.vsender_id() result.senderId = data.vsender_id()
? peerFromUser( ? peerFromMTP(*data.vsender_id())
UserId(data.vsender_id().value_or_empty()))
: PeerId(); : PeerId();
result.recipientId = peerFromUser( result.recipientId = peerFromMTP(data.vrecipient_id());
UserId(data.vrecipient_id().v));
result.message = data.vmessage() result.message = data.vmessage()
? ParseTextWithEntities(session, *data.vmessage()) ? ParseTextWithEntities(session, *data.vmessage())
: TextWithEntities(); : TextWithEntities();

View file

@ -259,9 +259,9 @@ enum class RequirePremiumState {
[[nodiscard]] std::optional<Data::StarGift> FromTL( [[nodiscard]] std::optional<Data::StarGift> FromTL(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPstarGift &gift); const MTPstarGift &gift);
[[nodiscard]] std::optional<Data::UserStarGift> FromTL( [[nodiscard]] std::optional<Data::SavedStarGift> FromTL(
not_null<UserData*> to, not_null<PeerData*> to,
const MTPuserStarGift &gift); const MTPsavedStarGift &gift);
[[nodiscard]] Data::UniqueGiftModel FromTL( [[nodiscard]] Data::UniqueGiftModel FromTL(
not_null<Main::Session*> session, not_null<Main::Session*> session,

View file

@ -272,7 +272,9 @@ void SendExistingDocument(
return MTP_inputMediaDocument( return MTP_inputMediaDocument(
MTP_flags(0), MTP_flags(0),
document->mtpInput(), document->mtpInput(),
MTPInputPhoto(), // video_cover
MTPint(), // ttl_seconds MTPint(), // ttl_seconds
MTPint(), // video_timestamp
MTPstring()); // query MTPstring()); // query
}; };
SendExistingMedia( SendExistingMedia(
@ -550,6 +552,8 @@ void SendConfirmedFile(
| (file->spoiler ? Flag::f_spoiler : Flag())), | (file->spoiler ? Flag::f_spoiler : Flag())),
file->document, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
MTPint(), // video_timestamp
MTPint()); MTPint());
} else if (file->type == SendMediaType::Audio) { } else if (file->type == SendMediaType::Audio) {
const auto ttlSeconds = file->to.options.ttlSeconds; const auto ttlSeconds = file->to.options.ttlSeconds;
@ -560,6 +564,8 @@ void SendConfirmedFile(
| (ttlSeconds ? Flag::f_ttl_seconds : Flag())), | (ttlSeconds ? Flag::f_ttl_seconds : Flag())),
file->document, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
MTPint(), // video_timestamp
MTP_int(ttlSeconds)); MTP_int(ttlSeconds));
} else if (file->type == SendMediaType::Round) { } else if (file->type == SendMediaType::Round) {
using Flag = MTPDmessageMediaDocument::Flag; using Flag = MTPDmessageMediaDocument::Flag;
@ -571,6 +577,8 @@ void SendConfirmedFile(
| (file->spoiler ? Flag::f_spoiler : Flag())), | (file->spoiler ? Flag::f_spoiler : Flag())),
file->document, file->document,
MTPVector<MTPDocument>(), // alt_documents MTPVector<MTPDocument>(), // alt_documents
MTPPhoto(), // video_cover
MTPint(), // video_timestamp
MTP_int(ttlSeconds)); MTP_int(ttlSeconds));
} else { } else {
Unexpected("Type in sendFilesConfirmed."); Unexpected("Type in sendFilesConfirmed.");

View file

@ -1812,7 +1812,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
_channelAmInRequests.emplace(channel, requestId); _channelAmInRequests.emplace(channel, requestId);
using Flag = ChannelDataFlag; using Flag = ChannelDataFlag;
chatParticipants().loadSimilarChannels(channel); chatParticipants().loadSimilarPeers(channel);
const auto settings = &AyuSettings::getInstance(); const auto settings = &AyuSettings::getInstance();
if (!settings->collapseSimilarChannels) { if (!settings->collapseSimilarChannels) {
@ -3422,7 +3422,8 @@ void ApiWrap::forwardMessages(
MTP_int(topMsgId), MTP_int(topMsgId),
MTP_int(action.options.scheduled), MTP_int(action.options.scheduled),
(sendAs ? sendAs->input : MTP_inputPeerEmpty()), (sendAs ? sendAs->input : MTP_inputPeerEmpty()),
Data::ShortcutIdToMTP(_session, action.options.shortcutId) Data::ShortcutIdToMTP(_session, action.options.shortcutId),
MTPint() // video_timestamp
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
if (!scheduled) { if (!scheduled) {
this->updates().checkForSentToScheduled(result); this->updates().checkForSentToScheduled(result);
@ -4218,7 +4219,9 @@ void ApiWrap::uploadAlbumMedia(
fields.vid(), fields.vid(),
fields.vaccess_hash(), fields.vaccess_hash(),
fields.vfile_reference()), fields.vfile_reference()),
MTPInputPhoto(), // video_cover
MTP_int(data.vttl_seconds().value_or_empty()), MTP_int(data.vttl_seconds().value_or_empty()),
MTPint(), // video_timestamp
MTPstring()); // query MTPstring()); // query
sendAlbumWithUploaded(item, groupId, media); sendAlbumWithUploaded(item, groupId, media);
} break; } break;

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,10 @@ namespace Api {
struct GiftCode; struct GiftCode;
} // namespace Api } // namespace Api
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data { namespace Data {
struct Boost; struct Boost;
struct CreditsHistoryEntry; struct CreditsHistoryEntry;
@ -21,6 +25,14 @@ struct GiveawayResults;
struct SubscriptionEntry; struct SubscriptionEntry;
} // namespace Data } // namespace Data
namespace Main {
class Session;
} // namespace Main
namespace Settings {
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Ui { namespace Ui {
class GenericBox; class GenericBox;
class VerticalLayout; class VerticalLayout;
@ -54,29 +66,37 @@ void ResolveGiveawayInfo(
std::optional<Data::GiveawayStart> start, std::optional<Data::GiveawayStart> start,
std::optional<Data::GiveawayResults> results); std::optional<Data::GiveawayResults> results);
[[nodiscard]] QString TonAddressUrl(
not_null<Main::Session*> session,
const QString &address);
void AddStarGiftTable( void AddStarGiftTable(
not_null<Window::SessionNavigation*> controller, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::CreditsHistoryEntry &entry, const Data::CreditsHistoryEntry &entry,
Fn<void(bool)> toggleVisibility,
Fn<void()> convertToStars, Fn<void()> convertToStars,
Fn<void()> startUpgrade); Fn<void()> startUpgrade);
void AddCreditsHistoryEntryTable( void AddCreditsHistoryEntryTable(
not_null<Window::SessionNavigation*> controller, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::CreditsHistoryEntry &entry); const Data::CreditsHistoryEntry &entry);
void AddSubscriptionEntryTable( void AddSubscriptionEntryTable(
not_null<Window::SessionNavigation*> controller, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::SubscriptionEntry &s); const Data::SubscriptionEntry &s);
void AddSubscriberEntryTable( void AddSubscriberEntryTable(
not_null<Window::SessionNavigation*> controller, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
not_null<PeerData*> peer, not_null<PeerData*> peer,
TimeId date); TimeId date);
void AddCreditsBoostTable( void AddCreditsBoostTable(
not_null<Window::SessionNavigation*> controller, std::shared_ptr<ChatHelpers::Show> show,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
Settings::CreditsEntryBoxStyleOverrides st,
const Data::Boost &boost); const Data::Boost &boost);

View file

@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) {
if (peer != item->history()->peer) { if (peer != item->history()->peer) {
return {}; 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()) { if (!item->suggestBanReport()) {
result.allCanBan = false; result.allCanBan = false;
} }

View file

@ -265,7 +265,7 @@ struct IconSelector {
const auto manager = &controller->session().data().customEmojiManager(); const auto manager = &controller->session().data().customEmojiManager();
auto factory = [=](DocumentId id, Fn<void()> repaint) 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; const auto tag = Data::CustomEmojiManager::SizeTag::Large;
if (id == kDefaultIconId) { if (id == kDefaultIconId) {
return std::make_unique<DefaultIconEmoji>( return std::make_unique<DefaultIconEmoji>(
@ -288,7 +288,7 @@ struct IconSelector {
.show = controller->uiShow(), .show = controller->uiShow(),
.mode = EmojiListWidget::Mode::TopicIcon, .mode = EmojiListWidget::Mode::TopicIcon,
.paused = Window::PausedIn(controller, PauseReason::Layer), .paused = Window::PausedIn(controller, PauseReason::Layer),
.customRecentList = recent(), .customRecentList = DocumentListToRecent(recent()),
.customRecentFactory = std::move(factory), .customRecentFactory = std::move(factory),
.st = &st::reactPanelEmojiPan, .st = &st::reactPanelEmojiPan,
}), }),
@ -297,7 +297,7 @@ struct IconSelector {
icons->requestDefaultIfUnknown(); icons->requestDefaultIfUnknown();
icons->defaultUpdates( icons->defaultUpdates(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
selector->provideRecent(recent()); selector->provideRecent(DocumentListToRecent(recent()));
}, selector->lifetime()); }, selector->lifetime());
placeFooter(selector->createFooter()); placeFooter(selector->createFooter());

View file

@ -313,6 +313,7 @@ PreviewWrap::PreviewWrap(
WebPageCollage(), WebPageCollage(),
nullptr, // iv nullptr, // iv
nullptr, // stickerSet nullptr, // stickerSet
nullptr, // uniqueGift
0, // duration 0, // duration
QString(), // author QString(), // author
false, // hasLargeMedia false, // hasLargeMedia
@ -525,7 +526,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) {
struct SetValues { struct SetValues {
uint8 colorIndex = 0; uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0; DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0; EmojiStatusId statusId;
TimeId statusUntil = 0; TimeId statusUntil = 0;
bool statusChanged = false; bool statusChanged = false;
}; };
@ -807,7 +808,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto state = right->lifetime().make_state<State>(); const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen( state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) { ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id); emojiIdChosen(chosen.id.documentId);
}, raw->lifetime()); }, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
@ -871,13 +872,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto customTextColor = [=] { const auto customTextColor = [=] {
return style->coloredValues(false, state->index).name; return style->coloredValues(false, state->index).name;
}; };
const auto controller = show->resolveWindow( const auto controller = show->resolveWindow();
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) { if (controller) {
state->panel.show({ state->panel.show({
.controller = controller, .controller = controller,
.button = right, .button = right,
.ensureAddedEmojiId = state->emojiId, .ensureAddedEmojiId = { state->emojiId },
.customTextColor = customTextColor, .customTextColor = customTextColor,
.backgroundEmojiMode = true, .backgroundEmojiMode = true,
}); });
@ -901,8 +901,8 @@ int ColorSelector::resizeGetHeight(int newWidth) {
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
rpl::producer<DocumentId> statusIdValue, rpl::producer<EmojiStatusId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen, Fn<void(EmojiStatusId,TimeId)> statusIdChosen,
bool group) { bool group) {
const auto button = ButtonStyleWithRightEmoji( const auto button = ButtonStyleWithRightEmoji(
parent, parent,
@ -924,20 +924,20 @@ int ColorSelector::resizeGetHeight(int newWidth) {
struct State { struct State {
EmojiStatusPanel panel; EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji; std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId statusId = 0; EmojiStatusId statusId;
}; };
const auto state = right->lifetime().make_state<State>(); const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen( state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) { ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
statusIdChosen(chosen.id, chosen.until); statusIdChosen({ chosen.id }, chosen.until);
}, raw->lifetime()); }, raw->lifetime());
const auto session = &show->session(); 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->statusId = id;
state->emoji = id state->emoji = id
? session->data().customEmojiManager().create( ? session->data().customEmojiManager().create(
id, Data::EmojiStatusCustomId(id),
[=] { right->update(); }) [=] { right->update(); })
: nullptr; : nullptr;
right->resize( right->resize(
@ -985,13 +985,12 @@ int ColorSelector::resizeGetHeight(int newWidth) {
}, right->lifetime()); }, right->lifetime());
raw->setClickedCallback([=] { raw->setClickedCallback([=] {
const auto controller = show->resolveWindow( const auto controller = show->resolveWindow();
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) { if (controller) {
state->panel.show({ state->panel.show({
.controller = controller, .controller = controller,
.button = right, .button = right,
.ensureAddedEmojiId = state->statusId, .ensureAddedEmojiId = { state->statusId },
.channelStatusMode = true, .channelStatusMode = true,
}); });
} }
@ -1183,7 +1182,7 @@ void EditPeerColorBox(
struct State { struct State {
rpl::variable<uint8> index; rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId; rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId; rpl::variable<EmojiStatusId> statusId;
TimeId statusUntil = 0; TimeId statusUntil = 0;
bool statusChanged = false; bool statusChanged = false;
bool changing = false; bool changing = false;
@ -1263,8 +1262,7 @@ void EditPeerColorBox(
{ &st::menuBlueIconWallpaper } { &st::menuBlueIconWallpaper }
); );
button->setClickedCallback([=] { button->setClickedCallback([=] {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo; if (const auto strong = show->resolveWindow()) {
if (const auto strong = show->resolveWindow(usage)) {
show->show(Box<BackgroundBox>(strong, channel)); show->show(Box<BackgroundBox>(strong, channel));
} }
}); });
@ -1321,7 +1319,7 @@ void EditPeerColorBox(
show, show,
channel, channel,
state->statusId.value(), state->statusId.value(),
[=](DocumentId id, TimeId until) { [=](EmojiStatusId id, TimeId until) {
state->statusId = id; state->statusId = id;
state->statusUntil = until; state->statusUntil = until;
state->statusChanged = true; state->statusChanged = true;
@ -1471,8 +1469,7 @@ void CheckBoostLevel(
return; return;
} }
const auto openStatistics = [=] { const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow( if (const auto controller = show->resolveWindow()) {
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer)); controller->showSection(Info::Boosts::Make(peer));
} }
}; };

View file

@ -1242,7 +1242,9 @@ void Controller::fillManageSection() {
? channel->canViewMembers() ? channel->canViewMembers()
: chat->amIn(); : chat->amIn();
const auto canViewKicked = isChannel const auto canViewKicked = isChannel
&& (channel->isBroadcast() || channel->isGigagroup()); && (channel->isMegagroup()
? (channel->isBroadcast() || channel->isGigagroup())
: true);
const auto hasRecentActions = isChannel const auto hasRecentActions = isChannel
&& (channel->hasAdminRights() || channel->amCreator()); && (channel->hasAdminRights() || channel->amCreator());
const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer) const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer)

View file

@ -1019,7 +1019,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::AddSkip(content); Ui::AddSkip(content);
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);
Ui::AddSkip(content); Ui::AddSkip(content);

View file

@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "mtproto/mtproto_config.h" // megagroupSizeMax
#include "apiwrap.h" #include "apiwrap.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -49,7 +50,6 @@ namespace {
constexpr auto kSlowmodeValues = 7; constexpr auto kSlowmodeValues = 7;
constexpr auto kBoostsUnrestrictValues = 5; constexpr auto kBoostsUnrestrictValues = 5;
constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
[[nodiscard]] auto Dependencies(PowerSaving::Flags) [[nodiscard]] auto Dependencies(PowerSaving::Flags)
@ -1191,8 +1191,11 @@ void ShowEditPeerPermissionsBox(
}); });
if (const auto channel = peer->asChannel()) { if (const auto channel = peer->asChannel()) {
constexpr auto kThresholdOffset = int(1000);
const auto threshold = -kThresholdOffset
+ channel->session().serverConfig().megagroupSizeMax;
if (channel->amCreator() if (channel->amCreator()
&& channel->membersCount() >= kSuggestGigagroupThreshold) { && channel->membersCount() >= threshold) {
AddSuggestGigagroup( AddSuggestGigagroup(
inner, inner,
AboutGigagroupCallback( AboutGigagroupCallback(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "chat_helpers/emoji_list_widget.h"
#include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
@ -491,7 +492,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
panelList.erase( panelList.erase(
ranges::remove(panelList, paid->selectAnimation->id), ranges::remove(panelList, paid->selectAnimation->id),
end(panelList)); end(panelList));
panel->selector()->provideRecentEmoji(panelList); panel->selector()->provideRecentEmoji(
ChatHelpers::DocumentListToRecent(panelList));
panel->setDesiredHeightValues( panel->setDesiredHeightValues(
1., 1.,
st::emojiPanMinHeight / 2, st::emojiPanMinHeight / 2,

View file

@ -486,20 +486,23 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
object_ptr<Ui::BoxContent> PrepareShortInfoBox( object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation, std::shared_ptr<ChatHelpers::Show> show,
const style::ShortInfoBox *stOverride) { 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 = [=] { const auto videoIsPaused = [=] {
return navigation->parentController()->isGifPausedAtLeastFor( return show->paused(Window::GifPauseReason::Layer);
Window::GifPauseReason::Layer);
}; };
auto menuFiller = [=](Ui::Menu::MenuCallback addAction) { auto menuFiller = [=](Ui::Menu::MenuCallback addAction) {
const auto controller = navigation->parentController();
const auto peerSeparateId = Window::SeparateId(peer); 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), [=] { addAction(tr::lng_context_new_window(tr::now), [=] {
Ui::PreventDelayedActivation(); Ui::PreventDelayedActivation();
controller->showInNewWindow(peer); window->showInNewWindow(peer);
}, &st::menuIconNewWindow); }, &st::menuIconNewWindow);
} }
}; };
@ -511,6 +514,13 @@ object_ptr<Ui::BoxContent> PrepareShortInfoBox(
stOverride); 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) { rpl::producer<QString> PrepareShortInfoStatus(not_null<PeerData*> peer) {
return StatusValue(peer); return StatusValue(peer);
} }

View file

@ -16,6 +16,10 @@ struct ShortInfoCover;
struct ShortInfoBox; struct ShortInfoBox;
} // namespace style } // namespace style
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Ui::Menu { namespace Ui::Menu {
struct MenuCallback; struct MenuCallback;
} // namespace Ui::Menu } // namespace Ui::Menu
@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic {
Fn<void(Ui::Menu::MenuCallback)> menuFiller, Fn<void(Ui::Menu::MenuCallback)> menuFiller,
const style::ShortInfoBox *stOverride = nullptr); 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( [[nodiscard]] object_ptr<Ui::BoxContent> PrepareShortInfoBox(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,

View file

@ -1139,8 +1139,7 @@ void PreviewBox(
button->resizeToWidth(width); button->resizeToWidth(width);
if (!descriptor.fromSettings) { if (!descriptor.fromSettings) {
button->setClickedCallback([=] { button->setClickedCallback([=] {
const auto window = show->resolveWindow( const auto window = show->resolveWindow();
ChatHelpers::WindowUsage::PremiumPromo);
if (!window) { if (!window) {
return; return;
} }

View file

@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "styles/style_calls.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
@ -202,16 +203,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
, _api(&_descriptor.session->mtp()) , _api(&_descriptor.session->mtp())
, _select( , _select(
this, this,
(_descriptor.stMultiSelect (_descriptor.st.multiSelect
? *_descriptor.stMultiSelect ? *_descriptor.st.multiSelect
: st::defaultMultiSelect), : st::defaultMultiSelect),
tr::lng_participant_filter()) tr::lng_participant_filter())
, _comment( , _comment(
this, this,
object_ptr<Ui::InputField>( object_ptr<Ui::InputField>(
this, this,
(_descriptor.stComment (_descriptor.st.comment
? *_descriptor.stComment ? *_descriptor.st.comment
: st::shareComment), : st::shareComment),
Ui::InputField::Mode::MultiLine, Ui::InputField::Mode::MultiLine,
tr::lng_photos_comment()), tr::lng_photos_comment()),
@ -256,7 +257,7 @@ void ShareBox::prepareCommentField() {
.session = _descriptor.session, .session = _descriptor.session,
.show = Main::MakeSessionShow(show, _descriptor.session), .show = Main::MakeSessionShow(show, _descriptor.session),
.field = field, .field = field,
.fieldStyle = _descriptor.stLabel, .fieldStyle = _descriptor.st.label,
}); });
} }
field->setSubmitSettings(Core::App().settings().sendSubmitWay()); field->setSubmitSettings(Core::App().settings().sendSubmitWay());
@ -566,6 +567,9 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
submit(action.options); submit(action.options);
return; return;
} }
const auto st = _descriptor.st.scheduleBox
? *_descriptor.st.scheduleBox
: HistoryView::ScheduleBoxStyleArgs();
uiShow()->showBox( uiShow()->showBox(
HistoryView::PrepareScheduleBox( HistoryView::PrepareScheduleBox(
this, this,
@ -574,7 +578,7 @@ void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
[=](Api::SendOptions options) { submit(options); }, [=](Api::SendOptions options) { submit(options); },
action.options, action.options,
HistoryView::DefaultScheduleTime(), HistoryView::DefaultScheduleTime(),
_descriptor.scheduleBoxStyle)); st));
}); });
_menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom); _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
const auto result = FillSendMenu( const auto result = FillSendMenu(
@ -709,7 +713,9 @@ ShareBox::Inner::Inner(
: RpWidget(parent) : RpWidget(parent)
, _descriptor(descriptor) , _descriptor(descriptor)
, _show(std::move(show)) , _show(std::move(show))
, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList) , _st(_descriptor.st.peerList
? *_descriptor.st.peerList
: st::shareBoxList)
, _defaultChatsIndexed( , _defaultChatsIndexed(
std::make_unique<Dialogs::IndexedList>( std::make_unique<Dialogs::IndexedList>(
Dialogs::SortMode::Add)) Dialogs::SortMode::Add))
@ -1586,7 +1592,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
MTP_int(topMsgId), MTP_int(topMsgId),
MTP_int(options.scheduled), MTP_int(options.scheduled),
MTP_inputPeerEmpty(), // send_as MTP_inputPeerEmpty(), // send_as
Data::ShortcutIdToMTP(session, options.shortcutId) Data::ShortcutIdToMTP(session, options.shortcutId),
MTPint() // video_timestamp
)).done([=](const MTPUpdates &updates, mtpRequestId reqId) { )).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
threadHistory->session().api().applyUpdates(updates); threadHistory->session().api().applyUpdates(updates);
state->requests.remove(reqId); 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( void FastShareMessage(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item) { not_null<HistoryItem*> item,
ShareBoxStyleOverrides st) {
const auto history = item->history(); const auto history = item->history();
const auto owner = &history->owner(); const auto owner = &history->owner();
const auto session = &history->session(); const auto session = &history->session();
@ -1695,6 +1730,7 @@ void FastShareMessage(
history, history,
msgIds), msgIds),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.st = st,
.forwardOptions = { .forwardOptions = {
.sendersCount = ItemsForwardSendersCount(items), .sendersCount = ItemsForwardSendersCount(items),
.captionsCount = ItemsForwardCaptionsCount(items), .captionsCount = ItemsForwardCaptionsCount(items),
@ -1706,19 +1742,22 @@ void FastShareMessage(
void FastShareMessage( void FastShareMessage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item) { not_null<HistoryItem*> item,
FastShareMessage(controller->uiShow(), item); ShareBoxStyleOverrides st) {
FastShareMessage(controller->uiShow(), item, st);
} }
void FastShareLink( void FastShareLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
const QString &url) { const QString &url,
FastShareLink(controller->uiShow(), url); ShareBoxStyleOverrides st) {
FastShareLink(controller->uiShow(), url, st);
} }
void FastShareLink( void FastShareLink(
std::shared_ptr<Main::SessionShow> show, 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 box = std::make_shared<QPointer<Ui::BoxContent>>();
const auto sending = std::make_shared<bool>(); const auto sending = std::make_shared<bool>();
auto copyCallback = [=] { auto copyCallback = [=] {
@ -1782,6 +1821,7 @@ void FastShareLink(
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
.submitCallback = std::move(submitCallback), .submitCallback = std::move(submitCallback),
.filterCallback = std::move(filterCallback), .filterCallback = std::move(filterCallback),
.st = st,
.premiumRequiredError = SharePremiumRequiredError(), .premiumRequiredError = SharePremiumRequiredError(),
}), }),
Ui::LayerOption::KeepOther, Ui::LayerOption::KeepOther,

View file

@ -61,18 +61,31 @@ class PopupMenu;
class ShareBox; 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( void FastShareMessage(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<HistoryItem*> item); not_null<HistoryItem*> item,
ShareBoxStyleOverrides st = {});
void FastShareMessage( void FastShareMessage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item); not_null<HistoryItem*> item,
ShareBoxStyleOverrides st = {});
void FastShareLink( void FastShareLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
const QString &url); const QString &url,
ShareBoxStyleOverrides st = {});
void FastShareLink( void FastShareLink(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
const QString &url); const QString &url,
ShareBoxStyleOverrides st = {});
struct RecipientPremiumRequiredError; struct RecipientPremiumRequiredError;
[[nodiscard]] auto SharePremiumRequiredError() [[nodiscard]] auto SharePremiumRequiredError()
@ -100,16 +113,12 @@ public:
FilterCallback filterCallback; FilterCallback filterCallback;
object_ptr<Ui::RpWidget> bottomWidget = { nullptr }; object_ptr<Ui::RpWidget> bottomWidget = { nullptr };
rpl::producer<QString> copyLinkText; rpl::producer<QString> copyLinkText;
const style::MultiSelect *stMultiSelect = nullptr; ShareBoxStyleOverrides st;
const style::InputField *stComment = nullptr;
const style::PeerList *st = nullptr;
const style::InputField *stLabel = nullptr;
struct { struct {
int sendersCount = 0; int sendersCount = 0;
int captionsCount = 0; int captionsCount = 0;
bool show = false; bool show = false;
} forwardOptions; } forwardOptions;
HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle;
using PremiumRequiredError = RecipientPremiumRequiredError; using PremiumRequiredError = RecipientPremiumRequiredError;
Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError; Fn<PremiumRequiredError(not_null<UserData*>)> premiumRequiredError;

View file

@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/star_gift_box.h" #include "boxes/star_gift_box.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "base/random.h" #include "base/random.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "api/api_premium.h"
#include "boxes/filters/edit_filter_chats_list.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/gift_premium_box.h"
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h" #include "boxes/send_credits_box.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.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_panel.h"
#include "chat_helpers/tabbed_selector.h" #include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_credits.h" #include "data/data_credits.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.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_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/stickers/data_custom_emoji.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.h"
#include "settings/settings_credits_graphics.h" #include "settings/settings_credits_graphics.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "ui/boxes/boost_box.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/controls/emoji_button.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/fields/input_field.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.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_menu_icons.h"
#include "styles/style_premium.h" #include "styles/style_premium.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_widgets.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
@ -206,8 +216,12 @@ auto GenerateGiftMedia(
Element *replacing, Element *replacing,
not_null<PeerData*> recipient, not_null<PeerData*> recipient,
const GiftDetails &data) const GiftDetails &data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> { -> Fn<void(
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) { 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; const auto &descriptor = data.descriptor;
auto pushText = [&]( auto pushText = [&](
TextWithEntities text, TextWithEntities text,
@ -241,18 +255,26 @@ auto GenerateGiftMedia(
replacing, replacing,
sticker, sticker,
st::giftBoxPreviewStickerPadding)); st::giftBoxPreviewStickerPadding));
const auto title = v::match(descriptor, [&](GiftTypePremium gift) { auto title = v::match(descriptor, [&](GiftTypePremium gift) {
return tr::lng_action_gift_premium_months( return tr::lng_action_gift_premium_months(
tr::now, tr::now,
lt_count, lt_count,
gift.months); gift.months,
Text::Bold);
}, [&](const GiftTypeStars &gift) { }, [&](const GiftTypeStars &gift) {
return recipient->isSelf() 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::lng_action_gift_got_subtitle(
tr::now, tr::now,
lt_user, 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) { auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
return tr::lng_action_gift_premium_about( return tr::lng_action_gift_premium_about(
@ -267,8 +289,14 @@ auto GenerateGiftMedia(
? tr::lng_action_gift_self_about_unique( ? tr::lng_action_gift_self_about_unique(
tr::now, tr::now,
Text::RichLangValue) Text::RichLangValue)
: (recipient->isBroadcast() && gift.info.starsToUpgrade)
? tr::lng_action_gift_channel_about_unique(
tr::now,
Text::RichLangValue)
: (recipient->isSelf() : (recipient->isSelf()
? tr::lng_action_gift_self_about ? tr::lng_action_gift_self_about
: recipient->isBroadcast()
? tr::lng_action_gift_channel_about
: tr::lng_action_gift_got_stars_text)( : tr::lng_action_gift_got_stars_text)(
tr::now, tr::now,
lt_count, lt_count,
@ -278,15 +306,20 @@ auto GenerateGiftMedia(
auto description = data.text.empty() auto description = data.text.empty()
? std::move(textFallback) ? std::move(textFallback)
: data.text; : 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( pushText(
std::move(description), std::move(description),
st::giftBoxPreviewTextPadding, st::giftBoxPreviewTextPadding,
{}, {},
Core::MarkedTextContext{ context);
.session = &parent->history()->session(),
.customEmojiRepaint = [parent] { parent->repaint(); },
});
push(HistoryView::MakeGenericButtonPart( push(HistoryView::MakeGenericButtonPart(
(data.upgraded (data.upgraded
@ -475,6 +508,15 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
? tr::lng_action_gift_unique_received(tr::now, lt_user, name) ? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
: _recipient->isSelf() : _recipient->isSelf()
? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost) ? 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::lng_action_gift_received(
tr::now, tr::now,
lt_user, lt_user,
@ -652,6 +694,21 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
for (auto &gift : gifts) { for (auto &gift : gifts) {
list.push_back({ .info = gift }); 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]; auto &map = Map[session];
if (map.last != list) { if (map.last != list) {
map.last = list; map.last = list;
@ -1060,7 +1117,7 @@ void SendGift(
.giftId = gift.info.id, .giftId = gift.info.id,
.randomId = details.randomId, .randomId = details.randomId,
.message = details.text, .message = details.text,
.user = peer->asUser(), .recipient = peer,
.limitedCount = gift.info.limitedCount, .limitedCount = gift.info.limitedCount,
.anonymous = details.anonymous, .anonymous = details.anonymous,
.upgraded = details.upgraded, .upgraded = details.upgraded,
@ -1145,7 +1202,7 @@ void SendStarsFormRequest(
void UpgradeGift( void UpgradeGift(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
MsgId messageId, Data::SavedStarGiftId savedId,
bool keepDetails, bool keepDetails,
int stars, int stars,
Fn<void(Payments::CheckoutResult)> done) { Fn<void(Payments::CheckoutResult)> done) {
@ -1165,7 +1222,7 @@ void UpgradeGift(
using Flag = MTPpayments_UpgradeStarGift::Flag; using Flag = MTPpayments_UpgradeStarGift::Flag;
session->api().request(MTPpayments_UpgradeStarGift( session->api().request(MTPpayments_UpgradeStarGift(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()), MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare) Api::InputSavedStarGiftId(savedId)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result); session->api().applyUpdates(result);
formDone(Payments::CheckoutResult::Paid, &result); formDone(Payments::CheckoutResult::Paid, &result);
@ -1182,7 +1239,7 @@ void UpgradeGift(
window, window,
MTP_inputInvoiceStarGiftUpgrade( MTP_inputInvoiceStarGiftUpgrade(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()), MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare)), Api::InputSavedStarGiftId(savedId)),
std::move(formDone)); std::move(formDone));
} }
@ -1211,7 +1268,7 @@ void AddUpgradeButton(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Main::Session*> session,
int cost, int cost,
QString name, not_null<PeerData*> peer,
Fn<void(bool)> toggled, Fn<void(bool)> toggled,
Fn<void()> preview) { Fn<void()> preview) {
const auto button = container->add( const auto button = container->add(
@ -1258,25 +1315,105 @@ void AddUpgradeButton(
AddSkip(container); AddSkip(container);
const auto about = AddDividerText( const auto about = AddDividerText(
container, container,
tr::lng_gift_send_unique_about( (peer->isBroadcast()
lt_user, ? tr::lng_gift_send_unique_about_channel(
rpl::single(TextWithEntities{ name }), lt_name,
lt_link, rpl::single(TextWithEntities{ peer->name() }),
tr::lng_gift_send_unique_link() | Text::ToLink(), lt_link,
Text::WithEntities)); 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 &...) { about->setClickHandlerFilter([=](const auto &...) {
preview(); preview();
return false; 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( void SendGiftBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api, std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDescriptor &descriptor) { 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->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title()); box->setTitle(tr::lng_gift_send_title());
box->addTopButton(st::boxTitleClose, [=] { box->addTopButton(st::boxTitleClose, [=] {
@ -1373,18 +1510,14 @@ void SendGiftBox(
session, session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (const auto stars = std::get_if<GiftTypeStars>(&descriptor)) { if (stars) {
const auto cost = stars->info.starsToUpgrade; const auto cost = stars->info.starsToUpgrade;
if (cost > 0 && !peer->isSelf()) { if (cost > 0 && !peer->isSelf()) {
const auto user = peer->asUser();
Assert(user != nullptr);
const auto id = stars->info.id; const auto id = stars->info.id;
const auto name = user->shortName();
const auto showing = std::make_shared<bool>(); const auto showing = std::make_shared<bool>();
AddDivider(container); AddDivider(container);
AddSkip(container); AddSkip(container);
AddUpgradeButton(container, session, cost, name, [=](bool on) { AddUpgradeButton(container, session, cost, peer, [=](bool on) {
auto now = state->details.current(); auto now = state->details.current();
now.upgraded = on; now.upgraded = on;
state->details = std::move(now); state->details = std::move(now);
@ -1397,7 +1530,7 @@ void SendGiftBox(
.controller = window, .controller = window,
.stargiftId = id, .stargiftId = id,
.ready = [=](bool) { *showing = false; }, .ready = [=](bool) { *showing = false; },
.user = user, .peer = peer,
.cost = int(cost), .cost = int(cost),
}); });
}); });
@ -1425,6 +1558,8 @@ void SendGiftBox(
}, [&](const GiftTypeStars &) { }, [&](const GiftTypeStars &) {
AddDividerText(container, peer->isSelf() AddDividerText(container, peer->isSelf()
? tr::lng_gift_send_anonymous_self() ? tr::lng_gift_send_anonymous_self()
: peer->isBroadcast()
? tr::lng_gift_send_anonymous_about_channel()
: tr::lng_gift_send_anonymous_about( : tr::lng_gift_send_anonymous_about(
lt_user, lt_user,
rpl::single(peer->shortName()), rpl::single(peer->shortName()),
@ -1454,6 +1589,9 @@ void SendGiftBox(
}; };
SendGift(window, peer, api, details, done); SendGift(window, peer, api, details, done);
}); });
if (limited) {
AddSoldLeftSlider(button, *stars);
}
SetButtonMarkedLabel( SetButtonMarkedLabel(
button, button,
(peer->isSelf() (peer->isSelf()
@ -1704,7 +1842,7 @@ void GiftBox(
window->showSettings(Settings::CreditsId()); window->showSettings(Settings::CreditsId());
return false; return false;
}; };
if (!peer->isSelf()) { if (peer->isUser() && !peer->isSelf()) {
const auto premiumClickHandlerFilter = [=](const auto &...) { const auto premiumClickHandlerFilter = [=](const auto &...) {
Settings::ShowPremium(window, u"gift_send"_q); Settings::ShowPremium(window, u"gift_send"_q);
return false; return false;
@ -1724,9 +1862,16 @@ void GiftBox(
AddBlock(content, window, { AddBlock(content, window, {
.subtitle = (peer->isSelf() .subtitle = (peer->isSelf()
? tr::lng_gift_self_title() ? tr::lng_gift_self_title()
: peer->isBroadcast()
? tr::lng_gift_channel_title()
: tr::lng_gift_stars_subtitle()), : tr::lng_gift_stars_subtitle()),
.about = (peer->isSelf() .about = (peer->isSelf()
? tr::lng_gift_self_about(Text::WithEntities) ? 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( : tr::lng_gift_stars_about(
lt_name, lt_name,
rpl::single(Text::Bold(peer->shortName())), rpl::single(Text::Bold(peer->shortName())),
@ -2112,6 +2257,242 @@ void AddUniqueGiftCover(
}, cover->lifetime()); }, 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 { struct UpgradeArgs : StarGiftUpgradeArgs {
std::vector<Data::UniqueGiftModel> models; std::vector<Data::UniqueGiftModel> models;
std::vector<Data::UniqueGiftPattern> patterns; std::vector<Data::UniqueGiftPattern> patterns;
@ -2162,7 +2543,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
auto &patterns = state->data.patterns; auto &patterns = state->data.patterns;
auto &backdrops = state->data.backdrops; auto &backdrops = state->data.backdrops;
consumer.put_next(Data::UniqueGift{ consumer.put_next(Data::UniqueGift{
.title = (state->data.itemId .title = (state->data.savedId
? tr::lng_gift_upgrade_title(tr::now) ? tr::lng_gift_upgrade_title(tr::now)
: tr::lng_gift_upgrade_preview_title(tr::now)), : tr::lng_gift_upgrade_preview_title(tr::now)),
.model = models[index(state->modelIndices, models)], .model = models[index(state->modelIndices, models)],
@ -2186,11 +2567,13 @@ void AddUpgradeGiftCover(
AddUniqueGiftCover( AddUniqueGiftCover(
container, container,
MakeUpgradeGiftStream(args), MakeUpgradeGiftStream(args),
(args.itemId (args.savedId
? tr::lng_gift_upgrade_about() ? tr::lng_gift_upgrade_about()
: tr::lng_gift_upgrade_preview_about( : (args.peer->isBroadcast()
lt_name, ? tr::lng_gift_upgrade_preview_about_channel
rpl::single(args.user->shortName())))); : tr::lng_gift_upgrade_preview_about)(
lt_name,
rpl::single(args.peer->shortName()))));
} }
void UpgradeBox( void UpgradeBox(
@ -2207,26 +2590,15 @@ void UpgradeBox(
const auto infoRow = [&]( const auto infoRow = [&](
rpl::producer<QString> title, rpl::producer<QString> title,
rpl::producer<QString> text, rpl::producer<QString> text,
not_null<const style::icon*> icon, not_null<const style::icon*> icon) {
bool newBadge = false) {
auto raw = container->add( auto raw = container->add(
object_ptr<Ui::VerticalLayout>(container)); object_ptr<Ui::VerticalLayout>(container));
const auto widget = raw->add( raw->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
raw, raw,
std::move(title) | Ui::Text::ToBold(), std::move(title) | Ui::Text::ToBold(),
st::defaultFlatLabel), st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding); 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( raw->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
raw, raw,
@ -2250,15 +2622,14 @@ void UpgradeBox(
infoRow( infoRow(
tr::lng_gift_upgrade_tradable_title(), tr::lng_gift_upgrade_tradable_title(),
tr::lng_gift_upgrade_tradable_about(), tr::lng_gift_upgrade_tradable_about(),
&st::menuIconTradable, &st::menuIconTradable);
true);
struct State { struct State {
bool sent = false; bool sent = false;
bool preserveDetails = false; bool preserveDetails = false;
}; };
const auto state = std::make_shared<State>(); const auto state = std::make_shared<State>();
const auto preview = !args.itemId; const auto preview = !args.savedId;
if (!preview) { if (!preview) {
const auto skip = st::defaultVerticalListSkip; const auto skip = st::defaultVerticalListSkip;
@ -2303,13 +2674,13 @@ void UpgradeBox(
if (result != Payments::CheckoutResult::Paid) { if (result != Payments::CheckoutResult::Paid) {
state->sent = false; state->sent = false;
} else { } else {
controller->showPeerHistory(args.user); controller->showPeerHistory(args.peer);
if (const auto strong = weak.data()) { if (const auto strong = weak.data()) {
strong->closeBox(); strong->closeBox();
} }
} }
}; };
UpgradeGift(controller, args.itemId, keepDetails, cost, done); UpgradeGift(controller, args.savedId, keepDetails, cost, done);
}); });
if (!preview) { if (!preview) {
auto star = session->data().customEmojiManager().creditsEmoji(); auto star = session->data().customEmojiManager().creditsEmoji();
@ -2339,7 +2710,7 @@ void UpgradeBox(
} }
}, box->lifetime()); }, box->lifetime());
AddUniqueCloseButton(box); AddUniqueCloseButton(box, {});
} }
const std::vector<PatternPoint> &PatternPoints() { const std::vector<PatternPoint> &PatternPoints() {
@ -2458,7 +2829,7 @@ void PaintPoints(
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
const auto weak = base::make_weak(args.controller); 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( session->api().request(MTPpayments_GetStarGiftUpgradePreview(
MTP_long(args.stargiftId) MTP_long(args.stargiftId)
)).done([=](const MTPpayments_StarGiftUpgradePreview &result) { )).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
@ -2494,19 +2865,51 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
}).send(); }).send();
} }
void AddUniqueCloseButton(not_null<GenericBox*> box) { void AddUniqueCloseButton(
const auto button = Ui::CreateChild<IconButton>( not_null<GenericBox*> box,
Settings::CreditsEntryBoxStyleOverrides st,
Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
const auto close = Ui::CreateChild<IconButton>(
box, box,
st::uniqueCloseButton); st::uniqueCloseButton);
button->show(); const auto menu = fillMenu
button->raise(); ? 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) { box->widthValue() | rpl::start_with_next([=](int width) {
button->moveToRight(0, 0, width); close->moveToRight(0, 0, width);
button->raise(); close->raise();
}, button->lifetime()); if (menu) {
button->setClickedCallback([=] { menu->moveToRight(close->width(), 0, width);
menu->raise();
}
}, close->lifetime());
close->setClickedCallback([=] {
box->closeBox(); 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( void RequestStarsFormAndSubmit(
@ -2539,25 +2942,29 @@ void RequestStarsFormAndSubmit(
done(Payments::CheckoutResult::Failed, nullptr); done(Payments::CheckoutResult::Failed, nullptr);
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) { const auto type = error.type();
strong->showToast(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(); }).send();
} }
void ShowGiftTransferredToast( void ShowGiftTransferredToast(
base::weak_ptr<Window::SessionController> weak, base::weak_ptr<Window::SessionController> weak,
not_null<PeerData*> to, not_null<PeerData*> to,
const MTPUpdates &result) { const Data::UniqueGift &gift) {
const auto gift = FindUniqueGift(&to->session(), result); if (const auto strong = weak.get()) {
if (const auto strong = gift ? weak.get() : nullptr) {
strong->showToast({ strong->showToast({
.title = tr::lng_gift_transferred_title(tr::now), .title = tr::lng_gift_transferred_title(tr::now),
.text = tr::lng_gift_transferred_about( .text = tr::lng_gift_transferred_about(
tr::now, tr::now,
lt_name, lt_name,
Text::Bold(Data::UniqueGiftName(*gift)), Text::Bold(Data::UniqueGiftName(gift)),
lt_recipient, lt_recipient,
Text::Bold(to->shortName()), Text::Bold(to->shortName()),
Ui::Text::WithEntities), Ui::Text::WithEntities),

View file

@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/data_star_gift.h"
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data { namespace Data {
struct UniqueGift; struct UniqueGift;
struct GiftCode; struct GiftCode;
@ -17,6 +23,11 @@ namespace Payments {
enum class CheckoutResult; enum class CheckoutResult;
} // namespace Payments } // namespace Payments
namespace Settings {
struct GiftWearBoxStyleOverride;
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
@ -27,6 +38,7 @@ class CustomEmoji;
namespace Ui { namespace Ui {
class PopupMenu;
class GenericBox; class GenericBox;
class VerticalLayout; class VerticalLayout;
@ -41,6 +53,16 @@ void AddUniqueGiftCover(
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
rpl::producer<Data::UniqueGift> data, rpl::producer<Data::UniqueGift> data,
rpl::producer<QString> subtitleOverride = nullptr); 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 { struct PatternPoint {
QPointF position; QPointF position;
@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs {
not_null<Window::SessionController*> controller; not_null<Window::SessionController*> controller;
base::required<uint64> stargiftId; base::required<uint64> stargiftId;
Fn<void(bool)> ready; Fn<void(bool)> ready;
not_null<UserData*> user; not_null<PeerData*> peer;
MsgId itemId = 0; Data::SavedStarGiftId savedId;
int cost = 0; int cost = 0;
bool canAddSender = false; bool canAddSender = false;
bool canAddComment = false; bool canAddComment = false;
@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs {
}; };
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args); 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( void RequestStarsFormAndSubmit(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit(
void ShowGiftTransferredToast( void ShowGiftTransferredToast(
base::weak_ptr<Window::SessionController> weak, base::weak_ptr<Window::SessionController> weak,
not_null<PeerData*> to, not_null<PeerData*> to,
const MTPUpdates &result); const Data::UniqueGift &gift);
} // namespace Ui } // namespace Ui

View file

@ -817,9 +817,7 @@ void StickerSetBox::updateButtons() {
- st.buttonPadding.left() - st.buttonPadding.left()
- st.buttonPadding.left()); - st.buttonPadding.left());
button->setClickedCallback([=] { button->setClickedCallback([=] {
using namespace ChatHelpers; if (const auto window = _show->resolveWindow()) {
const auto usage = WindowUsage::PremiumPromo;
if (const auto window = _show->resolveWindow(usage)) {
Settings::ShowPremium(window, u"animated_emoji"_q); Settings::ShowPremium(window, u"animated_emoji"_q);
} }
}); });

View file

@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/transfer_gift_box.h" #include "boxes/transfer_gift_box.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_cloud_password.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/passcode_box.h"
#include "data/data_session.h"
#include "data/data_star_gift.h" #include "data/data_star_gift.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle. #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/boxes/confirm_box.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" // peerListSingleRow. #include "styles/style_boxes.h" // peerListSingleRow.
#include "styles/style_dialogs.h" // recentPeersSpecialName. #include "styles/style_dialogs.h" // recentPeersSpecialName.
#include "styles/style_layers.h" // boxLabel.
namespace { namespace {
@ -41,13 +47,15 @@ public:
Controller( Controller(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, 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(); void noSearchSubmit();
private: private:
void prepareViewHook() override; void prepareViewHook() override;
void setupExportOption();
bool overrideKeyboardNavigation( bool overrideKeyboardNavigation(
int direction, int direction,
@ -58,34 +66,150 @@ private:
not_null<UserData*> user) override; not_null<UserData*> user) override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
const not_null<Window::SessionController*> _window;
const std::shared_ptr<Data::UniqueGift> _gift; 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; 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( [[nodiscard]] ExportOption MakeExportOption(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerListBox*> box,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId giftId,
TimeId when) { TimeId when) {
struct State {
bool exporting = false;
};
const auto state = std::make_shared<State>();
const auto activate = [=] { const auto activate = [=] {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
const auto weak = Ui::MakeWeak(box);
const auto left = (when > now) ? (when - now) : 0; const auto left = (when > now) ? (when - now) : 0;
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 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({ window->show(Ui::MakeInformBox({
.text = (!hours .text = tr::lng_gift_transfer_unlocks_about(
? tr::lng_gift_transfer_unlocks_update_about() lt_when,
: tr::lng_gift_transfer_unlocks_about( ((hours >= 24)
lt_when, ? tr::lng_gift_transfer_unlocks_when_days(
((hours >= 24) lt_count,
? tr::lng_gift_transfer_unlocks_when_days( rpl::single((hours / 24) * 1.))
lt_count, : tr::lng_gift_transfer_unlocks_when_hours(
rpl::single((hours / 24) * 1.)) lt_count,
: tr::lng_gift_transfer_unlocks_when_hours( rpl::single(hours * 1.)))),
lt_count, .title = tr::lng_gift_transfer_unlocks_title(),
rpl::single(hours * 1.))))),
.title = (!hours
? tr::lng_gift_transfer_unlocks_update_title()
: tr::lng_gift_transfer_unlocks_title()),
})); }));
}; };
@ -130,10 +254,14 @@ private:
const style::PeerListItem &computeSt( const style::PeerListItem &computeSt(
const style::PeerListItem &st) const override { 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: private:
mutable style::PeerListItem _st;
bool _available = false; bool _available = false;
}; };
@ -228,18 +356,27 @@ private:
Controller::Controller( Controller::Controller(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, 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()) : ContactsBoxController(&window->session())
, _window(window)
, _gift(std::move(gift)) , _gift(std::move(gift))
, _giftId(giftId)
, _choose(std::move(choose)) { , _choose(std::move(choose)) {
if (const auto when = _gift->exportAt) { if (_gift->exportAt) {
_exportOption = MakeExportOption(window, when);
}
if (_exportOption.content) {
setStyleOverrides(&st::peerListSmallSkips); 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() { void Controller::noSearchSubmit() {
if (const auto onstack = _exportOption.activate) { if (const auto onstack = _exportOption.activate) {
onstack(); onstack();
@ -258,11 +395,6 @@ void Controller::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_gift_transfer_title( delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
lt_name, lt_name,
rpl::single(UniqueGiftName(*_gift)))); rpl::single(UniqueGiftName(*_gift))));
setupExportOption();
}
void Controller::setupExportOption() {
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
} }
std::unique_ptr<PeerListRow> Controller::createRow( std::unique_ptr<PeerListRow> Controller::createRow(
@ -277,14 +409,18 @@ std::unique_ptr<PeerListRow> Controller::createRow(
} }
void Controller::rowClicked(not_null<PeerListRow*> row) { 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( void TransferGift(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
not_null<PeerData*> to, not_null<PeerData*> to,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
MsgId messageId, Data::SavedStarGiftId savedId,
Fn<void(Payments::CheckoutResult)> done) { Fn<void(Payments::CheckoutResult)> done) {
Expects(to->isUser()); Expects(to->isUser());
@ -293,33 +429,37 @@ void TransferGift(
auto formDone = [=]( auto formDone = [=](
Payments::CheckoutResult result, Payments::CheckoutResult result,
const MTPUpdates *updates) { const MTPUpdates *updates) {
if (result == Payments::CheckoutResult::Paid && updates) { done(result);
if (result == Payments::CheckoutResult::Paid) {
if (const auto strong = weak.get()) { 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) { if (gift->starsForTransfer <= 0) {
session->api().request(MTPpayments_TransferStarGift( session->api().request(MTPpayments_TransferStarGift(
MTP_int(messageId.bare), Api::InputSavedStarGiftId(savedId),
to->asUser()->inputUser to->input
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result); session->api().applyUpdates(result);
formDone(Payments::CheckoutResult::Paid, &result); formDone(Payments::CheckoutResult::Paid, &result);
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
formDone(Payments::CheckoutResult::Failed, nullptr);
if (const auto strong = weak.get()) { if (const auto strong = weak.get()) {
strong->showToast(error.type()); strong->showToast(error.type());
} }
formDone(Payments::CheckoutResult::Failed, nullptr);
}).send(); }).send();
return; return;
} }
Ui::RequestStarsFormAndSubmit( Ui::RequestStarsFormAndSubmit(
window, window,
MTP_inputInvoiceStarGiftTransfer( MTP_inputInvoiceStarGiftTransfer(
MTP_int(messageId.bare), Api::InputSavedStarGiftId(savedId),
to->asUser()->inputUser), to->input),
std::move(formDone)); std::move(formDone));
} }
@ -327,7 +467,8 @@ void ShowTransferToBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) { Data::SavedStarGiftId savedId,
Fn<void()> closeParentBox) {
const auto stars = gift->starsForTransfer; const auto stars = gift->starsForTransfer;
controller->show(Box([=](not_null<Ui::GenericBox*> box) { controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_gift_transfer_title( box->setTitle(tr::lng_gift_transfer_title(
@ -353,16 +494,24 @@ void ShowTransferToBox(
state->sent = true; state->sent = true;
const auto weak = Ui::MakeWeak(box); const auto weak = Ui::MakeWeak(box);
const auto done = [=](Payments::CheckoutResult result) { 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; state->sent = false;
} else { } else {
controller->showPeerHistory(peer); if (savedId.isUser()) {
controller->showPeerHistory(peer);
}
closeParentBox();
if (const auto strong = weak.data()) { if (const auto strong = weak.data()) {
strong->closeBox(); strong->closeBox();
} }
} }
}; };
TransferGift(controller, peer, gift, msgId, done); TransferGift(controller, peer, gift, savedId, done);
}; };
Ui::ConfirmBox(box, { Ui::ConfirmBox(box, {
@ -395,15 +544,18 @@ void ShowTransferToBox(
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) { Data::SavedStarGiftId savedId) {
auto controller = std::make_unique<Controller>( auto controller = std::make_unique<Controller>(
window, window,
gift, gift,
[=](not_null<PeerData*> peer) { savedId,
ShowTransferToBox(window, peer, gift, msgId); [=](not_null<PeerData*> peer, Fn<void()> done) {
ShowTransferToBox(window, peer, gift, savedId, done);
}); });
const auto controllerRaw = controller.get(); const auto controllerRaw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) { auto initBox = [=](not_null<PeerListBox*> box) {
controllerRaw->init(box);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->noSearchSubmits() | rpl::start_with_next([=] { box->noSearchSubmits() | rpl::start_with_next([=] {

View file

@ -13,9 +13,10 @@ class SessionController;
namespace Data { namespace Data {
struct UniqueGift; struct UniqueGift;
class SavedStarGiftId;
} // namespace Data } // namespace Data
void ShowTransferGiftBox( void ShowTransferGiftBox(
not_null<Window::SessionController*> window, not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId); Data::SavedStarGiftId savedId);

View file

@ -1441,6 +1441,19 @@ groupCallScheduleTimeField: InputField(groupCallScheduleDateField) {
placeholderFont: font(14px); 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 }}; groupCallCalendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", groupCallMemberNotJoinedStatus }};
groupCallCalendarNextDisabled: icon {{ "calendar_down", groupCallMemberNotJoinedStatus }}; groupCallCalendarNextDisabled: icon {{ "calendar_down", groupCallMemberNotJoinedStatus }};

View file

@ -1431,10 +1431,12 @@ void Members::Controller::addMuteActionsToContextMenu(
auto volumeItem = base::make_unique_q<MenuVolumeItem>( auto volumeItem = base::make_unique_q<MenuVolumeItem>(
menu->menu(), menu->menu(),
st::groupCallPopupVolumeMenu, st::groupCallPopupVolumeMenu,
st::groupCallMenuVolumeSlider,
otherParticipantStateValue, otherParticipantStateValue,
_call->rtmp() ? _call->rtmpVolume() : row->volume(), _call->rtmp() ? _call->rtmpVolume() : row->volume(),
Group::kMaxVolume, Group::kMaxVolume,
muted); muted,
st::groupCallMenuVolumePadding);
mutesFromVolume = volumeItem->toggleMuteRequests(); mutesFromVolume = volumeItem->toggleMuteRequests();

View file

@ -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_menu.h" // LeaveBox.
#include "calls/group/calls_group_common.h" #include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h" #include "calls/group/calls_choose_join_as.h"
#include "calls/group/calls_volume_item.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "ui/widgets/level_meter.h" #include "ui/widgets/level_meter.h"
#include "ui/widgets/continuous_sliders.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_audio_input_tester.h"
#include "webrtc/webrtc_device_resolver.h" #include "webrtc/webrtc_device_resolver.h"
#include "settings/settings_calls.h" #include "settings/settings_calls.h"
#include "settings/settings_credits_graphics.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_invite_links.h" #include "api/api_invite_links.h"
@ -183,22 +185,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
return Data::CanSend(thread, ChatRestriction::SendOther); return Data::CanSend(thread, ChatRestriction::SendOther);
}; };
const auto scheduleStyle = [&] { const auto st = ::Settings::DarkCreditsEntryBoxStyle();
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;
};
auto result = Box<ShareBox>(ShareBox::Descriptor{ auto result = Box<ShareBox>(ShareBox::Descriptor{
.session = &peer->session(), .session = &peer->session(),
.copyCallback = std::move(copyCallback), .copyCallback = std::move(copyCallback),
@ -211,11 +198,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
: rpl::single(false)), : rpl::single(false)),
tr::lng_group_call_copy_speaker_link(), tr::lng_group_call_copy_speaker_link(),
tr::lng_group_call_copy_listener_link()), tr::lng_group_call_copy_listener_link()),
.stMultiSelect = &st::groupCallMultiSelect, .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
.stComment = &st::groupCallShareBoxComment,
.st = &st::groupCallShareBoxList,
.stLabel = &st::groupCallField,
.scheduleBoxStyle = scheduleStyle(),
.premiumRequiredError = SharePremiumRequiredError(), .premiumRequiredError = SharePremiumRequiredError(),
}); });
*box = result.data(); *box = result.data();
@ -731,6 +714,50 @@ void SettingsBox(
addDivider(); addDivider();
Ui::AddSkip(layout); 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()) { if (peer->canManageGroupCall()) {
layout->add(object_ptr<Ui::SettingsButton>( layout->add(object_ptr<Ui::SettingsButton>(

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animation_value.h" #include "ui/effects/animation_value.h"
#include "ui/effects/cross_line.h" #include "ui/effects/cross_line.h"
#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/continuous_sliders.h"
#include "ui/rect.h"
#include "styles/style_calls.h" #include "styles/style_calls.h"
#include "ui/paint/arcs.h" #include "ui/paint/arcs.h"
@ -41,20 +42,21 @@ constexpr auto kVolumeStickedValues
MenuVolumeItem::MenuVolumeItem( MenuVolumeItem::MenuVolumeItem(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::Menu &st, const style::Menu &st,
const style::MediaSlider &stSlider,
rpl::producer<Group::ParticipantState> participantState, rpl::producer<Group::ParticipantState> participantState,
int startVolume, int startVolume,
int maxVolume, int maxVolume,
bool muted) bool muted,
const QMargins &padding)
: Ui::Menu::ItemBase(parent, st) : Ui::Menu::ItemBase(parent, st)
, _maxVolume(maxVolume) , _maxVolume(maxVolume)
, _cloudMuted(muted) , _cloudMuted(muted)
, _localMuted(muted) , _localMuted(muted)
, _slider(base::make_unique_q<Ui::MediaSlider>( , _slider(base::make_unique_q<Ui::MediaSlider>(this, stSlider))
this,
st::groupCallMenuVolumeSlider))
, _dummyAction(new QAction(parent)) , _dummyAction(new QAction(parent))
, _st(st) , _st(st)
, _stCross(st::groupCallMuteCrossLine) , _stCross(st::groupCallMuteCrossLine)
, _padding(padding)
, _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true)) , _crossLineMute(std::make_unique<Ui::CrossLineAnimation>(_stCross, true))
, _arcs(std::make_unique<Ui::Paint::ArcsAnimation>( , _arcs(std::make_unique<Ui::Paint::ArcsAnimation>(
st::groupCallSpeakerArcsAnimation, st::groupCallSpeakerArcsAnimation,
@ -71,7 +73,7 @@ MenuVolumeItem::MenuVolumeItem(
sizeValue( sizeValue(
) | rpl::start_with_next([=](const QSize &size) { ) | rpl::start_with_next([=](const QSize &size) {
const auto geometry = QRect(QPoint(), size); const auto geometry = QRect(QPoint(), size);
_itemRect = geometry - st::groupCallMenuVolumePadding; _itemRect = geometry - _padding;
_speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size()); _speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size());
_arcPosition = _speakerRect.center() _arcPosition = _speakerRect.center()
+ QPoint(0, st::groupCallMenuSpeakerArcsSkip); + QPoint(0, st::groupCallMenuSpeakerArcsSkip);
@ -280,9 +282,7 @@ bool MenuVolumeItem::isEnabled() const {
} }
int MenuVolumeItem::contentHeight() const { int MenuVolumeItem::contentHeight() const {
return st::groupCallMenuVolumePadding.top() return rect::m::sum::v(_padding) + _stCross.icon.height();
+ st::groupCallMenuVolumePadding.bottom()
+ _stCross.icon.height();
} }
rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const { rpl::producer<bool> MenuVolumeItem::toggleMuteRequests() const {

View file

@ -31,10 +31,12 @@ public:
MenuVolumeItem( MenuVolumeItem(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::Menu &st, const style::Menu &st,
const style::MediaSlider &stSlider,
rpl::producer<Group::ParticipantState> participantState, rpl::producer<Group::ParticipantState> participantState,
int startVolume, int startVolume,
int maxVolume, int maxVolume,
bool muted); bool muted,
const QMargins &padding);
not_null<QAction*> action() const override; not_null<QAction*> action() const override;
bool isEnabled() const override; bool isEnabled() const override;
@ -71,6 +73,7 @@ private:
const not_null<QAction*> _dummyAction; const not_null<QAction*> _dummyAction;
const style::Menu &_st; const style::Menu &_st;
const style::CrossLineAnimation &_stCross; const style::CrossLineAnimation &_stCross;
const QMargins &_padding;
const std::unique_ptr<Ui::CrossLineAnimation> _crossLineMute; const std::unique_ptr<Ui::CrossLineAnimation> _crossLineMute;
Ui::Animations::Simple _crossLineAnimation; Ui::Animations::Simple _crossLineAnimation;

View file

@ -40,6 +40,7 @@ TabbedSearch {
ComposeIcons { ComposeIcons {
settings: icon; settings: icon;
collectibles: icon;
recent: icon; recent: icon;
recentActive: icon; recentActive: icon;
@ -587,6 +588,7 @@ sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roun
defaultComposeIcons: ComposeIcons { defaultComposeIcons: ComposeIcons {
settings: icon {{ "emoji/emoji_settings", emojiIconFg }}; settings: icon {{ "emoji/emoji_settings", emojiIconFg }};
collectibles: icon {{ "menu/unique", emojiIconFg }};
recent: icon {{ "emoji/emoji_recent", emojiIconFg }}; recent: icon {{ "emoji/emoji_recent", emojiIconFg }};
recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }}; recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }};

View file

@ -18,6 +18,7 @@ struct ComposeFeatures {
bool attachBotsMenu : 1 = true; bool attachBotsMenu : 1 = true;
bool inlineBots : 1 = true; bool inlineBots : 1 = true;
bool megagroupSet : 1 = true; bool megagroupSet : 1 = true;
bool collectibleStatus : 1 = false;
bool stickersSettings : 1 = true; bool stickersSettings : 1 = true;
bool openStickerSets : 1 = true; bool openStickerSets : 1 = true;
bool autocompleteHashtags : 1 = true; bool autocompleteHashtags : 1 = true;

View file

@ -19,7 +19,7 @@ rpl::producer<bool> Show::adjustShadowLeft() const {
} }
ResolveWindow ResolveWindowDefault() { ResolveWindow ResolveWindowDefault() {
return [](not_null<Main::Session*> session, WindowUsage usage) return [](not_null<Main::Session*> session)
-> Window::SessionController* { -> Window::SessionController* {
const auto check = [&](Window::Controller *window) { const auto check = [&](Window::Controller *window) {
if (const auto controller = window->sessionController()) { if (const auto controller = window->sessionController()) {
@ -45,8 +45,8 @@ ResolveWindow ResolveWindowDefault() {
}; };
} }
Window::SessionController *Show::resolveWindow(WindowUsage usage) const { Window::SessionController *Show::resolveWindow() const {
return ResolveWindowDefault()(&session(), usage); return ResolveWindowDefault()(&session());
} }
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -40,13 +40,8 @@ enum class PauseReason {
using PauseReasons = base::flags<PauseReason>; using PauseReasons = base::flags<PauseReason>;
inline constexpr bool is_flag_type(PauseReason) { return true; }; inline constexpr bool is_flag_type(PauseReason) { return true; };
enum class WindowUsage {
PremiumPromo,
};
using ResolveWindow = Fn<Window::SessionController*( using ResolveWindow = Fn<Window::SessionController*(
not_null<Main::Session*>, not_null<Main::Session*>)>;
WindowUsage)>;
[[nodiscard]] ResolveWindow ResolveWindowDefault(); [[nodiscard]] ResolveWindow ResolveWindowDefault();
class Show : public Main::SessionShow { class Show : public Main::SessionShow {
@ -68,8 +63,7 @@ public:
virtual void processChosenSticker(FileChosen &&chosen) const = 0; virtual void processChosenSticker(FileChosen &&chosen) const = 0;
[[nodiscard]] virtual Window::SessionController *resolveWindow( [[nodiscard]] virtual Window::SessionController *resolveWindow() const;
WindowUsage) const;
}; };
} // namespace ChatHelpers } // namespace ChatHelpers

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/sticker_set_box.h" #include "boxes/sticker_set_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "layout/layout_position.h" #include "layout/layout_position.h"
#include "data/data_emoji_statuses.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -134,6 +135,7 @@ struct EmojiListWidget::CustomEmojiInstance {
}; };
struct EmojiListWidget::RecentOne { struct EmojiListWidget::RecentOne {
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
Ui::Text::CustomEmoji *custom = nullptr; Ui::Text::CustomEmoji *custom = nullptr;
RecentEmojiId id; RecentEmojiId id;
mutable QImage premiumLock; mutable QImage premiumLock;
@ -447,6 +449,13 @@ void EmojiColorPicker::drawVariant(QPainter &p, int variant) {
w.y() + _innerPosition.y()); 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( EmojiListWidget::EmojiListWidget(
QWidget *parent, QWidget *parent,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
@ -510,6 +519,11 @@ EmojiListWidget::EmojiListWidget(
refreshCustom(); refreshCustom();
} }
}, lifetime()); }, lifetime());
} else if (_mode == Mode::EmojiStatus && _features.collectibleStatus) {
session().data().emojiStatuses().collectiblesUpdates(
) | rpl::start_with_next([=] {
refreshCustom();
}, lifetime());
} }
_customSingleSize = Data::FrameSizeFromTag( _customSingleSize = Data::FrameSizeFromTag(
@ -656,7 +670,8 @@ void EmojiListWidget::applyNextSearchQuery() {
void EmojiListWidget::showPreview() { void EmojiListWidget::showPreview() {
if (const auto over = std::get_if<OverEmoji>(&_pressed)) { if (const auto over = std::get_if<OverEmoji>(&_pressed)) {
if (const auto custom = lookupCustomEmoji(over)) { if (const auto custom = lookupCustomEmoji(over)) {
_show->showMediaPreview(custom->stickerSetOrigin(), custom); const auto document = custom.document;
_show->showMediaPreview(document->stickerSetOrigin(), document);
_previewShown = true; _previewShown = true;
} }
} }
@ -706,7 +721,7 @@ void EmojiListWidget::appendPremiumSearchResults() {
} }
void EmojiListWidget::provideRecent( void EmojiListWidget::provideRecent(
const std::vector<DocumentId> &customRecentList) { const std::vector<EmojiStatusId> &customRecentList) {
clearSelection(); clearSelection();
fillRecentFrom(customRecentList); fillRecentFrom(customRecentList);
resizeToWidth(width()); 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(); const auto test = session().isTestMode();
_recent.clear(); _recent.clear();
_recent.reserve(list.size()); _recent.reserve(list.size());
@ -1114,10 +1130,15 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
_recentCustomIds.emplace(fakeId); _recentCustomIds.emplace(fakeId);
} else { } else {
_recent.push_back({ _recent.push_back({
.collectible = id.collectible,
.custom = resolveCustomRecent(id), .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 over = OverEmoji{ section, index };
const auto emoji = lookupOverEmoji(&over); const auto emoji = lookupOverEmoji(&over);
const auto custom = lookupCustomEmoji(&over); const auto custom = lookupCustomEmoji(&over);
if (custom && custom->sticker()) { if (custom.collectible) {
const auto sticker = custom->sticker(); return;
}
const auto document = custom.document;
if (document && document->sticker()) {
const auto sticker = document->sticker();
const auto emoji = sticker->alt; const auto emoji = sticker->alt;
const auto setId = sticker->set.id; const auto setId = sticker->set.id;
if (!emoji.isEmpty()) { if (!emoji.isEmpty()) {
@ -1168,7 +1193,7 @@ void EmojiListWidget::fillRecentMenu(
EntityType::CustomEmoji, EntityType::CustomEmoji,
0, 0,
int(emoji.size()), int(emoji.size()),
Data::SerializeCustomEmojiId(custom) Data::SerializeCustomEmojiId(document)
}); });
addAction(tr::lng_emoji_copy(tr::now), [=] { addAction(tr::lng_emoji_copy(tr::now), [=] {
TextUtilities::SetClipboardText(data); TextUtilities::SetClipboardText(data);
@ -1192,8 +1217,8 @@ void EmojiListWidget::fillRecentMenu(
auto id = RecentEmojiId{ emoji }; auto id = RecentEmojiId{ emoji };
if (custom) { if (custom) {
id.data = RecentEmojiDocument{ id.data = RecentEmojiDocument{
.id = custom->id, .id = custom.document->id,
.test = custom->session().isTestMode(), .test = custom.document->session().isTestMode(),
}; };
} }
addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] { addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] {
@ -1229,7 +1254,7 @@ void EmojiListWidget::fillEmojiStatusMenu(
int section, int section,
int index) { int index) {
const auto chosen = lookupCustomEmoji(index, section); const auto chosen = lookupCustomEmoji(index, section);
if (!chosen) { if (!chosen || chosen.collectible) {
return; return;
} }
const auto selectWith = [=](TimeId scheduled) { const auto selectWith = [=](TimeId scheduled) {
@ -1573,12 +1598,14 @@ bool EmojiListWidget::checkPickerHide() {
return false; return false;
} }
DocumentData *EmojiListWidget::lookupCustomEmoji( EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji(
const OverEmoji *over) const { 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 index,
int section) const { int section) const {
if (_searchMode) { if (_searchMode) {
@ -1586,22 +1613,30 @@ DocumentData *EmojiListWidget::lookupCustomEmoji(
const auto document = std::get_if<RecentEmojiDocument>( const auto document = std::get_if<RecentEmojiDocument>(
&_searchResults[index].id.data); &_searchResults[index].id.data);
if (document) { 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()) { } 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>( const auto document = std::get_if<RecentEmojiDocument>(
&_recent[index].id.data); &recent.id.data);
if (document) { if (document) {
return session().data().document(document->id); return { session().data().document(document->id) };
} }
} else if (section >= _staticCount } else if (section >= _staticCount
&& index < _custom[section - _staticCount].list.size()) { && index < _custom[section - _staticCount].list.size()) {
auto &set = _custom[section - _staticCount]; 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 { EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
@ -1643,9 +1678,11 @@ EmojiChosen EmojiListWidget::lookupChosen(
} }
FileChosen EmojiListWidget::lookupChosen( FileChosen EmojiListWidget::lookupChosen(
not_null<DocumentData*> custom, ResolvedCustom custom,
const OverEmoji *over, const OverEmoji *over,
Api::SendOptions options) { Api::SendOptions options) {
Expects(custom.document != nullptr);
_grabbingChosen = true; _grabbingChosen = true;
const auto guard = gsl::finally([&] { _grabbingChosen = false; }); const auto guard = gsl::finally([&] { _grabbingChosen = false; });
const auto rect = over ? emojiRect(over->section, over->index) : QRect(); const auto rect = over ? emojiRect(over->section, over->index) : QRect();
@ -1655,13 +1692,14 @@ FileChosen EmojiListWidget::lookupChosen(
) : QRect(); ) : QRect();
return { return {
.document = custom, .document = custom.document,
.options = options, .options = options,
.messageSendingFrom = { .messageSendingFrom = {
.type = Ui::MessageSendingAnimationFrom::Type::Emoji, .type = Ui::MessageSendingAnimationFrom::Type::Emoji,
.globalStartGeometry = over ? mapToGlobal(emoji) : QRect(), .globalStartGeometry = over ? mapToGlobal(emoji) : QRect(),
.frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(), .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) const auto id = hasColorButton(button->section)
? 0 ? 0
: _custom[button->section - _staticCount].id; : _custom[button->section - _staticCount].id;
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (hasColorButton(button->section)) { if (hasColorButton(button->section)) {
_pickerSelected = pressed; _pickerSelected = pressed;
showPicker(); showPicker();
@ -1759,7 +1796,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
removeSet(id); removeSet(id);
} else if (hasAddButton(button->section)) { } else if (hasAddButton(button->section)) {
_localSetsManager->install(id); _localSetsManager->install(id);
} else if (const auto resolved = _show->resolveWindow(usage)) { } else if (const auto resolved = _show->resolveWindow()) {
_jumpedToPremium.fire({}); _jumpedToPremium.fire({});
switch (_mode) { switch (_mode) {
case Mode::Full: case Mode::Full:
@ -1792,6 +1829,8 @@ void EmojiListWidget::displaySet(uint64 setId) {
} else { } else {
return; return;
} }
} else if (setId == Data::Stickers::CollectibleSetId) {
return;
} }
const auto &sets = session().data().stickers().sets(); const auto &sets = session().data().stickers().sets();
auto it = sets.find(setId); auto it = sets.find(setId);
@ -1830,6 +1869,7 @@ void EmojiListWidget::removeSet(uint64 setId) {
Assert(i != end(_custom)); Assert(i != end(_custom));
const auto removeLocally = !_megagroupSet->canEditEmoji(); const auto removeLocally = !_megagroupSet->canEditEmoji();
removeMegagroupSet(removeLocally); removeMegagroupSet(removeLocally);
} else if (setId == Data::Stickers::CollectibleSetId) {
} else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { } else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) {
checkHideWithBox(std::move(box)); checkHideWithBox(std::move(box));
} }
@ -1933,6 +1973,8 @@ bool EmojiListWidget::hasRemoveButton(int index) const {
return true; return true;
} }
return !set.list.empty() && _megagroupSet->canEditEmoji(); return !set.list.empty() && _megagroupSet->canEditEmoji();
} else if (set.id == Data::Stickers::CollectibleSetId) {
return false;
} }
return set.canRemove && !set.premiumRequired; return set.canRemove && !set.premiumRequired;
} }
@ -1962,7 +2004,8 @@ bool EmojiListWidget::hasAddButton(int index) const {
const auto &set = _custom[index - _staticCount]; const auto &set = _custom[index - _staticCount];
return !set.canRemove return !set.canRemove
&& !set.premiumRequired && !set.premiumRequired
&& set.id != Data::Stickers::MegagroupSetId; && set.id != Data::Stickers::MegagroupSetId
&& set.id != Data::Stickers::CollectibleSetId;
} }
QRect EmojiListWidget::addButtonRect(int index) const { QRect EmojiListWidget::addButtonRect(int index) const {
@ -1991,8 +2034,9 @@ bool EmojiListWidget::hasButton(int index) const {
} else if (index >= _staticCount } else if (index >= _staticCount
&& index < _staticCount + _custom.size()) { && index < _staticCount + _custom.size()) {
const auto &custom = _custom[index - _staticCount]; const auto &custom = _custom[index - _staticCount];
return (custom.id != Data::Stickers::MegagroupSetId) return (custom.id != Data::Stickers::CollectibleSetId)
|| custom.canRemove; && ((custom.id != Data::Stickers::MegagroupSetId)
|| custom.canRemove);
} }
return false; return false;
} }
@ -2020,6 +2064,7 @@ QRect EmojiListWidget::buttonRect(
auto EmojiListWidget::rightButton(int index) const -> const RightButton & { auto EmojiListWidget::rightButton(int index) const -> const RightButton & {
Expects(index >= _staticCount Expects(index >= _staticCount
&& index < _staticCount + _custom.size()); && index < _staticCount + _custom.size());
return hasAddButton(index) return hasAddButton(index)
? _add ? _add
: _custom[index - _staticCount].canRemove : _custom[index - _staticCount].canRemove
@ -2279,11 +2324,12 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>(); auto set = std::vector<CustomOne>();
set.reserve(list.size()); set.reserve(list.size());
for (const auto document : list) { for (const auto document : list) {
if (_restrictedCustomList.contains(document->id)) { const auto id = EmojiStatusId{ document->id };
if (_restrictedCustomList.contains(id.documentId)) {
continue; continue;
} else if (const auto sticker = document->sticker()) { } else if (const auto sticker = document->sticker()) {
set.push_back({ set.push_back({
.custom = resolveCustomEmoji(document, lookupId), .custom = resolveCustomEmoji(id, document, lookupId),
.document = document, .document = document,
.emoji = Ui::Emoji::Find(sticker->alt), .emoji = Ui::Emoji::Find(sticker->alt),
}); });
@ -2305,6 +2351,7 @@ void EmojiListWidget::refreshCustom() {
.premiumRequired = premium && premiumMayBeBought, .premiumRequired = premium && premiumMayBeBought,
}); });
}; };
refreshEmojiStatusCollectibles();
refreshMegagroupStickers(push, GroupStickersPlace::Visible); refreshMegagroupStickers(push, GroupStickersPlace::Visible);
for (const auto setId : owner->stickers().emojiSetsOrder()) { for (const auto setId : owner->stickers().emojiSetsOrder()) {
push(setId, true); push(setId, true);
@ -2337,18 +2384,17 @@ Fn<void()> EmojiListWidget::repaintCallback(
} }
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji( not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
EmojiStatusId id,
not_null<DocumentData*> document, not_null<DocumentData*> document,
uint64 setId) { uint64 setId) {
Expects(document->sticker() != nullptr);
const auto documentId = document->id; 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; const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly;
if (i != end(_customEmoji) && !recentOnly) { if (i != end(_customEmoji) && !recentOnly) {
return i->second.emoji.get(); return i->second.emoji.get();
} }
auto instance = document->owner().customEmojiManager().create( auto instance = document->owner().customEmojiManager().create(
document, Data::EmojiStatusCustomId(id),
repaintCallback(documentId, setId), repaintCallback(documentId, setId),
Data::CustomEmojiManager::SizeTag::Large); Data::CustomEmojiManager::SizeTag::Large);
if (recentOnly) { if (recentOnly) {
@ -2362,7 +2408,7 @@ not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomEmoji(
return i->second.emoji.get(); return i->second.emoji.get();
} }
return _customEmoji.emplace( return _customEmoji.emplace(
documentId, id,
CustomEmojiInstance{ .emoji = std::move(instance) } CustomEmojiInstance{ .emoji = std::move(instance) }
).first->second.emoji.get(); ).first->second.emoji.get();
} }
@ -2380,31 +2426,78 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent(
not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent( not_null<Ui::Text::CustomEmoji*> EmojiListWidget::resolveCustomRecent(
DocumentId documentId) { 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)) { if (i != end(_customRecent)) {
return i->second.get(); return i->second.get();
} }
const auto j = _customEmoji.find(documentId); const auto j = _customEmoji.find(id);
if (j != end(_customEmoji)) { if (j != end(_customEmoji)) {
return j->second.emoji.get(); return j->second.emoji.get();
} }
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId()); auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId());
if (_customRecentFactory) { if (_customRecentFactory && !id.collectible) {
return _customRecent.emplace( return _customRecent.emplace(
documentId, id.documentId,
_customRecentFactory(documentId, std::move(repaint)) _customRecentFactory(id.documentId, std::move(repaint))
).first->second.get(); ).first->second.get();
} }
auto custom = session().data().customEmojiManager().create( auto custom = session().data().customEmojiManager().create(
documentId, Data::EmojiStatusCustomId(id),
std::move(repaint), std::move(repaint),
Data::CustomEmojiManager::SizeTag::Large); Data::CustomEmojiManager::SizeTag::Large);
return _customEmoji.emplace( return _customEmoji.emplace(
documentId, id,
CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true } CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true }
).first->second.emoji.get(); ).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( void EmojiListWidget::refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push, Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place) { GroupStickersPlace place) {
@ -2639,8 +2732,11 @@ void EmojiListWidget::setSelected(OverState newSelected) {
} else if (_previewShown && _pressed != _selected) { } else if (_previewShown && _pressed != _selected) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) { if (const auto over = std::get_if<OverEmoji>(&_selected)) {
if (const auto custom = lookupCustomEmoji(over)) { if (const auto custom = lookupCustomEmoji(over)) {
const auto document = custom.document;
_pressed = _selected; _pressed = _selected;
_show->showMediaPreview(custom->stickerSetOrigin(), custom); _show->showMediaPreview(
document->stickerSetOrigin(),
document);
} }
} }
} }

View file

@ -83,12 +83,15 @@ enum class EmojiListMode {
MessageEffects, MessageEffects,
}; };
[[nodiscard]] std::vector<EmojiStatusId> DocumentListToRecent(
const std::vector<DocumentId> &documents);
struct EmojiListDescriptor { struct EmojiListDescriptor {
std::shared_ptr<Show> show; std::shared_ptr<Show> show;
EmojiListMode mode = EmojiListMode::Full; EmojiListMode mode = EmojiListMode::Full;
Fn<QColor()> customTextColor; Fn<QColor()> customTextColor;
Fn<bool()> paused; Fn<bool()> paused;
std::vector<DocumentId> customRecentList; std::vector<EmojiStatusId> customRecentList;
Fn<std::unique_ptr<Ui::Text::CustomEmoji>( Fn<std::unique_ptr<Ui::Text::CustomEmoji>(
DocumentId, DocumentId,
Fn<void()>)> customRecentFactory; Fn<void()>)> customRecentFactory;
@ -137,7 +140,7 @@ public:
[[nodiscard]] rpl::producer<> jumpedToPremium() const; [[nodiscard]] rpl::producer<> jumpedToPremium() const;
[[nodiscard]] rpl::producer<> escapes() const; [[nodiscard]] rpl::producer<> escapes() const;
void provideRecent(const std::vector<DocumentId> &customRecentList); void provideRecent(const std::vector<EmojiStatusId> &customRecentList);
void prepareExpanding(); void prepareExpanding();
void paintExpanding( void paintExpanding(
@ -186,6 +189,7 @@ private:
bool collapsed = false; bool collapsed = false;
}; };
struct CustomOne { struct CustomOne {
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
not_null<Ui::Text::CustomEmoji*> custom; not_null<Ui::Text::CustomEmoji*> custom;
not_null<DocumentData*> document; not_null<DocumentData*> document;
EmojiPtr emoji = nullptr; EmojiPtr emoji = nullptr;
@ -253,6 +257,14 @@ private:
int finalHeight = 0; int finalHeight = 0;
bool expanding = false; bool expanding = false;
}; };
struct ResolvedCustom {
DocumentData *document = nullptr;
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
explicit operator bool() const {
return document != nullptr;
}
};
template <typename Callback> template <typename Callback>
bool enumerateSections(Callback callback) const; bool enumerateSections(Callback callback) const;
@ -271,6 +283,7 @@ private:
Visible, Visible,
Hidden, Hidden,
}; };
void refreshEmojiStatusCollectibles();
void refreshMegagroupStickers( void refreshMegagroupStickers(
Fn<void(uint64 setId, bool installed)> push, Fn<void(uint64 setId, bool installed)> push,
GroupStickersPlace place); GroupStickersPlace place);
@ -296,16 +309,16 @@ private:
int index); int index);
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji( [[nodiscard]] ResolvedCustom lookupCustomEmoji(
const OverEmoji *over) const; const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji( [[nodiscard]] ResolvedCustom lookupCustomEmoji(
int index, int index,
int section) const; int section) const;
[[nodiscard]] EmojiChosen lookupChosen( [[nodiscard]] EmojiChosen lookupChosen(
EmojiPtr emoji, EmojiPtr emoji,
not_null<const OverEmoji*> over); not_null<const OverEmoji*> over);
[[nodiscard]] FileChosen lookupChosen( [[nodiscard]] FileChosen lookupChosen(
not_null<DocumentData*> custom, ResolvedCustom custom,
const OverEmoji *over, const OverEmoji *over,
Api::SendOptions options = Api::SendOptions()); Api::SendOptions options = Api::SendOptions());
void selectEmoji(EmojiChosen data); void selectEmoji(EmojiChosen data);
@ -370,14 +383,17 @@ private:
void repaintCustom(uint64 setId); void repaintCustom(uint64 setId);
void fillRecent(); void fillRecent();
void fillRecentFrom(const std::vector<DocumentId> &list); void fillRecentFrom(const std::vector<EmojiStatusId> &list);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji( [[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomEmoji(
EmojiStatusId id,
not_null<DocumentData*> document, not_null<DocumentData*> document,
uint64 setId); uint64 setId);
[[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent( [[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent(
Core::RecentEmojiId customId); Core::RecentEmojiId customId);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent( [[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
DocumentId documentId); DocumentId documentId);
[[nodiscard]] not_null<Ui::Text::CustomEmoji*> resolveCustomRecent(
EmojiStatusId id);
[[nodiscard]] Fn<void()> repaintCallback( [[nodiscard]] Fn<void()> repaintCallback(
DocumentId documentId, DocumentId documentId,
uint64 setId); uint64 setId);
@ -415,7 +431,7 @@ private:
QVector<EmojiPtr> _emoji[kEmojiSectionCount]; QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom; std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList; base::flat_set<DocumentId> _restrictedCustomList;
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji; base::flat_map<EmojiStatusId, CustomEmojiInstance> _customEmoji;
base::flat_map< base::flat_map<
DocumentId, DocumentId,
std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent; std::unique_ptr<Ui::Text::CustomEmoji>> _customRecent;

View file

@ -123,9 +123,9 @@ constexpr auto kLinkProtocols = {
void EditLinkBox( void EditLinkBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
const QString &startText, const TextWithTags &startText,
const QString &startLink, const QString &startLink,
Fn<void(QString, QString)> callback, Fn<void(TextWithTags, QString)> callback,
const style::InputField *fieldStyle, const style::InputField *fieldStyle,
Fn<QString(QString)> validate) { Fn<QString(QString)> validate) {
Expects(callback != nullptr); Expects(callback != nullptr);
@ -137,6 +137,7 @@ void EditLinkBox(
object_ptr<Ui::InputField>( object_ptr<Ui::InputField>(
content, content,
fieldSt, fieldSt,
Ui::InputField::Mode::SingleLine,
tr::lng_formatting_link_text(), tr::lng_formatting_link_text(),
startText), startText),
st::markdownLinkFieldPadding); st::markdownLinkFieldPadding);
@ -181,9 +182,9 @@ void EditLinkBox(
url->move(placeholder->pos()); url->move(placeholder->pos());
const auto submit = [=] { const auto submit = [=] {
const auto linkText = text->getLastText(); const auto linkText = text->getTextWithTags();
const auto linkUrl = validate(url->getLastText()); const auto linkUrl = validate(url->getLastText());
if (linkText.isEmpty()) { if (linkText.text.isEmpty()) {
text->showError(); text->showError();
return; return;
} else if (linkUrl.isEmpty()) { } else if (linkUrl.isEmpty()) {
@ -222,7 +223,7 @@ void EditLinkBox(
box->setWidth(st::boxWidth); box->setWidth(st::boxWidth);
box->setFocusCallback([=] { box->setFocusCallback([=] {
if (startText.isEmpty()) { if (startText.text.isEmpty()) {
text->setFocusFast(); text->setFocusFast();
} else { } else {
if (!url->empty()) { if (!url->empty()) {
@ -383,7 +384,7 @@ bool EditTextChanged(
Fn<bool( Fn<bool(
Ui::InputField::EditLinkSelection selection, Ui::InputField::EditLinkSelection selection,
QString text, TextWithTags text,
QString link, QString link,
EditLinkAction action)> DefaultEditLinkCallback( EditLinkAction action)> DefaultEditLinkCallback(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
@ -392,14 +393,14 @@ Fn<bool(
const auto weak = Ui::MakeWeak(field); const auto weak = Ui::MakeWeak(field);
return [=]( return [=](
EditLinkSelection selection, EditLinkSelection selection,
QString text, TextWithTags text,
QString link, QString link,
EditLinkAction action) { EditLinkAction action) {
if (action == EditLinkAction::Check) { if (action == EditLinkAction::Check) {
return Ui::InputField::IsValidMarkdownLink(link) return Ui::InputField::IsValidMarkdownLink(link)
&& !TextUtilities::IsMentionLink(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()) { if (const auto strong = weak.data()) {
strong->commitMarkdownLinkEdit(selection, text, link); strong->commitMarkdownLinkEdit(selection, text, link);
} }
@ -470,7 +471,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
[[nodiscard]] Fn<bool( [[nodiscard]] Fn<bool(
Ui::InputField::EditLinkSelection selection, Ui::InputField::EditLinkSelection selection,
QString text, TextWithTags text,
QString link, QString link,
EditLinkAction action)> FactcheckEditLinkCallback( EditLinkAction action)> FactcheckEditLinkCallback(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
@ -478,7 +479,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
const auto weak = Ui::MakeWeak(field); const auto weak = Ui::MakeWeak(field);
return [=]( return [=](
EditLinkSelection selection, EditLinkSelection selection,
QString text, TextWithTags text,
QString link, QString link,
EditLinkAction action) { EditLinkAction action) {
const auto validate = [=](QString url) { const auto validate = [=](QString url) {
@ -493,7 +494,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) {
if (action == EditLinkAction::Check) { if (action == EditLinkAction::Check) {
return IsGoodFactcheckUrl(link); 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()) { if (const auto strong = weak.data()) {
strong->commitMarkdownLinkEdit(selection, text, link); strong->commitMarkdownLinkEdit(selection, text, link);
} }

View file

@ -46,7 +46,7 @@ class Show;
Fn<bool( Fn<bool(
Ui::InputField::EditLinkSelection selection, Ui::InputField::EditLinkSelection selection,
QString text, TextWithTags text,
QString link, QString link,
Ui::InputField::EditLinkAction action)> DefaultEditLinkCallback( Ui::InputField::EditLinkAction action)> DefaultEditLinkCallback(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,

View file

@ -1467,6 +1467,8 @@ void StickersListFooter::paintSetIconToCache(
return &st().icons.people; return &st().icons.people;
} else if (const auto section = SetIdEmojiSection(icon.setId)) { } else if (const auto section = SetIdEmojiSection(icon.setId)) {
return sectionIcon(*section, selected); return sectionIcon(*section, selected);
} else if (icon.setId == Data::Stickers::CollectibleSetId) {
return &st().icons.collectibles;
} }
return sectionIcon(Section::Recent, selected); return sectionIcon(Section::Recent, selected);
}()); }());

View file

@ -245,8 +245,7 @@ StickersListWidget::StickersListWidget(
} }
_settings->addClickHandler([=] { _settings->addClickHandler([=] {
if (const auto window = _show->resolveWindow( if (const auto window = _show->resolveWindow()) {
WindowUsage::PremiumPromo)) {
// While media viewer can't show StickersBox. // While media viewer can't show StickersBox.
using Section = StickersBox::Section; using Section = StickersBox::Section;
window->show( window->show(

View file

@ -294,7 +294,11 @@ void TabbedPanel::otherLeave() {
if (_a_opacity.animating()) { if (_a_opacity.animating()) {
hideByTimerOrLeave(); hideByTimerOrLeave();
} else { } 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);
} }
} }

View file

@ -1018,7 +1018,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) {
} }
void TabbedSelector::provideRecentEmoji( void TabbedSelector::provideRecentEmoji(
const std::vector<DocumentId> &customRecentList) { const std::vector<EmojiStatusId> &customRecentList) {
for (const auto &tab : _tabs) { for (const auto &tab : _tabs) {
if (tab.type() == SelectorTab::Emoji) { if (tab.type() == SelectorTab::Emoji) {
const auto emoji = static_cast<EmojiListWidget*>(tab.widget()); const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
@ -1062,8 +1062,7 @@ void TabbedSelector::checkRestrictedPeer() {
st::stickersRestrictedLabel); st::stickersRestrictedLabel);
const auto lifting = error.boostsToLift; const auto lifting = error.boostsToLift;
_restrictedLabel->setClickHandlerFilter([=](auto...) { _restrictedLabel->setClickHandlerFilter([=](auto...) {
const auto window = show->resolveWindow( const auto window = show->resolveWindow();
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), lifting); window->resolveBoostState(peer->asChannel(), lifting);
return false; return false;
}); });

View file

@ -62,6 +62,7 @@ struct FileChosen {
not_null<DocumentData*> document; not_null<DocumentData*> document;
Api::SendOptions options; Api::SendOptions options;
Ui::MessageSendingAnimationFrom messageSendingFrom; Ui::MessageSendingAnimationFrom messageSendingFrom;
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
TextWithTags caption; TextWithTags caption;
}; };
@ -154,7 +155,7 @@ public:
void refreshStickers(); void refreshStickers();
void setCurrentPeer(PeerData *peer); void setCurrentPeer(PeerData *peer);
void provideRecentEmoji( void provideRecentEmoji(
const std::vector<DocumentId> &customRecentList); const std::vector<EmojiStatusId> &customRecentList);
void hideFinished(); void hideFinished();
void showStarted(); void showStarted();

View file

@ -341,7 +341,7 @@ QByteArray Settings::serialize() const {
<< _photoEditorBrush << _photoEditorBrush
<< qint32(_groupCallNoiseSuppression ? 1 : 0) << qint32(_groupCallNoiseSuppression ? 1 : 0)
<< qint32(SerializePlaybackSpeed(_voicePlaybackSpeed)) << qint32(SerializePlaybackSpeed(_voicePlaybackSpeed))
<< qint32(_closeToTaskbar.current() ? 1 : 0) << qint32(_closeBehavior)
<< _customDeviceModel.current() << _customDeviceModel.current()
<< qint32(_playerRepeatMode.current()) << qint32(_playerRepeatMode.current())
<< qint32(_playerOrderMode.current()) << qint32(_playerOrderMode.current())
@ -499,7 +499,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
QByteArray proxy; QByteArray proxy;
qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value()); qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value());
QByteArray photoEditorBrush = _photoEditorBrush; QByteArray photoEditorBrush = _photoEditorBrush;
qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0; qint32 closeBehavior = qint32(_closeBehavior);
QString customDeviceModel = _customDeviceModel.current(); QString customDeviceModel = _customDeviceModel.current();
qint32 playerRepeatMode = static_cast<qint32>(_playerRepeatMode.current()); qint32 playerRepeatMode = static_cast<qint32>(_playerRepeatMode.current());
qint32 playerOrderMode = static_cast<qint32>(_playerOrderMode.current()); qint32 playerOrderMode = static_cast<qint32>(_playerOrderMode.current());
@ -695,7 +695,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
stream >> voicePlaybackSpeed; stream >> voicePlaybackSpeed;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> closeToTaskbar; stream >> closeBehavior;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> customDeviceModel; stream >> customDeviceModel;
@ -1005,7 +1005,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
: Tooltip(0)); : Tooltip(0));
}(); }();
_photoEditorBrush = photoEditorBrush; _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; _customDeviceModel = customDeviceModel;
_accountsOrder = accountsOrder; _accountsOrder = accountsOrder;
const auto uncheckedPlayerRepeatMode = static_cast<Media::RepeatMode>(playerRepeatMode); const auto uncheckedPlayerRepeatMode = static_cast<Media::RepeatMode>(playerRepeatMode);

View file

@ -110,6 +110,11 @@ public:
TrayOnly = 1, TrayOnly = 1,
WindowOnly = 2, WindowOnly = 2,
}; };
enum class CloseBehavior {
Quit = 0,
CloseToTaskbar = 1,
RunInBackground = 2,
};
static constexpr auto kDefaultVolume = 0.9; static constexpr auto kDefaultVolume = 0.9;
@ -751,17 +756,11 @@ public:
_hiddenGroupCallTooltips |= value; _hiddenGroupCallTooltips |= value;
} }
void setCloseToTaskbar(bool value) { void setCloseBehavior(CloseBehavior value) {
_closeToTaskbar = value; _closeBehavior = value;
} }
[[nodiscard]] bool closeToTaskbar() const { [[nodiscard]] CloseBehavior closeBehavior() const {
return _closeToTaskbar.current(); return _closeBehavior;
}
[[nodiscard]] rpl::producer<bool> closeToTaskbarValue() const {
return _closeToTaskbar.value();
}
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
return _closeToTaskbar.changes();
} }
void setTrayIconMonochrome(bool value) { void setTrayIconMonochrome(bool value) {
_trayIconMonochrome = value; _trayIconMonochrome = value;
@ -1048,7 +1047,7 @@ private:
bool _disableOpenGL = false; bool _disableOpenGL = false;
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray; rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips; base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
rpl::variable<bool> _closeToTaskbar = false; CloseBehavior _closeBehavior = CloseBehavior::Quit;
rpl::variable<bool> _trayIconMonochrome = true; rpl::variable<bool> _trayIconMonochrome = true;
rpl::variable<QString> _customDeviceModel; rpl::variable<QString> _customDeviceModel;
rpl::variable<Media::RepeatMode> _playerRepeatMode; rpl::variable<Media::RepeatMode> _playerRepeatMode;

View file

@ -1348,6 +1348,21 @@ bool ResolveChatLink(
return true; 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 } // namespace
const std::vector<LocalUrlHandler> &LocalUrlHandlers() { const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
@ -1440,6 +1455,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"^stars_topup/?\\?(.+)(#|$)"_q, u"^stars_topup/?\\?(.+)(#|$)"_q,
ResolveTopUp ResolveTopUp
}, },
{
u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
ResolveUniqueGift
},
{ {
u"^user\\?(.+)(#|$)"_q, u"^user\\?(.+)(#|$)"_q,
AyuUrlHandlers::ResolveUser 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)) { } else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
const auto slug = chatlinkMatch->captured(1); const auto slug = chatlinkMatch->captured(1);
return u"tg://message?slug="_q + slug; 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"^" } else if (const auto privateMatch = regex_match(u"^"
"c/(\\-?\\d+)" "c/(\\-?\\d+)"
"(" "("
@ -1690,4 +1712,51 @@ bool StartUrlRequiresActivate(const QString &url) {
: !InternalPassportLink(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 } // namespace Core

View file

@ -11,6 +11,14 @@ namespace qthelp {
class RegularExpressionMatch; class RegularExpressionMatch;
} // namespace qthelp } // namespace qthelp
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Settings {
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Window { namespace Window {
class SessionController; class SessionController;
} // namespace Window } // namespace Window
@ -34,4 +42,12 @@ struct LocalUrlHandler {
[[nodiscard]] bool StartUrlRequiresActivate(const QString &url); [[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 } // namespace Core

View file

@ -100,6 +100,8 @@ const auto CommandByName = base::flat_map<QString, Command>{
{ u"read_chat"_q , Command::ReadChat }, { u"read_chat"_q , Command::ReadChat },
{ u"show_chat_menu"_q , Command::ShowChatMenu },
// Shortcuts that have no default values. // Shortcuts that have no default values.
{ u"message"_q , Command::JustSendMessage }, { u"message"_q , Command::JustSendMessage },
{ u"message_silently"_q , Command::SendSilentMessage }, { 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::ShowContacts , u"show_contacts"_q },
{ Command::ReadChat , u"read_chat"_q }, { Command::ReadChat , u"read_chat"_q },
{ Command::ShowChatMenu , u"show_chat_menu"_q },
}; };
[[maybe_unused]] constexpr auto kNoValue = { [[maybe_unused]] constexpr auto kNoValue = {
@ -435,6 +439,8 @@ void Manager::fillDefaults() {
set(u"ctrl+j"_q, Command::ShowContacts); set(u"ctrl+j"_q, Command::ShowContacts);
set(u"ctrl+r"_q, Command::ReadChat); set(u"ctrl+r"_q, Command::ReadChat);
set(u"ctrl+="_q, Command::ShowChatMenu);
} }
void Manager::writeDefaultFile() { void Manager::writeDefaultFile() {

View file

@ -71,6 +71,8 @@ enum class Command {
MediaViewerFullscreen, MediaViewerFullscreen,
ShowChatMenu,
SupportReloadTemplates, SupportReloadTemplates,
SupportToggleMuted, SupportToggleMuted,
SupportScrollToCurrent, SupportScrollToCurrent,

View file

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

View file

@ -859,6 +859,17 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
session().changes().peerUpdated(this, UpdateFlag::Slowmode); 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 { int ChannelData::boostsApplied() const {
if (const auto info = mgInfo.get()) { if (const auto info = mgInfo.get()) {
return info->boostsApplied; return info->boostsApplied;
@ -1138,7 +1149,8 @@ void ApplyChannelUpdate(
| Flag::ViewAsMessages | Flag::ViewAsMessages
| Flag::CanViewRevenue | Flag::CanViewRevenue
| Flag::PaidMediaAllowed | Flag::PaidMediaAllowed
| Flag::CanViewCreditsRevenue; | Flag::CanViewCreditsRevenue
| Flag::StargiftsAvailable;
channel->setFlags((channel->flags() & ~mask) channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants() | (update.is_can_view_participants()
@ -1159,6 +1171,9 @@ void ApplyChannelUpdate(
| (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()) | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag())
| (update.is_can_view_stars_revenue() | (update.is_can_view_stars_revenue()
? Flag::CanViewCreditsRevenue ? Flag::CanViewCreditsRevenue
: Flag())
| (update.is_stargifts_available()
? Flag::StargiftsAvailable
: Flag())); : Flag()));
channel->setUserpicPhoto(update.vchat_photo()); channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) { if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
@ -1172,6 +1187,7 @@ void ApplyChannelUpdate(
channel->setRestrictedCount(update.vbanned_count().value_or_empty()); channel->setRestrictedCount(update.vbanned_count().value_or_empty());
channel->setKickedCount(update.vkicked_count().value_or_empty()); channel->setKickedCount(update.vkicked_count().value_or_empty());
channel->setSlowmodeSeconds(update.vslowmode_seconds().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()) { if (const auto next = update.vslowmode_next_send_date()) {
channel->growSlowmodeLastMessage( channel->growSlowmodeLastMessage(
next->v - channel->slowmodeSeconds()); next->v - channel->slowmodeSeconds());

View file

@ -69,6 +69,7 @@ enum class ChannelDataFlag : uint64 {
PaidMediaAllowed = (1ULL << 33), PaidMediaAllowed = (1ULL << 33),
CanViewCreditsRevenue = (1ULL << 34), CanViewCreditsRevenue = (1ULL << 34),
SignatureProfiles = (1ULL << 35), SignatureProfiles = (1ULL << 35),
StargiftsAvailable = (1ULL << 36),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;
@ -253,6 +254,9 @@ public:
[[nodiscard]] bool viewForumAsMessages() const { [[nodiscard]] bool viewForumAsMessages() const {
return flags() & Flag::ViewAsMessages; return flags() & Flag::ViewAsMessages;
} }
[[nodiscard]] bool stargiftsAvailable() const {
return flags() & Flag::StargiftsAvailable;
}
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(
not_null<PeerData*> participant); not_null<PeerData*> participant);
@ -452,6 +456,9 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const; [[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when); void growSlowmodeLastMessage(TimeId when);
[[nodiscard]] int peerGiftsCount() const;
void setPeerGiftsCount(int count);
[[nodiscard]] int boostsApplied() const; [[nodiscard]] int boostsApplied() const;
[[nodiscard]] int boostsUnrestrict() const; [[nodiscard]] int boostsUnrestrict() const;
[[nodiscard]] bool unrestrictedByBoosts() const; [[nodiscard]] bool unrestrictedByBoosts() const;
@ -522,6 +529,7 @@ private:
std::vector<Data::UnavailableReason> &&reasons) override; std::vector<Data::UnavailableReason> &&reasons) override;
Flags _flags = ChannelDataFlags(Flag::Forbidden); Flags _flags = ChannelDataFlags(Flag::Forbidden);
int _peerGiftsCount = 0;
PtsWaiter _ptsWaiter; PtsWaiter _ptsWaiter;

View file

@ -416,8 +416,7 @@ void ShowSendErrorToast(
return; return;
} }
const auto boost = [=] { const auto boost = [=] {
const auto window = show->resolveWindow( const auto window = show->resolveWindow();
ChatHelpers::WindowUsage::PremiumPromo);
window->resolveBoostState(peer->asChannel(), error.boostsToLift); window->resolveBoostState(peer->asChannel(), error.boostsToLift);
}; };
show->showToast({ show->showToast({

View file

@ -66,6 +66,8 @@ struct CreditsHistoryEntry final {
uint64 bareGiftStickerId = 0; uint64 bareGiftStickerId = 0;
uint64 bareGiftOwnerId = 0; uint64 bareGiftOwnerId = 0;
uint64 bareActorId = 0; uint64 bareActorId = 0;
uint64 bareGiftListPeerId = 0;
uint64 giftSavedId = 0;
uint64 stargiftId = 0; uint64 stargiftId = 0;
std::shared_ptr<UniqueGift> uniqueGift; std::shared_ptr<UniqueGift> uniqueGift;
StarsAmount starrefAmount; StarsAmount starrefAmount;
@ -89,6 +91,7 @@ struct CreditsHistoryEntry final {
bool giftUpgraded : 1 = false; bool giftUpgraded : 1 = false;
bool savedToProfile : 1 = false; bool savedToProfile : 1 = false;
bool fromGiftsList : 1 = false; bool fromGiftsList : 1 = false;
bool fromGiftSlug : 1 = false;
bool soldOutInfo : 1 = false; bool soldOutInfo : 1 = false;
bool canUpgradeGift : 1 = false; bool canUpgradeGift : 1 = false;
bool hasGiftComment : 1 = false; bool hasGiftComment : 1 = false;

View file

@ -11,7 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_star_gift.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_wall_paper.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/timer_rpl.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 kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000);
[[nodiscard]] std::vector<DocumentId> ListFromMTP( [[nodiscard]] EmojiStatusCollectible ParseEmojiStatusCollectible(
const MTPDaccount_emojiStatuses &data) { const MTPDemojiStatusCollectible &data) {
const auto &list = data.vstatuses().v; return EmojiStatusCollectible{
auto result = std::vector<DocumentId>(); .id = data.vcollectible_id().v,
result.reserve(list.size()); .documentId = data.vdocument_id().v,
for (const auto &status : list) { .title = qs(data.vtitle()),
const auto parsed = ParseEmojiStatus(status); .slug = qs(data.vslug()),
if (!parsed.id) { .patternDocumentId = data.vpattern_document_id().v,
LOG(("API Error: emojiStatusEmpty in account.emojiStatuses.")); .centerColor = Ui::ColorFromSerialized(data.vcenter_color()),
} else { .edgeColor = Ui::ColorFromSerialized(data.vedge_color()),
result.push_back(parsed.id); .patternColor = Ui::ColorFromSerialized(data.vpattern_color()),
} .textColor = Ui::ColorFromSerialized(data.vtext_color()),
} };
return result;
} }
} // namespace } // namespace
@ -84,6 +85,10 @@ void EmojiStatuses::refreshChannelColored() {
requestChannelColored(); requestChannelColored();
} }
void EmojiStatuses::refreshCollectibles() {
requestCollectibles();
}
void EmojiStatuses::refreshRecentDelayed() { void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) { if (_recentRequestId || _recentRequestScheduled) {
return; 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) { switch (type) {
case Type::Recent: return _recent; case Type::Recent: return _recent;
case Type::Default: return _default; case Type::Default: return _default;
case Type::Colored: return _colored; case Type::Colored: return _colored;
case Type::ChannelDefault: return _channelDefault; case Type::ChannelDefault: return _channelDefault;
case Type::ChannelColored: return _channelColored; case Type::ChannelColored: return _channelColored;
case Type::Collectibles: return _collectibles;
} }
Unexpected("Type in EmojiStatuses::list."); 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 { rpl::producer<> EmojiStatuses::recentUpdates() const {
return _recentUpdated.events(); return _recentUpdated.events();
} }
@ -119,6 +149,10 @@ rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
return _channelDefaultUpdated.events(); return _channelDefaultUpdated.events();
} }
rpl::producer<> EmojiStatuses::collectiblesUpdates() const {
return _collectiblesUpdated.events();
}
void EmojiStatuses::registerAutomaticClear( void EmojiStatuses::registerAutomaticClear(
not_null<PeerData*> peer, not_null<PeerData*> peer,
TimeId until) { TimeId until) {
@ -253,7 +287,7 @@ void EmojiStatuses::processClearing() {
} }
++i; ++i;
} else { } else {
i->first->setEmojiStatus(0, 0); i->first->setEmojiStatus(EmojiStatusId());
i = clearing.erase(i); 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) { void EmojiStatuses::processClearingIn(TimeId wait) {
const auto waitms = wait * crl::time(1000); const auto waitms = wait * crl::time(1000);
_clearingTimer.callOnce(std::min(waitms, kMaxTimeout)); _clearingTimer.callOnce(std::min(waitms, kMaxTimeout));
@ -327,6 +377,7 @@ void EmojiStatuses::requestColored() {
_coloredRequestId = 0; _coloredRequestId = 0;
result.match([&](const MTPDmessages_stickerSet &data) { result.match([&](const MTPDmessages_stickerSet &data) {
updateColored(data); updateColored(data);
refreshCollectibles();
}, [](const MTPDmessages_stickerSetNotModified &) { }, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified.")); LOG(("API Error: Unexpected messages.stickerSetNotModified."));
}); });
@ -374,15 +425,34 @@ void EmojiStatuses::requestChannelColored() {
}).send(); }).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) { void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v; _recentHash = data.vhash().v;
_recent = ListFromMTP(data); _recent = parse(data);
_recentUpdated.fire({}); _recentUpdated.fire({});
} }
void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) { void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) {
_defaultHash = data.vhash().v; _defaultHash = data.vhash().v;
_default = ListFromMTP(data); _default = parse(data);
_defaultUpdated.fire({}); _defaultUpdated.fire({});
} }
@ -391,7 +461,9 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
_colored.clear(); _colored.clear();
_colored.reserve(list.size()); _colored.reserve(list.size());
for (const auto &sticker : data.vdocuments().v) { for (const auto &sticker : data.vdocuments().v) {
_colored.push_back(_owner->processDocument(sticker)->id); _colored.push_back({
.documentId = _owner->processDocument(sticker)->id,
});
} }
_coloredUpdated.fire({}); _coloredUpdated.fire({});
} }
@ -399,7 +471,7 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
void EmojiStatuses::updateChannelDefault( void EmojiStatuses::updateChannelDefault(
const MTPDaccount_emojiStatuses &data) { const MTPDaccount_emojiStatuses &data) {
_channelDefaultHash = data.vhash().v; _channelDefaultHash = data.vhash().v;
_channelDefault = ListFromMTP(data); _channelDefault = parse(data);
_channelDefaultUpdated.fire({}); _channelDefaultUpdated.fire({});
} }
@ -409,18 +481,27 @@ void EmojiStatuses::updateChannelColored(
_channelColored.clear(); _channelColored.clear();
_channelColored.reserve(list.size()); _channelColored.reserve(list.size());
for (const auto &sticker : data.vdocuments().v) { for (const auto &sticker : data.vdocuments().v) {
_channelColored.push_back(_owner->processDocument(sticker)->id); _channelColored.push_back({
.documentId = _owner->processDocument(sticker)->id,
});
} }
_channelColoredUpdated.fire({}); _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); set(_owner->session().user(), id, until);
} }
void EmojiStatuses::set( void EmojiStatuses::set(
not_null<PeerData*> peer, not_null<PeerData*> peer,
DocumentId id, EmojiStatusId id,
TimeId until) { TimeId until) {
auto &api = _owner->session().api(); auto &api = _owner->session().api();
auto &requestId = _sentRequests[peer]; auto &requestId = _sentRequests[peer];
@ -437,11 +518,19 @@ void EmojiStatuses::set(
_sentRequests.remove(peer); _sentRequests.remove(peer);
}).send(); }).send();
}; };
using EFlag = MTPDemojiStatus::Flag;
using CFlag = MTPDinputEmojiStatusCollectible::Flag;
const auto status = !id const auto status = !id
? MTP_emojiStatusEmpty() ? MTP_emojiStatusEmpty()
: !until : id.collectible
? MTP_emojiStatus(MTP_long(id)) ? MTP_inputEmojiStatusCollectible(
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)); 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()) { if (peer->isSelf()) {
send(MTPaccount_UpdateEmojiStatus(status)); send(MTPaccount_UpdateEmojiStatus(status));
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
@ -449,14 +538,30 @@ void EmojiStatuses::set(
} }
} }
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) { EmojiStatusId EmojiStatuses::fromUniqueGift(
return status.match([](const MTPDemojiStatus &data) { const Data::UniqueGift &gift) {
return EmojiStatusData{ data.vdocument_id().v }; const auto collectibleId = gift.id;
}, [](const MTPDemojiStatusUntil &data) { auto &collectible = _collectibleData[collectibleId];
return EmojiStatusData{ data.vdocument_id().v, data.vuntil().v }; if (!collectible) {
}, [](const MTPDemojiStatusEmpty &) { collectible = std::make_shared<EmojiStatusCollectible>(
return EmojiStatusData(); 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 } // namespace Data

View file

@ -21,6 +21,27 @@ namespace Data {
class DocumentMedia; class DocumentMedia;
class Session; 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 { class EmojiStatuses final {
public: public:
@ -38,6 +59,7 @@ public:
void refreshColored(); void refreshColored();
void refreshChannelDefault(); void refreshChannelDefault();
void refreshChannelColored(); void refreshChannelColored();
void refreshCollectibles();
enum class Type { enum class Type {
Recent, Recent,
@ -45,15 +67,21 @@ public:
Colored, Colored,
ChannelDefault, ChannelDefault,
ChannelColored, 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<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const; [[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> channelDefaultUpdates() const; [[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
[[nodiscard]] rpl::producer<> collectiblesUpdates() const;
void set(DocumentId id, TimeId until = 0); void set(EmojiStatusId id, TimeId until = 0);
void set(not_null<PeerData*> peer, DocumentId 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); void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
@ -79,31 +107,42 @@ private:
void requestColored(); void requestColored();
void requestChannelDefault(); void requestChannelDefault();
void requestChannelColored(); void requestChannelColored();
void requestCollectibles();
void updateRecent(const MTPDaccount_emojiStatuses &data); void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data);
void updateColored(const MTPDmessages_stickerSet &data); void updateColored(const MTPDmessages_stickerSet &data);
void updateChannelDefault(const MTPDaccount_emojiStatuses &data); void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
void updateChannelColored(const MTPDmessages_stickerSet &data); void updateChannelColored(const MTPDmessages_stickerSet &data);
void updateCollectibles(const MTPDaccount_emojiStatuses &data);
void processClearingIn(TimeId wait); void processClearingIn(TimeId wait);
void processClearing(); void processClearing();
[[nodiscard]] std::vector<EmojiStatusId> parse(
const MTPDaccount_emojiStatuses &data);
template <typename Request> template <typename Request>
void requestGroups(not_null<GroupsType*> type, Request &&request); void requestGroups(not_null<GroupsType*> type, Request &&request);
const not_null<Session*> _owner; const not_null<Session*> _owner;
std::vector<DocumentId> _recent; std::vector<EmojiStatusId> _recent;
std::vector<DocumentId> _default; std::vector<EmojiStatusId> _default;
std::vector<DocumentId> _colored; std::vector<EmojiStatusId> _colored;
std::vector<DocumentId> _channelDefault; std::vector<EmojiStatusId> _channelDefault;
std::vector<DocumentId> _channelColored; std::vector<EmojiStatusId> _channelColored;
std::vector<EmojiStatusId> _collectibles;
rpl::event_stream<> _recentUpdated; rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated; rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _coloredUpdated; rpl::event_stream<> _coloredUpdated;
rpl::event_stream<> _channelDefaultUpdated; rpl::event_stream<> _channelDefaultUpdated;
rpl::event_stream<> _channelColoredUpdated; rpl::event_stream<> _channelColoredUpdated;
rpl::event_stream<> _collectiblesUpdated;
base::flat_map<
CollectibleId,
std::shared_ptr<EmojiStatusCollectible>> _collectibleData;
mtpRequestId _recentRequestId = 0; mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false; bool _recentRequestScheduled = false;
@ -119,6 +158,9 @@ private:
mtpRequestId _channelColoredRequestId = 0; mtpRequestId _channelColoredRequestId = 0;
mtpRequestId _collectiblesRequestId = 0;
uint64 _collectiblesHash = 0;
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests; base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
base::flat_map<not_null<PeerData*>, TimeId> _clearing; 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 } // namespace Data

View file

@ -59,6 +59,24 @@ struct FileReferenceAccumulator {
push(data.vdocuments()); push(data.vdocuments());
}, [&](const MTPDwebPageAttributeStickerSet &data) { }, [&](const MTPDwebPageAttributeStickerSet &data) {
push(data.vstickers()); 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) { void push(const MTPWebPage &data) {

View file

@ -897,12 +897,10 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
const auto muted = this->muted(); const auto muted = this->muted();
result.messages = count; result.messages = count;
result.chats = count ? 1 : 0; result.chats = count ? 1 : 0;
result.chatsTopic = count ? 1 : 0;
result.mentions = unreadMentions().has() ? 1 : 0; result.mentions = unreadMentions().has() ? 1 : 0;
result.reactions = unreadReactions().has() ? 1 : 0; result.reactions = unreadReactions().has() ? 1 : 0;
result.messagesMuted = muted ? result.messages : 0; result.messagesMuted = muted ? result.messages : 0;
result.chatsMuted = muted ? result.chats : 0; result.chatsMuted = muted ? result.chats : 0;
result.chatsTopicMuted = muted ? result.chats : 0;
result.reactionsMuted = muted ? result.reactions : 0; result.reactionsMuted = muted ? result.reactions : 0;
result.known = known; result.known = known;
return result; return result;

View file

@ -2375,13 +2375,13 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
not_null<HistoryView::Element*> message, not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent, not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) { HistoryView::Element *replacing) {
if (const auto raw = _data.unique.get()) { if (const auto &unique = _data.unique) {
return std::make_unique<HistoryView::MediaGeneric>( return std::make_unique<HistoryView::MediaGeneric>(
message, message,
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw), HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
HistoryView::MediaGenericDescriptor{ HistoryView::MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftBoxSize.width(), .maxWidth = st::msgServiceGiftBoxSize.width(),
.paintBg = HistoryView::UniqueGiftBg(message, raw), .paintBg = HistoryView::UniqueGiftBg(message, unique),
.service = true, .service = true,
}); });
} }

View file

@ -141,6 +141,8 @@ struct GiftCode {
std::shared_ptr<UniqueGift> unique; std::shared_ptr<UniqueGift> unique;
TextWithEntities message; TextWithEntities message;
ChannelData *channel = nullptr; ChannelData *channel = nullptr;
PeerData *channelFrom = nullptr;
uint64 channelSavedId = 0;
MsgId giveawayMsgId = 0; MsgId giveawayMsgId = 0;
MsgId upgradeMsgId = 0; MsgId upgradeMsgId = 0;
int starsConverted = 0; int starsConverted = 0;

View file

@ -652,6 +652,20 @@ bool PeerData::canManageTopics() const {
return false; 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 { bool PeerData::canEditMessagesIndefinitely() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return user->isSelf(); return user->isSelf();
@ -911,6 +925,16 @@ void PeerData::fullUpdated() {
setLoadedStatus(LoadedStatus::Full); 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() { UserData *PeerData::asUser() {
return isUser() ? static_cast<UserData*>(this) : nullptr; return isUser() ? static_cast<UserData*>(this) : nullptr;
} }
@ -1115,11 +1139,11 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
} }
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) { void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = Data::ParseEmojiStatus(status); const auto parsed = owner().emojiStatuses().parse(status);
setEmojiStatus(parsed.id, parsed.until); setEmojiStatus(parsed.id, parsed.until);
} }
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) { void PeerData::setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until) {
if (_emojiStatusId != emojiStatusId) { if (_emojiStatusId != emojiStatusId) {
_emojiStatusId = emojiStatusId; _emojiStatusId = emojiStatusId;
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus); session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
@ -1127,10 +1151,17 @@ void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
owner().emojiStatuses().registerAutomaticClear(this, until); owner().emojiStatuses().registerAutomaticClear(this, until);
} }
DocumentId PeerData::emojiStatusId() const { EmojiStatusId PeerData::emojiStatusId() const {
return _emojiStatusId; return _emojiStatusId;
} }
bool PeerData::isBot() const {
if (const auto user = asUser()) {
return user->isBot();
}
return false;
}
bool PeerData::isSelf() const { bool PeerData::isSelf() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return (user->flags() & UserDataFlag::Self); 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) { void PeerData::setIsBlocked(bool is) {
const auto status = is const auto status = is
? BlockStatus::Blocked ? BlockStatus::Blocked

View file

@ -206,8 +206,8 @@ public:
bool changeBackgroundEmojiId(DocumentId id); bool changeBackgroundEmojiId(DocumentId id);
void setEmojiStatus(const MTPEmojiStatus &status); void setEmojiStatus(const MTPEmojiStatus &status);
void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0); void setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until = 0);
[[nodiscard]] DocumentId emojiStatusId() const; [[nodiscard]] EmojiStatusId emojiStatusId() const;
[[nodiscard]] bool isUser() const { [[nodiscard]] bool isUser() const {
return peerIsUser(id); return peerIsUser(id);
@ -218,6 +218,7 @@ public:
[[nodiscard]] bool isChannel() const { [[nodiscard]] bool isChannel() const {
return peerIsChannel(id); return peerIsChannel(id);
} }
[[nodiscard]] bool isBot() const;
[[nodiscard]] bool isSelf() const; [[nodiscard]] bool isSelf() const;
[[nodiscard]] bool isVerified() const; [[nodiscard]] bool isVerified() const;
[[nodiscard]] bool isPremium() const; [[nodiscard]] bool isPremium() const;
@ -267,6 +268,8 @@ public:
[[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] int slowmodeSecondsLeft() const;
[[nodiscard]] bool canManageGroupCall() const; [[nodiscard]] bool canManageGroupCall() const;
[[nodiscard]] UserData *asBot();
[[nodiscard]] const UserData *asBot() const;
[[nodiscard]] UserData *asUser(); [[nodiscard]] UserData *asUser();
[[nodiscard]] const UserData *asUser() const; [[nodiscard]] const UserData *asUser() const;
[[nodiscard]] ChatData *asChat(); [[nodiscard]] ChatData *asChat();
@ -381,6 +384,8 @@ public:
[[nodiscard]] bool canCreatePolls() const; [[nodiscard]] bool canCreatePolls() const;
[[nodiscard]] bool canCreateTopics() const; [[nodiscard]] bool canCreateTopics() const;
[[nodiscard]] bool canManageTopics() const; [[nodiscard]] bool canManageTopics() const;
[[nodiscard]] bool canManageGifts() const;
[[nodiscard]] bool canTransferGifts() const;
[[nodiscard]] bool canExportChatHistory() const; [[nodiscard]] bool canExportChatHistory() const;
// Returns true if about text was changed. // Returns true if about text was changed.
@ -483,6 +488,8 @@ public:
[[nodiscard]] bool hasUnreadStories() const; [[nodiscard]] bool hasUnreadStories() const;
void setStoriesState(StoriesState state); void setStoriesState(StoriesState state);
[[nodiscard]] int peerGiftsCount() const;
const PeerId id; const PeerId id;
MTPinputPeer input = MTP_inputPeerEmpty(); MTPinputPeer input = MTP_inputPeerEmpty();
@ -523,7 +530,7 @@ private:
base::flat_set<QString> _nameWords; // for filtering base::flat_set<QString> _nameWords; // for filtering
base::flat_set<QChar> _nameFirstLetters; base::flat_set<QChar> _nameFirstLetters;
DocumentId _emojiStatusId = 0; EmojiStatusId _emojiStatusId;
DocumentId _backgroundEmojiId = 0; DocumentId _backgroundEmojiId = 0;
crl::time _lastFullUpdate = 0; crl::time _lastFullUpdate = 0;

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "api/api_bot.h" #include "api/api_bot.h"
#include "api/api_premium.h"
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "api/api_user_names.h" #include "api/api_user_names.h"
#include "chat_helpers/stickers_lottie.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()) { if (const auto &status = data.vemoji_status()) {
result->setEmojiStatus(*status); result->setEmojiStatus(*status);
} else { } else {
result->setEmojiStatus(0); result->setEmojiStatus(EmojiStatusId());
} }
if (!minimal) { if (!minimal) {
if (const auto botInfoVersion = data.vbot_info_version()) { 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()) { if (const auto &status = data.vemoji_status()) {
channel->setEmojiStatus(*status); channel->setEmojiStatus(*status);
} else { } else {
channel->setEmojiStatus(0); channel->setEmojiStatus(EmojiStatusId());
} }
if (minimal) { if (minimal) {
if (channel->input.type() == mtpc_inputPeerEmpty if (channel->input.type() == mtpc_inputPeerEmpty
@ -3539,6 +3540,7 @@ not_null<WebPageData*> Session::processWebpage(
WebPageCollage(), WebPageCollage(),
nullptr, nullptr,
nullptr, nullptr,
nullptr,
0, 0,
QString(), QString(),
false, false,
@ -3565,6 +3567,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage(), WebPageCollage(),
nullptr, nullptr,
nullptr, nullptr,
nullptr,
0, 0,
QString(), QString(),
false, false,
@ -3584,6 +3587,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage &&collage, WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv, std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet, std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
@ -3603,6 +3607,7 @@ not_null<WebPageData*> Session::webpage(
std::move(collage), std::move(collage),
std::move(iv), std::move(iv),
std::move(stickerSet), std::move(stickerSet),
std::move(uniqueGift),
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,
@ -3637,6 +3642,7 @@ void Session::webpageApplyFields(
} }
return nullptr; return nullptr;
}; };
const auto lookupThemeDocument = [&]() -> DocumentData* { const auto lookupThemeDocument = [&]() -> DocumentData* {
if (const auto attributes = data.vattributes()) { if (const auto attributes = data.vattributes()) {
for (const auto &attribute : attributes->v) { for (const auto &attribute : attributes->v) {
@ -3647,6 +3653,8 @@ void Session::webpageApplyFields(
return (DocumentData*)nullptr; return (DocumentData*)nullptr;
}, [](const MTPDwebPageAttributeStickerSet &) { }, [](const MTPDwebPageAttributeStickerSet &) {
return (DocumentData*)nullptr; return (DocumentData*)nullptr;
}, [](const MTPDwebPageAttributeUniqueStarGift &) {
return (DocumentData*)nullptr;
}); });
if (result) { if (result) {
return result; return result;
@ -3655,6 +3663,7 @@ void Session::webpageApplyFields(
} }
return nullptr; return nullptr;
}; };
using WebPageStickerSetPtr = std::unique_ptr<WebPageStickerSet>; using WebPageStickerSetPtr = std::unique_ptr<WebPageStickerSet>;
const auto lookupStickerSet = [&]() -> WebPageStickerSetPtr { const auto lookupStickerSet = [&]() -> WebPageStickerSetPtr {
if (const auto attributes = data.vattributes()) { if (const auto attributes = data.vattributes()) {
@ -3678,6 +3687,21 @@ void Session::webpageApplyFields(
} }
return nullptr; 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 story = (Data::Story*)nullptr;
auto storyId = FullStoryId(); auto storyId = FullStoryId();
if (const auto attributes = data.vattributes()) { if (const auto attributes = data.vattributes()) {
@ -3770,6 +3794,7 @@ void Session::webpageApplyFields(
WebPageCollage(this, data), WebPageCollage(this, data),
std::move(iv), std::move(iv),
lookupStickerSet(), lookupStickerSet(),
lookupUniqueGift(),
data.vduration().value_or_empty(), data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()), qs(data.vauthor().value_or_empty()),
data.is_has_large_media(), data.is_has_large_media(),
@ -3790,6 +3815,7 @@ void Session::webpageApplyFields(
WebPageCollage &&collage, WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv, std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet, std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
@ -3808,6 +3834,7 @@ void Session::webpageApplyFields(
std::move(collage), std::move(collage),
std::move(iv), std::move(iv),
std::move(stickerSet), std::move(stickerSet),
std::move(uniqueGift),
duration, duration,
author, author,
hasLargeMedia, hasLargeMedia,

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_main_list.h" #include "dialogs/dialogs_main_list.h"
#include "data/data_groups.h" #include "data/data_groups.h"
#include "data/data_cloud_file.h" #include "data/data_cloud_file.h"
#include "data/data_star_gift.h"
#include "history/history_location_manager.h" #include "history/history_location_manager.h"
#include "base/timer.h" #include "base/timer.h"
@ -71,6 +72,7 @@ class BusinessInfo;
struct ReactionId; struct ReactionId;
struct UnavailableReason; struct UnavailableReason;
struct CreditsStatusSlice; struct CreditsStatusSlice;
struct UniqueGift;
struct RepliesReadTillUpdate { struct RepliesReadTillUpdate {
FullMsgId id; FullMsgId id;
@ -83,10 +85,11 @@ struct GiftUpdate {
Save, Save,
Unsave, Unsave,
Convert, Convert,
Transfer,
Delete, Delete,
}; };
FullMsgId itemId; Data::SavedStarGiftId id;
Action action = {}; Action action = {};
}; };
@ -336,7 +339,7 @@ public:
void notifyPinnedDialogsOrderUpdated(); void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
using CreditsSubsRebuilder = rpl::event_stream<Data::CreditsStatusSlice>; using CreditsSubsRebuilder = rpl::event_stream<CreditsStatusSlice>;
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>; using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder(); [[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const; [[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
@ -422,7 +425,7 @@ public:
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
FilterId filterId) const; FilterId filterId) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder( [[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 setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
void setPinnedFromEntryList(Dialogs::Key key, bool pinned); void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
void clearPinnedChats(Folder *folder); void clearPinnedChats(Folder *folder);
@ -430,7 +433,7 @@ public:
Folder *folder, Folder *folder,
const QVector<MTPDialogPeer> &list); const QVector<MTPDialogPeer> &list);
void applyPinnedTopics( void applyPinnedTopics(
not_null<Data::Forum*> forum, not_null<Forum*> forum,
const QVector<MTPint> &list); const QVector<MTPint> &list);
void reorderTwoPinnedChats( void reorderTwoPinnedChats(
FilterId filterId, FilterId filterId,
@ -624,6 +627,7 @@ public:
WebPageCollage &&collage, WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv, std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet, std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,
@ -908,6 +912,7 @@ private:
WebPageCollage &&collage, WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv, std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet, std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration, int duration,
const QString &author, const QString &author,
bool hasLargeMedia, bool hasLargeMedia,

View file

@ -37,7 +37,11 @@ struct UniqueGiftOriginalDetails {
}; };
struct UniqueGift { struct UniqueGift {
CollectibleId id = 0;
QString slug;
QString title; QString title;
QString ownerAddress;
QString ownerName;
PeerId ownerId = 0; PeerId ownerId = 0;
int number = 0; int number = 0;
int starsForTransfer = -1; int starsForTransfer = -1;
@ -71,13 +75,60 @@ struct StarGift {
const StarGift &) = default; 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; StarGift info;
SavedStarGiftId manageId;
TextWithEntities message; TextWithEntities message;
int64 starsConverted = 0; int64 starsConverted = 0;
int64 starsUpgradedBySender = 0; int64 starsUpgradedBySender = 0;
PeerId fromId = 0; PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0; TimeId date = 0;
bool upgradable = false; bool upgradable = false;
bool anonymous = false; bool anonymous = false;

View file

@ -87,6 +87,7 @@ using UpdateFlag = StoryUpdate::Flag;
}, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDmediaAreaStarGift &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -110,6 +111,7 @@ using UpdateFlag = StoryUpdate::Flag;
}, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaChannelPost &data) {
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDmediaAreaStarGift &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -133,6 +135,7 @@ using UpdateFlag = StoryUpdate::Flag;
}); });
}, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaUrl &data) {
}, [&](const MTPDmediaAreaWeather &data) { }, [&](const MTPDmediaAreaWeather &data) {
}, [&](const MTPDmediaAreaStarGift &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -154,6 +157,11 @@ using UpdateFlag = StoryUpdate::Flag;
.url = qs(data.vurl()), .url = qs(data.vurl()),
}); });
}, [&](const MTPDmediaAreaWeather &data) { }, [&](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) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {
@ -180,6 +188,7 @@ using UpdateFlag = StoryUpdate::Flag;
-274., -274.,
1'000'000.)), 1'000'000.)),
}); });
}, [&](const MTPDmediaAreaStarGift &data) {
}, [&](const MTPDinputMediaAreaChannelPost &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) {
LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); LOG(("API Error: Unexpected inputMediaAreaChannelPost from API."));
}, [&](const MTPDinputMediaAreaVenue &data) { }, [&](const MTPDinputMediaAreaVenue &data) {

View file

@ -18,7 +18,6 @@ struct PeerSubscription final {
} }
}; };
using PhotoId = uint64;
struct SubscriptionEntry final { struct SubscriptionEntry final {
explicit operator bool() const { explicit operator bool() const {
return !id.isEmpty(); return !id.isEmpty();
@ -31,7 +30,7 @@ struct SubscriptionEntry final {
QDateTime until; QDateTime until;
PeerSubscription subscription; PeerSubscription subscription;
uint64 barePeerId = 0; uint64 barePeerId = 0;
PhotoId photoId = PhotoId(0); uint64 photoId = 0;
bool cancelled = false; bool cancelled = false;
bool cancelledByBot = false; bool cancelledByBot = false;
bool expired = false; bool expired = false;

View file

@ -37,6 +37,7 @@ using Options = base::flags<Option>;
namespace Data { namespace Data {
struct FileOrigin; struct FileOrigin;
struct EmojiStatusCollectible;
struct UploadState { struct UploadState {
explicit UploadState(int64 size) : size(size) { explicit UploadState(int64 size) : size(size) {
@ -139,6 +140,23 @@ using WallPaperId = uint64;
using CallId = uint64; using CallId = uint64;
using BotAppId = uint64; using BotAppId = uint64;
using EffectId = 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); constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL);

View file

@ -224,6 +224,7 @@ bool WebPageData::applyChanges(
WebPageCollage &&newCollage, WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv, std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet, std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
@ -278,6 +279,7 @@ bool WebPageData::applyChanges(
&& (!iv == !newIv) && (!iv == !newIv)
&& (!iv || iv->partial() == newIv->partial()) && (!iv || iv->partial() == newIv->partial())
&& (!stickerSet == !newStickerSet) && (!stickerSet == !newStickerSet)
&& (!uniqueGift == !newUniqueGift)
&& duration == newDuration && duration == newDuration
&& author == resultAuthor && author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0) && hasLargeMedia == (newHasLargeMedia ? 1 : 0)
@ -300,6 +302,7 @@ bool WebPageData::applyChanges(
collage = std::move(newCollage); collage = std::move(newCollage);
iv = std::move(newIv); iv = std::move(newIv);
stickerSet = std::move(newStickerSet); stickerSet = std::move(newStickerSet);
uniqueGift = std::move(newUniqueGift);
duration = newDuration; duration = newDuration;
author = resultAuthor; author = resultAuthor;
pendingTill = newPendingTill; pendingTill = newPendingTill;

View file

@ -15,6 +15,7 @@ class ChannelData;
namespace Data { namespace Data {
class Session; class Session;
struct UniqueGift;
} // namespace Data } // namespace Data
namespace Iv { namespace Iv {
@ -101,6 +102,7 @@ struct WebPageData {
WebPageCollage &&newCollage, WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv, std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet, std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
int newDuration, int newDuration,
const QString &newAuthor, const QString &newAuthor,
bool newHasLargeMedia, bool newHasLargeMedia,
@ -129,6 +131,7 @@ struct WebPageData {
WebPageCollage collage; WebPageCollage collage;
std::unique_ptr<Iv::Data> iv; std::unique_ptr<Iv::Data> iv;
std::unique_ptr<WebPageStickerSet> stickerSet; std::unique_ptr<WebPageStickerSet> stickerSet;
std::shared_ptr<Data::UniqueGift> uniqueGift;
int duration = 0; int duration = 0;
TimeId pendingTill = 0; TimeId pendingTill = 0;
uint32 version : 30 = 0; uint32 version : 30 = 0;

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity. #include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity.
#include "data/data_peer.h" #include "data/data_peer.h"
@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "storage/file_download.h" // kMaxFileInMemory #include "storage/file_download.h" // kMaxFileInMemory
#include "ui/chat/chats_filter_tag.h" #include "ui/chat/chats_filter_tag.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/credits_graphics.h" #include "ui/effects/credits_graphics.h"
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/text/custom_emoji_instance.h" #include "ui/text/custom_emoji_instance.h"
@ -117,6 +119,10 @@ private:
return u"force-static:"_q; return u"force-static:"_q;
} }
[[nodiscard]] QString CollectiblePrefix() {
return u"collectible:"_q;
}
[[nodiscard]] QString InternalPadding(QMargins value) { [[nodiscard]] QString InternalPadding(QMargins value) {
return value.isNull() ? QString() : QString(",%1,%2,%3,%4" return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
).arg(value.left() ).arg(value.left()
@ -568,6 +574,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
const auto ratio = style::DevicePixelRatio(); const auto ratio = style::DevicePixelRatio();
const auto size = EmojiSizeFromTag(tag) / ratio; const auto size = EmojiSizeFromTag(tag) / ratio;
return userpic(data, std::move(update), size); 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)) { } else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
return MakeTopicIconEmoji(parsed, std::move(update), tag); 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 } // namespace Data

View file

@ -223,4 +223,8 @@ void InsertCustomEmoji(
[[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory( [[nodiscard]] Ui::Text::CustomEmojiFactory ReactedMenuFactory(
not_null<Main::Session*> session); not_null<Main::Session*> session);
[[nodiscard]] QString CollectibleCustomEmojiId(
Data::EmojiStatusCollectible &data);
[[nodiscard]] QString EmojiStatusCustomId(const EmojiStatusId &id);
} // namespace Data } // namespace Data

View file

@ -88,8 +88,7 @@ void MaybeShowPremiumToast(
return; return;
} }
const auto filter = [=](const auto ...) { const auto filter = [=](const auto ...) {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo; if (const auto controller = show->resolveWindow()) {
if (const auto controller = show->resolveWindow(usage)) {
Settings::ShowPremium(controller, ref); Settings::ShowPremium(controller, ref);
} }
return false; 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( void Stickers::specialSetReceived(
uint64 setId, uint64 setId,
const QString &setTitle, const QString &setTitle,

View file

@ -65,6 +65,9 @@ public:
// For setting up megagroup sticker set. // For setting up megagroup sticker set.
static constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL; static constexpr auto MegagroupSetId = 0xFFFFFFFFFFFFFFEFULL;
// For collectible emoji statuses.
static constexpr auto CollectibleSetId = 0xFFFFFFFFFFFFFFF8ULL;
void notifyUpdated(StickersType type); void notifyUpdated(StickersType type);
[[nodiscard]] rpl::producer<StickersType> updated() const; [[nodiscard]] rpl::producer<StickersType> updated() const;
[[nodiscard]] rpl::producer<> updated(StickersType type) const; [[nodiscard]] rpl::producer<> updated(StickersType type) const;
@ -244,6 +247,8 @@ public:
[[nodiscard]] auto getEmojiListFromSet(not_null<DocumentData*> document) [[nodiscard]] auto getEmojiListFromSet(not_null<DocumentData*> document)
-> std::optional<std::vector<not_null<EmojiPtr>>>; -> std::optional<std::vector<not_null<EmojiPtr>>>;
[[nodiscard]] not_null<StickersSet*> collectibleSet();
not_null<StickersSet*> feedSet(const MTPStickerSet &data); not_null<StickersSet*> feedSet(const MTPStickerSet &data);
not_null<StickersSet*> feedSet(const MTPStickerSetCovered &data); not_null<StickersSet*> feedSet(const MTPStickerSetCovered &data);
not_null<StickersSet*> feedSetFull(const MTPDmessages_stickerSet &data); not_null<StickersSet*> feedSetFull(const MTPDmessages_stickerSet &data);

View file

@ -32,14 +32,10 @@ struct UnreadState {
int messagesMuted = 0; int messagesMuted = 0;
int chats = 0; int chats = 0;
int chatsMuted = 0; int chatsMuted = 0;
int chatsTopic = 0;
int chatsTopicMuted = 0;
int marks = 0; int marks = 0;
int marksMuted = 0; int marksMuted = 0;
int reactions = 0; int reactions = 0;
int reactionsMuted = 0; int reactionsMuted = 0;
int forums = 0;
int forumsMuted = 0;
int mentions = 0; int mentions = 0;
bool known = false; bool known = false;
@ -48,14 +44,10 @@ struct UnreadState {
messagesMuted += other.messagesMuted; messagesMuted += other.messagesMuted;
chats += other.chats; chats += other.chats;
chatsMuted += other.chatsMuted; chatsMuted += other.chatsMuted;
chatsTopic += other.chatsTopic;
chatsTopicMuted += other.chatsTopicMuted;
marks += other.marks; marks += other.marks;
marksMuted += other.marksMuted; marksMuted += other.marksMuted;
reactions += other.reactions; reactions += other.reactions;
reactionsMuted += other.reactionsMuted; reactionsMuted += other.reactionsMuted;
forums += other.forums;
forumsMuted += other.forumsMuted;
mentions += other.mentions; mentions += other.mentions;
return *this; return *this;
} }
@ -64,14 +56,10 @@ struct UnreadState {
messagesMuted -= other.messagesMuted; messagesMuted -= other.messagesMuted;
chats -= other.chats; chats -= other.chats;
chatsMuted -= other.chatsMuted; chatsMuted -= other.chatsMuted;
chatsTopic -= other.chatsTopic;
chatsTopicMuted -= other.chatsTopicMuted;
marks -= other.marks; marks -= other.marks;
marksMuted -= other.marksMuted; marksMuted -= other.marksMuted;
reactions -= other.reactions; reactions -= other.reactions;
reactionsMuted -= other.reactionsMuted; reactionsMuted -= other.reactionsMuted;
forums -= other.forums;
forumsMuted -= other.forumsMuted;
mentions -= other.mentions; mentions -= other.mentions;
return *this; return *this;
} }

View file

@ -1199,6 +1199,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
p.translate(0, (_previewResults.size() - to) * _st->height); p.translate(0, (_previewResults.size() - to) * _st->height);
} }
} }
if (!_searchResults.empty()) { if (!_searchResults.empty()) {
const auto text = showUnreadInSearchResults const auto text = showUnreadInSearchResults
? u"Search results"_q ? u"Search results"_q
@ -1215,7 +1216,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
const auto filterFont = filterOver const auto filterFont = filterOver
? st::searchedBarFont->underline() ? st::searchedBarFont->underline()
: st::searchedBarFont; : st::searchedBarFont;
if (_searchState.tab == ChatSearchTab::MyMessages) { if (hasChatTypeFilter()) {
const auto text = ChatTypeFilterLabel(_searchState.filter); const auto text = ChatTypeFilterLabel(_searchState.filter);
if (!_chatTypeFilterWidth) { if (!_chatTypeFilterWidth) {
_chatTypeFilterWidth = filterFont->width(text); _chatTypeFilterWidth = filterFont->width(text);
@ -1522,6 +1523,21 @@ void InnerWidget::paintSearchTags(
_searchTags->paint(p, position, context.now, context.paused); _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) { void InnerWidget::mouseMoveEvent(QMouseEvent *e) {
if (_chatPreviewTouchGlobal || _touchDragStartGlobal) { if (_chatPreviewTouchGlobal || _touchDragStartGlobal) {
return; return;
@ -1747,7 +1763,7 @@ void InnerWidget::selectByMouse(QPoint globalPosition) {
} }
auto selectedChatTypeFilter = false; auto selectedChatTypeFilter = false;
const auto from = skip - st::searchedBarHeight; const auto from = skip - st::searchedBarHeight;
if (mouseY <= skip && mouseY >= from) { if (hasChatTypeFilter() && mouseY <= skip && mouseY >= from) {
const auto left = width() const auto left = width()
- _chatTypeFilterWidth - _chatTypeFilterWidth
- 2 * st::searchedBarPosition.x(); - 2 * st::searchedBarPosition.x();
@ -2834,7 +2850,9 @@ bool InnerWidget::scheduleChatPreview(QPoint positionOverride) {
void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
_menu = nullptr; _menu = nullptr;
if (e->reason() == QContextMenuEvent::Mouse) { const auto fromMouse = e->reason() == QContextMenuEvent::Mouse;
if (fromMouse) {
selectByMouse(e->globalPos()); selectByMouse(e->globalPos());
} }
@ -2897,6 +2915,9 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
if (_menuRow.key) { if (_menuRow.key) {
updateDialogRow(base::take(_menuRow)); updateDialogRow(base::take(_menuRow));
} }
if (!fromMouse) {
return;
}
const auto globalPosition = QCursor::pos(); const auto globalPosition = QCursor::pos();
if (rect().contains(mapFromGlobal(globalPosition))) { if (rect().contains(mapFromGlobal(globalPosition))) {
setMouseTracking(true); setMouseTracking(true);
@ -2998,6 +3019,11 @@ void InnerWidget::dragPinnedFromTouch() {
updateReorderPinned(now); updateReorderPinned(now);
} }
bool InnerWidget::hasChatTypeFilter() const {
return !_searchResults.empty()
&& (_searchState.tab == ChatSearchTab::MyMessages);
}
void InnerWidget::searchRequested(bool loading) { void InnerWidget::searchRequested(bool loading) {
_searchWaiting = false; _searchWaiting = false;
_searchLoading = loading; _searchLoading = loading;

View file

@ -141,6 +141,8 @@ public:
void refreshEmpty(); void refreshEmpty();
void resizeEmpty(); void resizeEmpty();
void showPeerMenu();
[[nodiscard]] bool isUserpicPress() const; [[nodiscard]] bool isUserpicPress() const;
[[nodiscard]] bool isUserpicPressOnWide() const; [[nodiscard]] bool isUserpicPressOnWide() const;
void cancelChatPreview(); void cancelChatPreview();
@ -461,6 +463,7 @@ private:
void handleChatListEntryRefreshes(); void handleChatListEntryRefreshes();
void moveSearchIn(); void moveSearchIn();
void dragPinnedFromTouch(); void dragPinnedFromTouch();
[[nodiscard]] bool hasChatTypeFilter() const;
void saveChatsFilterScrollState(FilterId filterId); void saveChatsFilterScrollState(FilterId filterId);
void restoreChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId);

View file

@ -1231,6 +1231,12 @@ void Widget::setupShortcuts() {
} }
return false; return false;
}); });
request->check(Command::ShowChatMenu, 1) && request->handle([=] {
if (_inner) {
_inner->showPeerMenu();
}
return true;
});
} }
}, lifetime()); }, lifetime());
} }

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