diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 10e2d8efd..266d8218a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1096,8 +1096,8 @@ PRIVATE info/saved/info_saved_sublists_widget.h info/settings/info_settings_widget.cpp info/settings/info_settings_widget.h - info/similar_channels/info_similar_channels_widget.cpp - info/similar_channels/info_similar_channels_widget.h + info/similar_peers/info_similar_peers_widget.cpp + info/similar_peers/info_similar_peers_widget.h info/statistics/info_statistics_common.h info/statistics/info_statistics_inner_widget.cpp info/statistics/info_statistics_inner_widget.h @@ -1217,6 +1217,8 @@ PRIVATE media/audio/media_audio_loader.h media/audio/media_audio_loaders.cpp media/audio/media_audio_loaders.h + media/audio/media_audio_local_cache.cpp + media/audio/media_audio_local_cache.h media/audio/media_audio_track.cpp media/audio/media_audio_track.h media/audio/media_child_ffmpeg_loader.cpp diff --git a/Telegram/Resources/icons/chat/markup_webview.png b/Telegram/Resources/icons/chat/markup_webview.png new file mode 100644 index 000000000..801937d7c Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview.png differ diff --git a/Telegram/Resources/icons/chat/markup_webview@2x.png b/Telegram/Resources/icons/chat/markup_webview@2x.png new file mode 100644 index 000000000..068f4f00b Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview@2x.png differ diff --git a/Telegram/Resources/icons/chat/markup_webview@3x.png b/Telegram/Resources/icons/chat/markup_webview@3x.png new file mode 100644 index 000000000..d52aa42c9 Binary files /dev/null and b/Telegram/Resources/icons/chat/markup_webview@3x.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff.png b/Telegram/Resources/icons/menu/nft_takeoff.png new file mode 100644 index 000000000..464d149bd Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff@2x.png b/Telegram/Resources/icons/menu/nft_takeoff@2x.png new file mode 100644 index 000000000..6603a8194 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff@2x.png differ diff --git a/Telegram/Resources/icons/menu/nft_takeoff@3x.png b/Telegram/Resources/icons/menu/nft_takeoff@3x.png new file mode 100644 index 000000000..bd0d91eaf Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_takeoff@3x.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear.png b/Telegram/Resources/icons/menu/nft_wear.png new file mode 100644 index 000000000..868711c1b Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear@2x.png b/Telegram/Resources/icons/menu/nft_wear@2x.png new file mode 100644 index 000000000..d94838dbe Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear@2x.png differ diff --git a/Telegram/Resources/icons/menu/nft_wear@3x.png b/Telegram/Resources/icons/menu/nft_wear@3x.png new file mode 100644 index 000000000..35545df89 Binary files /dev/null and b/Telegram/Resources/icons/menu/nft_wear@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 04ee96cc1..87554281d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_update_fail" = "Update check failed :("; "lng_settings_workmode_tray" = "Show tray icon"; "lng_settings_workmode_window" = "Show taskbar icon"; +"lng_settings_window_close" = "When window closed"; +"lng_settings_run_in_background" = "Run in the background"; +"lng_settings_quit_on_close" = "Quit the application"; "lng_settings_close_to_taskbar" = "Close to taskbar"; "lng_settings_monochrome_icon" = "Use monochrome icon"; "lng_settings_window_system" = "Window title bar"; @@ -1346,6 +1349,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_similar_channels#one" = "{count} similar channel"; "lng_profile_similar_channels#other" = "{count} similar channels"; +"lng_profile_similar_bots#one" = "{count} similar bot"; +"lng_profile_similar_bots#other" = "{count} similar bots"; "lng_profile_saved_messages#one" = "{count} saved message"; "lng_profile_saved_messages#other" = "{count} saved messages"; "lng_profile_peer_gifts#one" = "{count} gift"; @@ -2020,16 +2025,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_gift_sent" = "You sent a gift for {cost}"; "lng_action_gift_unique_sent" = "You sent a unique collectible item"; "lng_action_gift_upgraded" = "{user} turned the gift from you into a unique collectible"; +"lng_action_gift_upgraded_channel" = "{user} turned this gift to {channel} into a unique collectible"; +"lng_action_gift_upgraded_self_channel" = "You turned this gift to {channel} into a unique collectible"; "lng_action_gift_upgraded_mine" = "You turned the gift from {user} into a unique collectible"; "lng_action_gift_upgraded_self" = "You turned this gift into a unique collectible"; "lng_action_gift_transferred" = "{user} transferred you a gift"; +"lng_action_gift_transferred_channel" = "{user} transferred a gift to {channel}"; +"lng_action_gift_transferred_unknown" = "Someone transferred you a gift"; +"lng_action_gift_transferred_unknown_channel" = "Someone transferred a gift to {channel}"; +"lng_action_gift_transferred_self" = "You transferred a unique collectible"; +"lng_action_gift_transferred_self_channel" = "You transferred a gift to {channel}"; "lng_action_gift_transferred_mine" = "You transferred a gift to {user}"; "lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; +"lng_action_gift_sent_channel" = "{user} sent a gift to {name} for {cost}"; +"lng_action_gift_sent_self_channel" = "You sent a gift to {name} for {cost}"; "lng_action_gift_self_bought" = "You bought a gift for {cost}"; "lng_action_gift_self_subtitle" = "Saved Gift"; "lng_action_gift_self_about#one" = "Display this gift on your page or convert it to **{count}** Star."; "lng_action_gift_self_about#other" = "Display this gift on your page or convert it to **{count}** Stars."; "lng_action_gift_self_about_unique" = "You can display this gift on your page or turn it into unique collectible and send to others."; +"lng_action_gift_channel_about#one" = "Display this gift in channel's Gifts or convert it to **{count}** Star."; +"lng_action_gift_channel_about#other" = "Display this gift in channel's Gifts or convert it to **{count}** Stars."; +"lng_action_gift_channel_about_unique" = "You can display this gift in channel's Gifts or turn it into unique collectible."; "lng_action_gift_for_stars#one" = "{count} Star"; "lng_action_gift_for_stars#other" = "{count} Stars"; "lng_action_gift_got_subtitle" = "Gift from {user}"; @@ -2038,6 +2055,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_gift_got_upgradable_text" = "Upgrade this gift to a unique collectible."; "lng_action_gift_got_gift_text" = "You can keep this gift on your page."; "lng_action_gift_can_remove_text" = "You can remove this gift from your page."; +"lng_action_gift_got_gift_channel" = "You can keep this gift in channel's Gifts."; +"lng_action_gift_can_remove_channel" = "You can remove this gift from channel's Gifts."; "lng_action_gift_sent_subtitle" = "Gift for {user}"; "lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star."; "lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars."; @@ -2108,9 +2127,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_similar_channels_premium_all_link" = "Telegram Premium"; "lng_similar_channels_show_more" = "Show more channels"; +"lng_similar_bots_title" = "Similar bots"; +"lng_similar_bots_premium_all#one" = "Subscribe to {link} to unlock up to **{count}** similar bot."; +"lng_similar_bots_premium_all#other" = "Subscribe to {link} to unlock up to **{count}** similar bots."; +"lng_similar_bots_show_more" = "Show more bots"; + "lng_peer_gifts_title" = "Gifts"; "lng_peer_gifts_about" = "These gifts were sent to {user} by other users."; "lng_peer_gifts_about_mine" = "These gifts were sent to you by other users. Click on a gift to convert it to Stars or change its privacy settings."; +"lng_peer_gifts_notify" = "Notify About New Gifts"; +"lng_peer_gifts_notify_enabled" = "You will receive a message from Telegram when your channel receives a gift."; +"lng_peer_gifts_filter_by_value" = "Sort by Value"; +"lng_peer_gifts_filter_by_date" = "Sort by Date"; +"lng_peer_gifts_filter_unlimited" = "Unlimited"; +"lng_peer_gifts_filter_limited" = "Limited"; +"lng_peer_gifts_filter_unique" = "Unique"; +"lng_peer_gifts_filter_saved" = "Displayed"; +"lng_peer_gifts_filter_unsaved" = "Hidden"; "lng_premium_gift_duration_months#one" = "for {count} month"; "lng_premium_gift_duration_months#other" = "for {count} months"; @@ -2373,6 +2406,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_stickers_add" = "Choose sticker set"; "lng_group_emoji" = "Select Emoji Pack"; "lng_group_emoji_description" = "Choose an emoji pack that will be available to all members within the group."; +"lng_collectible_emoji" = "Collectibles"; "lng_premium" = "Premium"; "lng_premium_free" = "Free"; @@ -2960,6 +2994,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_group_needs_level_emoji#one" = "Your group needs to reach **Level {count}** to set emoji pack."; "lng_boost_group_needs_level_emoji#other" = "Your group needs to reach **Level {count}** to set emoji pack."; +"lng_boost_channel_title_wear" = "Wear Item"; +"lng_boost_channel_needs_level_wear#one" = "Your channel needs **Level {count}** to wear collectibles."; +"lng_boost_channel_needs_level_wear#other" = "Your channel needs **Level {count}** to wear collectibles."; + "lng_boost_channel_ask" = "Ask your **Premium** subscribers to boost your channel with this link:"; "lng_boost_channel_ask_button" = "Copy Link"; "lng_boost_channel_or" = "or"; @@ -3239,10 +3277,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_send_anonymous" = "Hide My Name"; "lng_gift_send_anonymous_self" = "Hide my name and message from visitors to my profile."; "lng_gift_send_anonymous_about" = "You can hide your name and message from visitors to {user}'s profile. {recipient} will still see your name and message."; +"lng_gift_send_anonymous_about_channel" = "You can hide your name and message from all visitors of this channel except its admins."; "lng_gift_send_unique" = "Make Unique for {price}"; "lng_gift_send_unique_about" = "Enable this to let {user} turn your gift into a unique collectible. {link}"; +"lng_gift_send_unique_about_channel" = "Enable this to let the admins of {name} turn your gift into a unique collectible. {link}"; "lng_gift_send_unique_link" = "Learn More >"; "lng_gift_send_premium_about" = "Only {user} will see your message."; +"lng_gift_send_limited_sold#one" = "{count} sold"; +"lng_gift_send_limited_sold#other" = "{count} sold"; +"lng_gift_send_limited_left#one" = "{count} left"; +"lng_gift_send_limited_left#other" = "{count} left"; "lng_gift_send_button" = "Send a Gift for {cost}"; "lng_gift_send_button_self" = "Buy a Gift for {cost}"; "lng_gift_sent_title" = "Gift Sent!"; @@ -3254,20 +3298,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_price_unique" = "Unique"; "lng_gift_view_unpack" = "Unpack"; "lng_gift_anonymous_hint" = "Only you can see the sender's name."; +"lng_gift_anonymous_hint_channel" = "Only admins of this channel can see the sender's name."; "lng_gift_hidden_hint" = "This gift is hidden. Only you can see it."; -"lng_gift_visible_hint" = "This gift is visible to visitors of your page."; +"lng_gift_hidden_unique" = "This gift is not displayed on your page."; +"lng_gift_visible_hint" = "This gift is visible on your page."; +"lng_gift_hidden_hint_channel" = "This gift is hidden from visitors of your channel."; +"lng_gift_visible_hint_channel" = "This gift is visible in your channel's Gifts."; +"lng_gift_in_blockchain" = "This gift is in TON blockchain. {link}"; +"lng_gift_in_blockchain_link" = "View >"; +"lng_gift_visible_hide" = "Hide >"; +"lng_gift_show_on_page" = "Display on my Page"; +"lng_gift_show_on_channel" = "Display in channel's Gifts"; "lng_gift_availability" = "Availability"; "lng_gift_from_hidden" = "Hidden User"; -"lng_gift_visibility" = "Visibility"; -"lng_gift_visibility_shown" = "Visible on your page"; -"lng_gift_visibility_hidden" = "Not visible on your page"; -"lng_gift_visibility_show" = "show"; -"lng_gift_visibility_hide" = "hide"; "lng_gift_self_status" = "buy yourself a gift"; "lng_gift_self_title" = "Buy a Gift"; "lng_gift_self_about" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later."; +"lng_gift_channel_title" = "Send a Gift"; +"lng_gift_channel_about" = "Select a gift to show appreciation for {name}."; "lng_gift_unique_owner" = "Owner"; -"lng_gift_unique_owner_change" = "change"; +"lng_gift_unique_address_copied" = "Address copied to clipboard."; "lng_gift_unique_status" = "Status"; "lng_gift_unique_status_non" = "Non-Unique"; "lng_gift_unique_status_upgrade" = "upgrade"; @@ -3291,14 +3341,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_convert_sure_title" = "Convert Gift to Stars"; "lng_gift_convert_sure_confirm#one" = "Do you want to convert this gift from {user} to **{count} Star**?"; "lng_gift_convert_sure_confirm#other" = "Do you want to convert this gift from {user} to **{count} Stars**?"; +"lng_gift_convert_sure_confirm_channel#one" = "Do you want to convert this gift to {channel} to **{count} Star**?"; +"lng_gift_convert_sure_confirm_channel#other" = "Do you want to convert this gift to {channel} to **{count} Stars**?"; "lng_gift_convert_sure_limit#one" = "Conversion is available for the next **{count} day**."; "lng_gift_convert_sure_limit#other" = "Conversion is available for the next **{count} days**."; "lng_gift_convert_sure_caution" = "This action cannot be undone. This will permanently destroy the gift."; "lng_gift_convert_sure" = "Convert"; "lng_gift_display_done" = "The gift is now shown on your profile page."; +"lng_gift_display_done_channel" = "The gift is now shown in channel's Gifts."; "lng_gift_display_done_hide" = "The gift is now hidden from your profile page."; +"lng_gift_display_done_hide_channel" = "The gift is now hidden from channel's Gifts."; "lng_gift_got_stars#one" = "You got **{count} Star** for this gift."; "lng_gift_got_stars#other" = "You got **{count} Stars** for this gift."; +"lng_gift_channel_got#one" = "Channel got **{count} Star** for this gift."; +"lng_gift_channel_got#other" = "Channel got **{count} Stars** for this gift."; "lng_gift_sold_out_title" = "Sold Out!"; "lng_gift_sold_out_text#one" = "All {count} gift was already sold."; "lng_gift_sold_out_text#other" = "All {count} gifts were already sold."; @@ -3309,6 +3365,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_upgrade_about" = "Turn your gift into a unique collectible\nthat you can transfer or auction."; "lng_gift_upgrade_preview_title" = "Make Unique"; "lng_gift_upgrade_preview_about" = "Let {name} turn your gift into a unique collectible."; +"lng_gift_upgrade_preview_about_channel" = "Let the admins of {name} turn your gift into a unique collectible."; "lng_gift_upgrade_unique_title" = "Unique"; "lng_gift_upgrade_unique_about" = "Get a unique number, model, backdrop and symbol for your gift."; "lng_gift_upgrade_transferable_title" = "Transferable"; @@ -3328,6 +3385,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transferred_about" = "{name} was successfully transferred to {recipient}."; "lng_gift_transfer_title" = "Transfer {name}"; "lng_gift_transfer_via_blockchain" = "Send via Blockchain"; +"lng_gift_transfer_password_title" = "Two-step verification"; +"lng_gift_transfer_password_description" = "Please enter your password to transfer."; +"lng_gift_transfer_password_about" = "You can withdraw only if you have:"; +"lng_gift_transfer_confirm_title" = "Manage with Fragment"; +"lng_gift_transfer_confirm_text" = "You can use Fragment, a third-party service, to transfer {name} to your TON account. After that, you can manage it as an NFT with any TON wallet outside Telegram.\n\nYou can also move such NFTs back to your Telegram account via Fragment."; +"lng_gift_transfer_confirm_button" = "Open Fragment"; "lng_gift_transfer_unlocks_days#one" = "unlocks in {count} day"; "lng_gift_transfer_unlocks_days#other" = "unlocks in {count} days"; "lng_gift_transfer_unlocks_hours#one" = "unlocks in {count} hour"; @@ -3344,6 +3407,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_transfer_sure_for" = "Do you want to transfer ownership of {name} to {recipient} for {price}?"; "lng_gift_transfer_button" = "Transfer"; "lng_gift_transfer_button_for" = "Transfer for {price}"; +"lng_gift_transfer_wear" = "Wear"; +"lng_gift_transfer_take_off" = "Take Off"; +"lng_gift_wear_title" = "Wear {name}"; +"lng_gift_wear_about" = "and get these benefits:"; +"lng_gift_wear_badge_title" = "Radiant Badge"; +"lng_gift_wear_badge_about" = "The glittering icon of this item will be displayed next to your name."; +"lng_gift_wear_badge_about_channel" = "The glittering icon of this item will be displayed next to channel's name."; +"lng_gift_wear_proof_title" = "Proof of Ownership"; +"lng_gift_wear_proof_about" = "Clicking the icon of this item next to your name will show its info and owner."; +"lng_gift_wear_proof_about_channel" = "Clicking the icon of this item next to channel's name will show its info and owner."; +"lng_gift_wear_start" = "Start Wearing"; +"lng_gift_wear_subscribe" = "Subscribe to {link} to wear collectibles."; +"lng_gift_wear_start_toast" = "You put on {name}"; +"lng_gift_wear_end_toast" = "You took off {name}"; "lng_accounts_limit_title" = "Limit Reached"; "lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account."; @@ -5520,6 +5597,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_view_button_iv" = "Instant View"; "lng_view_button_stickerset" = "View stickers"; "lng_view_button_emojipack" = "View emoji"; +"lng_view_button_collectible" = "View collectible"; "lng_sponsored_hide_ads" = "Hide"; "lng_sponsored_title" = "What are sponsored messages?"; @@ -5836,6 +5914,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boosts_list_tab_gifts#other" = "{count} Gifts"; "lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways"; +"lng_boosts_prepaid_giveaway_title_subtext" = "Select a giveaway you already paid for to set it up."; "lng_boosts_prepaid_giveaway_single" = "Prepaid giveaway"; "lng_boosts_prepaid_giveaway_quantity#one" = "{count} Telegram Premium"; "lng_boosts_prepaid_giveaway_quantity#other" = "{count} Telegram Premium"; @@ -5895,6 +5974,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels"; "lng_channel_earn_chart_top_hours" = "Ad impressions"; "lng_channel_earn_chart_revenue" = "Ad rewards"; +"lng_channel_earn_chart_overriden_detail_credits" = "Rewards in Stars"; "lng_channel_earn_chart_overriden_detail_currency" = "Rewards in TON"; "lng_channel_earn_chart_overriden_detail_usd" = "Rewards in USD"; "lng_channel_earn_currency_history" = "TON Transactions"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 5f8a2f3e8..a9f716c9e 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -10,7 +10,7 @@ + Version="5.10.7.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index d4fea4b71..2d5590296 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,10,3,0 - PRODUCTVERSION 5,10,3,0 + FILEVERSION 5,10,7,0 + PRODUCTVERSION 5,10,7,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop" - VALUE "FileVersion", "5.10.3.0" + VALUE "FileVersion", "5.10.7.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.10.3.0" + VALUE "ProductVersion", "5.10.7.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index db48deca9..39318b864 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 5,10,3,0 - PRODUCTVERSION 5,10,3,0 + FILEVERSION 5,10,7,0 + PRODUCTVERSION 5,10,7,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Radolyn Labs" VALUE "FileDescription", "AyuGram Desktop Updater" - VALUE "FileVersion", "5.10.3.0" + VALUE "FileVersion", "5.10.7.0" VALUE "LegalCopyright", "Copyright (C) 2014-2025" VALUE "ProductName", "AyuGram Desktop" - VALUE "ProductVersion", "5.10.3.0" + VALUE "ProductVersion", "5.10.7.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/api/api_chat_participants.cpp b/Telegram/SourceFiles/api/api_chat_participants.cpp index 10663d1de..478390798 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.cpp +++ b/Telegram/SourceFiles/api/api_chat_participants.cpp @@ -211,11 +211,10 @@ void ApplyBotsList( Data::PeerUpdate::Flag::FullInfo); } -[[nodiscard]] ChatParticipants::Channels ParseSimilar( +[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels( not_null session, const MTPmessages_Chats &chats) { - auto result = ChatParticipants::Channels(); - std::vector>(); + auto result = ChatParticipants::Peers(); chats.match([&](const auto &data) { const auto &list = data.vchats().v; result.list.reserve(list.size()); @@ -234,10 +233,29 @@ void ApplyBotsList( return result; } -[[nodiscard]] ChatParticipants::Channels ParseSimilar( +[[nodiscard]] ChatParticipants::Peers ParseSimilarChannels( not_null channel, const MTPmessages_Chats &chats) { - return ParseSimilar(&channel->session(), chats); + return ParseSimilarChannels(&channel->session(), chats); +} + +[[nodiscard]] ChatParticipants::Peers ParseSimilarBots( + not_null 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()) { + if (session->premiumPossible()) { + result.more = data.vcount().v - data.vusers().v.size(); + } + } + }); + return result; } } // namespace @@ -782,52 +800,65 @@ void ChatParticipants::unblock( _kickRequests.emplace(kick, requestId); } -void ChatParticipants::loadSimilarChannels(not_null channel) { - if (!channel->isBroadcast()) { - return; - } else if (const auto i = _similar.find(channel); i != end(_similar)) { +void ChatParticipants::loadSimilarPeers(not_null peer) { + if (const auto i = _similar.find(peer); i != end(_similar)) { if (i->second.requestId - || !i->second.channels.more - || !channel->session().premium()) { + || !i->second.peers.more + || !peer->session().premium()) { return; } } - using Flag = MTPchannels_GetChannelRecommendations::Flag; - _similar[channel].requestId = _api.request( - MTPchannels_GetChannelRecommendations( - MTP_flags(Flag::f_channel), - channel->inputChannel) - ).done([=](const MTPmessages_Chats &result) { - auto &similar = _similar[channel]; - similar.requestId = 0; - auto parsed = ParseSimilar(channel, result); - if (similar.channels == parsed) { - return; - } - similar.channels = std::move(parsed); - if (const auto history = channel->owner().historyLoaded(channel)) { - if (const auto item = history->joinedMessageInstance()) { - history->owner().requestItemResize(item); + if (const auto channel = peer->asBroadcast()) { + using Flag = MTPchannels_GetChannelRecommendations::Flag; + _similar[peer].requestId = _api.request( + MTPchannels_GetChannelRecommendations( + MTP_flags(Flag::f_channel), + channel->inputChannel) + ).done([=](const MTPmessages_Chats &result) { + auto &similar = _similar[channel]; + similar.requestId = 0; + auto parsed = ParseSimilarChannels(channel, result); + if (similar.peers == parsed) { + return; } - } - _similarLoaded.fire_copy(channel); - }).send(); + similar.peers = std::move(parsed); + if (const auto history = channel->owner().historyLoaded(channel)) { + if (const auto item = history->joinedMessageInstance()) { + history->owner().requestItemResize(item); + } + } + _similarLoaded.fire_copy(channel); + }).send(); + } else if (const auto bot = peer->asBot()) { + _similar[peer].requestId = _api.request( + MTPbots_GetBotRecommendations(bot->inputUser) + ).done([=](const MTPusers_Users &result) { + auto &similar = _similar[peer]; + similar.requestId = 0; + auto parsed = ParseSimilarBots(&peer->session(), result); + if (similar.peers == parsed) { + return; + } + similar.peers = std::move(parsed); + _similarLoaded.fire_copy(peer); + }).send(); + } } -auto ChatParticipants::similar(not_null channel) --> const Channels & { - const auto i = channel->isBroadcast() - ? _similar.find(channel) +auto ChatParticipants::similar(not_null peer) +-> const Peers & { + const auto i = (peer->isBroadcast() || peer->isBot()) + ? _similar.find(peer) : end(_similar); if (i != end(_similar)) { - return i->second.channels; + return i->second.peers; } - static const auto empty = Channels(); + static const auto empty = Peers(); return empty; } auto ChatParticipants::similarLoaded() const --> rpl::producer> { +-> rpl::producer> { return _similarLoaded.events(); } @@ -841,15 +872,15 @@ void ChatParticipants::loadRecommendations() { MTP_inputChannelEmpty()) ).done([=](const MTPmessages_Chats &result) { _recommendations.requestId = 0; - auto parsed = ParseSimilar(_session, result); - _recommendations.channels = std::move(parsed); - _recommendations.channels.more = 0; + auto parsed = ParseSimilarChannels(_session, result); + _recommendations.peers = std::move(parsed); + _recommendations.peers.more = 0; _recommendationsLoaded = true; }).send(); } -const ChatParticipants::Channels &ChatParticipants::recommendations() const { - return _recommendations.channels; +const ChatParticipants::Peers &ChatParticipants::recommendations() const { + return _recommendations.peers; } rpl::producer<> ChatParticipants::recommendationsLoaded() const { diff --git a/Telegram/SourceFiles/api/api_chat_participants.h b/Telegram/SourceFiles/api/api_chat_participants.h index df332522d..4f073eb8e 100644 --- a/Telegram/SourceFiles/api/api_chat_participants.h +++ b/Telegram/SourceFiles/api/api_chat_participants.h @@ -138,27 +138,27 @@ public: not_null channel, not_null participant); - void loadSimilarChannels(not_null channel); + void loadSimilarPeers(not_null peer); - struct Channels { - std::vector> list; + struct Peers { + std::vector> list; int more = 0; friend inline bool operator==( - const Channels &, - const Channels &) = default; + const Peers &, + const Peers &) = default; }; - [[nodiscard]] const Channels &similar(not_null channel); + [[nodiscard]] const Peers &similar(not_null peer); [[nodiscard]] auto similarLoaded() const - -> rpl::producer>; + -> rpl::producer>; void loadRecommendations(); - [[nodiscard]] const Channels &recommendations() const; + [[nodiscard]] const Peers &recommendations() const; [[nodiscard]] rpl::producer<> recommendationsLoaded() const; private: - struct SimilarChannels { - Channels channels; + struct SimilarPeers { + Peers peers; mtpRequestId requestId = 0; }; @@ -186,10 +186,10 @@ private: not_null>; base::flat_map _kickRequests; - base::flat_map, SimilarChannels> _similar; - rpl::event_stream> _similarLoaded; + base::flat_map, SimilarPeers> _similar; + rpl::event_stream> _similarLoaded; - SimilarChannels _recommendations; + SimilarPeers _recommendations; rpl::variable _recommendationsLoaded = false; }; diff --git a/Telegram/SourceFiles/api/api_credits.cpp b/Telegram/SourceFiles/api/api_credits.cpp index 7d17ca41e..bdb10d02d 100644 --- a/Telegram/SourceFiles/api/api_credits.cpp +++ b/Telegram/SourceFiles/api/api_credits.cpp @@ -513,4 +513,12 @@ void EditCreditsSubscription( )).done(done).fail([=](const MTP::Error &e) { fail(e.type()); }).send(); } +MTPInputSavedStarGift InputSavedStarGiftId(const Data::SavedStarGiftId &id) { + return id.isUser() + ? MTP_inputSavedStarGiftUser(MTP_int(id.userMessageId().bare)) + : MTP_inputSavedStarGiftChat( + id.chat()->input, + MTP_long(id.chatSavedId())); +} + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_credits.h b/Telegram/SourceFiles/api/api_credits.h index 4f697cf09..c7005073e 100644 --- a/Telegram/SourceFiles/api/api_credits.h +++ b/Telegram/SourceFiles/api/api_credits.h @@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_credits_earn.h" #include "mtproto/sender.h" +namespace Data { +class SavedStarGiftId; +} // namespace Data + namespace Main { class Session; } // namespace Main @@ -116,4 +120,7 @@ void EditCreditsSubscription( Fn done, Fn fail); +[[nodiscard]] MTPInputSavedStarGift InputSavedStarGiftId( + const Data::SavedStarGiftId &id); + } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 662bfc980..313d6bfef 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -283,7 +283,9 @@ mtpRequestId EditTextMessage( return MTP_inputMediaDocument( MTP_flags(flags), document->mtpInput(), + MTPInputPhoto(), // video_cover MTP_int(media->ttlSeconds()), + MTPint(), // video_timestamp MTPstring()); // query }; takeFileReference = [=] { return document->fileReference(); }; diff --git a/Telegram/SourceFiles/api/api_media.cpp b/Telegram/SourceFiles/api/api_media.cpp index 22fc37631..3bbc00f5f 100644 --- a/Telegram/SourceFiles/api/api_media.cpp +++ b/Telegram/SourceFiles/api/api_media.cpp @@ -121,6 +121,8 @@ MTPInputMedia PrepareUploadedDocument( ComposeSendingDocumentAttributes(document), MTP_vector( ranges::to>(info.attachedStickers)), + MTPInputPhoto(), // video_cover + MTP_int(0), // video_timestamp MTP_int(ttlSeconds)); } diff --git a/Telegram/SourceFiles/api/api_messages_search.cpp b/Telegram/SourceFiles/api/api_messages_search.cpp index 4fbcaebf0..8bc7a4e3d 100644 --- a/Telegram/SourceFiles/api/api_messages_search.cpp +++ b/Telegram/SourceFiles/api/api_messages_search.cpp @@ -103,6 +103,7 @@ void MessagesSearch::searchRequest() { _requestId = _history->session().api().request(MTPmessages_Search( MTP_flags((fromPeer ? Flag::f_from_id : Flag()) | (savedPeer ? Flag::f_saved_peer_id : Flag()) + | (_request.topMsgId ? Flag::f_top_msg_id : Flag()) | (_request.tags.empty() ? Flag() : Flag::f_saved_reaction)), _history->peer->input, MTP_string(_request.query), @@ -111,7 +112,7 @@ void MessagesSearch::searchRequest() { MTP_vector_from_range(_request.tags | ranges::views::transform( Data::ReactionToMTP )), - MTPint(), // top_msg_id + MTP_int(_request.topMsgId), // top_msg_id MTP_inputMessagesFilterEmpty(), MTP_int(0), // min_date MTP_int(0), // max_date diff --git a/Telegram/SourceFiles/api/api_messages_search.h b/Telegram/SourceFiles/api/api_messages_search.h index 76046aaa9..b650b8799 100644 --- a/Telegram/SourceFiles/api/api_messages_search.h +++ b/Telegram/SourceFiles/api/api_messages_search.h @@ -32,6 +32,7 @@ public: QString query; PeerData *from = nullptr; std::vector tags; + MsgId topMsgId; friend inline bool operator==( const Request &, diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.cpp b/Telegram/SourceFiles/api/api_messages_search_merged.cpp index 1990cfd21..5a26e4386 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.cpp +++ b/Telegram/SourceFiles/api/api_messages_search_merged.cpp @@ -64,6 +64,10 @@ MessagesSearchMerged::MessagesSearchMerged(not_null history) } } +void MessagesSearchMerged::disableMigrated() { + _migratedSearch = std::nullopt; +} + void MessagesSearchMerged::addFound(const FoundMessages &data) { for (const auto &message : data.messages) { _concatedFound.messages.push_back(message); diff --git a/Telegram/SourceFiles/api/api_messages_search_merged.h b/Telegram/SourceFiles/api/api_messages_search_merged.h index 1e7c0493b..1a949c6ed 100644 --- a/Telegram/SourceFiles/api/api_messages_search_merged.h +++ b/Telegram/SourceFiles/api/api_messages_search_merged.h @@ -29,6 +29,7 @@ public: void clear(); void search(const Request &search); void searchMore(); + void disableMigrated(); [[nodiscard]] const FoundMessages &messages() const; [[nodiscard]] const Request &request() const; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 682f34cde..3bd36b40c 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -805,8 +805,14 @@ std::optional FromTL( auto result = Data::StarGift{ .id = uint64(data.vid().v), .unique = std::make_shared(Data::UniqueGift{ + .id = data.vid().v, + .slug = qs(data.vslug()), .title = qs(data.vtitle()), - .ownerId = peerFromUser(UserId(data.vowner_id().v)), + .ownerAddress = qs(data.vowner_address().value_or_empty()), + .ownerName = qs(data.vowner_name().value_or_empty()), + .ownerId = (data.vowner_id() + ? peerFromMTP(*data.vowner_id()) + : PeerId()), .number = data.vnum().v, .model = *model, .pattern = *pattern, @@ -829,9 +835,9 @@ std::optional FromTL( }); } -std::optional FromTL( - not_null to, - const MTPuserStarGift &gift) { +std::optional FromTL( + not_null to, + const MTPsavedStarGift &gift) { const auto session = &to->session(); const auto &data = gift.data(); auto parsed = FromTL(session, data.vgift()); @@ -841,8 +847,12 @@ std::optional FromTL( unique->starsForTransfer = data.vtransfer_stars().value_or(-1); unique->exportAt = data.vcan_export_at().value_or_empty(); } - return Data::UserStarGift{ + using Id = Data::SavedStarGiftId; + return Data::SavedStarGift{ .info = std::move(*parsed), + .manageId = (to->isUser() + ? Id::User(data.vmsg_id().value_or_empty()) + : Id::Chat(to, data.vsaved_id().value_or_empty())), .message = (data.vmessage() ? TextWithEntities{ .text = qs(data.vmessage()->data().vtext()), @@ -855,9 +865,8 @@ std::optional FromTL( .starsUpgradedBySender = int64( data.vupgrade_stars().value_or_empty()), .fromId = (data.vfrom_id() - ? peerFromUser(data.vfrom_id()->v) + ? peerFromMTP(*data.vfrom_id()) : PeerId()), - .messageId = data.vmsg_id().value_or_empty(), .date = data.vdate().v, .upgradable = data.is_can_upgrade(), .anonymous = data.is_name_hidden(), @@ -910,11 +919,9 @@ Data::UniqueGiftOriginalDetails FromTL( auto result = Data::UniqueGiftOriginalDetails(); result.date = data.vdate().v; result.senderId = data.vsender_id() - ? peerFromUser( - UserId(data.vsender_id().value_or_empty())) + ? peerFromMTP(*data.vsender_id()) : PeerId(); - result.recipientId = peerFromUser( - UserId(data.vrecipient_id().v)); + result.recipientId = peerFromMTP(data.vrecipient_id()); result.message = data.vmessage() ? ParseTextWithEntities(session, *data.vmessage()) : TextWithEntities(); diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 4617da755..a7757a490 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -259,9 +259,9 @@ enum class RequirePremiumState { [[nodiscard]] std::optional FromTL( not_null session, const MTPstarGift &gift); -[[nodiscard]] std::optional FromTL( - not_null to, - const MTPuserStarGift &gift); +[[nodiscard]] std::optional FromTL( + not_null to, + const MTPsavedStarGift &gift); [[nodiscard]] Data::UniqueGiftModel FromTL( not_null session, diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index d0b897a07..193e2e71b 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -272,7 +272,9 @@ void SendExistingDocument( return MTP_inputMediaDocument( MTP_flags(0), document->mtpInput(), + MTPInputPhoto(), // video_cover MTPint(), // ttl_seconds + MTPint(), // video_timestamp MTPstring()); // query }; SendExistingMedia( @@ -550,6 +552,8 @@ void SendConfirmedFile( | (file->spoiler ? Flag::f_spoiler : Flag())), file->document, MTPVector(), // alt_documents + MTPPhoto(), // video_cover + MTPint(), // video_timestamp MTPint()); } else if (file->type == SendMediaType::Audio) { const auto ttlSeconds = file->to.options.ttlSeconds; @@ -560,6 +564,8 @@ void SendConfirmedFile( | (ttlSeconds ? Flag::f_ttl_seconds : Flag())), file->document, MTPVector(), // alt_documents + MTPPhoto(), // video_cover + MTPint(), // video_timestamp MTP_int(ttlSeconds)); } else if (file->type == SendMediaType::Round) { using Flag = MTPDmessageMediaDocument::Flag; @@ -571,6 +577,8 @@ void SendConfirmedFile( | (file->spoiler ? Flag::f_spoiler : Flag())), file->document, MTPVector(), // alt_documents + MTPPhoto(), // video_cover + MTPint(), // video_timestamp MTP_int(ttlSeconds)); } else { Unexpected("Type in sendFilesConfirmed."); diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 7e5e5a0b6..86cfd12f2 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1812,7 +1812,7 @@ void ApiWrap::joinChannel(not_null channel) { _channelAmInRequests.emplace(channel, requestId); using Flag = ChannelDataFlag; - chatParticipants().loadSimilarChannels(channel); + chatParticipants().loadSimilarPeers(channel); const auto settings = &AyuSettings::getInstance(); if (!settings->collapseSimilarChannels) { @@ -3422,7 +3422,8 @@ void ApiWrap::forwardMessages( MTP_int(topMsgId), MTP_int(action.options.scheduled), (sendAs ? sendAs->input : MTP_inputPeerEmpty()), - Data::ShortcutIdToMTP(_session, action.options.shortcutId) + Data::ShortcutIdToMTP(_session, action.options.shortcutId), + MTPint() // video_timestamp )).done([=](const MTPUpdates &result) { if (!scheduled) { this->updates().checkForSentToScheduled(result); @@ -4218,7 +4219,9 @@ void ApiWrap::uploadAlbumMedia( fields.vid(), fields.vaccess_hash(), fields.vfile_reference()), + MTPInputPhoto(), // video_cover MTP_int(data.vttl_seconds().value_or_empty()), + MTPint(), // video_timestamp MTPstring()); // query sendAlbumWithUploaded(item, groupId, media); } break; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 2b396aec4..987538be5 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_credits.h" +#include "data/data_emoji_statuses.h" #include "data/data_media_types.h" // Data::GiveawayStart. #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" @@ -30,11 +31,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. +#include "info/profile/info_profile_badge.h" +#include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" +#include "main/main_app_config.h" #include "main/main_session.h" #include "mainwidget.h" #include "payments/payments_checkout_process.h" #include "payments/payments_form.h" +#include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" #include "ui/basic_click_handlers.h" // UrlClickHandler::Open. #include "ui/boxes/boost_box.h" // StartFireworks. @@ -127,29 +132,46 @@ constexpr auto kRarityTooltipDuration = 3 * crl::time(1000); : tr::lng_premium_gift_duration_years; } +[[nodiscard]] object_ptr MakeMaybeMultilineTokenValue( + not_null table, + const QString &token, + Settings::CreditsEntryBoxStyleOverrides st) { + constexpr auto kOneLineCount = 24; + const auto oneLine = token.length() <= kOneLineCount; + return object_ptr( + table, + rpl::single( + Ui::Text::Wrapped({ token }, EntityType::Code, {})), + (oneLine + ? table->st().defaultValue + : st.tableValueMultiline + ? *st.tableValueMultiline + : st::giveawayGiftCodeValueMultiline)); +} + [[nodiscard]] object_ptr MakePeerTableValue( - not_null parent, - not_null controller, + not_null table, + std::shared_ptr show, PeerId id, rpl::producer button = nullptr, Fn handler = nullptr) { - auto result = object_ptr(parent); + auto result = object_ptr(table); const auto raw = result.data(); const auto &st = st::giveawayGiftCodeUserpic; raw->resize(raw->width(), st.photoSize); - const auto peer = controller->session().data().peer(id); + const auto peer = show->session().data().peer(id); const auto userpic = Ui::CreateChild(raw, peer, st); const auto label = Ui::CreateChild( raw, (button && handler) ? peer->shortName() : peer->name(), - st::giveawayGiftCodeValue); + table->st().defaultValue); const auto send = (button && handler) ? Ui::CreateChild( raw, std::move(button), - st::starGiftSmallButton) + table->st().smallButton) : nullptr; if (send) { send->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); @@ -171,27 +193,109 @@ constexpr auto kRarityTooltipDuration = 3 * crl::time(1000); send->moveToLeft( position.x() + label->width() + st::normalFont->spacew, (position.y() - + st::giveawayGiftCodeValue.style.font->ascent - - st::starGiftSmallButton.style.font->ascent), + + table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), width); } }, label->lifetime()); userpic->setAttribute(Qt::WA_TransparentForMouseEvents); label->setAttribute(Qt::WA_TransparentForMouseEvents); - label->setTextColorOverride(st::windowActiveTextFg->c); + label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c); raw->setClickedCallback([=] { - controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller)); + show->showBox(PrepareShortInfoBox(peer, show)); + }); + + return result; +} + +[[nodiscard]] object_ptr MakePeerWithStatusValue( + not_null table, + std::shared_ptr show, + PeerId id, + Fn, EmojiStatusId)> pushStatusId) { + auto result = object_ptr(table); + const auto raw = result.data(); + + const auto &st = st::giveawayGiftCodeUserpic; + raw->resize(raw->width(), st.photoSize); + + const auto peer = show->session().data().peer(id); + const auto userpic = Ui::CreateChild(raw, peer, st); + const auto label = Ui::CreateChild( + raw, + peer->name(), + table->st().defaultValue); + + using namespace Info::Profile; + struct State { + rpl::variable content; + }; + const auto state = label->lifetime().make_state(); + state->content = EmojiStatusIdValue( + peer + ) | rpl::map([=](EmojiStatusId emojiStatusId) { + if (!peer->session().premium() + || (!peer->isSelf() && !emojiStatusId)) { + return Badge::Content(); + } + return Badge::Content{ + .badge = BadgeType::Premium, + .emojiStatusId = emojiStatusId, + }; + }); + const auto badge = label->lifetime().make_state( + raw, + st::infoPeerBadge, + &peer->session(), + state->content.value(), + nullptr, + [=] { return show->paused(ChatHelpers::PauseReason::Layer); }); + state->content.value( + ) | rpl::start_with_next([=](const Badge::Content &content) { + if (const auto widget = badge->widget()) { + pushStatusId(widget, content.emojiStatusId); + } + }, raw->lifetime()); + + rpl::combine( + raw->widthValue(), + rpl::single(rpl::empty) | rpl::then(badge->updated()) + ) | rpl::start_with_next([=](int width, const auto &) { + const auto position = st::giveawayGiftCodeNamePosition; + const auto badgeWidget = badge->widget(); + const auto badgeSkip = badgeWidget + ? (st::normalFont->spacew + badgeWidget->width()) + : 0; + label->resizeToNaturalWidth(width - position.x() - badgeSkip); + label->moveToLeft(position.x(), position.y(), width); + const auto top = (raw->height() - userpic->height()) / 2; + userpic->moveToLeft(0, top, width); + if (badgeWidget) { + badgeWidget->moveToLeft( + position.x() + label->width() + st::normalFont->spacew, + (position.y() + + table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), + width); + } + }, label->lifetime()); + + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + label->setTextColorOverride(table->st().defaultValue.palette.linkFg->c); + + raw->setClickedCallback([=] { + show->showBox(PrepareShortInfoBox(peer, show)); }); return result; } [[nodiscard]] object_ptr MakeHiddenPeerTableValue( - not_null parent, - not_null controller) { - auto result = object_ptr(parent); + not_null table) { + auto result = object_ptr(table); const auto raw = result.data(); const auto &st = st::giveawayGiftCodeUserpic; @@ -208,7 +312,7 @@ constexpr auto kRarityTooltipDuration = 3 * crl::time(1000); const auto label = Ui::CreateChild( raw, tr::lng_gift_from_hidden(), - st::giveawayGiftCodeValue); + table->st().defaultValue); raw->widthValue( ) | rpl::start_with_next([=](int width) { const auto position = st::giveawayGiftCodeNamePosition; @@ -235,7 +339,7 @@ void AddTableRow( ? object_ptr( table, std::move(label), - st::giveawayGiftCodeLabel) + table->st().defaultLabel) : object_ptr(nullptr)), std::move(value), st::giveawayGiftCodeLabelMargin, @@ -243,23 +347,23 @@ void AddTableRow( } [[nodiscard]] object_ptr MakeAttributeValue( - not_null parent, + not_null table, const Data::UniqueGiftAttribute &attribute, Fn, int)> showTooltip) { - auto result = object_ptr(parent); + auto result = object_ptr(table); const auto raw = result.data(); const auto label = Ui::CreateChild( raw, attribute.name, - st::giveawayGiftCodeValue); + table->st().defaultValue); const auto permille = attribute.rarityPermille; const auto text = QString::number(permille / 10.) + '%'; const auto rarity = Ui::CreateChild( raw, rpl::single(text), - st::starGiftSmallButton); + table->st().smallButton); rarity->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); rpl::combine( @@ -273,8 +377,8 @@ void AddTableRow( label->moveToLeft(0, 0, width); rarity->moveToLeft( label->width() + st::normalFont->spacew, - (st::giveawayGiftCodeValue.style.font->ascent - - st::starGiftSmallButton.style.font->ascent), + (table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), width); }, label->lifetime()); @@ -294,14 +398,14 @@ void AddTableRow( } [[nodiscard]] object_ptr MakeStarGiftStarsValue( - not_null parent, - not_null controller, + not_null table, + std::shared_ptr show, const Data::CreditsHistoryEntry &entry, Fn convertToStars) { - auto result = object_ptr(parent); + auto result = object_ptr(table); const auto raw = result.data(); - const auto session = &controller->session(); + const auto session = &show->session(); const auto makeContext = [session](Fn update) { return Core::MarkedTextContext{ .session = session, @@ -313,7 +417,7 @@ void AddTableRow( raw, rpl::single(star.append( ' ' + Lang::FormatStarsAmountDecimal(entry.credits))), - st::giveawayGiftCodeValue, + table->st().defaultValue, st::defaultPopupMenu, std::move(makeContext)); @@ -323,7 +427,7 @@ void AddTableRow( tr::lng_gift_sell_small( lt_count_decimal, rpl::single(entry.starsConverted * 1.)), - st::starGiftSmallButton) + table->st().smallButton) : nullptr; if (convert) { convert->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); @@ -341,8 +445,8 @@ void AddTableRow( if (convert) { convert->moveToLeft( label->width() + st::normalFont->spacew, - (st::giveawayGiftCodeValue.style.font->ascent - - st::starGiftSmallButton.style.font->ascent), + (table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), width); } }, label->lifetime()); @@ -358,94 +462,46 @@ void AddTableRow( return result; } -[[nodiscard]] object_ptr MakeVisibilityTableValue( - not_null parent, - not_null controller, - bool savedToProfile, - Fn toggleVisibility) { - auto result = object_ptr(parent); - const auto raw = result.data(); - - const auto label = Ui::CreateChild( - raw, - (savedToProfile - ? tr::lng_gift_visibility_shown() - : tr::lng_gift_visibility_hidden()), - st::giveawayGiftCodeValue, - st::defaultPopupMenu); - - const auto toggle = Ui::CreateChild( - raw, - (savedToProfile - ? tr::lng_gift_visibility_hide() - : tr::lng_gift_visibility_show()), - st::starGiftSmallButton); - toggle->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - toggle->setClickedCallback([=] { - toggleVisibility(!savedToProfile); - }); - - rpl::combine( - raw->widthValue(), - toggle->widthValue() - ) | rpl::start_with_next([=](int width, int toggleWidth) { - const auto toggleSkip = toggleWidth - ? (st::normalFont->spacew + toggleWidth) - : 0; - label->resizeToNaturalWidth(width - toggleSkip); - label->moveToLeft(0, 0, width); - toggle->moveToLeft( - label->width() + st::normalFont->spacew, - (st::giveawayGiftCodeValue.style.font->ascent - - st::starGiftSmallButton.style.font->ascent), - width); - }, label->lifetime()); - - label->heightValue() | rpl::start_with_next([=](int height) { - raw->resize( - raw->width(), - height + st::giveawayGiftCodeValueMargin.bottom()); - }, raw->lifetime()); - - label->setAttribute(Qt::WA_TransparentForMouseEvents); - - return result; -} - [[nodiscard]] object_ptr MakeNonUniqueStatusTableValue( - not_null parent, - not_null controller, + not_null table, Fn startUpgrade) { - auto result = object_ptr(parent); + auto result = object_ptr(table); const auto raw = result.data(); const auto label = Ui::CreateChild( raw, tr::lng_gift_unique_status_non(), - st::giveawayGiftCodeValue, + table->st().defaultValue, st::defaultPopupMenu); - const auto upgrade = Ui::CreateChild( - raw, - tr::lng_gift_unique_status_upgrade(), - st::starGiftSmallButton); - upgrade->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - upgrade->setClickedCallback(startUpgrade); + const auto upgrade = startUpgrade + ? Ui::CreateChild( + raw, + tr::lng_gift_unique_status_upgrade(), + table->st().smallButton) + : (Ui::RoundButton*)(nullptr); + if (upgrade) { + using namespace Ui; + upgrade->setTextTransform(RoundButton::TextTransform::NoTransform); + upgrade->setClickedCallback(startUpgrade); + } rpl::combine( raw->widthValue(), - upgrade->widthValue() + upgrade ? upgrade->widthValue() : rpl::single(0) ) | rpl::start_with_next([=](int width, int toggleWidth) { const auto toggleSkip = toggleWidth ? (st::normalFont->spacew + toggleWidth) : 0; label->resizeToNaturalWidth(width - toggleSkip); label->moveToLeft(0, 0, width); - upgrade->moveToLeft( - label->width() + st::normalFont->spacew, - (st::giveawayGiftCodeValue.style.font->ascent - - st::starGiftSmallButton.style.font->ascent), - width); + if (upgrade) { + upgrade->moveToLeft( + label->width() + st::normalFont->spacew, + (table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), + width); + } }, label->lifetime()); label->heightValue() | rpl::start_with_next([=](int height) { @@ -467,7 +523,7 @@ not_null AddTableRow( auto widget = object_ptr( table, std::move(value), - st::giveawayGiftCodeValue, + table->st().defaultValue, st::defaultPopupMenu, std::move(makeContext)); const auto result = widget.data(); @@ -482,7 +538,7 @@ not_null AddTableRow( void AddTableRow( not_null table, rpl::producer label, - not_null controller, + std::shared_ptr show, PeerId id) { if (!id) { return; @@ -490,32 +546,33 @@ void AddTableRow( AddTableRow( table, std::move(label), - MakePeerTableValue(table, controller, id), + MakePeerTableValue(table, show, id), st::giveawayGiftCodePeerMargin); } void AddTable( not_null container, - not_null controller, + std::shared_ptr show, + Settings::CreditsEntryBoxStyleOverrides st, const Api::GiftCode ¤t, bool skipReason) { auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); if (current.from) { AddTableRow( table, tr::lng_gift_link_label_from(), - controller, + show, current.from); } if (current.from && current.to) { AddTableRow( table, tr::lng_gift_link_label_to(), - controller, + show, current.to); } else if (current.from) { AddTableRow( @@ -547,10 +604,12 @@ void AddTable( ) | rpl::type_erased()) : tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities))); reason->setClickHandlerFilter([=](const auto &...) { - controller->showPeerHistory( - current.from, - Window::SectionShow::Way::Forward, - current.giveawayId); + if (const auto window = show->resolveWindow()) { + window->showPeerHistory( + current.from, + Window::SectionShow::Way::Forward, + current.giveawayId); + } return false; }); } @@ -678,7 +737,8 @@ void GiftCodeBox( MakeLinkCopyIcon(box)), st::giveawayGiftCodeLinkMargin); - AddTable(box->verticalLayout(), controller, state->data.current(), false); + const auto show = controller->uiShow(); + AddTable(box->verticalLayout(), show, {}, state->data.current(), false); auto shareLink = tr::lng_gift_link_also_send_link( ) | rpl::map([](const QString &text) { @@ -760,7 +820,6 @@ void GiftCodeBox( }, button->lifetime()); } - void GiftCodePendingBox( not_null box, not_null controller, @@ -838,7 +897,8 @@ void GiftCodePendingBox( spoiler->show(); } - AddTable(box->verticalLayout(), controller, data, true); + const auto show = controller->uiShow(); + AddTable(box->verticalLayout(), show, {}, data, true); box->addRow( object_ptr( @@ -1188,60 +1248,184 @@ void ResolveGiveawayInfo( crl::guard(controller, show)); } +QString TonAddressUrl( + not_null session, + const QString &address) { + const auto prefix = session->appConfig().get( + u"ton_blockchain_explorer_url"_q, + u"https://tonviewer.com/"_q); + return prefix + address; +} + void AddStarGiftTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry, - Fn toggleVisibility, Fn convertToStars, Fn startUpgrade) { auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); - const auto session = &controller->session(); + const auto session = &show->session(); const auto unique = entry.uniqueGift.get(); const auto selfBareId = session->userPeerId().value; const auto giftToSelf = (peerId == session->userPeerId()) && (entry.in || entry.bareGiftOwnerId == selfBareId); - if (unique) { + const auto giftToChannel = entry.giftSavedId + && peerIsChannel(PeerId(entry.bareGiftListPeerId)); + + const auto raw = std::make_shared(nullptr); + const auto showTooltip = [=]( + not_null widget, + rpl::producer text) { + if (*raw) { + (*raw)->toggleAnimated(false); + } + const auto tooltip = Ui::CreateChild( + container, + Ui::MakeNiceTooltipLabel( + container, + std::move(text), + st::boxWideWidth, + st::defaultImportantTooltipLabel), + st::defaultImportantTooltip); + tooltip->toggleFast(false); + + const auto update = [=] { + const auto geometry = Ui::MapFrom( + container, + widget, + widget->rect()); + const auto countPosition = [=](QSize size) { + const auto left = geometry.x() + + (geometry.width() - size.width()) / 2; + const auto right = container->width() + - st::normalFont->spacew; + return QPoint( + std::max(std::min(left, right - size.width()), 0), + geometry.y() - size.height() - st::normalFont->descent); + }; + tooltip->pointAt(geometry, RectPart::Top, countPosition); + }; + container->widthValue( + ) | rpl::start_with_next(update, tooltip->lifetime()); + + update(); + tooltip->toggleAnimated(true); + + *raw = tooltip; + tooltip->shownValue() | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + crl::on_main(tooltip, [=] { + if (tooltip->isHidden()) { + if (*raw == tooltip) { + *raw = nullptr; + } + delete tooltip; + } + }); + }, tooltip->lifetime()); + + base::timer_once( + kRarityTooltipDuration + ) | rpl::start_with_next([=] { + tooltip->toggleAnimated(false); + }, tooltip->lifetime()); + }; + + if (unique && entry.bareGiftOwnerId) { const auto ownerId = PeerId(entry.bareGiftOwnerId); - const auto transfer = entry.in - && entry.bareMsgId - && (unique->starsForTransfer >= 0); - auto send = transfer ? tr::lng_gift_unique_owner_change() : nullptr; - auto handler = transfer ? Fn([=] { - ShowTransferGiftBox( - controller->parentController(), - entry.uniqueGift, - MsgId(entry.bareMsgId)); - }) : nullptr; + const auto was = std::make_shared>(); + const auto handleChange = [=]( + not_null badge, + EmojiStatusId emojiStatusId) { + const auto id = emojiStatusId.collectible + ? emojiStatusId.collectible->id + : 0; + const auto show = [&](const auto &phrase) { + showTooltip(badge, phrase( + lt_name, + rpl::single(Ui::Text::Bold(UniqueGiftName(*unique))), + Ui::Text::WithEntities)); + }; + if (!*was || *was == id) { + *was = id; + return; + } else if (*was == unique->id) { + show(tr::lng_gift_wear_end_toast); + } else if (id == unique->id) { + show(tr::lng_gift_wear_start_toast); + } + *was = id; + }; AddTableRow( table, tr::lng_gift_unique_owner(), - MakePeerTableValue(table, controller, ownerId, send, handler), + MakePeerWithStatusValue(table, show, ownerId, handleChange), st::giveawayGiftCodePeerMargin); - } else if (peerId) { - if (!giftToSelf) { - const auto user = session->data().peer(peerId)->asUser(); - const auto withSendButton = entry.in && user && !user->isBot(); - auto send = withSendButton ? tr::lng_gift_send_small() : nullptr; - auto handler = send ? Fn([=] { - Ui::ShowStarGiftBox(controller->parentController(), user); - }) : nullptr; + } else if (unique) { + if (!unique->ownerName.isEmpty()) { AddTableRow( table, - tr::lng_credits_box_history_entry_peer_in(), - MakePeerTableValue(table, controller, peerId, send, handler), + tr::lng_gift_unique_owner(), + rpl::single(TextWithEntities{ unique->ownerName })); + } else if (auto address = unique->ownerAddress; !address.isEmpty()) { + auto label = MakeMaybeMultilineTokenValue(table, address, st); + label->setClickHandlerFilter([=](const auto &...) { + TextUtilities::SetClipboardText( + TextForMimeData::Simple(address)); + show->showToast( + tr::lng_gift_unique_address_copied(tr::now)); + return false; + }); + AddTableRow( + table, + tr::lng_gift_unique_owner(), + std::move(label), + st::giveawayGiftCodeValueMargin); + } + } else if (giftToChannel) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer_in(), + (entry.bareActorId + ? MakePeerTableValue(table, show, PeerId(entry.bareActorId)) + : MakeHiddenPeerTableValue(table)), + st::giveawayGiftCodePeerMargin); + if (entry.bareGiftListPeerId) { + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer(), + MakePeerTableValue( + table, + show, + PeerId(entry.bareGiftListPeerId)), st::giveawayGiftCodePeerMargin); } + } else if (peerId && !giftToSelf) { + const auto user = session->data().peer(peerId)->asUser(); + const auto withSendButton = entry.in && user && !user->isBot(); + auto send = withSendButton ? tr::lng_gift_send_small() : nullptr; + auto handler = send ? Fn([=] { + if (const auto window = show->resolveWindow()) { + Ui::ShowStarGiftBox(window, user); + } + }) : nullptr; + AddTableRow( + table, + tr::lng_credits_box_history_entry_peer_in(), + MakePeerTableValue(table, show, peerId, send, handler), + st::giveawayGiftCodePeerMargin); } else if (!entry.soldOutInfo) { AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), - MakeHiddenPeerTableValue(table, controller), + MakeHiddenPeerTableValue(table), st::giveawayGiftCodePeerMargin); } if (!unique && !entry.firstSaleDate.isNull()) { @@ -1267,84 +1451,29 @@ void AddStarGiftTable( const auto marginWithButton = st::giveawayGiftCodeValueMargin - QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom()); if (unique) { - const auto raw = std::make_shared(nullptr); - const auto showTooltip = [=]( + const auto showRarity = [=]( not_null widget, int rarity) { - if (*raw) { - (*raw)->toggleAnimated(false); - } - const auto text = QString::number(rarity / 10.) + '%'; - const auto tooltip = Ui::CreateChild( - container, - Ui::MakeNiceTooltipLabel( - container, - tr::lng_gift_unique_rarity( - lt_percent, - rpl::single(TextWithEntities{ text }), - Ui::Text::WithEntities), - st::boxWideWidth, - st::defaultImportantTooltipLabel), - st::defaultImportantTooltip); - tooltip->toggleFast(false); - - const auto update = [=] { - const auto geometry = Ui::MapFrom( - container, - widget, - widget->rect()); - const auto countPosition = [=](QSize size) { - const auto left = geometry.x() - + (geometry.width() - size.width()) / 2; - const auto right = container->width() - - st::normalFont->spacew; - return QPoint( - std::max(std::min(left, right - size.width()), 0), - geometry.y() - size.height() - st::normalFont->descent); - }; - tooltip->pointAt(geometry, RectPart::Top, countPosition); - }; - container->widthValue( - ) | rpl::start_with_next(update, tooltip->lifetime()); - - update(); - tooltip->toggleAnimated(true); - - *raw = tooltip; - tooltip->shownValue() | rpl::filter( - !rpl::mappers::_1 - ) | rpl::start_with_next([=] { - crl::on_main(tooltip, [=] { - if (tooltip->isHidden()) { - if (*raw == tooltip) { - *raw = nullptr; - } - delete tooltip; - } - }); - }, tooltip->lifetime()); - - base::timer_once( - kRarityTooltipDuration - ) | rpl::start_with_next([=] { - tooltip->toggleAnimated(false); - }, tooltip->lifetime()); + const auto percent = QString::number(rarity / 10.) + '%'; + showTooltip(widget, tr::lng_gift_unique_rarity( + lt_percent, + rpl::single(TextWithEntities{ percent }), + Ui::Text::WithEntities)); }; - AddTableRow( table, tr::lng_gift_unique_model(), - MakeAttributeValue(container, unique->model, showTooltip), + MakeAttributeValue(table, unique->model, showRarity), marginWithButton); AddTableRow( table, tr::lng_gift_unique_backdrop(), - MakeAttributeValue(container, unique->backdrop, showTooltip), + MakeAttributeValue(table, unique->backdrop, showRarity), marginWithButton); AddTableRow( table, tr::lng_gift_unique_symbol(), - MakeAttributeValue(container, unique->pattern, showTooltip), + MakeAttributeValue(table, unique->pattern, showRarity), marginWithButton); } else { AddTableRow( @@ -1352,22 +1481,11 @@ void AddStarGiftTable( tr::lng_gift_link_label_value(), MakeStarGiftStarsValue( table, - controller, + show, entry, std::move(convertToStars)), marginWithButton); } - if (toggleVisibility) { - AddTableRow( - table, - tr::lng_gift_visibility(), - MakeVisibilityTableValue( - table, - controller, - entry.savedToProfile, - std::move(toggleVisibility)), - marginWithButton); - } if (entry.limitedCount > 0 && !entry.giftRefunded) { auto amount = rpl::single(TextWithEntities{ Lang::FormatCountDecimal(entry.limitedCount) @@ -1389,20 +1507,17 @@ void AddStarGiftTable( std::move(amount), Ui::Text::WithEntities))); } - if (!unique && startUpgrade) { + if (!unique && !entry.soldOutInfo) { AddTableRow( table, tr::lng_gift_unique_status(), - MakeNonUniqueStatusTableValue( - table, - controller, - std::move(startUpgrade)), + MakeNonUniqueStatusTableValue(table, std::move(startUpgrade)), marginWithButton); } if (unique) { const auto &original = unique->originalDetails; if (original.recipientId) { - const auto owner = &controller->session().data(); + const auto owner = &show->session().data(); const auto to = owner->peer(original.recipientId); const auto from = original.senderId ? owner->peer(original.senderId).get() @@ -1452,13 +1567,14 @@ void AddStarGiftTable( lt_text, rpl::single(original.message), Ui::Text::WithEntities))), - st::giveawayGiftMessage, + (st.tableValueMessage + ? *st.tableValueMessage + : st::giveawayGiftMessage), st::defaultPopupMenu, makeContext); const auto showBoxLink = [=](not_null peer) { return std::make_shared([=] { - controller->uiShow()->showBox( - PrepareShortInfoBox(peer, controller)); + show->showBox(PrepareShortInfoBox(peer, show)); }); }; label->setLink(1, showBoxLink(to)); @@ -1482,7 +1598,9 @@ void AddStarGiftTable( auto label = object_ptr( table, rpl::single(entry.description), - st::giveawayGiftMessage, + (st.tableValueMessage + ? *st.tableValueMessage + : st::giveawayGiftMessage), st::defaultPopupMenu, makeContext); label->setSelectable(true); @@ -1495,8 +1613,9 @@ void AddStarGiftTable( } void AddCreditsHistoryEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry) { if (!entry) { return; @@ -1504,12 +1623,12 @@ void AddCreditsHistoryEntryTable( auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); const auto actorId = PeerId(entry.bareActorId); const auto starrefRecipientId = PeerId(entry.starrefRecipientId); - const auto session = &controller->session(); + const auto session = &show->session(); if (entry.starrefCommission) { if (entry.starrefAmount) { AddTableRow( @@ -1529,7 +1648,7 @@ void AddCreditsHistoryEntryTable( AddTableRow( table, tr::lng_credits_box_history_entry_affiliate(), - controller, + show, starrefRecipientId); } if (peerId && entry.starrefCommission) { @@ -1538,7 +1657,7 @@ void AddCreditsHistoryEntryTable( (entry.starrefAmount ? tr::lng_credits_box_history_entry_referred : tr::lng_credits_box_history_entry_miniapp)(), - controller, + show, peerId); } if (actorId || (!entry.starrefCommission && peerId)) { @@ -1552,7 +1671,7 @@ void AddCreditsHistoryEntryTable( AddTableRow( table, std::move(text), - controller, + show, actorId ? actorId : peerId); } if (const auto msgId = MsgId(peerId ? entry.bareMsgId : 0)) { @@ -1565,9 +1684,11 @@ void AddCreditsHistoryEntryTable( auto label = object_ptr( table, rpl::single(Ui::Text::Link(link)), - st::giveawayGiftCodeValue); + table->st().defaultValue); label->setClickHandlerFilter([=](const auto &...) { - controller->showPeerHistory(channel, {}, msgId); + if (const auto window = show->resolveWindow()) { + window->showPeerHistory(channel, {}, msgId); + } return false; }); AddTableRow( @@ -1618,8 +1739,8 @@ void AddCreditsHistoryEntryTable( AddTableRow( table, tr::lng_gift_link_label_to(), - controller, - controller->session().userId()); + show, + show->session().userId()); } if (entry.bareGiveawayMsgId && entry.credits) { AddTableRow( @@ -1653,19 +1774,11 @@ void AddCreditsHistoryEntryTable( Ui::Text::WithEntities)); } if (!entry.id.isEmpty()) { - constexpr auto kOneLineCount = 24; - const auto oneLine = entry.id.length() <= kOneLineCount; - auto label = object_ptr( - table, - rpl::single( - Ui::Text::Wrapped({ entry.id }, EntityType::Code, {})), - oneLine - ? st::giveawayGiftCodeValue - : st::giveawayGiftCodeValueMultiline); + auto label = MakeMaybeMultilineTokenValue(table, entry.id, st); label->setClickHandlerFilter([=](const auto &...) { TextUtilities::SetClipboardText( TextForMimeData::Simple(entry.id)); - controller->showToast( + show->showToast( tr::lng_credits_box_history_entry_id_copied(tr::now)); return false; }); @@ -1705,8 +1818,9 @@ void AddCreditsHistoryEntryTable( } void AddSubscriptionEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::SubscriptionEntry &s) { if (!s) { return; @@ -1714,11 +1828,11 @@ void AddSubscriptionEntryTable( auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(s.barePeerId); const auto user = peerIsUser(peerId) - ? controller->session().data().peer(peerId)->asUser() + ? show->session().data().peer(peerId)->asUser() : nullptr; AddTableRow( table, @@ -1727,7 +1841,7 @@ void AddSubscriptionEntryTable( : (!s.title.isEmpty() && user && !user->botInfo) ? tr::lng_credits_subscription_row_to_business() : tr::lng_credits_subscription_row_to(), - controller, + show, peerId); if (!s.title.isEmpty()) { AddTableRow( @@ -1758,19 +1872,20 @@ void AddSubscriptionEntryTable( } void AddSubscriberEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, not_null peer, TimeId date) { auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); AddTableRow( table, tr::lng_group_invite_joined_row_subscriber(), - controller, + show, peer->id); if (const auto d = base::unixtime::parse(date); !d.isNull()) { AddTableRow( @@ -1781,23 +1896,24 @@ void AddSubscriberEntryTable( } void AddCreditsBoostTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::Boost &b) { auto table = container->add( object_ptr( container, - st::giveawayGiftCodeTable), + st.table ? *st.table : st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = b.giveawayMessage.peer; if (!peerId) { return; } - const auto from = controller->session().data().peer(peerId); + const auto from = show->session().data().peer(peerId); AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), - controller, + show, from->id); if (b.credits) { AddTableRow( @@ -1810,7 +1926,7 @@ void AddCreditsBoostTable( } { const auto link = CreateMessageLink( - &controller->session(), + &show->session(), peerId, b.giveawayMessage.msg.bare); if (!link.isEmpty()) { diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index d29c1fca4..ec3282e24 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -13,6 +13,10 @@ namespace Api { struct GiftCode; } // namespace Api +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { struct Boost; struct CreditsHistoryEntry; @@ -21,6 +25,14 @@ struct GiveawayResults; struct SubscriptionEntry; } // namespace Data +namespace Main { +class Session; +} // namespace Main + +namespace Settings { +struct CreditsEntryBoxStyleOverrides; +} // namespace Settings + namespace Ui { class GenericBox; class VerticalLayout; @@ -54,29 +66,37 @@ void ResolveGiveawayInfo( std::optional start, std::optional results); +[[nodiscard]] QString TonAddressUrl( + not_null session, + const QString &address); + void AddStarGiftTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry, - Fn toggleVisibility, Fn convertToStars, Fn startUpgrade); void AddCreditsHistoryEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::CreditsHistoryEntry &entry); void AddSubscriptionEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::SubscriptionEntry &s); void AddSubscriberEntryTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, not_null peer, TimeId date); void AddCreditsBoostTable( - not_null controller, + std::shared_ptr show, not_null container, + Settings::CreditsEntryBoxStyleOverrides st, const Data::Boost &boost); diff --git a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp index 49df5b0ea..8007b9383 100644 --- a/Telegram/SourceFiles/boxes/moderate_messages_box.cpp +++ b/Telegram/SourceFiles/boxes/moderate_messages_box.cpp @@ -72,6 +72,16 @@ ModerateOptions CalculateModerateOptions(const HistoryItemsList &items) { if (peer != item->history()->peer) { return {}; } + { + const auto author = item->author(); + if (author == peer) { + return {}; + } else if (const auto channel = author->asChannel()) { + if (channel->linkedChat() == peer) { + return {}; + } + } + } if (!item->suggestBanReport()) { result.allCanBan = false; } diff --git a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp index be668fcd3..fe675532a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.cpp @@ -265,7 +265,7 @@ struct IconSelector { const auto manager = &controller->session().data().customEmojiManager(); auto factory = [=](DocumentId id, Fn repaint) - -> std::unique_ptr { + -> std::unique_ptr { const auto tag = Data::CustomEmojiManager::SizeTag::Large; if (id == kDefaultIconId) { return std::make_unique( @@ -288,7 +288,7 @@ struct IconSelector { .show = controller->uiShow(), .mode = EmojiListWidget::Mode::TopicIcon, .paused = Window::PausedIn(controller, PauseReason::Layer), - .customRecentList = recent(), + .customRecentList = DocumentListToRecent(recent()), .customRecentFactory = std::move(factory), .st = &st::reactPanelEmojiPan, }), @@ -297,7 +297,7 @@ struct IconSelector { icons->requestDefaultIfUnknown(); icons->defaultUpdates( ) | rpl::start_with_next([=] { - selector->provideRecent(recent()); + selector->provideRecent(DocumentListToRecent(recent())); }, selector->lifetime()); placeFooter(selector->createFooter()); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index b2e292a3c..8b827dc7c 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -313,6 +313,7 @@ PreviewWrap::PreviewWrap( WebPageCollage(), nullptr, // iv nullptr, // stickerSet + nullptr, // uniqueGift 0, // duration QString(), // author false, // hasLargeMedia @@ -525,7 +526,7 @@ void LevelBadge::paintEvent(QPaintEvent *e) { struct SetValues { uint8 colorIndex = 0; DocumentId backgroundEmojiId = 0; - DocumentId statusId = 0; + EmojiStatusId statusId; TimeId statusUntil = 0; bool statusChanged = false; }; @@ -807,7 +808,7 @@ int ColorSelector::resizeGetHeight(int newWidth) { const auto state = right->lifetime().make_state(); state->panel.someCustomChosen( ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) { - emojiIdChosen(chosen.id); + emojiIdChosen(chosen.id.documentId); }, raw->lifetime()); std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { @@ -871,13 +872,12 @@ int ColorSelector::resizeGetHeight(int newWidth) { const auto customTextColor = [=] { return style->coloredValues(false, state->index).name; }; - const auto controller = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo); + const auto controller = show->resolveWindow(); if (controller) { state->panel.show({ .controller = controller, .button = right, - .ensureAddedEmojiId = state->emojiId, + .ensureAddedEmojiId = { state->emojiId }, .customTextColor = customTextColor, .backgroundEmojiMode = true, }); @@ -901,8 +901,8 @@ int ColorSelector::resizeGetHeight(int newWidth) { not_null parent, std::shared_ptr show, not_null channel, - rpl::producer statusIdValue, - Fn statusIdChosen, + rpl::producer statusIdValue, + Fn statusIdChosen, bool group) { const auto button = ButtonStyleWithRightEmoji( parent, @@ -924,20 +924,20 @@ int ColorSelector::resizeGetHeight(int newWidth) { struct State { EmojiStatusPanel panel; std::unique_ptr emoji; - DocumentId statusId = 0; + EmojiStatusId statusId; }; const auto state = right->lifetime().make_state(); state->panel.someCustomChosen( ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) { - statusIdChosen(chosen.id, chosen.until); + statusIdChosen({ chosen.id }, chosen.until); }, raw->lifetime()); const auto session = &show->session(); - std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) { + std::move(statusIdValue) | rpl::start_with_next([=](EmojiStatusId id) { state->statusId = id; state->emoji = id ? session->data().customEmojiManager().create( - id, + Data::EmojiStatusCustomId(id), [=] { right->update(); }) : nullptr; right->resize( @@ -985,13 +985,12 @@ int ColorSelector::resizeGetHeight(int newWidth) { }, right->lifetime()); raw->setClickedCallback([=] { - const auto controller = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo); + const auto controller = show->resolveWindow(); if (controller) { state->panel.show({ .controller = controller, .button = right, - .ensureAddedEmojiId = state->statusId, + .ensureAddedEmojiId = { state->statusId }, .channelStatusMode = true, }); } @@ -1183,7 +1182,7 @@ void EditPeerColorBox( struct State { rpl::variable index; rpl::variable emojiId; - rpl::variable statusId; + rpl::variable statusId; TimeId statusUntil = 0; bool statusChanged = false; bool changing = false; @@ -1263,8 +1262,7 @@ void EditPeerColorBox( { &st::menuBlueIconWallpaper } ); button->setClickedCallback([=] { - const auto usage = ChatHelpers::WindowUsage::PremiumPromo; - if (const auto strong = show->resolveWindow(usage)) { + if (const auto strong = show->resolveWindow()) { show->show(Box(strong, channel)); } }); @@ -1321,7 +1319,7 @@ void EditPeerColorBox( show, channel, state->statusId.value(), - [=](DocumentId id, TimeId until) { + [=](EmojiStatusId id, TimeId until) { state->statusId = id; state->statusUntil = until; state->statusChanged = true; @@ -1471,8 +1469,7 @@ void CheckBoostLevel( return; } const auto openStatistics = [=] { - if (const auto controller = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo)) { + if (const auto controller = show->resolveWindow()) { controller->showSection(Info::Boosts::Make(peer)); } }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp index a16505f6a..b5cad4e65 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp @@ -1242,7 +1242,9 @@ void Controller::fillManageSection() { ? channel->canViewMembers() : chat->amIn(); const auto canViewKicked = isChannel - && (channel->isBroadcast() || channel->isGigagroup()); + && (channel->isMegagroup() + ? (channel->isBroadcast() || channel->isGigagroup()) + : true); const auto hasRecentActions = isChannel && (channel->hasAdminRights() || channel->amCreator()); const auto hasStarRef = Info::BotStarRef::Join::Allowed(_peer) diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp index 29504ab63..2797b5394 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp @@ -1019,7 +1019,8 @@ void Controller::rowClicked(not_null row) { Ui::AddSkip(content); Ui::AddSkip(content); - AddSubscriberEntryTable(controller, content, row->peer(), data.date); + const auto show = controller->uiShow(); + AddSubscriberEntryTable(show, content, {}, row->peer(), data.date); Ui::AddSkip(content); Ui::AddSkip(content); diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 81236068e..1b6996fad 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_controller.h" #include "main/main_session.h" +#include "mtproto/mtproto_config.h" // megagroupSizeMax #include "apiwrap.h" #include "settings/settings_common.h" #include "styles/style_layers.h" @@ -49,7 +50,6 @@ namespace { constexpr auto kSlowmodeValues = 7; constexpr auto kBoostsUnrestrictValues = 5; -constexpr auto kSuggestGigagroupThreshold = 199000; constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000); [[nodiscard]] auto Dependencies(PowerSaving::Flags) @@ -1191,8 +1191,11 @@ void ShowEditPeerPermissionsBox( }); if (const auto channel = peer->asChannel()) { + constexpr auto kThresholdOffset = int(1000); + const auto threshold = -kThresholdOffset + + channel->session().serverConfig().megagroupSizeMax; if (channel->amCreator() - && channel->membersCount() >= kSuggestGigagroupThreshold) { + && channel->membersCount() >= threshold) { AddSuggestGigagroup( inner, AboutGigagroupCallback( diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp index f7b1a6b4c..96ed8c949 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_reactions.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" +#include "chat_helpers/emoji_list_widget.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" @@ -491,7 +492,8 @@ object_ptr AddReactionsSelector( panelList.erase( ranges::remove(panelList, paid->selectAnimation->id), end(panelList)); - panel->selector()->provideRecentEmoji(panelList); + panel->selector()->provideRecentEmoji( + ChatHelpers::DocumentListToRecent(panelList)); panel->setDesiredHeightValues( 1., st::emojiPanMinHeight / 2, diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp index 760a821c7..9341b7089 100644 --- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp @@ -486,20 +486,23 @@ object_ptr PrepareShortInfoBox( object_ptr PrepareShortInfoBox( not_null peer, - not_null navigation, + std::shared_ptr show, const style::ShortInfoBox *stOverride) { - const auto open = [=] { navigation->showPeerHistory(peer); }; + const auto open = [=] { + if (const auto window = show->resolveWindow()) { + window->showPeerHistory(peer); + } + }; const auto videoIsPaused = [=] { - return navigation->parentController()->isGifPausedAtLeastFor( - Window::GifPauseReason::Layer); + return show->paused(Window::GifPauseReason::Layer); }; auto menuFiller = [=](Ui::Menu::MenuCallback addAction) { - const auto controller = navigation->parentController(); const auto peerSeparateId = Window::SeparateId(peer); - if (controller->windowId() != peerSeparateId) { + const auto window = show->resolveWindow(); + if (window && window->windowId() != peerSeparateId) { addAction(tr::lng_context_new_window(tr::now), [=] { Ui::PreventDelayedActivation(); - controller->showInNewWindow(peer); + window->showInNewWindow(peer); }, &st::menuIconNewWindow); } }; @@ -511,6 +514,13 @@ object_ptr PrepareShortInfoBox( stOverride); } +object_ptr PrepareShortInfoBox( + not_null peer, + not_null navigation, + const style::ShortInfoBox *stOverride) { + return PrepareShortInfoBox(peer, navigation->uiShow(), stOverride); +} + rpl::producer PrepareShortInfoStatus(not_null peer) { return StatusValue(peer); } diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h index 2edd5cd92..2213f4da2 100644 --- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h +++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.h @@ -16,6 +16,10 @@ struct ShortInfoCover; struct ShortInfoBox; } // namespace style +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Ui::Menu { struct MenuCallback; } // namespace Ui::Menu @@ -42,6 +46,11 @@ struct PreparedShortInfoUserpic { Fn menuFiller, const style::ShortInfoBox *stOverride = nullptr); +[[nodiscard]] object_ptr PrepareShortInfoBox( + not_null peer, + std::shared_ptr show, + const style::ShortInfoBox *stOverride = nullptr); + [[nodiscard]] object_ptr PrepareShortInfoBox( not_null peer, not_null navigation, diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 2ef807604..570fb75e6 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -1139,8 +1139,7 @@ void PreviewBox( button->resizeToWidth(width); if (!descriptor.fromSettings) { button->setClickedCallback([=] { - const auto window = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo); + const auto window = show->resolveWindow(); if (!window) { return; } diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index 78412ebe1..0c8079585 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "core/application.h" #include "core/core_settings.h" +#include "styles/style_calls.h" #include "styles/style_layers.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" @@ -202,16 +203,16 @@ ShareBox::ShareBox(QWidget*, Descriptor &&descriptor) , _api(&_descriptor.session->mtp()) , _select( this, - (_descriptor.stMultiSelect - ? *_descriptor.stMultiSelect + (_descriptor.st.multiSelect + ? *_descriptor.st.multiSelect : st::defaultMultiSelect), tr::lng_participant_filter()) , _comment( this, object_ptr( this, - (_descriptor.stComment - ? *_descriptor.stComment + (_descriptor.st.comment + ? *_descriptor.st.comment : st::shareComment), Ui::InputField::Mode::MultiLine, tr::lng_photos_comment()), @@ -256,7 +257,7 @@ void ShareBox::prepareCommentField() { .session = _descriptor.session, .show = Main::MakeSessionShow(show, _descriptor.session), .field = field, - .fieldStyle = _descriptor.stLabel, + .fieldStyle = _descriptor.st.label, }); } field->setSubmitSettings(Core::App().settings().sendSubmitWay()); @@ -566,6 +567,9 @@ void ShareBox::showMenu(not_null parent) { submit(action.options); return; } + const auto st = _descriptor.st.scheduleBox + ? *_descriptor.st.scheduleBox + : HistoryView::ScheduleBoxStyleArgs(); uiShow()->showBox( HistoryView::PrepareScheduleBox( this, @@ -574,7 +578,7 @@ void ShareBox::showMenu(not_null parent) { [=](Api::SendOptions options) { submit(options); }, action.options, HistoryView::DefaultScheduleTime(), - _descriptor.scheduleBoxStyle)); + st)); }); _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom); const auto result = FillSendMenu( @@ -709,7 +713,9 @@ ShareBox::Inner::Inner( : RpWidget(parent) , _descriptor(descriptor) , _show(std::move(show)) -, _st(_descriptor.st ? *_descriptor.st : st::shareBoxList) +, _st(_descriptor.st.peerList + ? *_descriptor.st.peerList + : st::shareBoxList) , _defaultChatsIndexed( std::make_unique( Dialogs::SortMode::Add)) @@ -1586,7 +1592,8 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( MTP_int(topMsgId), MTP_int(options.scheduled), MTP_inputPeerEmpty(), // send_as - Data::ShortcutIdToMTP(session, options.shortcutId) + Data::ShortcutIdToMTP(session, options.shortcutId), + MTPint() // video_timestamp )).done([=](const MTPUpdates &updates, mtpRequestId reqId) { threadHistory->session().api().applyUpdates(updates); state->requests.remove(reqId); @@ -1624,9 +1631,37 @@ ShareBox::SubmitCallback ShareBox::DefaultForwardCallback( }; } +ShareBoxStyleOverrides DarkShareBoxStyle() { + using namespace HistoryView; + + const auto schedule = [&] { + auto date = Ui::ChooseDateTimeStyleArgs(); + date.labelStyle = &st::groupCallBoxLabel; + date.dateFieldStyle = &st::groupCallScheduleDateField; + date.timeFieldStyle = &st::groupCallScheduleTimeField; + date.separatorStyle = &st::callMuteButtonLabel; + date.atStyle = &st::callMuteButtonLabel; + date.calendarStyle = &st::groupCallCalendarColors; + + auto st = ScheduleBoxStyleArgs(); + st.topButtonStyle = &st::groupCallMenuToggle; + st.popupMenuStyle = &st::groupCallPopupMenu; + st.chooseDateTimeArgs = std::move(date); + return st; + }; + return { + .multiSelect = &st::groupCallMultiSelect, + .comment = &st::groupCallShareBoxComment, + .peerList = &st::groupCallShareBoxList, + .label = &st::groupCallField, + .scheduleBox = std::make_shared(schedule()), + }; +} + void FastShareMessage( std::shared_ptr show, - not_null item) { + not_null item, + ShareBoxStyleOverrides st) { const auto history = item->history(); const auto owner = &history->owner(); const auto session = &history->session(); @@ -1695,6 +1730,7 @@ void FastShareMessage( history, msgIds), .filterCallback = std::move(filterCallback), + .st = st, .forwardOptions = { .sendersCount = ItemsForwardSendersCount(items), .captionsCount = ItemsForwardCaptionsCount(items), @@ -1706,19 +1742,22 @@ void FastShareMessage( void FastShareMessage( not_null controller, - not_null item) { - FastShareMessage(controller->uiShow(), item); + not_null item, + ShareBoxStyleOverrides st) { + FastShareMessage(controller->uiShow(), item, st); } void FastShareLink( not_null controller, - const QString &url) { - FastShareLink(controller->uiShow(), url); + const QString &url, + ShareBoxStyleOverrides st) { + FastShareLink(controller->uiShow(), url, st); } void FastShareLink( std::shared_ptr show, - const QString &url) { + const QString &url, + ShareBoxStyleOverrides st) { const auto box = std::make_shared>(); const auto sending = std::make_shared(); auto copyCallback = [=] { @@ -1782,6 +1821,7 @@ void FastShareLink( .copyCallback = std::move(copyCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), + .st = st, .premiumRequiredError = SharePremiumRequiredError(), }), Ui::LayerOption::KeepOther, diff --git a/Telegram/SourceFiles/boxes/share_box.h b/Telegram/SourceFiles/boxes/share_box.h index ef6960af0..f21b4505b 100644 --- a/Telegram/SourceFiles/boxes/share_box.h +++ b/Telegram/SourceFiles/boxes/share_box.h @@ -61,18 +61,31 @@ class PopupMenu; class ShareBox; +struct ShareBoxStyleOverrides { + const style::MultiSelect *multiSelect = nullptr; + const style::InputField *comment = nullptr; + const style::PeerList *peerList = nullptr; + const style::InputField *label = nullptr; + std::shared_ptr scheduleBox; +}; +[[nodiscard]] ShareBoxStyleOverrides DarkShareBoxStyle(); + void FastShareMessage( std::shared_ptr show, - not_null item); + not_null item, + ShareBoxStyleOverrides st = {}); void FastShareMessage( not_null controller, - not_null item); + not_null item, + ShareBoxStyleOverrides st = {}); void FastShareLink( not_null controller, - const QString &url); + const QString &url, + ShareBoxStyleOverrides st = {}); void FastShareLink( std::shared_ptr show, - const QString &url); + const QString &url, + ShareBoxStyleOverrides st = {}); struct RecipientPremiumRequiredError; [[nodiscard]] auto SharePremiumRequiredError() @@ -100,16 +113,12 @@ public: FilterCallback filterCallback; object_ptr bottomWidget = { nullptr }; rpl::producer copyLinkText; - const style::MultiSelect *stMultiSelect = nullptr; - const style::InputField *stComment = nullptr; - const style::PeerList *st = nullptr; - const style::InputField *stLabel = nullptr; + ShareBoxStyleOverrides st; struct { int sendersCount = 0; int captionsCount = 0; bool show = false; } forwardOptions; - HistoryView::ScheduleBoxStyleArgs scheduleBoxStyle; using PremiumRequiredError = RecipientPremiumRequiredError; Fn)> premiumRequiredError; diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 2ba9fddc2..50011546d 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/star_gift_box.h" #include "apiwrap.h" +#include "api/api_credits.h" +#include "api/api_premium.h" #include "base/event_filter.h" #include "base/random.h" #include "base/timer_rpl.h" #include "base/unixtime.h" -#include "api/api_premium.h" #include "boxes/filters/edit_filter_chats_list.h" +#include "boxes/peers/edit_peer_color_box.h" #include "boxes/gift_premium_box.h" #include "boxes/peer_list_controllers.h" +#include "boxes/premium_preview_box.h" #include "boxes/send_credits_box.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" @@ -24,10 +27,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" +#include "data/data_channel.h" #include "data/data_credits.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_emoji_statuses.h" #include "data/data_file_origin.h" +#include "data/data_peer_values.h" +#include "data/data_premium_limits.h" #include "data/data_session.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" @@ -51,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_credits.h" #include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" +#include "ui/boxes/boost_box.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "ui/controls/emoji_button.h" @@ -70,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/fields/input_field.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" +#include "ui/widgets/popup_menu.h" #include "ui/widgets/shadow.h" #include "window/themes/window_theme.h" #include "window/section_widget.h" @@ -82,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_menu_icons.h" #include "styles/style_premium.h" #include "styles/style_settings.h" +#include "styles/style_widgets.h" #include @@ -206,8 +216,12 @@ auto GenerateGiftMedia( Element *replacing, not_null recipient, const GiftDetails &data) --> Fn)>)> { - return [=](Fn)> push) { +-> Fn, + Fn)>)> { + return [=]( + not_null media, + Fn)> push) { const auto &descriptor = data.descriptor; auto pushText = [&]( TextWithEntities text, @@ -241,18 +255,26 @@ auto GenerateGiftMedia( replacing, sticker, st::giftBoxPreviewStickerPadding)); - const auto title = v::match(descriptor, [&](GiftTypePremium gift) { + auto title = v::match(descriptor, [&](GiftTypePremium gift) { return tr::lng_action_gift_premium_months( tr::now, lt_count, - gift.months); + gift.months, + Text::Bold); }, [&](const GiftTypeStars &gift) { return recipient->isSelf() - ? tr::lng_action_gift_self_subtitle(tr::now) + ? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold) : tr::lng_action_gift_got_subtitle( tr::now, lt_user, - recipient->session().user()->shortName()); + TextWithEntities() + .append(Text::SingleCustomEmoji( + recipient->owner().customEmojiManager( + ).peerUserpicEmojiData( + recipient->session().user()))) + .append(' ') + .append(recipient->session().user()->shortName()), + Text::Bold); }); auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) { return tr::lng_action_gift_premium_about( @@ -267,8 +289,14 @@ auto GenerateGiftMedia( ? tr::lng_action_gift_self_about_unique( tr::now, Text::RichLangValue) + : (recipient->isBroadcast() && gift.info.starsToUpgrade) + ? tr::lng_action_gift_channel_about_unique( + tr::now, + Text::RichLangValue) : (recipient->isSelf() ? tr::lng_action_gift_self_about + : recipient->isBroadcast() + ? tr::lng_action_gift_channel_about : tr::lng_action_gift_got_stars_text)( tr::now, lt_count, @@ -278,15 +306,20 @@ auto GenerateGiftMedia( auto description = data.text.empty() ? std::move(textFallback) : data.text; - pushText(Text::Bold(title), st::giftBoxPreviewTitlePadding); + const auto context = Core::MarkedTextContext{ + .session = &parent->history()->session(), + .customEmojiRepaint = [parent] { parent->repaint(); }, + }; + pushText( + std::move(title), + st::giftBoxPreviewTitlePadding, + {}, + context); pushText( std::move(description), st::giftBoxPreviewTextPadding, {}, - Core::MarkedTextContext{ - .session = &parent->history()->session(), - .customEmojiRepaint = [parent] { parent->repaint(); }, - }); + context); push(HistoryView::MakeGenericButtonPart( (data.upgraded @@ -475,6 +508,15 @@ void PreviewWrap::prepare(rpl::producer details) { ? tr::lng_action_gift_unique_received(tr::now, lt_user, name) : _recipient->isSelf() ? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost) + : _recipient->isBroadcast() + ? tr::lng_action_gift_sent_channel( + tr::now, + lt_user, + name, + lt_name, + _recipient->name(), + lt_cost, + cost) : tr::lng_action_gift_received( tr::now, lt_user, @@ -652,6 +694,21 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { for (auto &gift : gifts) { list.push_back({ .info = gift }); } + ranges::sort(list, []( + const GiftTypeStars &a, + const GiftTypeStars &b) { + if (!a.info.limitedCount && !b.info.limitedCount) { + return a.info.stars <= b.info.stars; + } else if (!a.info.limitedCount) { + return true; + } else if (!b.info.limitedCount) { + return false; + } else if (a.info.limitedLeft != b.info.limitedLeft) { + return a.info.limitedLeft > b.info.limitedLeft; + } + return a.info.stars <= b.info.stars; + }); + auto &map = Map[session]; if (map.last != list) { map.last = list; @@ -1060,7 +1117,7 @@ void SendGift( .giftId = gift.info.id, .randomId = details.randomId, .message = details.text, - .user = peer->asUser(), + .recipient = peer, .limitedCount = gift.info.limitedCount, .anonymous = details.anonymous, .upgraded = details.upgraded, @@ -1145,7 +1202,7 @@ void SendStarsFormRequest( void UpgradeGift( not_null window, - MsgId messageId, + Data::SavedStarGiftId savedId, bool keepDetails, int stars, Fn done) { @@ -1165,7 +1222,7 @@ void UpgradeGift( using Flag = MTPpayments_UpgradeStarGift::Flag; session->api().request(MTPpayments_UpgradeStarGift( MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()), - MTP_int(messageId.bare) + Api::InputSavedStarGiftId(savedId) )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); formDone(Payments::CheckoutResult::Paid, &result); @@ -1182,7 +1239,7 @@ void UpgradeGift( window, MTP_inputInvoiceStarGiftUpgrade( MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()), - MTP_int(messageId.bare)), + Api::InputSavedStarGiftId(savedId)), std::move(formDone)); } @@ -1211,7 +1268,7 @@ void AddUpgradeButton( not_null container, not_null session, int cost, - QString name, + not_null peer, Fn toggled, Fn preview) { const auto button = container->add( @@ -1258,25 +1315,105 @@ void AddUpgradeButton( AddSkip(container); const auto about = AddDividerText( container, - tr::lng_gift_send_unique_about( - lt_user, - rpl::single(TextWithEntities{ name }), - lt_link, - tr::lng_gift_send_unique_link() | Text::ToLink(), - Text::WithEntities)); + (peer->isBroadcast() + ? tr::lng_gift_send_unique_about_channel( + lt_name, + rpl::single(TextWithEntities{ peer->name() }), + lt_link, + tr::lng_gift_send_unique_link() | Text::ToLink(), + Text::WithEntities) + : tr::lng_gift_send_unique_about( + lt_user, + rpl::single(TextWithEntities{ peer->shortName() }), + lt_link, + tr::lng_gift_send_unique_link() | Text::ToLink(), + Text::WithEntities))); about->setClickHandlerFilter([=](const auto &...) { preview(); return false; }); } +void AddSoldLeftSlider( + not_null button, + const GiftTypeStars &gift) { + const auto still = gift.info.limitedLeft; + const auto total = gift.info.limitedCount; + const auto slider = CreateChild(button->parentWidget()); + struct State { + Text::String still; + Text::String sold; + int height = 0; + }; + const auto state = slider->lifetime().make_state(); + const auto sold = total - still; + state->still.setText( + st::semiboldTextStyle, + tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still)); + state->sold.setText( + st::semiboldTextStyle, + tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold)); + state->height = st::giftLimitedPadding.top() + + st::semiboldFont->height + + st::giftLimitedPadding.bottom(); + button->geometryValue() | rpl::start_with_next([=](QRect geometry) { + const auto space = st::giftLimitedBox.buttonPadding.top(); + const auto skip = (space - state->height) / 2; + slider->setGeometry( + geometry.x(), + geometry.y() - skip - state->height, + geometry.width(), + state->height); + }, slider->lifetime()); + slider->paintRequest() | rpl::start_with_next([=] { + const auto &padding = st::giftLimitedPadding; + const auto left = (padding.left() * 2) + state->still.maxWidth(); + const auto right = (padding.right() * 2) + state->sold.maxWidth(); + const auto space = slider->width() - left - right; + if (space <= 0) { + return; + } + const auto edge = left + ((space * still) / total); + + const auto radius = st::buttonRadius; + auto p = QPainter(slider); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgOver); + p.drawRoundedRect( + edge - (radius * 3), + 0, + slider->width() - (edge - (radius * 3)), + state->height, + radius, + radius); + p.setBrush(st::windowBgActive); + p.drawRoundedRect(0, 0, edge, state->height, radius, radius); + + p.setPen(st::windowFgActive); + state->still.draw(p, { + .position = { padding.left(), padding.top() }, + .availableWidth = left, + }); + p.setPen(st::windowSubTextFg); + state->sold.draw(p, { + .position = { left + space + padding.right(), padding.top() }, + .availableWidth = right, + }); + }, slider->lifetime()); +} + void SendGiftBox( not_null box, not_null window, not_null peer, std::shared_ptr api, const GiftDescriptor &descriptor) { - box->setStyle(st::giftBox); + const auto stars = std::get_if(&descriptor); + const auto limited = stars + && (stars->info.limitedCount > stars->info.limitedLeft) + && (stars->info.limitedLeft > 0); + box->setStyle(limited ? st::giftLimitedBox : st::giftBox); box->setWidth(st::boxWideWidth); box->setTitle(tr::lng_gift_send_title()); box->addTopButton(st::boxTitleClose, [=] { @@ -1373,18 +1510,14 @@ void SendGiftBox( session, { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); - if (const auto stars = std::get_if(&descriptor)) { + if (stars) { const auto cost = stars->info.starsToUpgrade; if (cost > 0 && !peer->isSelf()) { - const auto user = peer->asUser(); - Assert(user != nullptr); - const auto id = stars->info.id; - const auto name = user->shortName(); const auto showing = std::make_shared(); AddDivider(container); AddSkip(container); - AddUpgradeButton(container, session, cost, name, [=](bool on) { + AddUpgradeButton(container, session, cost, peer, [=](bool on) { auto now = state->details.current(); now.upgraded = on; state->details = std::move(now); @@ -1397,7 +1530,7 @@ void SendGiftBox( .controller = window, .stargiftId = id, .ready = [=](bool) { *showing = false; }, - .user = user, + .peer = peer, .cost = int(cost), }); }); @@ -1425,6 +1558,8 @@ void SendGiftBox( }, [&](const GiftTypeStars &) { AddDividerText(container, peer->isSelf() ? tr::lng_gift_send_anonymous_self() + : peer->isBroadcast() + ? tr::lng_gift_send_anonymous_about_channel() : tr::lng_gift_send_anonymous_about( lt_user, rpl::single(peer->shortName()), @@ -1454,6 +1589,9 @@ void SendGiftBox( }; SendGift(window, peer, api, details, done); }); + if (limited) { + AddSoldLeftSlider(button, *stars); + } SetButtonMarkedLabel( button, (peer->isSelf() @@ -1704,7 +1842,7 @@ void GiftBox( window->showSettings(Settings::CreditsId()); return false; }; - if (!peer->isSelf()) { + if (peer->isUser() && !peer->isSelf()) { const auto premiumClickHandlerFilter = [=](const auto &...) { Settings::ShowPremium(window, u"gift_send"_q); return false; @@ -1724,9 +1862,16 @@ void GiftBox( AddBlock(content, window, { .subtitle = (peer->isSelf() ? tr::lng_gift_self_title() + : peer->isBroadcast() + ? tr::lng_gift_channel_title() : tr::lng_gift_stars_subtitle()), .about = (peer->isSelf() ? tr::lng_gift_self_about(Text::WithEntities) + : peer->isBroadcast() + ? tr::lng_gift_channel_about( + lt_name, + rpl::single(Text::Bold(peer->name())), + Text::WithEntities) : tr::lng_gift_stars_about( lt_name, rpl::single(Text::Bold(peer->shortName())), @@ -2112,6 +2257,242 @@ void AddUniqueGiftCover( }, cover->lifetime()); } +void AddWearGiftCover( + not_null container, + const Data::UniqueGift &data, + not_null peer) { + const auto cover = container->add(object_ptr(container)); + + const auto title = CreateChild( + cover, + rpl::single(peer->name()), + st::uniqueGiftTitle); + title->setTextColorOverride(QColor(255, 255, 255)); + const auto subtitle = CreateChild( + 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 emoji; + base::flat_map emojis; + rpl::lifetime lifetime; + }; + const auto state = cover->lifetime().make_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 show, + not_null peer, + const Data::UniqueGift &gift, + Settings::GiftWearBoxStyleOverride st) { + show->show(Box([=](not_null 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 title, + rpl::producer text, + not_null icon) { + auto raw = content->add( + object_ptr(content)); + raw->add( + object_ptr( + raw, + std::move(title) | Ui::Text::ToBold(), + st.infoTitle ? *st.infoTitle : st::defaultFlatLabel), + st::settingsPremiumRowTitlePadding); + raw->add( + object_ptr( + raw, + std::move(text), + st.infoAbout ? *st.infoAbout : st::boxDividerLabel), + st::settingsPremiumRowAboutPadding); + object_ptr( + raw, + *icon, + st::starrefInfoIconPosition); + }; + + content->add( + object_ptr( + content, + tr::lng_gift_wear_title( + lt_name, + rpl::single(UniqueGiftName(gift))), + st.title ? *st.title : st::uniqueGiftTitle), + st::settingsPremiumRowTitlePadding); + content->add( + object_ptr( + 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(); + 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(); + } + const auto reason = [&]() -> Ui::AskBoostReason { + return { Ui::AskBoostWearCollectible{ + wanted + } }; + }(); + return std::make_optional(reason); + }, [=] { *checking = false; }); + } else if (session->premium()) { + box->closeBox(); + emojiStatuses->set(peer, id); + } else { + const auto link = Ui::Text::Bold( + tr::lng_send_as_premium_required_link(tr::now)); + Settings::ShowPremiumPromoToast( + show, + tr::lng_gift_wear_subscribe( + tr::now, + lt_link, + Ui::Text::Link(link), + Ui::Text::WithEntities), + u"wear_collectibles"_q); + } + }); + const auto lock = Ui::Text::SingleCustomEmoji( + session->data().customEmojiManager().registerInternalEmoji( + st::historySendDisabledIcon, + st::giftBoxLockMargins, + true)); + auto label = rpl::combine( + tr::lng_gift_wear_start(), + Data::AmPremiumValue(&show->session()) + ) | rpl::map([=](const QString &text, bool premium) { + auto result = TextWithEntities(); + if (!premium && peer->isSelf()) { + result.append(lock); + } + result.append(text); + return result; + }); + SetButtonMarkedLabel( + button, + std::move(label), + session, + st::creditsBoxButtonLabel, + &st::giftBox.button.textFg); + + rpl::combine( + box->widthValue(), + button->widthValue() + ) | rpl::start_with_next([=](int outer, int inner) { + const auto padding = st::giftBox.buttonPadding; + const auto wanted = outer - padding.left() - padding.right(); + if (inner != wanted) { + button->resizeToWidth(wanted); + button->moveToLeft(padding.left(), padding.top()); + } + }, box->lifetime()); + + AddUniqueCloseButton(box, {}); + })); +} + struct UpgradeArgs : StarGiftUpgradeArgs { std::vector models; std::vector patterns; @@ -2162,7 +2543,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs { auto &patterns = state->data.patterns; auto &backdrops = state->data.backdrops; consumer.put_next(Data::UniqueGift{ - .title = (state->data.itemId + .title = (state->data.savedId ? tr::lng_gift_upgrade_title(tr::now) : tr::lng_gift_upgrade_preview_title(tr::now)), .model = models[index(state->modelIndices, models)], @@ -2186,11 +2567,13 @@ void AddUpgradeGiftCover( AddUniqueGiftCover( container, MakeUpgradeGiftStream(args), - (args.itemId + (args.savedId ? tr::lng_gift_upgrade_about() - : tr::lng_gift_upgrade_preview_about( - lt_name, - rpl::single(args.user->shortName())))); + : (args.peer->isBroadcast() + ? tr::lng_gift_upgrade_preview_about_channel + : tr::lng_gift_upgrade_preview_about)( + lt_name, + rpl::single(args.peer->shortName())))); } void UpgradeBox( @@ -2207,26 +2590,15 @@ void UpgradeBox( const auto infoRow = [&]( rpl::producer title, rpl::producer text, - not_null icon, - bool newBadge = false) { + not_null icon) { auto raw = container->add( object_ptr(container)); - const auto widget = raw->add( + raw->add( object_ptr( raw, std::move(title) | Ui::Text::ToBold(), st::defaultFlatLabel), st::settingsPremiumRowTitlePadding); - if (newBadge) { - const auto badge = NewBadge::CreateNewBadge( - raw, - tr::lng_soon_badge(Ui::Text::Upper)); - widget->geometryValue( - ) | rpl::start_with_next([=](QRect geometry) { - badge->move(st::settingsPremiumNewBadgePosition - + QPoint(widget->x() + widget->width(), widget->y())); - }, badge->lifetime()); - } raw->add( object_ptr( raw, @@ -2250,15 +2622,14 @@ void UpgradeBox( infoRow( tr::lng_gift_upgrade_tradable_title(), tr::lng_gift_upgrade_tradable_about(), - &st::menuIconTradable, - true); + &st::menuIconTradable); struct State { bool sent = false; bool preserveDetails = false; }; const auto state = std::make_shared(); - const auto preview = !args.itemId; + const auto preview = !args.savedId; if (!preview) { const auto skip = st::defaultVerticalListSkip; @@ -2303,13 +2674,13 @@ void UpgradeBox( if (result != Payments::CheckoutResult::Paid) { state->sent = false; } else { - controller->showPeerHistory(args.user); + controller->showPeerHistory(args.peer); if (const auto strong = weak.data()) { strong->closeBox(); } } }; - UpgradeGift(controller, args.itemId, keepDetails, cost, done); + UpgradeGift(controller, args.savedId, keepDetails, cost, done); }); if (!preview) { auto star = session->data().customEmojiManager().creditsEmoji(); @@ -2339,7 +2710,7 @@ void UpgradeBox( } }, box->lifetime()); - AddUniqueCloseButton(box); + AddUniqueCloseButton(box, {}); } const std::vector &PatternPoints() { @@ -2458,7 +2829,7 @@ void PaintPoints( void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { const auto weak = base::make_weak(args.controller); - const auto session = &args.user->session(); + const auto session = &args.peer->session(); session->api().request(MTPpayments_GetStarGiftUpgradePreview( MTP_long(args.stargiftId) )).done([=](const MTPpayments_StarGiftUpgradePreview &result) { @@ -2494,19 +2865,51 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) { }).send(); } -void AddUniqueCloseButton(not_null box) { - const auto button = Ui::CreateChild( +void AddUniqueCloseButton( + not_null box, + Settings::CreditsEntryBoxStyleOverrides st, + Fn)> fillMenu) { + const auto close = Ui::CreateChild( box, st::uniqueCloseButton); - button->show(); - button->raise(); + const auto menu = fillMenu + ? Ui::CreateChild(box, st::uniqueMenuButton) + : nullptr; + close->show(); + close->raise(); + if (menu) { + menu->show(); + menu->raise(); + } box->widthValue() | rpl::start_with_next([=](int width) { - button->moveToRight(0, 0, width); - button->raise(); - }, button->lifetime()); - button->setClickedCallback([=] { + close->moveToRight(0, 0, width); + close->raise(); + if (menu) { + menu->moveToRight(close->width(), 0, width); + menu->raise(); + } + }, close->lifetime()); + close->setClickedCallback([=] { box->closeBox(); }); + if (menu) { + const auto state = menu->lifetime().make_state< + base::unique_qptr + >(); + menu->setClickedCallback([=] { + if (*state) { + *state = nullptr; + return; + } + *state = base::make_unique_q( + menu, + st.menu ? *st.menu : st::popupMenuWithIcons); + fillMenu(state->get()); + if (!(*state)->empty()) { + (*state)->popup(QCursor::pos()); + } + }); + } } void RequestStarsFormAndSubmit( @@ -2539,25 +2942,29 @@ void RequestStarsFormAndSubmit( done(Payments::CheckoutResult::Failed, nullptr); }); }).fail([=](const MTP::Error &error) { - if (const auto strong = weak.get()) { - strong->showToast(error.type()); + const auto type = error.type(); + if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) { + done(Payments::CheckoutResult::Cancelled, nullptr); + } else { + if (const auto strong = weak.get()) { + strong->showToast(type); + } + done(Payments::CheckoutResult::Failed, nullptr); } - done(Payments::CheckoutResult::Failed, nullptr); }).send(); } void ShowGiftTransferredToast( base::weak_ptr weak, not_null to, - const MTPUpdates &result) { - const auto gift = FindUniqueGift(&to->session(), result); - if (const auto strong = gift ? weak.get() : nullptr) { + const Data::UniqueGift &gift) { + if (const auto strong = weak.get()) { strong->showToast({ .title = tr::lng_gift_transferred_title(tr::now), .text = tr::lng_gift_transferred_about( tr::now, lt_name, - Text::Bold(Data::UniqueGiftName(*gift)), + Text::Bold(Data::UniqueGiftName(gift)), lt_recipient, Text::Bold(to->shortName()), Ui::Text::WithEntities), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index bacb3997d..f00f4ae3c 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_star_gift.h" + +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + namespace Data { struct UniqueGift; struct GiftCode; @@ -17,6 +23,11 @@ namespace Payments { enum class CheckoutResult; } // namespace Payments +namespace Settings { +struct GiftWearBoxStyleOverride; +struct CreditsEntryBoxStyleOverrides; +} // namespace Settings + namespace Window { class SessionController; } // namespace Window @@ -27,6 +38,7 @@ class CustomEmoji; namespace Ui { +class PopupMenu; class GenericBox; class VerticalLayout; @@ -41,6 +53,16 @@ void AddUniqueGiftCover( not_null container, rpl::producer data, rpl::producer subtitleOverride = nullptr); +void AddWearGiftCover( + not_null container, + const Data::UniqueGift &data, + not_null peer); + +void ShowUniqueGiftWearBox( + std::shared_ptr show, + not_null peer, + const Data::UniqueGift &gift, + Settings::GiftWearBoxStyleOverride st); struct PatternPoint { QPointF position; @@ -63,8 +85,8 @@ struct StarGiftUpgradeArgs { not_null controller; base::required stargiftId; Fn ready; - not_null user; - MsgId itemId = 0; + not_null peer; + Data::SavedStarGiftId savedId; int cost = 0; bool canAddSender = false; bool canAddComment = false; @@ -73,7 +95,10 @@ struct StarGiftUpgradeArgs { }; void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args); -void AddUniqueCloseButton(not_null box); +void AddUniqueCloseButton( + not_null box, + Settings::CreditsEntryBoxStyleOverrides st, + Fn)> fillMenu = nullptr); void RequestStarsFormAndSubmit( not_null window, @@ -83,6 +108,6 @@ void RequestStarsFormAndSubmit( void ShowGiftTransferredToast( base::weak_ptr weak, not_null to, - const MTPUpdates &result); + const Data::UniqueGift &gift); } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index cf271a888..9ab4bfd59 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -817,9 +817,7 @@ void StickerSetBox::updateButtons() { - st.buttonPadding.left() - st.buttonPadding.left()); button->setClickedCallback([=] { - using namespace ChatHelpers; - const auto usage = WindowUsage::PremiumPromo; - if (const auto window = _show->resolveWindow(usage)) { + if (const auto window = _show->resolveWindow()) { Settings::ShowPremium(window, u"animated_emoji"_q); } }); diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp index d92204e0b..8d297bb27 100644 --- a/Telegram/SourceFiles/boxes/transfer_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.cpp @@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/transfer_gift_box.h" #include "apiwrap.h" +#include "api/api_credits.h" +#include "api/api_cloud_password.h" #include "base/unixtime.h" +#include "boxes/passcode_box.h" +#include "data/data_session.h" #include "data/data_star_gift.h" #include "data/data_user.h" #include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle. @@ -21,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/basic_click_handlers.h" #include "ui/empty_userpic.h" #include "ui/painter.h" #include "ui/vertical_list.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" // peerListSingleRow. #include "styles/style_dialogs.h" // recentPeersSpecialName. +#include "styles/style_layers.h" // boxLabel. namespace { @@ -41,13 +47,15 @@ public: Controller( not_null window, std::shared_ptr gift, - Fn)> choose); + Data::SavedStarGiftId savedId, + Fn, Fn)> choose); + + void init(not_null box); void noSearchSubmit(); private: void prepareViewHook() override; - void setupExportOption(); bool overrideKeyboardNavigation( int direction, @@ -58,34 +66,150 @@ private: not_null user) override; void rowClicked(not_null row) override; + const not_null _window; const std::shared_ptr _gift; - const Fn)> _choose; + const Data::SavedStarGiftId _giftId; + const Fn, Fn)> _choose; ExportOption _exportOption; + QPointer _box; }; +void ConfirmExportBox( + not_null box, + std::shared_ptr gift, + Fn close)> confirmed) { + box->setTitle(tr::lng_gift_transfer_confirm_title()); + box->addRow(object_ptr( + 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, + not_null parent, + std::shared_ptr gift, + Data::SavedStarGiftId giftId, + Fn boxShown, + Fn wentToUrl) { + struct State { + bool loading = false; + rpl::lifetime lifetime; + }; + const auto state = std::make_shared(); + 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 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(session, fields)); + boxShown(); + }); + }).send(); +} + [[nodiscard]] ExportOption MakeExportOption( not_null window, + not_null box, + std::shared_ptr gift, + Data::SavedStarGiftId giftId, TimeId when) { + struct State { + bool exporting = false; + }; + const auto state = std::make_shared(); const auto activate = [=] { const auto now = base::unixtime::now(); + const auto weak = Ui::MakeWeak(box); const auto left = (when > now) ? (when - now) : 0; const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0; + if (!hours) { + window->show(Box(ConfirmExportBox, gift, [=](Fn close) { + if (state->exporting) { + return; + } + state->exporting = true; + ExportOnBlockchain(window, box, gift, giftId, [=] { + state->exporting = false; + close(); + }, [=] { + if (const auto strong = weak.data()) { + strong->closeBox(); + } + close(); + }); + })); + return; + } window->show(Ui::MakeInformBox({ - .text = (!hours - ? tr::lng_gift_transfer_unlocks_update_about() - : tr::lng_gift_transfer_unlocks_about( - lt_when, - ((hours >= 24) - ? tr::lng_gift_transfer_unlocks_when_days( - lt_count, - rpl::single((hours / 24) * 1.)) - : tr::lng_gift_transfer_unlocks_when_hours( - lt_count, - rpl::single(hours * 1.))))), - .title = (!hours - ? tr::lng_gift_transfer_unlocks_update_title() - : tr::lng_gift_transfer_unlocks_title()), + .text = tr::lng_gift_transfer_unlocks_about( + lt_when, + ((hours >= 24) + ? tr::lng_gift_transfer_unlocks_when_days( + lt_count, + rpl::single((hours / 24) * 1.)) + : tr::lng_gift_transfer_unlocks_when_hours( + lt_count, + rpl::single(hours * 1.)))), + .title = tr::lng_gift_transfer_unlocks_title(), })); }; @@ -130,10 +254,14 @@ private: const style::PeerListItem &computeSt( const style::PeerListItem &st) const override { - return _available ? st::recentPeersSpecialName : st; + _st = st; + _st.namePosition.setY( + st::recentPeersSpecialName.namePosition.y()); + return _available ? _st : st; } private: + mutable style::PeerListItem _st; bool _available = false; }; @@ -228,18 +356,27 @@ private: Controller::Controller( not_null window, std::shared_ptr gift, - Fn)> choose) + Data::SavedStarGiftId giftId, + Fn, Fn)> choose) : ContactsBoxController(&window->session()) +, _window(window) , _gift(std::move(gift)) +, _giftId(giftId) , _choose(std::move(choose)) { - if (const auto when = _gift->exportAt) { - _exportOption = MakeExportOption(window, when); - } - if (_exportOption.content) { + if (_gift->exportAt) { setStyleOverrides(&st::peerListSmallSkips); } } +void Controller::init(not_null box) { + _box = box; + if (const auto when = _gift->exportAt) { + _exportOption = MakeExportOption(_window, box, _gift, _giftId, when); + delegate()->peerListSetAboveWidget(std::move(_exportOption.content)); + delegate()->peerListRefreshRows(); + } +} + void Controller::noSearchSubmit() { if (const auto onstack = _exportOption.activate) { onstack(); @@ -258,11 +395,6 @@ void Controller::prepareViewHook() { delegate()->peerListSetTitle(tr::lng_gift_transfer_title( lt_name, rpl::single(UniqueGiftName(*_gift)))); - setupExportOption(); -} - -void Controller::setupExportOption() { - delegate()->peerListSetAboveWidget(std::move(_exportOption.content)); } std::unique_ptr Controller::createRow( @@ -277,14 +409,18 @@ std::unique_ptr Controller::createRow( } void Controller::rowClicked(not_null row) { - _choose(row->peer()); + _choose(row->peer(), [parentBox = _box] { + if (const auto strong = parentBox.data()) { + strong->closeBox(); + } + }); } void TransferGift( not_null window, not_null to, std::shared_ptr gift, - MsgId messageId, + Data::SavedStarGiftId savedId, Fn done) { Expects(to->isUser()); @@ -293,33 +429,37 @@ void TransferGift( auto formDone = [=]( Payments::CheckoutResult result, const MTPUpdates *updates) { - if (result == Payments::CheckoutResult::Paid && updates) { + done(result); + if (result == Payments::CheckoutResult::Paid) { if (const auto strong = weak.get()) { - Ui::ShowGiftTransferredToast(strong, to, *updates); + strong->session().data().notifyGiftUpdate({ + .id = savedId, + .action = Data::GiftUpdate::Action::Transfer, + }); + Ui::ShowGiftTransferredToast(strong, to, *gift); } } - done(result); }; if (gift->starsForTransfer <= 0) { session->api().request(MTPpayments_TransferStarGift( - MTP_int(messageId.bare), - to->asUser()->inputUser + Api::InputSavedStarGiftId(savedId), + to->input )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); formDone(Payments::CheckoutResult::Paid, &result); }).fail([=](const MTP::Error &error) { + formDone(Payments::CheckoutResult::Failed, nullptr); if (const auto strong = weak.get()) { strong->showToast(error.type()); } - formDone(Payments::CheckoutResult::Failed, nullptr); }).send(); return; } Ui::RequestStarsFormAndSubmit( window, MTP_inputInvoiceStarGiftTransfer( - MTP_int(messageId.bare), - to->asUser()->inputUser), + Api::InputSavedStarGiftId(savedId), + to->input), std::move(formDone)); } @@ -327,7 +467,8 @@ void ShowTransferToBox( not_null controller, not_null peer, std::shared_ptr gift, - MsgId msgId) { + Data::SavedStarGiftId savedId, + Fn closeParentBox) { const auto stars = gift->starsForTransfer; controller->show(Box([=](not_null box) { box->setTitle(tr::lng_gift_transfer_title( @@ -353,16 +494,24 @@ void ShowTransferToBox( state->sent = true; const auto weak = Ui::MakeWeak(box); const auto done = [=](Payments::CheckoutResult result) { - if (result != Payments::CheckoutResult::Paid) { + if (result == Payments::CheckoutResult::Cancelled) { + closeParentBox(); + if (const auto strong = weak.data()) { + strong->closeBox(); + } + } else if (result != Payments::CheckoutResult::Paid) { state->sent = false; } else { - controller->showPeerHistory(peer); + if (savedId.isUser()) { + controller->showPeerHistory(peer); + } + closeParentBox(); if (const auto strong = weak.data()) { strong->closeBox(); } } }; - TransferGift(controller, peer, gift, msgId, done); + TransferGift(controller, peer, gift, savedId, done); }; Ui::ConfirmBox(box, { @@ -395,15 +544,18 @@ void ShowTransferToBox( void ShowTransferGiftBox( not_null window, std::shared_ptr gift, - MsgId msgId) { + Data::SavedStarGiftId savedId) { auto controller = std::make_unique( window, gift, - [=](not_null peer) { - ShowTransferToBox(window, peer, gift, msgId); + savedId, + [=](not_null peer, Fn done) { + ShowTransferToBox(window, peer, gift, savedId, done); }); const auto controllerRaw = controller.get(); auto initBox = [=](not_null box) { + controllerRaw->init(box); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->noSearchSubmits() | rpl::start_with_next([=] { diff --git a/Telegram/SourceFiles/boxes/transfer_gift_box.h b/Telegram/SourceFiles/boxes/transfer_gift_box.h index 2b5ee2240..e058e88c3 100644 --- a/Telegram/SourceFiles/boxes/transfer_gift_box.h +++ b/Telegram/SourceFiles/boxes/transfer_gift_box.h @@ -13,9 +13,10 @@ class SessionController; namespace Data { struct UniqueGift; +class SavedStarGiftId; } // namespace Data void ShowTransferGiftBox( not_null window, std::shared_ptr gift, - MsgId msgId); + Data::SavedStarGiftId savedId); diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 234bdb8c0..d48cfdaa0 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1441,6 +1441,19 @@ groupCallScheduleTimeField: InputField(groupCallScheduleDateField) { placeholderFont: font(14px); } +groupCallVolumeSettings: Menu(groupCallPopupVolumeMenu) { + widthMin: 210px; + itemBg: groupCallMembersBg; + itemBgOver: groupCallMembersBgOver; +} +groupCallVolumeSettingsPadding: margins(24px, 8px, 24px, 6px); +groupCallVolumeSettingsSlider: MediaSlider(groupCallMenuVolumeSlider) { + activeFg: groupCallMenuBg; + inactiveFg: groupCallMenuBg; + activeFgOver: groupCallMenuBg; + inactiveFgOver: groupCallMenuBg; +} + // groupCallCalendarPreviousDisabled: icon {{ "calendar_down-flip_vertical", groupCallMemberNotJoinedStatus }}; groupCallCalendarNextDisabled: icon {{ "calendar_down", groupCallMemberNotJoinedStatus }}; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 2853340a5..928c3b1f5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1431,10 +1431,12 @@ void Members::Controller::addMuteActionsToContextMenu( auto volumeItem = base::make_unique_q( menu->menu(), st::groupCallPopupVolumeMenu, + st::groupCallMenuVolumeSlider, otherParticipantStateValue, _call->rtmp() ? _call->rtmpVolume() : row->volume(), Group::kMaxVolume, - muted); + muted, + st::groupCallMenuVolumePadding); mutesFromVolume = volumeItem->toggleMuteRequests(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 980842b99..7ae977bfd 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_menu.h" // LeaveBox. #include "calls/group/calls_group_common.h" #include "calls/group/calls_choose_join_as.h" +#include "calls/group/calls_volume_item.h" #include "calls/calls_instance.h" #include "ui/widgets/level_meter.h" #include "ui/widgets/continuous_sliders.h" @@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "webrtc/webrtc_audio_input_tester.h" #include "webrtc/webrtc_device_resolver.h" #include "settings/settings_calls.h" +#include "settings/settings_credits_graphics.h" #include "main/main_session.h" #include "apiwrap.h" #include "api/api_invite_links.h" @@ -183,22 +185,7 @@ object_ptr ShareInviteLinkBox( return Data::CanSend(thread, ChatRestriction::SendOther); }; - const auto scheduleStyle = [&] { - auto date = Ui::ChooseDateTimeStyleArgs(); - date.labelStyle = &st::groupCallBoxLabel; - date.dateFieldStyle = &st::groupCallScheduleDateField; - date.timeFieldStyle = &st::groupCallScheduleTimeField; - date.separatorStyle = &st::callMuteButtonLabel; - date.atStyle = &st::callMuteButtonLabel; - date.calendarStyle = &st::groupCallCalendarColors; - - auto st = HistoryView::ScheduleBoxStyleArgs(); - st.topButtonStyle = &st::groupCallMenuToggle; - st.popupMenuStyle = &st::groupCallPopupMenu; - st.chooseDateTimeArgs = std::move(date); - return st; - }; - + const auto st = ::Settings::DarkCreditsEntryBoxStyle(); auto result = Box(ShareBox::Descriptor{ .session = &peer->session(), .copyCallback = std::move(copyCallback), @@ -211,11 +198,7 @@ object_ptr ShareInviteLinkBox( : rpl::single(false)), tr::lng_group_call_copy_speaker_link(), tr::lng_group_call_copy_listener_link()), - .stMultiSelect = &st::groupCallMultiSelect, - .stComment = &st::groupCallShareBoxComment, - .st = &st::groupCallShareBoxList, - .stLabel = &st::groupCallField, - .scheduleBoxStyle = scheduleStyle(), + .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(), .premiumRequiredError = SharePremiumRequiredError(), }); *box = result.data(); @@ -731,6 +714,50 @@ void SettingsBox( addDivider(); Ui::AddSkip(layout); } + if (rtmp) { + const auto volumeItem = layout->add( + object_ptr( + layout, + st::groupCallVolumeSettings, + st::groupCallVolumeSettingsSlider, + call->otherParticipantStateValue( + ) | rpl::filter([=](const Group::ParticipantState &data) { + return data.peer == peer; + }), + call->rtmpVolume(), + Group::kMaxVolume, + false, + st::groupCallVolumeSettingsPadding)); + + const auto toggleMute = crl::guard(layout, [=](bool m, bool local) { + if (call) { + call->toggleMute({ + .peer = peer, + .mute = m, + .locallyOnly = local, + }); + } + }); + const auto changeVolume = crl::guard(layout, [=](int v, bool local) { + if (call) { + call->changeVolume({ + .peer = peer, + .volume = std::clamp(v, 1, Group::kMaxVolume), + .locallyOnly = local, + }); + } + }); + + volumeItem->toggleMuteLocallyRequests( + ) | rpl::start_with_next([=](bool muted) { + toggleMute(muted, true); + }, volumeItem->lifetime()); + + volumeItem->changeVolumeLocallyRequests( + ) | rpl::start_with_next([=](int volume) { + changeVolume(volume, true); + }, volumeItem->lifetime()); + } if (peer->canManageGroupCall()) { layout->add(object_ptr( diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp index d34a51060..69529b9f6 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.cpp +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animation_value.h" #include "ui/effects/cross_line.h" #include "ui/widgets/continuous_sliders.h" +#include "ui/rect.h" #include "styles/style_calls.h" #include "ui/paint/arcs.h" @@ -41,20 +42,21 @@ constexpr auto kVolumeStickedValues MenuVolumeItem::MenuVolumeItem( not_null parent, const style::Menu &st, + const style::MediaSlider &stSlider, rpl::producer participantState, int startVolume, int maxVolume, - bool muted) + bool muted, + const QMargins &padding) : Ui::Menu::ItemBase(parent, st) , _maxVolume(maxVolume) , _cloudMuted(muted) , _localMuted(muted) -, _slider(base::make_unique_q( - this, - st::groupCallMenuVolumeSlider)) +, _slider(base::make_unique_q(this, stSlider)) , _dummyAction(new QAction(parent)) , _st(st) , _stCross(st::groupCallMuteCrossLine) +, _padding(padding) , _crossLineMute(std::make_unique(_stCross, true)) , _arcs(std::make_unique( st::groupCallSpeakerArcsAnimation, @@ -71,7 +73,7 @@ MenuVolumeItem::MenuVolumeItem( sizeValue( ) | rpl::start_with_next([=](const QSize &size) { const auto geometry = QRect(QPoint(), size); - _itemRect = geometry - st::groupCallMenuVolumePadding; + _itemRect = geometry - _padding; _speakerRect = QRect(_itemRect.topLeft(), _stCross.icon.size()); _arcPosition = _speakerRect.center() + QPoint(0, st::groupCallMenuSpeakerArcsSkip); @@ -280,9 +282,7 @@ bool MenuVolumeItem::isEnabled() const { } int MenuVolumeItem::contentHeight() const { - return st::groupCallMenuVolumePadding.top() - + st::groupCallMenuVolumePadding.bottom() - + _stCross.icon.height(); + return rect::m::sum::v(_padding) + _stCross.icon.height(); } rpl::producer MenuVolumeItem::toggleMuteRequests() const { diff --git a/Telegram/SourceFiles/calls/group/calls_volume_item.h b/Telegram/SourceFiles/calls/group/calls_volume_item.h index 86eadd1f9..af2d38d40 100644 --- a/Telegram/SourceFiles/calls/group/calls_volume_item.h +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.h @@ -31,10 +31,12 @@ public: MenuVolumeItem( not_null parent, const style::Menu &st, + const style::MediaSlider &stSlider, rpl::producer participantState, int startVolume, int maxVolume, - bool muted); + bool muted, + const QMargins &padding); not_null action() const override; bool isEnabled() const override; @@ -71,6 +73,7 @@ private: const not_null _dummyAction; const style::Menu &_st; const style::CrossLineAnimation &_stCross; + const QMargins &_padding; const std::unique_ptr _crossLineMute; Ui::Animations::Simple _crossLineAnimation; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 926117bcf..7da5ba6c6 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -40,6 +40,7 @@ TabbedSearch { ComposeIcons { settings: icon; + collectibles: icon; recent: icon; recentActive: icon; @@ -587,6 +588,7 @@ sendBoxAlbumGroupButtonMediaDelete: icon {{ "send_media/send_media_delete", roun defaultComposeIcons: ComposeIcons { settings: icon {{ "emoji/emoji_settings", emojiIconFg }}; + collectibles: icon {{ "menu/unique", emojiIconFg }}; recent: icon {{ "emoji/emoji_recent", emojiIconFg }}; recentActive: icon {{ "emoji/emoji_recent", emojiSubIconFgActive }}; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h index 2f33d8163..dedf4ff06 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_features.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_features.h @@ -18,6 +18,7 @@ struct ComposeFeatures { bool attachBotsMenu : 1 = true; bool inlineBots : 1 = true; bool megagroupSet : 1 = true; + bool collectibleStatus : 1 = false; bool stickersSettings : 1 = true; bool openStickerSets : 1 = true; bool autocompleteHashtags : 1 = true; diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp index ba904b77c..192937b6e 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.cpp @@ -19,7 +19,7 @@ rpl::producer Show::adjustShadowLeft() const { } ResolveWindow ResolveWindowDefault() { - return [](not_null session, WindowUsage usage) + return [](not_null session) -> Window::SessionController* { const auto check = [&](Window::Controller *window) { if (const auto controller = window->sessionController()) { @@ -45,8 +45,8 @@ ResolveWindow ResolveWindowDefault() { }; } -Window::SessionController *Show::resolveWindow(WindowUsage usage) const { - return ResolveWindowDefault()(&session(), usage); +Window::SessionController *Show::resolveWindow() const { + return ResolveWindowDefault()(&session()); } } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h index 28fc7ff47..95ed8046e 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h @@ -40,13 +40,8 @@ enum class PauseReason { using PauseReasons = base::flags; inline constexpr bool is_flag_type(PauseReason) { return true; }; -enum class WindowUsage { - PremiumPromo, -}; - using ResolveWindow = Fn, - WindowUsage)>; + not_null)>; [[nodiscard]] ResolveWindow ResolveWindowDefault(); class Show : public Main::SessionShow { @@ -68,8 +63,7 @@ public: virtual void processChosenSticker(FileChosen &&chosen) const = 0; - [[nodiscard]] virtual Window::SessionController *resolveWindow( - WindowUsage) const; + [[nodiscard]] virtual Window::SessionController *resolveWindow() const; }; } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 05e6b6764..6c6ad61dd 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/sticker_set_box.h" #include "lang/lang_keys.h" #include "layout/layout_position.h" +#include "data/data_emoji_statuses.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_channel.h" @@ -134,6 +135,7 @@ struct EmojiListWidget::CustomEmojiInstance { }; struct EmojiListWidget::RecentOne { + std::shared_ptr collectible; Ui::Text::CustomEmoji *custom = nullptr; RecentEmojiId id; mutable QImage premiumLock; @@ -447,6 +449,13 @@ void EmojiColorPicker::drawVariant(QPainter &p, int variant) { w.y() + _innerPosition.y()); } +std::vector DocumentListToRecent( + const std::vector &documents) { + return documents | ranges::views::transform([](DocumentId id) { + return EmojiStatusId{ .documentId = id }; + }) | ranges::to_vector; +} + EmojiListWidget::EmojiListWidget( QWidget *parent, not_null controller, @@ -510,6 +519,11 @@ EmojiListWidget::EmojiListWidget( refreshCustom(); } }, lifetime()); + } else if (_mode == Mode::EmojiStatus && _features.collectibleStatus) { + session().data().emojiStatuses().collectiblesUpdates( + ) | rpl::start_with_next([=] { + refreshCustom(); + }, lifetime()); } _customSingleSize = Data::FrameSizeFromTag( @@ -656,7 +670,8 @@ void EmojiListWidget::applyNextSearchQuery() { void EmojiListWidget::showPreview() { if (const auto over = std::get_if(&_pressed)) { if (const auto custom = lookupCustomEmoji(over)) { - _show->showMediaPreview(custom->stickerSetOrigin(), custom); + const auto document = custom.document; + _show->showMediaPreview(document->stickerSetOrigin(), document); _previewShown = true; } } @@ -706,7 +721,7 @@ void EmojiListWidget::appendPremiumSearchResults() { } void EmojiListWidget::provideRecent( - const std::vector &customRecentList) { + const std::vector &customRecentList) { clearSelection(); fillRecentFrom(customRecentList); resizeToWidth(width()); @@ -1094,7 +1109,8 @@ void EmojiListWidget::fillRecent() { } } -void EmojiListWidget::fillRecentFrom(const std::vector &list) { +void EmojiListWidget::fillRecentFrom( + const std::vector &list) { const auto test = session().isTestMode(); _recent.clear(); _recent.reserve(list.size()); @@ -1114,10 +1130,15 @@ void EmojiListWidget::fillRecentFrom(const std::vector &list) { _recentCustomIds.emplace(fakeId); } else { _recent.push_back({ + .collectible = id.collectible, .custom = resolveCustomRecent(id), - .id = { RecentEmojiDocument{ .id = id, .test = test } }, + .id = { + RecentEmojiDocument{ .id = id.documentId, .test = test }, + }, }); - _recentCustomIds.emplace(id); + _recentCustomIds.emplace(id.collectible + ? id.collectible->documentId + : id.documentId); } } } @@ -1158,8 +1179,12 @@ void EmojiListWidget::fillRecentMenu( const auto over = OverEmoji{ section, index }; const auto emoji = lookupOverEmoji(&over); const auto custom = lookupCustomEmoji(&over); - if (custom && custom->sticker()) { - const auto sticker = custom->sticker(); + if (custom.collectible) { + return; + } + const auto document = custom.document; + if (document && document->sticker()) { + const auto sticker = document->sticker(); const auto emoji = sticker->alt; const auto setId = sticker->set.id; if (!emoji.isEmpty()) { @@ -1168,7 +1193,7 @@ void EmojiListWidget::fillRecentMenu( EntityType::CustomEmoji, 0, int(emoji.size()), - Data::SerializeCustomEmojiId(custom) + Data::SerializeCustomEmojiId(document) }); addAction(tr::lng_emoji_copy(tr::now), [=] { TextUtilities::SetClipboardText(data); @@ -1192,8 +1217,8 @@ void EmojiListWidget::fillRecentMenu( auto id = RecentEmojiId{ emoji }; if (custom) { id.data = RecentEmojiDocument{ - .id = custom->id, - .test = custom->session().isTestMode(), + .id = custom.document->id, + .test = custom.document->session().isTestMode(), }; } addAction(tr::lng_emoji_remove_recent(tr::now), crl::guard(this, [=] { @@ -1229,7 +1254,7 @@ void EmojiListWidget::fillEmojiStatusMenu( int section, int index) { const auto chosen = lookupCustomEmoji(index, section); - if (!chosen) { + if (!chosen || chosen.collectible) { return; } const auto selectWith = [=](TimeId scheduled) { @@ -1573,12 +1598,14 @@ bool EmojiListWidget::checkPickerHide() { return false; } -DocumentData *EmojiListWidget::lookupCustomEmoji( +EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji( const OverEmoji *over) const { - return over ? lookupCustomEmoji(over->index, over->section) : nullptr; + return over + ? lookupCustomEmoji(over->index, over->section) + : ResolvedCustom(); } -DocumentData *EmojiListWidget::lookupCustomEmoji( +EmojiListWidget::ResolvedCustom EmojiListWidget::lookupCustomEmoji( int index, int section) const { if (_searchMode) { @@ -1586,22 +1613,30 @@ DocumentData *EmojiListWidget::lookupCustomEmoji( const auto document = std::get_if( &_searchResults[index].id.data); if (document) { - return session().data().document(document->id); + return { session().data().document(document->id) }; } } - return nullptr; + return {}; } else if (section == int(Section::Recent) && index < _recent.size()) { + const auto &recent = _recent[index]; + if (recent.collectible) { + return { + session().data().document(recent.collectible->documentId), + recent.collectible, + }; + } const auto document = std::get_if( - &_recent[index].id.data); + &recent.id.data); if (document) { - return session().data().document(document->id); + return { session().data().document(document->id) }; } } else if (section >= _staticCount && index < _custom[section - _staticCount].list.size()) { auto &set = _custom[section - _staticCount]; - return set.list[index].document; + auto &entry = set.list[index]; + return { entry.document, entry.collectible }; } - return nullptr; + return {}; } EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const { @@ -1643,9 +1678,11 @@ EmojiChosen EmojiListWidget::lookupChosen( } FileChosen EmojiListWidget::lookupChosen( - not_null custom, + ResolvedCustom custom, const OverEmoji *over, Api::SendOptions options) { + Expects(custom.document != nullptr); + _grabbingChosen = true; const auto guard = gsl::finally([&] { _grabbingChosen = false; }); const auto rect = over ? emojiRect(over->section, over->index) : QRect(); @@ -1655,13 +1692,14 @@ FileChosen EmojiListWidget::lookupChosen( ) : QRect(); return { - .document = custom, + .document = custom.document, .options = options, .messageSendingFrom = { .type = Ui::MessageSendingAnimationFrom::Type::Emoji, .globalStartGeometry = over ? mapToGlobal(emoji) : QRect(), .frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(), }, + .collectible = custom.collectible, }; } @@ -1751,7 +1789,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { const auto id = hasColorButton(button->section) ? 0 : _custom[button->section - _staticCount].id; - const auto usage = ChatHelpers::WindowUsage::PremiumPromo; if (hasColorButton(button->section)) { _pickerSelected = pressed; showPicker(); @@ -1759,7 +1796,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { removeSet(id); } else if (hasAddButton(button->section)) { _localSetsManager->install(id); - } else if (const auto resolved = _show->resolveWindow(usage)) { + } else if (const auto resolved = _show->resolveWindow()) { _jumpedToPremium.fire({}); switch (_mode) { case Mode::Full: @@ -1792,6 +1829,8 @@ void EmojiListWidget::displaySet(uint64 setId) { } else { return; } + } else if (setId == Data::Stickers::CollectibleSetId) { + return; } const auto &sets = session().data().stickers().sets(); auto it = sets.find(setId); @@ -1830,6 +1869,7 @@ void EmojiListWidget::removeSet(uint64 setId) { Assert(i != end(_custom)); const auto removeLocally = !_megagroupSet->canEditEmoji(); removeMegagroupSet(removeLocally); + } else if (setId == Data::Stickers::CollectibleSetId) { } else if (auto box = MakeConfirmRemoveSetBox(&session(), labelSt, setId)) { checkHideWithBox(std::move(box)); } @@ -1933,6 +1973,8 @@ bool EmojiListWidget::hasRemoveButton(int index) const { return true; } return !set.list.empty() && _megagroupSet->canEditEmoji(); + } else if (set.id == Data::Stickers::CollectibleSetId) { + return false; } return set.canRemove && !set.premiumRequired; } @@ -1962,7 +2004,8 @@ bool EmojiListWidget::hasAddButton(int index) const { const auto &set = _custom[index - _staticCount]; return !set.canRemove && !set.premiumRequired - && set.id != Data::Stickers::MegagroupSetId; + && set.id != Data::Stickers::MegagroupSetId + && set.id != Data::Stickers::CollectibleSetId; } QRect EmojiListWidget::addButtonRect(int index) const { @@ -1991,8 +2034,9 @@ bool EmojiListWidget::hasButton(int index) const { } else if (index >= _staticCount && index < _staticCount + _custom.size()) { const auto &custom = _custom[index - _staticCount]; - return (custom.id != Data::Stickers::MegagroupSetId) - || custom.canRemove; + return (custom.id != Data::Stickers::CollectibleSetId) + && ((custom.id != Data::Stickers::MegagroupSetId) + || custom.canRemove); } return false; } @@ -2020,6 +2064,7 @@ QRect EmojiListWidget::buttonRect( auto EmojiListWidget::rightButton(int index) const -> const RightButton & { Expects(index >= _staticCount && index < _staticCount + _custom.size()); + return hasAddButton(index) ? _add : _custom[index - _staticCount].canRemove @@ -2279,11 +2324,12 @@ void EmojiListWidget::refreshCustom() { auto set = std::vector(); set.reserve(list.size()); for (const auto document : list) { - if (_restrictedCustomList.contains(document->id)) { + const auto id = EmojiStatusId{ document->id }; + if (_restrictedCustomList.contains(id.documentId)) { continue; } else if (const auto sticker = document->sticker()) { set.push_back({ - .custom = resolveCustomEmoji(document, lookupId), + .custom = resolveCustomEmoji(id, document, lookupId), .document = document, .emoji = Ui::Emoji::Find(sticker->alt), }); @@ -2305,6 +2351,7 @@ void EmojiListWidget::refreshCustom() { .premiumRequired = premium && premiumMayBeBought, }); }; + refreshEmojiStatusCollectibles(); refreshMegagroupStickers(push, GroupStickersPlace::Visible); for (const auto setId : owner->stickers().emojiSetsOrder()) { push(setId, true); @@ -2337,18 +2384,17 @@ Fn EmojiListWidget::repaintCallback( } not_null EmojiListWidget::resolveCustomEmoji( + EmojiStatusId id, not_null document, uint64 setId) { - Expects(document->sticker() != nullptr); - const auto documentId = document->id; - const auto i = _customEmoji.find(documentId); + const auto i = _customEmoji.find(id); const auto recentOnly = (i != end(_customEmoji)) && i->second.recentOnly; if (i != end(_customEmoji) && !recentOnly) { return i->second.emoji.get(); } auto instance = document->owner().customEmojiManager().create( - document, + Data::EmojiStatusCustomId(id), repaintCallback(documentId, setId), Data::CustomEmojiManager::SizeTag::Large); if (recentOnly) { @@ -2362,7 +2408,7 @@ not_null EmojiListWidget::resolveCustomEmoji( return i->second.emoji.get(); } return _customEmoji.emplace( - documentId, + id, CustomEmojiInstance{ .emoji = std::move(instance) } ).first->second.emoji.get(); } @@ -2380,31 +2426,78 @@ Ui::Text::CustomEmoji *EmojiListWidget::resolveCustomRecent( not_null EmojiListWidget::resolveCustomRecent( DocumentId documentId) { - const auto i = _customRecent.find(documentId); + return resolveCustomRecent(EmojiStatusId{ documentId }); +} + +not_null EmojiListWidget::resolveCustomRecent( + EmojiStatusId id) { + const auto i = id.collectible + ? end(_customRecent) + : _customRecent.find(id.documentId); if (i != end(_customRecent)) { return i->second.get(); } - const auto j = _customEmoji.find(documentId); + const auto j = _customEmoji.find(id); if (j != end(_customEmoji)) { return j->second.emoji.get(); } + const auto documentId = id.collectible + ? id.collectible->documentId + : id.documentId; auto repaint = repaintCallback(documentId, RecentEmojiSectionSetId()); - if (_customRecentFactory) { + if (_customRecentFactory && !id.collectible) { return _customRecent.emplace( - documentId, - _customRecentFactory(documentId, std::move(repaint)) + id.documentId, + _customRecentFactory(id.documentId, std::move(repaint)) ).first->second.get(); } auto custom = session().data().customEmojiManager().create( - documentId, + Data::EmojiStatusCustomId(id), std::move(repaint), Data::CustomEmojiManager::SizeTag::Large); return _customEmoji.emplace( - documentId, + id, CustomEmojiInstance{ .emoji = std::move(custom), .recentOnly = true } ).first->second.emoji.get(); } +void EmojiListWidget::refreshEmojiStatusCollectibles() { + if (_mode != Mode::EmojiStatus || !_features.collectibleStatus) { + return; + } + const auto type = Data::EmojiStatuses::Type::Collectibles; + const auto &list = session().data().emojiStatuses().list(type); + const auto setId = Data::Stickers::CollectibleSetId; + auto set = std::vector(); + set.reserve(list.size()); + for (const auto &status : list) { + const auto documentId = status.collectible + ? status.collectible->documentId + : status.documentId; + const auto document = session().data().document(documentId); + const auto sticker = document->sticker(); + set.push_back({ + .collectible = status.collectible, + .custom = resolveCustomEmoji(status, document, setId), + .document = document, + .emoji = sticker ? Ui::Emoji::Find(sticker->alt) : nullptr, + }); + } + if (set.empty()) { + return; + } + const auto collectibles = session().data().stickers().collectibleSet(); + _custom.push_back({ + .id = setId, + .set = collectibles, + .thumbnailDocument = nullptr, + .title = collectibles->title, + .list = std::move(set), + .canRemove = false, + .premiumRequired = !session().premium(), + }); +} + void EmojiListWidget::refreshMegagroupStickers( Fn push, GroupStickersPlace place) { @@ -2639,8 +2732,11 @@ void EmojiListWidget::setSelected(OverState newSelected) { } else if (_previewShown && _pressed != _selected) { if (const auto over = std::get_if(&_selected)) { if (const auto custom = lookupCustomEmoji(over)) { + const auto document = custom.document; _pressed = _selected; - _show->showMediaPreview(custom->stickerSetOrigin(), custom); + _show->showMediaPreview( + document->stickerSetOrigin(), + document); } } } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index ff474ad23..fd1a1f390 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -83,12 +83,15 @@ enum class EmojiListMode { MessageEffects, }; +[[nodiscard]] std::vector DocumentListToRecent( + const std::vector &documents); + struct EmojiListDescriptor { std::shared_ptr show; EmojiListMode mode = EmojiListMode::Full; Fn customTextColor; Fn paused; - std::vector customRecentList; + std::vector customRecentList; Fn( DocumentId, Fn)> customRecentFactory; @@ -137,7 +140,7 @@ public: [[nodiscard]] rpl::producer<> jumpedToPremium() const; [[nodiscard]] rpl::producer<> escapes() const; - void provideRecent(const std::vector &customRecentList); + void provideRecent(const std::vector &customRecentList); void prepareExpanding(); void paintExpanding( @@ -186,6 +189,7 @@ private: bool collapsed = false; }; struct CustomOne { + std::shared_ptr collectible; not_null custom; not_null document; EmojiPtr emoji = nullptr; @@ -253,6 +257,14 @@ private: int finalHeight = 0; bool expanding = false; }; + struct ResolvedCustom { + DocumentData *document = nullptr; + std::shared_ptr collectible; + + explicit operator bool() const { + return document != nullptr; + } + }; template bool enumerateSections(Callback callback) const; @@ -271,6 +283,7 @@ private: Visible, Hidden, }; + void refreshEmojiStatusCollectibles(); void refreshMegagroupStickers( Fn push, GroupStickersPlace place); @@ -296,16 +309,16 @@ private: int index); [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; - [[nodiscard]] DocumentData *lookupCustomEmoji( + [[nodiscard]] ResolvedCustom lookupCustomEmoji( const OverEmoji *over) const; - [[nodiscard]] DocumentData *lookupCustomEmoji( + [[nodiscard]] ResolvedCustom lookupCustomEmoji( int index, int section) const; [[nodiscard]] EmojiChosen lookupChosen( EmojiPtr emoji, not_null over); [[nodiscard]] FileChosen lookupChosen( - not_null custom, + ResolvedCustom custom, const OverEmoji *over, Api::SendOptions options = Api::SendOptions()); void selectEmoji(EmojiChosen data); @@ -370,14 +383,17 @@ private: void repaintCustom(uint64 setId); void fillRecent(); - void fillRecentFrom(const std::vector &list); + void fillRecentFrom(const std::vector &list); [[nodiscard]] not_null resolveCustomEmoji( + EmojiStatusId id, not_null document, uint64 setId); [[nodiscard]] Ui::Text::CustomEmoji *resolveCustomRecent( Core::RecentEmojiId customId); [[nodiscard]] not_null resolveCustomRecent( DocumentId documentId); + [[nodiscard]] not_null resolveCustomRecent( + EmojiStatusId id); [[nodiscard]] Fn repaintCallback( DocumentId documentId, uint64 setId); @@ -415,7 +431,7 @@ private: QVector _emoji[kEmojiSectionCount]; std::vector _custom; base::flat_set _restrictedCustomList; - base::flat_map _customEmoji; + base::flat_map _customEmoji; base::flat_map< DocumentId, std::unique_ptr> _customRecent; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 07da3ba6e..3dafbd289 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -123,9 +123,9 @@ constexpr auto kLinkProtocols = { void EditLinkBox( not_null box, std::shared_ptr show, - const QString &startText, + const TextWithTags &startText, const QString &startLink, - Fn callback, + Fn callback, const style::InputField *fieldStyle, Fn validate) { Expects(callback != nullptr); @@ -137,6 +137,7 @@ void EditLinkBox( object_ptr( content, fieldSt, + Ui::InputField::Mode::SingleLine, tr::lng_formatting_link_text(), startText), st::markdownLinkFieldPadding); @@ -181,9 +182,9 @@ void EditLinkBox( url->move(placeholder->pos()); const auto submit = [=] { - const auto linkText = text->getLastText(); + const auto linkText = text->getTextWithTags(); const auto linkUrl = validate(url->getLastText()); - if (linkText.isEmpty()) { + if (linkText.text.isEmpty()) { text->showError(); return; } else if (linkUrl.isEmpty()) { @@ -222,7 +223,7 @@ void EditLinkBox( box->setWidth(st::boxWidth); box->setFocusCallback([=] { - if (startText.isEmpty()) { + if (startText.text.isEmpty()) { text->setFocusFast(); } else { if (!url->empty()) { @@ -383,7 +384,7 @@ bool EditTextChanged( Fn DefaultEditLinkCallback( std::shared_ptr show, @@ -392,14 +393,14 @@ FncommitMarkdownLinkEdit(selection, text, link); } @@ -470,7 +471,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { [[nodiscard]] Fn FactcheckEditLinkCallback( std::shared_ptr show, @@ -478,7 +479,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { const auto weak = Ui::MakeWeak(field); return [=]( EditLinkSelection selection, - QString text, + TextWithTags text, QString link, EditLinkAction action) { const auto validate = [=](QString url) { @@ -493,7 +494,7 @@ void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { if (action == EditLinkAction::Check) { return IsGoodFactcheckUrl(link); } - auto callback = [=](const QString &text, const QString &link) { + auto callback = [=](const TextWithTags &text, const QString &link) { if (const auto strong = weak.data()) { strong->commitMarkdownLinkEdit(selection, text, link); } diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 0557a8951..15e5f04b6 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -46,7 +46,7 @@ class Show; Fn DefaultEditLinkCallback( std::shared_ptr show, diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 237865424..6ea61e00f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -1467,6 +1467,8 @@ void StickersListFooter::paintSetIconToCache( return &st().icons.people; } else if (const auto section = SetIdEmojiSection(icon.setId)) { return sectionIcon(*section, selected); + } else if (icon.setId == Data::Stickers::CollectibleSetId) { + return &st().icons.collectibles; } return sectionIcon(Section::Recent, selected); }()); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 87899e856..ad9592541 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -245,8 +245,7 @@ StickersListWidget::StickersListWidget( } _settings->addClickHandler([=] { - if (const auto window = _show->resolveWindow( - WindowUsage::PremiumPromo)) { + if (const auto window = _show->resolveWindow()) { // While media viewer can't show StickersBox. using Section = StickersBox::Section; window->show( diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp index 794803ee9..b67e89ead 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp @@ -294,7 +294,11 @@ void TabbedPanel::otherLeave() { if (_a_opacity.animating()) { hideByTimerOrLeave(); } else { - _hideTimer.callOnce(0); + // In case of animations disabled add some delay before hiding. + // Otherwise if emoji suggestions panel is shown in between + // (z-order wise) the emoji toggle button and tabbed panel, + // we won't be able to move cursor from the button to the panel. + _hideTimer.callOnce(anim::Disabled() ? kHideTimeoutMs : 0); } } diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 4e917681c..bca5ae7eb 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -1018,7 +1018,7 @@ void TabbedSelector::setCurrentPeer(PeerData *peer) { } void TabbedSelector::provideRecentEmoji( - const std::vector &customRecentList) { + const std::vector &customRecentList) { for (const auto &tab : _tabs) { if (tab.type() == SelectorTab::Emoji) { const auto emoji = static_cast(tab.widget()); @@ -1062,8 +1062,7 @@ void TabbedSelector::checkRestrictedPeer() { st::stickersRestrictedLabel); const auto lifting = error.boostsToLift; _restrictedLabel->setClickHandlerFilter([=](auto...) { - const auto window = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo); + const auto window = show->resolveWindow(); window->resolveBoostState(peer->asChannel(), lifting); return false; }); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index f96ca6b6d..ad8821d84 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -62,6 +62,7 @@ struct FileChosen { not_null document; Api::SendOptions options; Ui::MessageSendingAnimationFrom messageSendingFrom; + std::shared_ptr collectible; TextWithTags caption; }; @@ -154,7 +155,7 @@ public: void refreshStickers(); void setCurrentPeer(PeerData *peer); void provideRecentEmoji( - const std::vector &customRecentList); + const std::vector &customRecentList); void hideFinished(); void showStarted(); diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index deb71c0db..f655093ef 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -341,7 +341,7 @@ QByteArray Settings::serialize() const { << _photoEditorBrush << qint32(_groupCallNoiseSuppression ? 1 : 0) << qint32(SerializePlaybackSpeed(_voicePlaybackSpeed)) - << qint32(_closeToTaskbar.current() ? 1 : 0) + << qint32(_closeBehavior) << _customDeviceModel.current() << qint32(_playerRepeatMode.current()) << qint32(_playerOrderMode.current()) @@ -499,7 +499,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { QByteArray proxy; qint32 hiddenGroupCallTooltips = qint32(_hiddenGroupCallTooltips.value()); QByteArray photoEditorBrush = _photoEditorBrush; - qint32 closeToTaskbar = _closeToTaskbar.current() ? 1 : 0; + qint32 closeBehavior = qint32(_closeBehavior); QString customDeviceModel = _customDeviceModel.current(); qint32 playerRepeatMode = static_cast(_playerRepeatMode.current()); qint32 playerOrderMode = static_cast(_playerOrderMode.current()); @@ -695,7 +695,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { stream >> voicePlaybackSpeed; } if (!stream.atEnd()) { - stream >> closeToTaskbar; + stream >> closeBehavior; } if (!stream.atEnd()) { stream >> customDeviceModel; @@ -1005,7 +1005,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) { : Tooltip(0)); }(); _photoEditorBrush = photoEditorBrush; - _closeToTaskbar = (closeToTaskbar == 1); + const auto uncheckedCloseBehavior = static_cast(closeBehavior); + switch (uncheckedCloseBehavior) { + case CloseBehavior::CloseToTaskbar: + case CloseBehavior::RunInBackground: + case CloseBehavior::Quit: _closeBehavior = uncheckedCloseBehavior; break; + } _customDeviceModel = customDeviceModel; _accountsOrder = accountsOrder; const auto uncheckedPlayerRepeatMode = static_cast(playerRepeatMode); diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index e92c679fd..4f4711b23 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -110,6 +110,11 @@ public: TrayOnly = 1, WindowOnly = 2, }; + enum class CloseBehavior { + Quit = 0, + CloseToTaskbar = 1, + RunInBackground = 2, + }; static constexpr auto kDefaultVolume = 0.9; @@ -751,17 +756,11 @@ public: _hiddenGroupCallTooltips |= value; } - void setCloseToTaskbar(bool value) { - _closeToTaskbar = value; + void setCloseBehavior(CloseBehavior value) { + _closeBehavior = value; } - [[nodiscard]] bool closeToTaskbar() const { - return _closeToTaskbar.current(); - } - [[nodiscard]] rpl::producer closeToTaskbarValue() const { - return _closeToTaskbar.value(); - } - [[nodiscard]] rpl::producer closeToTaskbarChanges() const { - return _closeToTaskbar.changes(); + [[nodiscard]] CloseBehavior closeBehavior() const { + return _closeBehavior; } void setTrayIconMonochrome(bool value) { _trayIconMonochrome = value; @@ -1048,7 +1047,7 @@ private: bool _disableOpenGL = false; rpl::variable _workMode = WorkMode::WindowAndTray; base::flags _hiddenGroupCallTooltips; - rpl::variable _closeToTaskbar = false; + CloseBehavior _closeBehavior = CloseBehavior::Quit; rpl::variable _trayIconMonochrome = true; rpl::variable _customDeviceModel; rpl::variable _playerRepeatMode; diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 8e0e7a487..567caa00a 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1348,6 +1348,21 @@ bool ResolveChatLink( return true; } +bool ResolveUniqueGift( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto slug = match->captured(1); + if (slug.isEmpty()) { + return false; + } + ResolveAndShowUniqueGift(controller->uiShow(), slug); + return true; +} + } // namespace const std::vector &LocalUrlHandlers() { @@ -1440,6 +1455,10 @@ const std::vector &LocalUrlHandlers() { u"^stars_topup/?\\?(.+)(#|$)"_q, ResolveTopUp }, + { + u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q, + ResolveUniqueGift + }, { u"^user\\?(.+)(#|$)"_q, AyuUrlHandlers::ResolveUser @@ -1597,6 +1616,9 @@ QString TryConvertUrlToLocal(QString url) { } else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) { const auto slug = chatlinkMatch->captured(1); return u"tg://message?slug="_q + slug; + } else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) { + const auto slug = nftMatch->captured(1); + return u"tg://nft?slug="_q + slug; } else if (const auto privateMatch = regex_match(u"^" "c/(\\-?\\d+)" "(" @@ -1690,4 +1712,51 @@ bool StartUrlRequiresActivate(const QString &url) { : !InternalPassportLink(url); } +void ResolveAndShowUniqueGift( + std::shared_ptr show, + const QString &slug, + ::Settings::CreditsEntryBoxStyleOverrides st) { + struct Request { + base::weak_ptr 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 show, + const QString &slug) { + ResolveAndShowUniqueGift(std::move(show), slug, {}); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/local_url_handlers.h b/Telegram/SourceFiles/core/local_url_handlers.h index 9a5cc6a55..4f9b68003 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.h +++ b/Telegram/SourceFiles/core/local_url_handlers.h @@ -11,6 +11,14 @@ namespace qthelp { class RegularExpressionMatch; } // namespace qthelp +namespace ChatHelpers { +class Show; +} // namespace ChatHelpers + +namespace Settings { +struct CreditsEntryBoxStyleOverrides; +} // namespace Settings + namespace Window { class SessionController; } // namespace Window @@ -34,4 +42,12 @@ struct LocalUrlHandler { [[nodiscard]] bool StartUrlRequiresActivate(const QString &url); +void ResolveAndShowUniqueGift( + std::shared_ptr show, + const QString &slug, + ::Settings::CreditsEntryBoxStyleOverrides st); +void ResolveAndShowUniqueGift( + std::shared_ptr show, + const QString &slug); + } // namespace Core diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index a6e3afd6a..93c6db96a 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -100,6 +100,8 @@ const auto CommandByName = base::flat_map{ { u"read_chat"_q , Command::ReadChat }, + { u"show_chat_menu"_q , Command::ShowChatMenu }, + // Shortcuts that have no default values. { u"message"_q , Command::JustSendMessage }, { u"message_silently"_q , Command::SendSilentMessage }, @@ -147,6 +149,8 @@ const auto CommandNames = base::flat_map{ { Command::ShowContacts , u"show_contacts"_q }, { Command::ReadChat , u"read_chat"_q }, + + { Command::ShowChatMenu , u"show_chat_menu"_q }, }; [[maybe_unused]] constexpr auto kNoValue = { @@ -435,6 +439,8 @@ void Manager::fillDefaults() { set(u"ctrl+j"_q, Command::ShowContacts); set(u"ctrl+r"_q, Command::ReadChat); + + set(u"ctrl+="_q, Command::ShowChatMenu); } void Manager::writeDefaultFile() { diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h index 562c54e44..5355d1681 100644 --- a/Telegram/SourceFiles/core/shortcuts.h +++ b/Telegram/SourceFiles/core/shortcuts.h @@ -71,6 +71,8 @@ enum class Command { MediaViewerFullscreen, + ShowChatMenu, + SupportReloadTemplates, SupportToggleMuted, SupportScrollToCurrent, diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 104a71f31..ba95d96d5 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppFile = "AyuGram"_cs; -constexpr auto AppVersion = 5010003; -constexpr auto AppVersionStr = "5.10.3"; +constexpr auto AppVersion = 5010007; +constexpr auto AppVersionStr = "5.10.7"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 436272664..22041523d 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -859,6 +859,17 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) { session().changes().peerUpdated(this, UpdateFlag::Slowmode); } +int ChannelData::peerGiftsCount() const { + return _peerGiftsCount; +} + +void ChannelData::setPeerGiftsCount(int count) { + if (_peerGiftsCount != count) { + _peerGiftsCount = count; + session().changes().peerUpdated(this, UpdateFlag::PeerGifts); + } +} + int ChannelData::boostsApplied() const { if (const auto info = mgInfo.get()) { return info->boostsApplied; @@ -1138,7 +1149,8 @@ void ApplyChannelUpdate( | Flag::ViewAsMessages | Flag::CanViewRevenue | Flag::PaidMediaAllowed - | Flag::CanViewCreditsRevenue; + | Flag::CanViewCreditsRevenue + | Flag::StargiftsAvailable; channel->setFlags((channel->flags() & ~mask) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_view_participants() @@ -1159,6 +1171,9 @@ void ApplyChannelUpdate( | (update.is_can_view_revenue() ? Flag::CanViewRevenue : Flag()) | (update.is_can_view_stars_revenue() ? Flag::CanViewCreditsRevenue + : Flag()) + | (update.is_stargifts_available() + ? Flag::StargiftsAvailable : Flag())); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { @@ -1172,6 +1187,7 @@ void ApplyChannelUpdate( channel->setRestrictedCount(update.vbanned_count().value_or_empty()); channel->setKickedCount(update.vkicked_count().value_or_empty()); channel->setSlowmodeSeconds(update.vslowmode_seconds().value_or_empty()); + channel->setPeerGiftsCount(update.vstargifts_count().value_or_empty()); if (const auto next = update.vslowmode_next_send_date()) { channel->growSlowmodeLastMessage( next->v - channel->slowmodeSeconds()); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 4c40c61db..620cca9d1 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -69,6 +69,7 @@ enum class ChannelDataFlag : uint64 { PaidMediaAllowed = (1ULL << 33), CanViewCreditsRevenue = (1ULL << 34), SignatureProfiles = (1ULL << 35), + StargiftsAvailable = (1ULL << 36), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -253,6 +254,9 @@ public: [[nodiscard]] bool viewForumAsMessages() const { return flags() & Flag::ViewAsMessages; } + [[nodiscard]] bool stargiftsAvailable() const { + return flags() & Flag::StargiftsAvailable; + } [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( not_null participant); @@ -452,6 +456,9 @@ public: [[nodiscard]] TimeId slowmodeLastMessage() const; void growSlowmodeLastMessage(TimeId when); + [[nodiscard]] int peerGiftsCount() const; + void setPeerGiftsCount(int count); + [[nodiscard]] int boostsApplied() const; [[nodiscard]] int boostsUnrestrict() const; [[nodiscard]] bool unrestrictedByBoosts() const; @@ -522,6 +529,7 @@ private: std::vector &&reasons) override; Flags _flags = ChannelDataFlags(Flag::Forbidden); + int _peerGiftsCount = 0; PtsWaiter _ptsWaiter; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index c184bff5c..537663a36 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -416,8 +416,7 @@ void ShowSendErrorToast( return; } const auto boost = [=] { - const auto window = show->resolveWindow( - ChatHelpers::WindowUsage::PremiumPromo); + const auto window = show->resolveWindow(); window->resolveBoostState(peer->asChannel(), error.boostsToLift); }; show->showToast({ diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index bf25cde0e..adf692218 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -66,6 +66,8 @@ struct CreditsHistoryEntry final { uint64 bareGiftStickerId = 0; uint64 bareGiftOwnerId = 0; uint64 bareActorId = 0; + uint64 bareGiftListPeerId = 0; + uint64 giftSavedId = 0; uint64 stargiftId = 0; std::shared_ptr uniqueGift; StarsAmount starrefAmount; @@ -89,6 +91,7 @@ struct CreditsHistoryEntry final { bool giftUpgraded : 1 = false; bool savedToProfile : 1 = false; bool fromGiftsList : 1 = false; + bool fromGiftSlug : 1 = false; bool soldOutInfo : 1 = false; bool canUpgradeGift : 1 = false; bool hasGiftComment : 1 = false; diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index e00dd8910..73c159830 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -11,7 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_channel.h" #include "data/data_user.h" #include "data/data_session.h" +#include "data/data_star_gift.h" #include "data/data_document.h" +#include "data/data_wall_paper.h" #include "data/stickers/data_stickers.h" #include "base/unixtime.h" #include "base/timer_rpl.h" @@ -26,20 +28,19 @@ constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000); constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); constexpr auto kMaxTimeout = 6 * 60 * 60 * crl::time(1000); -[[nodiscard]] std::vector ListFromMTP( - const MTPDaccount_emojiStatuses &data) { - const auto &list = data.vstatuses().v; - auto result = std::vector(); - result.reserve(list.size()); - for (const auto &status : list) { - const auto parsed = ParseEmojiStatus(status); - if (!parsed.id) { - LOG(("API Error: emojiStatusEmpty in account.emojiStatuses.")); - } else { - result.push_back(parsed.id); - } - } - return result; +[[nodiscard]] EmojiStatusCollectible ParseEmojiStatusCollectible( + const MTPDemojiStatusCollectible &data) { + return EmojiStatusCollectible{ + .id = data.vcollectible_id().v, + .documentId = data.vdocument_id().v, + .title = qs(data.vtitle()), + .slug = qs(data.vslug()), + .patternDocumentId = data.vpattern_document_id().v, + .centerColor = Ui::ColorFromSerialized(data.vcenter_color()), + .edgeColor = Ui::ColorFromSerialized(data.vedge_color()), + .patternColor = Ui::ColorFromSerialized(data.vpattern_color()), + .textColor = Ui::ColorFromSerialized(data.vtext_color()), + }; } } // namespace @@ -84,6 +85,10 @@ void EmojiStatuses::refreshChannelColored() { requestChannelColored(); } +void EmojiStatuses::refreshCollectibles() { + requestCollectibles(); +} + void EmojiStatuses::refreshRecentDelayed() { if (_recentRequestId || _recentRequestScheduled) { return; @@ -96,17 +101,42 @@ void EmojiStatuses::refreshRecentDelayed() { }); } -const std::vector &EmojiStatuses::list(Type type) const { +const std::vector &EmojiStatuses::list(Type type) const { switch (type) { case Type::Recent: return _recent; case Type::Default: return _default; case Type::Colored: return _colored; case Type::ChannelDefault: return _channelDefault; case Type::ChannelColored: return _channelColored; + case Type::Collectibles: return _collectibles; } Unexpected("Type in EmojiStatuses::list."); } +EmojiStatusData EmojiStatuses::parse(const MTPEmojiStatus &status) { + return status.match([](const MTPDemojiStatus &data) { + return EmojiStatusData{ + .id = { .documentId = data.vdocument_id().v }, + .until = data.vuntil().value_or_empty(), + }; + }, [&](const MTPDemojiStatusCollectible &data) { + const auto collectibleId = data.vcollectible_id().v; + auto &collectible = _collectibleData[collectibleId]; + if (!collectible) { + collectible = std::make_shared( + ParseEmojiStatusCollectible(data)); + } + return EmojiStatusData{ + .id = { .collectible = collectible }, + .until = data.vuntil().value_or_empty(), + }; + }, [](const MTPDinputEmojiStatusCollectible &) { + return EmojiStatusData(); + }, [](const MTPDemojiStatusEmpty &) { + return EmojiStatusData(); + }); +} + rpl::producer<> EmojiStatuses::recentUpdates() const { return _recentUpdated.events(); } @@ -119,6 +149,10 @@ rpl::producer<> EmojiStatuses::channelDefaultUpdates() const { return _channelDefaultUpdated.events(); } +rpl::producer<> EmojiStatuses::collectiblesUpdates() const { + return _collectiblesUpdated.events(); +} + void EmojiStatuses::registerAutomaticClear( not_null peer, TimeId until) { @@ -253,7 +287,7 @@ void EmojiStatuses::processClearing() { } ++i; } else { - i->first->setEmojiStatus(0, 0); + i->first->setEmojiStatus(EmojiStatusId()); i = clearing.erase(i); } } @@ -271,6 +305,22 @@ void EmojiStatuses::processClearing() { } } +std::vector EmojiStatuses::parse( + const MTPDaccount_emojiStatuses &data) { + const auto &list = data.vstatuses().v; + auto result = std::vector(); + result.reserve(list.size()); + for (const auto &status : list) { + const auto parsed = parse(status); + if (!parsed.id) { + LOG(("API Error: empty status in account.emojiStatuses.")); + } else { + result.push_back(parsed.id); + } + } + return result; +} + void EmojiStatuses::processClearingIn(TimeId wait) { const auto waitms = wait * crl::time(1000); _clearingTimer.callOnce(std::min(waitms, kMaxTimeout)); @@ -327,6 +377,7 @@ void EmojiStatuses::requestColored() { _coloredRequestId = 0; result.match([&](const MTPDmessages_stickerSet &data) { updateColored(data); + refreshCollectibles(); }, [](const MTPDmessages_stickerSetNotModified &) { LOG(("API Error: Unexpected messages.stickerSetNotModified.")); }); @@ -374,15 +425,34 @@ void EmojiStatuses::requestChannelColored() { }).send(); } +void EmojiStatuses::requestCollectibles() { + if (_collectiblesRequestId) { + return; + } + auto &api = _owner->session().api(); + _collectiblesRequestId = api.request( + MTPaccount_GetCollectibleEmojiStatuses(MTP_long(_collectiblesHash)) + ).done([=](const MTPaccount_EmojiStatuses &result) { + _collectiblesRequestId = 0; + result.match([&](const MTPDaccount_emojiStatuses &data) { + updateCollectibles(data); + }, [&](const MTPDaccount_emojiStatusesNotModified &) { + }); + }).fail([=] { + _collectiblesRequestId = 0; + _collectiblesHash = 0; + }).send(); +} + void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) { _recentHash = data.vhash().v; - _recent = ListFromMTP(data); + _recent = parse(data); _recentUpdated.fire({}); } void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) { _defaultHash = data.vhash().v; - _default = ListFromMTP(data); + _default = parse(data); _defaultUpdated.fire({}); } @@ -391,7 +461,9 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) { _colored.clear(); _colored.reserve(list.size()); for (const auto &sticker : data.vdocuments().v) { - _colored.push_back(_owner->processDocument(sticker)->id); + _colored.push_back({ + .documentId = _owner->processDocument(sticker)->id, + }); } _coloredUpdated.fire({}); } @@ -399,7 +471,7 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) { void EmojiStatuses::updateChannelDefault( const MTPDaccount_emojiStatuses &data) { _channelDefaultHash = data.vhash().v; - _channelDefault = ListFromMTP(data); + _channelDefault = parse(data); _channelDefaultUpdated.fire({}); } @@ -409,18 +481,27 @@ void EmojiStatuses::updateChannelColored( _channelColored.clear(); _channelColored.reserve(list.size()); for (const auto &sticker : data.vdocuments().v) { - _channelColored.push_back(_owner->processDocument(sticker)->id); + _channelColored.push_back({ + .documentId = _owner->processDocument(sticker)->id, + }); } _channelColoredUpdated.fire({}); } -void EmojiStatuses::set(DocumentId id, TimeId until) { +void EmojiStatuses::updateCollectibles( + const MTPDaccount_emojiStatuses &data) { + _collectiblesHash = data.vhash().v; + _collectibles = parse(data); + _collectiblesUpdated.fire({}); +} + +void EmojiStatuses::set(EmojiStatusId id, TimeId until) { set(_owner->session().user(), id, until); } void EmojiStatuses::set( not_null peer, - DocumentId id, + EmojiStatusId id, TimeId until) { auto &api = _owner->session().api(); auto &requestId = _sentRequests[peer]; @@ -437,11 +518,19 @@ void EmojiStatuses::set( _sentRequests.remove(peer); }).send(); }; + using EFlag = MTPDemojiStatus::Flag; + using CFlag = MTPDinputEmojiStatusCollectible::Flag; const auto status = !id ? MTP_emojiStatusEmpty() - : !until - ? MTP_emojiStatus(MTP_long(id)) - : MTP_emojiStatusUntil(MTP_long(id), MTP_int(until)); + : id.collectible + ? MTP_inputEmojiStatusCollectible( + MTP_flags(until ? CFlag::f_until : CFlag()), + MTP_long(id.collectible->id), + MTP_int(until)) + : MTP_emojiStatus( + MTP_flags(until ? EFlag::f_until : EFlag()), + MTP_long(id.documentId), + MTP_int(until)); if (peer->isSelf()) { send(MTPaccount_UpdateEmojiStatus(status)); } else if (const auto channel = peer->asChannel()) { @@ -449,14 +538,30 @@ void EmojiStatuses::set( } } -EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) { - return status.match([](const MTPDemojiStatus &data) { - return EmojiStatusData{ data.vdocument_id().v }; - }, [](const MTPDemojiStatusUntil &data) { - return EmojiStatusData{ data.vdocument_id().v, data.vuntil().v }; - }, [](const MTPDemojiStatusEmpty &) { - return EmojiStatusData(); - }); +EmojiStatusId EmojiStatuses::fromUniqueGift( + const Data::UniqueGift &gift) { + const auto collectibleId = gift.id; + auto &collectible = _collectibleData[collectibleId]; + if (!collectible) { + collectible = std::make_shared( + EmojiStatusCollectible{ + .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 diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index 0a0b75ba2..f7809b586 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -21,6 +21,27 @@ namespace Data { class DocumentMedia; class Session; +struct UniqueGift; + +struct EmojiStatusCollectible { + CollectibleId id = 0; + DocumentId documentId = 0; + QString title; + QString slug; + DocumentId patternDocumentId = 0; + QColor centerColor; + QColor edgeColor; + QColor patternColor; + QColor textColor; + + explicit operator bool() const { + return id != 0; + } +}; +struct EmojiStatusData { + EmojiStatusId id; + TimeId until = 0; +}; class EmojiStatuses final { public: @@ -38,6 +59,7 @@ public: void refreshColored(); void refreshChannelDefault(); void refreshChannelColored(); + void refreshCollectibles(); enum class Type { Recent, @@ -45,15 +67,21 @@ public: Colored, ChannelDefault, ChannelColored, + Collectibles, }; - [[nodiscard]] const std::vector &list(Type type) const; + [[nodiscard]] const std::vector &list(Type type) const; + + [[nodiscard]] EmojiStatusData parse(const MTPEmojiStatus &status); [[nodiscard]] rpl::producer<> recentUpdates() const; [[nodiscard]] rpl::producer<> defaultUpdates() const; [[nodiscard]] rpl::producer<> channelDefaultUpdates() const; + [[nodiscard]] rpl::producer<> collectiblesUpdates() const; - void set(DocumentId id, TimeId until = 0); - void set(not_null peer, DocumentId id, TimeId until = 0); + void set(EmojiStatusId id, TimeId until = 0); + void set(not_null peer, EmojiStatusId id, TimeId until = 0); + [[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift); + [[nodiscard]] EmojiStatusCollectible *collectibleInfo(CollectibleId id); void registerAutomaticClear(not_null peer, TimeId until); @@ -79,31 +107,42 @@ private: void requestColored(); void requestChannelDefault(); void requestChannelColored(); + void requestCollectibles(); void updateRecent(const MTPDaccount_emojiStatuses &data); void updateDefault(const MTPDaccount_emojiStatuses &data); void updateColored(const MTPDmessages_stickerSet &data); void updateChannelDefault(const MTPDaccount_emojiStatuses &data); void updateChannelColored(const MTPDmessages_stickerSet &data); + void updateCollectibles(const MTPDaccount_emojiStatuses &data); void processClearingIn(TimeId wait); void processClearing(); + [[nodiscard]] std::vector parse( + const MTPDaccount_emojiStatuses &data); + template void requestGroups(not_null type, Request &&request); const not_null _owner; - std::vector _recent; - std::vector _default; - std::vector _colored; - std::vector _channelDefault; - std::vector _channelColored; + std::vector _recent; + std::vector _default; + std::vector _colored; + std::vector _channelDefault; + std::vector _channelColored; + std::vector _collectibles; rpl::event_stream<> _recentUpdated; rpl::event_stream<> _defaultUpdated; rpl::event_stream<> _coloredUpdated; rpl::event_stream<> _channelDefaultUpdated; rpl::event_stream<> _channelColoredUpdated; + rpl::event_stream<> _collectiblesUpdated; + + base::flat_map< + CollectibleId, + std::shared_ptr> _collectibleData; mtpRequestId _recentRequestId = 0; bool _recentRequestScheduled = false; @@ -119,6 +158,9 @@ private: mtpRequestId _channelColoredRequestId = 0; + mtpRequestId _collectiblesRequestId = 0; + uint64 _collectiblesHash = 0; + base::flat_map, mtpRequestId> _sentRequests; base::flat_map, TimeId> _clearing; @@ -133,10 +175,4 @@ private: }; -struct EmojiStatusData { - DocumentId id = 0; - TimeId until = 0; -}; -[[nodiscard]] EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status); - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_file_origin.cpp b/Telegram/SourceFiles/data/data_file_origin.cpp index 48f2be854..06c39cb91 100644 --- a/Telegram/SourceFiles/data/data_file_origin.cpp +++ b/Telegram/SourceFiles/data/data_file_origin.cpp @@ -59,6 +59,24 @@ struct FileReferenceAccumulator { push(data.vdocuments()); }, [&](const MTPDwebPageAttributeStickerSet &data) { push(data.vstickers()); + }, [&](const MTPDwebPageAttributeUniqueStarGift &data) { + push(data.vgift()); + }); + } + void push(const MTPStarGift &data) { + data.match([&](const MTPDstarGift &data) { + push(data.vsticker()); + }, [&](const MTPDstarGiftUnique &data) { + push(data.vattributes()); + }); + } + void push(const MTPStarGiftAttribute &data) { + data.match([&](const MTPDstarGiftAttributeModel &data) { + push(data.vdocument()); + }, [&](const MTPDstarGiftAttributePattern &data) { + push(data.vdocument()); + }, [&](const MTPDstarGiftAttributeBackdrop &data) { + }, [&](const MTPDstarGiftAttributeOriginalDetails &data) { }); } void push(const MTPWebPage &data) { diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index d8ebb396c..7e7998897 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -897,12 +897,10 @@ Dialogs::UnreadState ForumTopic::unreadStateFor( const auto muted = this->muted(); result.messages = count; result.chats = count ? 1 : 0; - result.chatsTopic = count ? 1 : 0; result.mentions = unreadMentions().has() ? 1 : 0; result.reactions = unreadReactions().has() ? 1 : 0; result.messagesMuted = muted ? result.messages : 0; result.chatsMuted = muted ? result.chats : 0; - result.chatsTopicMuted = muted ? result.chats : 0; result.reactionsMuted = muted ? result.reactions : 0; result.known = known; return result; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 8ba7609bf..b3d57000e 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2375,13 +2375,13 @@ std::unique_ptr MediaGiftBox::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - if (const auto raw = _data.unique.get()) { + if (const auto &unique = _data.unique) { return std::make_unique( message, - HistoryView::GenerateUniqueGiftMedia(message, replacing, raw), + HistoryView::GenerateUniqueGiftMedia(message, replacing, unique), HistoryView::MediaGenericDescriptor{ .maxWidth = st::msgServiceGiftBoxSize.width(), - .paintBg = HistoryView::UniqueGiftBg(message, raw), + .paintBg = HistoryView::UniqueGiftBg(message, unique), .service = true, }); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 83bdcd1e2..739cc7839 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -141,6 +141,8 @@ struct GiftCode { std::shared_ptr unique; TextWithEntities message; ChannelData *channel = nullptr; + PeerData *channelFrom = nullptr; + uint64 channelSavedId = 0; MsgId giveawayMsgId = 0; MsgId upgradeMsgId = 0; int starsConverted = 0; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 901d7b7c8..b26234ee6 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -652,6 +652,20 @@ bool PeerData::canManageTopics() const { return false; } +bool PeerData::canManageGifts() const { + if (const auto channel = asChannel()) { + return channel->canPostMessages(); + } + return isSelf(); +} + +bool PeerData::canTransferGifts() const { + if (const auto channel = asChannel()) { + return channel->amCreator(); + } + return isSelf(); +} + bool PeerData::canEditMessagesIndefinitely() const { if (const auto user = asUser()) { return user->isSelf(); @@ -911,6 +925,16 @@ void PeerData::fullUpdated() { setLoadedStatus(LoadedStatus::Full); } +UserData *PeerData::asBot() { + return isBot() ? static_cast(this) : nullptr; +} + +const UserData *PeerData::asBot() const { + return isBot() + ? static_cast(this) + : nullptr; +} + UserData *PeerData::asUser() { return isUser() ? static_cast(this) : nullptr; } @@ -1115,11 +1139,11 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) { } void PeerData::setEmojiStatus(const MTPEmojiStatus &status) { - const auto parsed = Data::ParseEmojiStatus(status); + const auto parsed = owner().emojiStatuses().parse(status); setEmojiStatus(parsed.id, parsed.until); } -void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) { +void PeerData::setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until) { if (_emojiStatusId != emojiStatusId) { _emojiStatusId = emojiStatusId; session().changes().peerUpdated(this, UpdateFlag::EmojiStatus); @@ -1127,10 +1151,17 @@ void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) { owner().emojiStatuses().registerAutomaticClear(this, until); } -DocumentId PeerData::emojiStatusId() const { +EmojiStatusId PeerData::emojiStatusId() const { return _emojiStatusId; } +bool PeerData::isBot() const { + if (const auto user = asUser()) { + return user->isBot(); + } + return false; +} + bool PeerData::isSelf() const { if (const auto user = asUser()) { return (user->flags() & UserDataFlag::Self); @@ -1515,6 +1546,15 @@ void PeerData::setStoriesState(StoriesState state) { } } +int PeerData::peerGiftsCount() const { + if (const auto user = asUser()) { + return user->peerGiftsCount(); + } else if (const auto channel = asChannel()) { + return channel->peerGiftsCount(); + } + return 0; +} + void PeerData::setIsBlocked(bool is) { const auto status = is ? BlockStatus::Blocked diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 8c3bf4b2a..89d5e7d18 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -206,8 +206,8 @@ public: bool changeBackgroundEmojiId(DocumentId id); void setEmojiStatus(const MTPEmojiStatus &status); - void setEmojiStatus(DocumentId emojiStatusId, TimeId until = 0); - [[nodiscard]] DocumentId emojiStatusId() const; + void setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until = 0); + [[nodiscard]] EmojiStatusId emojiStatusId() const; [[nodiscard]] bool isUser() const { return peerIsUser(id); @@ -218,6 +218,7 @@ public: [[nodiscard]] bool isChannel() const { return peerIsChannel(id); } + [[nodiscard]] bool isBot() const; [[nodiscard]] bool isSelf() const; [[nodiscard]] bool isVerified() const; [[nodiscard]] bool isPremium() const; @@ -267,6 +268,8 @@ public: [[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] bool canManageGroupCall() const; + [[nodiscard]] UserData *asBot(); + [[nodiscard]] const UserData *asBot() const; [[nodiscard]] UserData *asUser(); [[nodiscard]] const UserData *asUser() const; [[nodiscard]] ChatData *asChat(); @@ -381,6 +384,8 @@ public: [[nodiscard]] bool canCreatePolls() const; [[nodiscard]] bool canCreateTopics() const; [[nodiscard]] bool canManageTopics() const; + [[nodiscard]] bool canManageGifts() const; + [[nodiscard]] bool canTransferGifts() const; [[nodiscard]] bool canExportChatHistory() const; // Returns true if about text was changed. @@ -483,6 +488,8 @@ public: [[nodiscard]] bool hasUnreadStories() const; void setStoriesState(StoriesState state); + [[nodiscard]] int peerGiftsCount() const; + const PeerId id; MTPinputPeer input = MTP_inputPeerEmpty(); @@ -523,7 +530,7 @@ private: base::flat_set _nameWords; // for filtering base::flat_set _nameFirstLetters; - DocumentId _emojiStatusId = 0; + EmojiStatusId _emojiStatusId; DocumentId _backgroundEmojiId = 0; crl::time _lastFullUpdate = 0; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index c63a4556c..013fe104a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "mainwidget.h" #include "api/api_bot.h" +#include "api/api_premium.h" #include "api/api_text_entities.h" #include "api/api_user_names.h" #include "chat_helpers/stickers_lottie.h" @@ -717,7 +718,7 @@ not_null Session::processUser(const MTPUser &data) { if (const auto &status = data.vemoji_status()) { result->setEmojiStatus(*status); } else { - result->setEmojiStatus(0); + result->setEmojiStatus(EmojiStatusId()); } if (!minimal) { if (const auto botInfoVersion = data.vbot_info_version()) { @@ -908,7 +909,7 @@ not_null Session::processChat(const MTPChat &data) { if (const auto &status = data.vemoji_status()) { channel->setEmojiStatus(*status); } else { - channel->setEmojiStatus(0); + channel->setEmojiStatus(EmojiStatusId()); } if (minimal) { if (channel->input.type() == mtpc_inputPeerEmpty @@ -3539,6 +3540,7 @@ not_null Session::processWebpage( WebPageCollage(), nullptr, nullptr, + nullptr, 0, QString(), false, @@ -3565,6 +3567,7 @@ not_null Session::webpage( WebPageCollage(), nullptr, nullptr, + nullptr, 0, QString(), false, @@ -3584,6 +3587,7 @@ not_null Session::webpage( WebPageCollage &&collage, std::unique_ptr iv, std::unique_ptr stickerSet, + std::shared_ptr uniqueGift, int duration, const QString &author, bool hasLargeMedia, @@ -3603,6 +3607,7 @@ not_null Session::webpage( std::move(collage), std::move(iv), std::move(stickerSet), + std::move(uniqueGift), duration, author, hasLargeMedia, @@ -3637,6 +3642,7 @@ void Session::webpageApplyFields( } return nullptr; }; + const auto lookupThemeDocument = [&]() -> DocumentData* { if (const auto attributes = data.vattributes()) { for (const auto &attribute : attributes->v) { @@ -3647,6 +3653,8 @@ void Session::webpageApplyFields( return (DocumentData*)nullptr; }, [](const MTPDwebPageAttributeStickerSet &) { return (DocumentData*)nullptr; + }, [](const MTPDwebPageAttributeUniqueStarGift &) { + return (DocumentData*)nullptr; }); if (result) { return result; @@ -3655,6 +3663,7 @@ void Session::webpageApplyFields( } return nullptr; }; + using WebPageStickerSetPtr = std::unique_ptr; const auto lookupStickerSet = [&]() -> WebPageStickerSetPtr { if (const auto attributes = data.vattributes()) { @@ -3678,6 +3687,21 @@ void Session::webpageApplyFields( } return nullptr; }; + + using UniqueGiftPtr = std::shared_ptr; + const auto lookupUniqueGift = [&]() -> UniqueGiftPtr { + if (const auto attributes = data.vattributes()) { + for (const auto &attribute : attributes->v) { + return attribute.match([&]( + const MTPDwebPageAttributeUniqueStarGift &data) { + const auto gift = Api::FromTL(_session, data.vgift()); + return gift ? gift->unique : nullptr; + }, [](const auto &) -> UniqueGiftPtr { return nullptr; }); + } + } + return nullptr; + }; + auto story = (Data::Story*)nullptr; auto storyId = FullStoryId(); if (const auto attributes = data.vattributes()) { @@ -3770,6 +3794,7 @@ void Session::webpageApplyFields( WebPageCollage(this, data), std::move(iv), lookupStickerSet(), + lookupUniqueGift(), data.vduration().value_or_empty(), qs(data.vauthor().value_or_empty()), data.is_has_large_media(), @@ -3790,6 +3815,7 @@ void Session::webpageApplyFields( WebPageCollage &&collage, std::unique_ptr iv, std::unique_ptr stickerSet, + std::shared_ptr uniqueGift, int duration, const QString &author, bool hasLargeMedia, @@ -3808,6 +3834,7 @@ void Session::webpageApplyFields( std::move(collage), std::move(iv), std::move(stickerSet), + std::move(uniqueGift), duration, author, hasLargeMedia, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index eba5d0e8f..bf1520a59 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_main_list.h" #include "data/data_groups.h" #include "data/data_cloud_file.h" +#include "data/data_star_gift.h" #include "history/history_location_manager.h" #include "base/timer.h" @@ -71,6 +72,7 @@ class BusinessInfo; struct ReactionId; struct UnavailableReason; struct CreditsStatusSlice; +struct UniqueGift; struct RepliesReadTillUpdate { FullMsgId id; @@ -83,10 +85,11 @@ struct GiftUpdate { Save, Unsave, Convert, + Transfer, Delete, }; - FullMsgId itemId; + Data::SavedStarGiftId id; Action action = {}; }; @@ -336,7 +339,7 @@ public: void notifyPinnedDialogsOrderUpdated(); [[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const; - using CreditsSubsRebuilder = rpl::event_stream; + using CreditsSubsRebuilder = rpl::event_stream; using CreditsSubsRebuilderPtr = std::shared_ptr; [[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder(); [[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const; @@ -422,7 +425,7 @@ public: [[nodiscard]] const std::vector &pinnedChatsOrder( FilterId filterId) const; [[nodiscard]] const std::vector &pinnedChatsOrder( - not_null saved) const; + not_null saved) const; void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned); void setPinnedFromEntryList(Dialogs::Key key, bool pinned); void clearPinnedChats(Folder *folder); @@ -430,7 +433,7 @@ public: Folder *folder, const QVector &list); void applyPinnedTopics( - not_null forum, + not_null forum, const QVector &list); void reorderTwoPinnedChats( FilterId filterId, @@ -624,6 +627,7 @@ public: WebPageCollage &&collage, std::unique_ptr iv, std::unique_ptr stickerSet, + std::shared_ptr uniqueGift, int duration, const QString &author, bool hasLargeMedia, @@ -908,6 +912,7 @@ private: WebPageCollage &&collage, std::unique_ptr iv, std::unique_ptr stickerSet, + std::shared_ptr uniqueGift, int duration, const QString &author, bool hasLargeMedia, diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index 3f7578de1..e88cae6d1 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -37,7 +37,11 @@ struct UniqueGiftOriginalDetails { }; struct UniqueGift { + CollectibleId id = 0; + QString slug; QString title; + QString ownerAddress; + QString ownerName; PeerId ownerId = 0; int number = 0; int starsForTransfer = -1; @@ -71,13 +75,60 @@ struct StarGift { const StarGift &) = default; }; -struct UserStarGift { +class SavedStarGiftId { +public: + [[nodiscard]] static SavedStarGiftId User(MsgId messageId) { + auto result = SavedStarGiftId(); + result.entityId = uint64(messageId.bare); + return result; + } + [[nodiscard]] static SavedStarGiftId Chat( + not_null peer, + uint64 savedId) { + auto result = SavedStarGiftId(); + result.peer = peer; + result.entityId = savedId; + return result; + } + + [[nodiscard]] bool isUser() const { + return !peer; + } + [[nodiscard]] bool isChat() const { + return peer != nullptr; + } + + [[nodiscard]] MsgId userMessageId() const { + return peer ? MsgId(0) : MsgId(entityId); + } + [[nodiscard]] PeerData *chat() const { + return peer; + } + [[nodiscard]] uint64 chatSavedId() const { + return peer ? entityId : 0; + } + + explicit operator bool() const { + return entityId != 0; + } + + friend inline bool operator==( + const SavedStarGiftId &a, + const SavedStarGiftId &b) = default; + +private: + PeerData *peer = nullptr; + uint64 entityId = 0; + +}; + +struct SavedStarGift { StarGift info; + SavedStarGiftId manageId; TextWithEntities message; int64 starsConverted = 0; int64 starsUpgradedBySender = 0; PeerId fromId = 0; - MsgId messageId = 0; TimeId date = 0; bool upgradable = false; bool anonymous = false; diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 70a37ac21..d5a5fc57e 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -87,6 +87,7 @@ using UpdateFlag = StoryUpdate::Flag; }, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaWeather &data) { + }, [&](const MTPDmediaAreaStarGift &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -110,6 +111,7 @@ using UpdateFlag = StoryUpdate::Flag; }, [&](const MTPDmediaAreaChannelPost &data) { }, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaWeather &data) { + }, [&](const MTPDmediaAreaStarGift &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -133,6 +135,7 @@ using UpdateFlag = StoryUpdate::Flag; }); }, [&](const MTPDmediaAreaUrl &data) { }, [&](const MTPDmediaAreaWeather &data) { + }, [&](const MTPDmediaAreaStarGift &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -154,6 +157,11 @@ using UpdateFlag = StoryUpdate::Flag; .url = qs(data.vurl()), }); }, [&](const MTPDmediaAreaWeather &data) { + }, [&](const MTPDmediaAreaStarGift &data) { + result.emplace(UrlArea{ + .area = ParseArea(data.vcoordinates()), + .url = u"tg://nft?slug="_q + qs(data.vslug()), + }); }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { @@ -180,6 +188,7 @@ using UpdateFlag = StoryUpdate::Flag; -274., 1'000'000.)), }); + }, [&](const MTPDmediaAreaStarGift &data) { }, [&](const MTPDinputMediaAreaChannelPost &data) { LOG(("API Error: Unexpected inputMediaAreaChannelPost from API.")); }, [&](const MTPDinputMediaAreaVenue &data) { diff --git a/Telegram/SourceFiles/data/data_subscriptions.h b/Telegram/SourceFiles/data/data_subscriptions.h index c0d4de5a2..d6e72b911 100644 --- a/Telegram/SourceFiles/data/data_subscriptions.h +++ b/Telegram/SourceFiles/data/data_subscriptions.h @@ -18,7 +18,6 @@ struct PeerSubscription final { } }; -using PhotoId = uint64; struct SubscriptionEntry final { explicit operator bool() const { return !id.isEmpty(); @@ -31,7 +30,7 @@ struct SubscriptionEntry final { QDateTime until; PeerSubscription subscription; uint64 barePeerId = 0; - PhotoId photoId = PhotoId(0); + uint64 photoId = 0; bool cancelled = false; bool cancelledByBot = false; bool expired = false; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 5113eb6a8..cf9f6870a 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -37,6 +37,7 @@ using Options = base::flags