Merge tag 'v5.10.7' into dev

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

View file

@ -1096,8 +1096,8 @@ PRIVATE
info/saved/info_saved_sublists_widget.h
info/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -604,6 +604,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_update_fail" = "Update check failed :(";
"lng_settings_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";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1019,7 +1019,8 @@ void Controller::rowClicked(not_null<PeerListRow*> row) {
Ui::AddSkip(content);
Ui::AddSkip(content);
AddSubscriberEntryTable(controller, content, row->peer(), data.date);
const auto show = controller->uiShow();
AddSubscriberEntryTable(show, content, {}, row->peer(), data.date);
Ui::AddSkip(content);
Ui::AddSkip(content);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,14 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/star_gift_box.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "base/event_filter.h"
#include "base/random.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "api/api_premium.h"
#include "boxes/filters/edit_filter_chats_list.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_credits_box.h"
#include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h"
@ -24,10 +27,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/ui_integration.h"
#include "data/data_channel.h"
#include "data/data_credits.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/data_premium_limits.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
@ -51,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_credits.h"
#include "settings/settings_credits_graphics.h"
#include "settings/settings_premium.h"
#include "ui/boxes/boost_box.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/controls/emoji_button.h"
@ -70,6 +78,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "window/themes/window_theme.h"
#include "window/section_widget.h"
@ -82,6 +91,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include "styles/style_widgets.h"
#include <QtWidgets/QApplication>
@ -206,8 +216,12 @@ auto GenerateGiftMedia(
Element *replacing,
not_null<PeerData*> recipient,
const GiftDetails &data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto &descriptor = data.descriptor;
auto pushText = [&](
TextWithEntities text,
@ -241,18 +255,26 @@ auto GenerateGiftMedia(
replacing,
sticker,
st::giftBoxPreviewStickerPadding));
const auto title = v::match(descriptor, [&](GiftTypePremium gift) {
auto title = v::match(descriptor, [&](GiftTypePremium gift) {
return tr::lng_action_gift_premium_months(
tr::now,
lt_count,
gift.months);
gift.months,
Text::Bold);
}, [&](const GiftTypeStars &gift) {
return recipient->isSelf()
? tr::lng_action_gift_self_subtitle(tr::now)
? tr::lng_action_gift_self_subtitle(tr::now, Text::Bold)
: tr::lng_action_gift_got_subtitle(
tr::now,
lt_user,
recipient->session().user()->shortName());
TextWithEntities()
.append(Text::SingleCustomEmoji(
recipient->owner().customEmojiManager(
).peerUserpicEmojiData(
recipient->session().user())))
.append(' ')
.append(recipient->session().user()->shortName()),
Text::Bold);
});
auto textFallback = v::match(descriptor, [&](GiftTypePremium gift) {
return tr::lng_action_gift_premium_about(
@ -267,8 +289,14 @@ auto GenerateGiftMedia(
? tr::lng_action_gift_self_about_unique(
tr::now,
Text::RichLangValue)
: (recipient->isBroadcast() && gift.info.starsToUpgrade)
? tr::lng_action_gift_channel_about_unique(
tr::now,
Text::RichLangValue)
: (recipient->isSelf()
? tr::lng_action_gift_self_about
: recipient->isBroadcast()
? tr::lng_action_gift_channel_about
: tr::lng_action_gift_got_stars_text)(
tr::now,
lt_count,
@ -278,15 +306,20 @@ auto GenerateGiftMedia(
auto description = data.text.empty()
? std::move(textFallback)
: data.text;
pushText(Text::Bold(title), st::giftBoxPreviewTitlePadding);
const auto context = Core::MarkedTextContext{
.session = &parent->history()->session(),
.customEmojiRepaint = [parent] { parent->repaint(); },
};
pushText(
std::move(title),
st::giftBoxPreviewTitlePadding,
{},
context);
pushText(
std::move(description),
st::giftBoxPreviewTextPadding,
{},
Core::MarkedTextContext{
.session = &parent->history()->session(),
.customEmojiRepaint = [parent] { parent->repaint(); },
});
context);
push(HistoryView::MakeGenericButtonPart(
(data.upgraded
@ -475,6 +508,15 @@ void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
? tr::lng_action_gift_unique_received(tr::now, lt_user, name)
: _recipient->isSelf()
? tr::lng_action_gift_self_bought(tr::now, lt_cost, cost)
: _recipient->isBroadcast()
? tr::lng_action_gift_sent_channel(
tr::now,
lt_user,
name,
lt_name,
_recipient->name(),
lt_cost,
cost)
: tr::lng_action_gift_received(
tr::now,
lt_user,
@ -652,6 +694,21 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
for (auto &gift : gifts) {
list.push_back({ .info = gift });
}
ranges::sort(list, [](
const GiftTypeStars &a,
const GiftTypeStars &b) {
if (!a.info.limitedCount && !b.info.limitedCount) {
return a.info.stars <= b.info.stars;
} else if (!a.info.limitedCount) {
return true;
} else if (!b.info.limitedCount) {
return false;
} else if (a.info.limitedLeft != b.info.limitedLeft) {
return a.info.limitedLeft > b.info.limitedLeft;
}
return a.info.stars <= b.info.stars;
});
auto &map = Map[session];
if (map.last != list) {
map.last = list;
@ -1060,7 +1117,7 @@ void SendGift(
.giftId = gift.info.id,
.randomId = details.randomId,
.message = details.text,
.user = peer->asUser(),
.recipient = peer,
.limitedCount = gift.info.limitedCount,
.anonymous = details.anonymous,
.upgraded = details.upgraded,
@ -1145,7 +1202,7 @@ void SendStarsFormRequest(
void UpgradeGift(
not_null<Window::SessionController*> window,
MsgId messageId,
Data::SavedStarGiftId savedId,
bool keepDetails,
int stars,
Fn<void(Payments::CheckoutResult)> done) {
@ -1165,7 +1222,7 @@ void UpgradeGift(
using Flag = MTPpayments_UpgradeStarGift::Flag;
session->api().request(MTPpayments_UpgradeStarGift(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare)
Api::InputSavedStarGiftId(savedId)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
formDone(Payments::CheckoutResult::Paid, &result);
@ -1182,7 +1239,7 @@ void UpgradeGift(
window,
MTP_inputInvoiceStarGiftUpgrade(
MTP_flags(keepDetails ? Flag::f_keep_original_details : Flag()),
MTP_int(messageId.bare)),
Api::InputSavedStarGiftId(savedId)),
std::move(formDone));
}
@ -1211,7 +1268,7 @@ void AddUpgradeButton(
not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session,
int cost,
QString name,
not_null<PeerData*> peer,
Fn<void(bool)> toggled,
Fn<void()> preview) {
const auto button = container->add(
@ -1258,25 +1315,105 @@ void AddUpgradeButton(
AddSkip(container);
const auto about = AddDividerText(
container,
tr::lng_gift_send_unique_about(
lt_user,
rpl::single(TextWithEntities{ name }),
lt_link,
tr::lng_gift_send_unique_link() | Text::ToLink(),
Text::WithEntities));
(peer->isBroadcast()
? tr::lng_gift_send_unique_about_channel(
lt_name,
rpl::single(TextWithEntities{ peer->name() }),
lt_link,
tr::lng_gift_send_unique_link() | Text::ToLink(),
Text::WithEntities)
: tr::lng_gift_send_unique_about(
lt_user,
rpl::single(TextWithEntities{ peer->shortName() }),
lt_link,
tr::lng_gift_send_unique_link() | Text::ToLink(),
Text::WithEntities)));
about->setClickHandlerFilter([=](const auto &...) {
preview();
return false;
});
}
void AddSoldLeftSlider(
not_null<RoundButton*> button,
const GiftTypeStars &gift) {
const auto still = gift.info.limitedLeft;
const auto total = gift.info.limitedCount;
const auto slider = CreateChild<RpWidget>(button->parentWidget());
struct State {
Text::String still;
Text::String sold;
int height = 0;
};
const auto state = slider->lifetime().make_state<State>();
const auto sold = total - still;
state->still.setText(
st::semiboldTextStyle,
tr::lng_gift_send_limited_left(tr::now, lt_count_decimal, still));
state->sold.setText(
st::semiboldTextStyle,
tr::lng_gift_send_limited_sold(tr::now, lt_count_decimal, sold));
state->height = st::giftLimitedPadding.top()
+ st::semiboldFont->height
+ st::giftLimitedPadding.bottom();
button->geometryValue() | rpl::start_with_next([=](QRect geometry) {
const auto space = st::giftLimitedBox.buttonPadding.top();
const auto skip = (space - state->height) / 2;
slider->setGeometry(
geometry.x(),
geometry.y() - skip - state->height,
geometry.width(),
state->height);
}, slider->lifetime());
slider->paintRequest() | rpl::start_with_next([=] {
const auto &padding = st::giftLimitedPadding;
const auto left = (padding.left() * 2) + state->still.maxWidth();
const auto right = (padding.right() * 2) + state->sold.maxWidth();
const auto space = slider->width() - left - right;
if (space <= 0) {
return;
}
const auto edge = left + ((space * still) / total);
const auto radius = st::buttonRadius;
auto p = QPainter(slider);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
p.drawRoundedRect(
edge - (radius * 3),
0,
slider->width() - (edge - (radius * 3)),
state->height,
radius,
radius);
p.setBrush(st::windowBgActive);
p.drawRoundedRect(0, 0, edge, state->height, radius, radius);
p.setPen(st::windowFgActive);
state->still.draw(p, {
.position = { padding.left(), padding.top() },
.availableWidth = left,
});
p.setPen(st::windowSubTextFg);
state->sold.draw(p, {
.position = { left + space + padding.right(), padding.top() },
.availableWidth = right,
});
}, slider->lifetime());
}
void SendGiftBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDescriptor &descriptor) {
box->setStyle(st::giftBox);
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title());
box->addTopButton(st::boxTitleClose, [=] {
@ -1373,18 +1510,14 @@ void SendGiftBox(
session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (const auto stars = std::get_if<GiftTypeStars>(&descriptor)) {
if (stars) {
const auto cost = stars->info.starsToUpgrade;
if (cost > 0 && !peer->isSelf()) {
const auto user = peer->asUser();
Assert(user != nullptr);
const auto id = stars->info.id;
const auto name = user->shortName();
const auto showing = std::make_shared<bool>();
AddDivider(container);
AddSkip(container);
AddUpgradeButton(container, session, cost, name, [=](bool on) {
AddUpgradeButton(container, session, cost, peer, [=](bool on) {
auto now = state->details.current();
now.upgraded = on;
state->details = std::move(now);
@ -1397,7 +1530,7 @@ void SendGiftBox(
.controller = window,
.stargiftId = id,
.ready = [=](bool) { *showing = false; },
.user = user,
.peer = peer,
.cost = int(cost),
});
});
@ -1425,6 +1558,8 @@ void SendGiftBox(
}, [&](const GiftTypeStars &) {
AddDividerText(container, peer->isSelf()
? tr::lng_gift_send_anonymous_self()
: peer->isBroadcast()
? tr::lng_gift_send_anonymous_about_channel()
: tr::lng_gift_send_anonymous_about(
lt_user,
rpl::single(peer->shortName()),
@ -1454,6 +1589,9 @@ void SendGiftBox(
};
SendGift(window, peer, api, details, done);
});
if (limited) {
AddSoldLeftSlider(button, *stars);
}
SetButtonMarkedLabel(
button,
(peer->isSelf()
@ -1704,7 +1842,7 @@ void GiftBox(
window->showSettings(Settings::CreditsId());
return false;
};
if (!peer->isSelf()) {
if (peer->isUser() && !peer->isSelf()) {
const auto premiumClickHandlerFilter = [=](const auto &...) {
Settings::ShowPremium(window, u"gift_send"_q);
return false;
@ -1724,9 +1862,16 @@ void GiftBox(
AddBlock(content, window, {
.subtitle = (peer->isSelf()
? tr::lng_gift_self_title()
: peer->isBroadcast()
? tr::lng_gift_channel_title()
: tr::lng_gift_stars_subtitle()),
.about = (peer->isSelf()
? tr::lng_gift_self_about(Text::WithEntities)
: peer->isBroadcast()
? tr::lng_gift_channel_about(
lt_name,
rpl::single(Text::Bold(peer->name())),
Text::WithEntities)
: tr::lng_gift_stars_about(
lt_name,
rpl::single(Text::Bold(peer->shortName())),
@ -2112,6 +2257,242 @@ void AddUniqueGiftCover(
}, cover->lifetime());
}
void AddWearGiftCover(
not_null<VerticalLayout*> container,
const Data::UniqueGift &data,
not_null<PeerData*> peer) {
const auto cover = container->add(object_ptr<RpWidget>(container));
const auto title = CreateChild<FlatLabel>(
cover,
rpl::single(peer->name()),
st::uniqueGiftTitle);
title->setTextColorOverride(QColor(255, 255, 255));
const auto subtitle = CreateChild<FlatLabel>(
cover,
(peer->isChannel()
? tr::lng_chat_status_subscribers(
lt_count,
rpl::single(peer->asChannel()->membersCount() * 1.))
: tr::lng_status_online()),
st::uniqueGiftSubtitle);
subtitle->setTextColorOverride(data.backdrop.textColor);
struct State {
QImage gradient;
Data::UniqueGift gift;
Ui::PeerUserpicView view;
std::unique_ptr<Text::CustomEmoji> emoji;
base::flat_map<float64, QImage> emojis;
rpl::lifetime lifetime;
};
const auto state = cover->lifetime().make_state<State>(State{
.gift = data,
});
state->emoji = peer->owner().customEmojiManager().create(
state->gift.pattern.document,
[=] { cover->update(); },
Data::CustomEmojiSizeTag::Large);
cover->widthValue() | rpl::start_with_next([=](int width) {
const auto skip = st::uniqueGiftBottom;
if (width <= 3 * skip) {
return;
}
const auto available = width - 2 * skip;
title->resizeToWidth(available);
title->moveToLeft(skip, st::uniqueGiftTitleTop);
subtitle->resizeToWidth(available);
subtitle->moveToLeft(skip, st::uniqueGiftSubtitleTop);
cover->resize(width, subtitle->y() + subtitle->height() + skip);
}, cover->lifetime());
cover->paintRequest() | rpl::start_with_next([=] {
auto p = Painter(cover);
const auto width = cover->width();
const auto pointsHeight = st::uniqueGiftSubtitleTop;
const auto ratio = style::DevicePixelRatio();
if (state->gradient.size() != cover->size() * ratio) {
state->gradient = CreateGradient(cover->size(), state->gift);
}
p.drawImage(0, 0, state->gradient);
PaintPoints(
p,
PatternPoints(),
state->emojis,
state->emoji.get(),
state->gift,
QRect(0, 0, width, pointsHeight),
1.);
peer->paintUserpic(
p,
state->view,
(width - st::uniqueGiftUserpicSize) / 2,
st::uniqueGiftUserpicTop,
st::uniqueGiftUserpicSize);
}, cover->lifetime());
}
void ShowUniqueGiftWearBox(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
const Data::UniqueGift &gift,
Settings::GiftWearBoxStyleOverride st) {
show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setNoContentMargin(true);
box->setWidth((st::boxWidth + st::boxWideWidth) / 2); // =)
box->setStyle(st.box ? *st.box : st::upgradeGiftBox);
const auto channel = peer->isChannel();
const auto content = box->verticalLayout();
AddWearGiftCover(content, gift, peer);
AddSkip(content, st::defaultVerticalListSkip * 2);
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon) {
auto raw = content->add(
object_ptr<Ui::VerticalLayout>(content));
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st.infoTitle ? *st.infoTitle : st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(text),
st.infoAbout ? *st.infoAbout : st::boxDividerLabel),
st::settingsPremiumRowAboutPadding);
object_ptr<Info::Profile::FloatingIcon>(
raw,
*icon,
st::starrefInfoIconPosition);
};
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_gift_wear_title(
lt_name,
rpl::single(UniqueGiftName(gift))),
st.title ? *st.title : st::uniqueGiftTitle),
st::settingsPremiumRowTitlePadding);
content->add(
object_ptr<Ui::FlatLabel>(
content,
tr::lng_gift_wear_about(),
st.subtitle ? *st.subtitle : st::uniqueGiftSubtitle),
st::settingsPremiumRowAboutPadding);
infoRow(
tr::lng_gift_wear_badge_title(),
(channel
? tr::lng_gift_wear_badge_about_channel()
: tr::lng_gift_wear_badge_about()),
st.radiantIcon ? st.radiantIcon : &st::menuIconUnique);
//infoRow(
// tr::lng_gift_wear_design_title(),
// tr::lng_gift_wear_design_about(),
// &st::menuIconUniqueProfile);
infoRow(
tr::lng_gift_wear_proof_title(),
(channel
? tr::lng_gift_wear_proof_about_channel()
: tr::lng_gift_wear_proof_about()),
st.proofIcon ? st.proofIcon : &st::menuIconFactcheck);
const auto session = &show->session();
const auto checking = std::make_shared<bool>();
const auto button = box->addButton(rpl::single(QString()), [=] {
const auto emojiStatuses = &session->data().emojiStatuses();
const auto id = emojiStatuses->fromUniqueGift(gift);
if (!peer->isSelf()) {
if (*checking) {
return;
}
*checking = true;
const auto weak = Ui::MakeWeak(box);
CheckBoostLevel(show, peer, [=](int level) {
const auto limits = Data::LevelLimits(&peer->session());
const auto wanted = limits.channelEmojiStatusLevelMin();
if (level >= wanted) {
if (const auto strong = weak.data()) {
strong->closeBox();
}
emojiStatuses->set(peer, id);
return std::optional<Ui::AskBoostReason>();
}
const auto reason = [&]() -> Ui::AskBoostReason {
return { Ui::AskBoostWearCollectible{
wanted
} };
}();
return std::make_optional(reason);
}, [=] { *checking = false; });
} else if (session->premium()) {
box->closeBox();
emojiStatuses->set(peer, id);
} else {
const auto link = Ui::Text::Bold(
tr::lng_send_as_premium_required_link(tr::now));
Settings::ShowPremiumPromoToast(
show,
tr::lng_gift_wear_subscribe(
tr::now,
lt_link,
Ui::Text::Link(link),
Ui::Text::WithEntities),
u"wear_collectibles"_q);
}
});
const auto lock = Ui::Text::SingleCustomEmoji(
session->data().customEmojiManager().registerInternalEmoji(
st::historySendDisabledIcon,
st::giftBoxLockMargins,
true));
auto label = rpl::combine(
tr::lng_gift_wear_start(),
Data::AmPremiumValue(&show->session())
) | rpl::map([=](const QString &text, bool premium) {
auto result = TextWithEntities();
if (!premium && peer->isSelf()) {
result.append(lock);
}
result.append(text);
return result;
});
SetButtonMarkedLabel(
button,
std::move(label),
session,
st::creditsBoxButtonLabel,
&st::giftBox.button.textFg);
rpl::combine(
box->widthValue(),
button->widthValue()
) | rpl::start_with_next([=](int outer, int inner) {
const auto padding = st::giftBox.buttonPadding;
const auto wanted = outer - padding.left() - padding.right();
if (inner != wanted) {
button->resizeToWidth(wanted);
button->moveToLeft(padding.left(), padding.top());
}
}, box->lifetime());
AddUniqueCloseButton(box, {});
}));
}
struct UpgradeArgs : StarGiftUpgradeArgs {
std::vector<Data::UniqueGiftModel> models;
std::vector<Data::UniqueGiftPattern> patterns;
@ -2162,7 +2543,7 @@ struct UpgradeArgs : StarGiftUpgradeArgs {
auto &patterns = state->data.patterns;
auto &backdrops = state->data.backdrops;
consumer.put_next(Data::UniqueGift{
.title = (state->data.itemId
.title = (state->data.savedId
? tr::lng_gift_upgrade_title(tr::now)
: tr::lng_gift_upgrade_preview_title(tr::now)),
.model = models[index(state->modelIndices, models)],
@ -2186,11 +2567,13 @@ void AddUpgradeGiftCover(
AddUniqueGiftCover(
container,
MakeUpgradeGiftStream(args),
(args.itemId
(args.savedId
? tr::lng_gift_upgrade_about()
: tr::lng_gift_upgrade_preview_about(
lt_name,
rpl::single(args.user->shortName()))));
: (args.peer->isBroadcast()
? tr::lng_gift_upgrade_preview_about_channel
: tr::lng_gift_upgrade_preview_about)(
lt_name,
rpl::single(args.peer->shortName()))));
}
void UpgradeBox(
@ -2207,26 +2590,15 @@ void UpgradeBox(
const auto infoRow = [&](
rpl::producer<QString> title,
rpl::producer<QString> text,
not_null<const style::icon*> icon,
bool newBadge = false) {
not_null<const style::icon*> icon) {
auto raw = container->add(
object_ptr<Ui::VerticalLayout>(container));
const auto widget = raw->add(
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
std::move(title) | Ui::Text::ToBold(),
st::defaultFlatLabel),
st::settingsPremiumRowTitlePadding);
if (newBadge) {
const auto badge = NewBadge::CreateNewBadge(
raw,
tr::lng_soon_badge(Ui::Text::Upper));
widget->geometryValue(
) | rpl::start_with_next([=](QRect geometry) {
badge->move(st::settingsPremiumNewBadgePosition
+ QPoint(widget->x() + widget->width(), widget->y()));
}, badge->lifetime());
}
raw->add(
object_ptr<Ui::FlatLabel>(
raw,
@ -2250,15 +2622,14 @@ void UpgradeBox(
infoRow(
tr::lng_gift_upgrade_tradable_title(),
tr::lng_gift_upgrade_tradable_about(),
&st::menuIconTradable,
true);
&st::menuIconTradable);
struct State {
bool sent = false;
bool preserveDetails = false;
};
const auto state = std::make_shared<State>();
const auto preview = !args.itemId;
const auto preview = !args.savedId;
if (!preview) {
const auto skip = st::defaultVerticalListSkip;
@ -2303,13 +2674,13 @@ void UpgradeBox(
if (result != Payments::CheckoutResult::Paid) {
state->sent = false;
} else {
controller->showPeerHistory(args.user);
controller->showPeerHistory(args.peer);
if (const auto strong = weak.data()) {
strong->closeBox();
}
}
};
UpgradeGift(controller, args.itemId, keepDetails, cost, done);
UpgradeGift(controller, args.savedId, keepDetails, cost, done);
});
if (!preview) {
auto star = session->data().customEmojiManager().creditsEmoji();
@ -2339,7 +2710,7 @@ void UpgradeBox(
}
}, box->lifetime());
AddUniqueCloseButton(box);
AddUniqueCloseButton(box, {});
}
const std::vector<PatternPoint> &PatternPoints() {
@ -2458,7 +2829,7 @@ void PaintPoints(
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
const auto weak = base::make_weak(args.controller);
const auto session = &args.user->session();
const auto session = &args.peer->session();
session->api().request(MTPpayments_GetStarGiftUpgradePreview(
MTP_long(args.stargiftId)
)).done([=](const MTPpayments_StarGiftUpgradePreview &result) {
@ -2494,19 +2865,51 @@ void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
}).send();
}
void AddUniqueCloseButton(not_null<GenericBox*> box) {
const auto button = Ui::CreateChild<IconButton>(
void AddUniqueCloseButton(
not_null<GenericBox*> box,
Settings::CreditsEntryBoxStyleOverrides st,
Fn<void(not_null<Ui::PopupMenu*>)> fillMenu) {
const auto close = Ui::CreateChild<IconButton>(
box,
st::uniqueCloseButton);
button->show();
button->raise();
const auto menu = fillMenu
? Ui::CreateChild<IconButton>(box, st::uniqueMenuButton)
: nullptr;
close->show();
close->raise();
if (menu) {
menu->show();
menu->raise();
}
box->widthValue() | rpl::start_with_next([=](int width) {
button->moveToRight(0, 0, width);
button->raise();
}, button->lifetime());
button->setClickedCallback([=] {
close->moveToRight(0, 0, width);
close->raise();
if (menu) {
menu->moveToRight(close->width(), 0, width);
menu->raise();
}
}, close->lifetime());
close->setClickedCallback([=] {
box->closeBox();
});
if (menu) {
const auto state = menu->lifetime().make_state<
base::unique_qptr<Ui::PopupMenu>
>();
menu->setClickedCallback([=] {
if (*state) {
*state = nullptr;
return;
}
*state = base::make_unique_q<Ui::PopupMenu>(
menu,
st.menu ? *st.menu : st::popupMenuWithIcons);
fillMenu(state->get());
if (!(*state)->empty()) {
(*state)->popup(QCursor::pos());
}
});
}
}
void RequestStarsFormAndSubmit(
@ -2539,25 +2942,29 @@ void RequestStarsFormAndSubmit(
done(Payments::CheckoutResult::Failed, nullptr);
});
}).fail([=](const MTP::Error &error) {
if (const auto strong = weak.get()) {
strong->showToast(error.type());
const auto type = error.type();
if (type == u"STARGIFT_EXPORT_IN_PROGRESS"_q) {
done(Payments::CheckoutResult::Cancelled, nullptr);
} else {
if (const auto strong = weak.get()) {
strong->showToast(type);
}
done(Payments::CheckoutResult::Failed, nullptr);
}
done(Payments::CheckoutResult::Failed, nullptr);
}).send();
}
void ShowGiftTransferredToast(
base::weak_ptr<Window::SessionController> weak,
not_null<PeerData*> to,
const MTPUpdates &result) {
const auto gift = FindUniqueGift(&to->session(), result);
if (const auto strong = gift ? weak.get() : nullptr) {
const Data::UniqueGift &gift) {
if (const auto strong = weak.get()) {
strong->showToast({
.title = tr::lng_gift_transferred_title(tr::now),
.text = tr::lng_gift_transferred_about(
tr::now,
lt_name,
Text::Bold(Data::UniqueGiftName(*gift)),
Text::Bold(Data::UniqueGiftName(gift)),
lt_recipient,
Text::Bold(to->shortName()),
Ui::Text::WithEntities),

View file

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

View file

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

View file

@ -8,7 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/transfer_gift_box.h"
#include "apiwrap.h"
#include "api/api_credits.h"
#include "api/api_cloud_password.h"
#include "base/unixtime.h"
#include "boxes/passcode_box.h"
#include "data/data_session.h"
#include "data/data_star_gift.h"
#include "data/data_user.h"
#include "boxes/filters/edit_filter_chats_list.h" // CreatePe...tionSubtitle.
@ -21,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include "ui/basic_click_handlers.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h" // peerListSingleRow.
#include "styles/style_dialogs.h" // recentPeersSpecialName.
#include "styles/style_layers.h" // boxLabel.
namespace {
@ -41,13 +47,15 @@ public:
Controller(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
Fn<void(not_null<PeerData*>)> choose);
Data::SavedStarGiftId savedId,
Fn<void(not_null<PeerData*>, Fn<void()>)> choose);
void init(not_null<PeerListBox*> box);
void noSearchSubmit();
private:
void prepareViewHook() override;
void setupExportOption();
bool overrideKeyboardNavigation(
int direction,
@ -58,34 +66,150 @@ private:
not_null<UserData*> user) override;
void rowClicked(not_null<PeerListRow*> row) override;
const not_null<Window::SessionController*> _window;
const std::shared_ptr<Data::UniqueGift> _gift;
const Fn<void(not_null<PeerData*>)> _choose;
const Data::SavedStarGiftId _giftId;
const Fn<void(not_null<PeerData*>, Fn<void()>)> _choose;
ExportOption _exportOption;
QPointer<PeerListBox> _box;
};
void ConfirmExportBox(
not_null<Ui::GenericBox*> box,
std::shared_ptr<Data::UniqueGift> gift,
Fn<void(Fn<void()> close)> confirmed) {
box->setTitle(tr::lng_gift_transfer_confirm_title());
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_gift_transfer_confirm_text(
lt_name,
rpl::single(Ui::Text::Bold(UniqueGiftName(*gift))),
Ui::Text::WithEntities),
st::boxLabel));
box->addButton(tr::lng_gift_transfer_confirm_button(), [=] {
confirmed([weak = Ui::MakeWeak(box)] {
if (const auto strong = weak.data()) {
strong->closeBox();
}
});
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
void ExportOnBlockchain(
not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId giftId,
Fn<void()> boxShown,
Fn<void()> wentToUrl) {
struct State {
bool loading = false;
rpl::lifetime lifetime;
};
const auto state = std::make_shared<State>();
const auto session = &window->session();
const auto show = window->uiShow();
session->api().cloudPassword().reload();
session->api().request(
MTPpayments_GetStarGiftWithdrawalUrl(
Api::InputSavedStarGiftId(giftId),
MTP_inputCheckPasswordEmpty())
).fail([=](const MTP::Error &error) {
auto box = PrePasswordErrorBox(
error.type(),
session,
TextWithEntities{
tr::lng_gift_transfer_password_about(tr::now),
});
if (box) {
show->show(std::move(box));
boxShown();
return;
}
state->lifetime = session->api().cloudPassword().state(
) | rpl::take(
1
) | rpl::start_with_next([=](const Core::CloudPasswordState &pass) {
auto fields = PasscodeBox::CloudFields::From(pass);
fields.customTitle = tr::lng_gift_transfer_password_title();
fields.customDescription
= tr::lng_gift_transfer_password_description(tr::now);
fields.customSubmitButton = tr::lng_passcode_submit();
fields.customCheckCallback = crl::guard(parent, [=](
const Core::CloudPasswordResult &result,
QPointer<PasscodeBox> box) {
using ExportUrl = MTPpayments_StarGiftWithdrawalUrl;
session->api().request(
MTPpayments_GetStarGiftWithdrawalUrl(
Api::InputSavedStarGiftId(giftId),
result.result)
).done([=](const ExportUrl &result) {
UrlClickHandler::Open(qs(result.data().vurl()));
wentToUrl();
if (box) {
box->closeBox();
}
}).fail([=](const MTP::Error &error) {
const auto message = error.type();
if (box && !box->handleCustomCheckError(message)) {
show->showToast(message);
}
}).send();
});
show->show(Box<PasscodeBox>(session, fields));
boxShown();
});
}).send();
}
[[nodiscard]] ExportOption MakeExportOption(
not_null<Window::SessionController*> window,
not_null<PeerListBox*> box,
std::shared_ptr<Data::UniqueGift> gift,
Data::SavedStarGiftId giftId,
TimeId when) {
struct State {
bool exporting = false;
};
const auto state = std::make_shared<State>();
const auto activate = [=] {
const auto now = base::unixtime::now();
const auto weak = Ui::MakeWeak(box);
const auto left = (when > now) ? (when - now) : 0;
const auto hours = left ? std::max((left + 1800) / 3600, 1) : 0;
if (!hours) {
window->show(Box(ConfirmExportBox, gift, [=](Fn<void()> close) {
if (state->exporting) {
return;
}
state->exporting = true;
ExportOnBlockchain(window, box, gift, giftId, [=] {
state->exporting = false;
close();
}, [=] {
if (const auto strong = weak.data()) {
strong->closeBox();
}
close();
});
}));
return;
}
window->show(Ui::MakeInformBox({
.text = (!hours
? tr::lng_gift_transfer_unlocks_update_about()
: tr::lng_gift_transfer_unlocks_about(
lt_when,
((hours >= 24)
? tr::lng_gift_transfer_unlocks_when_days(
lt_count,
rpl::single((hours / 24) * 1.))
: tr::lng_gift_transfer_unlocks_when_hours(
lt_count,
rpl::single(hours * 1.))))),
.title = (!hours
? tr::lng_gift_transfer_unlocks_update_title()
: tr::lng_gift_transfer_unlocks_title()),
.text = tr::lng_gift_transfer_unlocks_about(
lt_when,
((hours >= 24)
? tr::lng_gift_transfer_unlocks_when_days(
lt_count,
rpl::single((hours / 24) * 1.))
: tr::lng_gift_transfer_unlocks_when_hours(
lt_count,
rpl::single(hours * 1.)))),
.title = tr::lng_gift_transfer_unlocks_title(),
}));
};
@ -130,10 +254,14 @@ private:
const style::PeerListItem &computeSt(
const style::PeerListItem &st) const override {
return _available ? st::recentPeersSpecialName : st;
_st = st;
_st.namePosition.setY(
st::recentPeersSpecialName.namePosition.y());
return _available ? _st : st;
}
private:
mutable style::PeerListItem _st;
bool _available = false;
};
@ -228,18 +356,27 @@ private:
Controller::Controller(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
Fn<void(not_null<PeerData*>)> choose)
Data::SavedStarGiftId giftId,
Fn<void(not_null<PeerData*>, Fn<void()>)> choose)
: ContactsBoxController(&window->session())
, _window(window)
, _gift(std::move(gift))
, _giftId(giftId)
, _choose(std::move(choose)) {
if (const auto when = _gift->exportAt) {
_exportOption = MakeExportOption(window, when);
}
if (_exportOption.content) {
if (_gift->exportAt) {
setStyleOverrides(&st::peerListSmallSkips);
}
}
void Controller::init(not_null<PeerListBox*> box) {
_box = box;
if (const auto when = _gift->exportAt) {
_exportOption = MakeExportOption(_window, box, _gift, _giftId, when);
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
delegate()->peerListRefreshRows();
}
}
void Controller::noSearchSubmit() {
if (const auto onstack = _exportOption.activate) {
onstack();
@ -258,11 +395,6 @@ void Controller::prepareViewHook() {
delegate()->peerListSetTitle(tr::lng_gift_transfer_title(
lt_name,
rpl::single(UniqueGiftName(*_gift))));
setupExportOption();
}
void Controller::setupExportOption() {
delegate()->peerListSetAboveWidget(std::move(_exportOption.content));
}
std::unique_ptr<PeerListRow> Controller::createRow(
@ -277,14 +409,18 @@ std::unique_ptr<PeerListRow> Controller::createRow(
}
void Controller::rowClicked(not_null<PeerListRow*> row) {
_choose(row->peer());
_choose(row->peer(), [parentBox = _box] {
if (const auto strong = parentBox.data()) {
strong->closeBox();
}
});
}
void TransferGift(
not_null<Window::SessionController*> window,
not_null<PeerData*> to,
std::shared_ptr<Data::UniqueGift> gift,
MsgId messageId,
Data::SavedStarGiftId savedId,
Fn<void(Payments::CheckoutResult)> done) {
Expects(to->isUser());
@ -293,33 +429,37 @@ void TransferGift(
auto formDone = [=](
Payments::CheckoutResult result,
const MTPUpdates *updates) {
if (result == Payments::CheckoutResult::Paid && updates) {
done(result);
if (result == Payments::CheckoutResult::Paid) {
if (const auto strong = weak.get()) {
Ui::ShowGiftTransferredToast(strong, to, *updates);
strong->session().data().notifyGiftUpdate({
.id = savedId,
.action = Data::GiftUpdate::Action::Transfer,
});
Ui::ShowGiftTransferredToast(strong, to, *gift);
}
}
done(result);
};
if (gift->starsForTransfer <= 0) {
session->api().request(MTPpayments_TransferStarGift(
MTP_int(messageId.bare),
to->asUser()->inputUser
Api::InputSavedStarGiftId(savedId),
to->input
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
formDone(Payments::CheckoutResult::Paid, &result);
}).fail([=](const MTP::Error &error) {
formDone(Payments::CheckoutResult::Failed, nullptr);
if (const auto strong = weak.get()) {
strong->showToast(error.type());
}
formDone(Payments::CheckoutResult::Failed, nullptr);
}).send();
return;
}
Ui::RequestStarsFormAndSubmit(
window,
MTP_inputInvoiceStarGiftTransfer(
MTP_int(messageId.bare),
to->asUser()->inputUser),
Api::InputSavedStarGiftId(savedId),
to->input),
std::move(formDone));
}
@ -327,7 +467,8 @@ void ShowTransferToBox(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) {
Data::SavedStarGiftId savedId,
Fn<void()> closeParentBox) {
const auto stars = gift->starsForTransfer;
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_gift_transfer_title(
@ -353,16 +494,24 @@ void ShowTransferToBox(
state->sent = true;
const auto weak = Ui::MakeWeak(box);
const auto done = [=](Payments::CheckoutResult result) {
if (result != Payments::CheckoutResult::Paid) {
if (result == Payments::CheckoutResult::Cancelled) {
closeParentBox();
if (const auto strong = weak.data()) {
strong->closeBox();
}
} else if (result != Payments::CheckoutResult::Paid) {
state->sent = false;
} else {
controller->showPeerHistory(peer);
if (savedId.isUser()) {
controller->showPeerHistory(peer);
}
closeParentBox();
if (const auto strong = weak.data()) {
strong->closeBox();
}
}
};
TransferGift(controller, peer, gift, msgId, done);
TransferGift(controller, peer, gift, savedId, done);
};
Ui::ConfirmBox(box, {
@ -395,15 +544,18 @@ void ShowTransferToBox(
void ShowTransferGiftBox(
not_null<Window::SessionController*> window,
std::shared_ptr<Data::UniqueGift> gift,
MsgId msgId) {
Data::SavedStarGiftId savedId) {
auto controller = std::make_unique<Controller>(
window,
gift,
[=](not_null<PeerData*> peer) {
ShowTransferToBox(window, peer, gift, msgId);
savedId,
[=](not_null<PeerData*> peer, Fn<void()> done) {
ShowTransferToBox(window, peer, gift, savedId, done);
});
const auto controllerRaw = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
controllerRaw->init(box);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->noSearchSubmits() | rpl::start_with_next([=] {

View file

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

View file

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

View file

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

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_menu.h" // LeaveBox.
#include "calls/group/calls_group_common.h"
#include "calls/group/calls_choose_join_as.h"
#include "calls/group/calls_volume_item.h"
#include "calls/calls_instance.h"
#include "ui/widgets/level_meter.h"
#include "ui/widgets/continuous_sliders.h"
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "webrtc/webrtc_audio_input_tester.h"
#include "webrtc/webrtc_device_resolver.h"
#include "settings/settings_calls.h"
#include "settings/settings_credits_graphics.h"
#include "main/main_session.h"
#include "apiwrap.h"
#include "api/api_invite_links.h"
@ -183,22 +185,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
return Data::CanSend(thread, ChatRestriction::SendOther);
};
const auto scheduleStyle = [&] {
auto date = Ui::ChooseDateTimeStyleArgs();
date.labelStyle = &st::groupCallBoxLabel;
date.dateFieldStyle = &st::groupCallScheduleDateField;
date.timeFieldStyle = &st::groupCallScheduleTimeField;
date.separatorStyle = &st::callMuteButtonLabel;
date.atStyle = &st::callMuteButtonLabel;
date.calendarStyle = &st::groupCallCalendarColors;
auto st = HistoryView::ScheduleBoxStyleArgs();
st.topButtonStyle = &st::groupCallMenuToggle;
st.popupMenuStyle = &st::groupCallPopupMenu;
st.chooseDateTimeArgs = std::move(date);
return st;
};
const auto st = ::Settings::DarkCreditsEntryBoxStyle();
auto result = Box<ShareBox>(ShareBox::Descriptor{
.session = &peer->session(),
.copyCallback = std::move(copyCallback),
@ -211,11 +198,7 @@ object_ptr<ShareBox> ShareInviteLinkBox(
: rpl::single(false)),
tr::lng_group_call_copy_speaker_link(),
tr::lng_group_call_copy_listener_link()),
.stMultiSelect = &st::groupCallMultiSelect,
.stComment = &st::groupCallShareBoxComment,
.st = &st::groupCallShareBoxList,
.stLabel = &st::groupCallField,
.scheduleBoxStyle = scheduleStyle(),
.st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
.premiumRequiredError = SharePremiumRequiredError(),
});
*box = result.data();
@ -731,6 +714,50 @@ void SettingsBox(
addDivider();
Ui::AddSkip(layout);
}
if (rtmp) {
const auto volumeItem = layout->add(
object_ptr<MenuVolumeItem>(
layout,
st::groupCallVolumeSettings,
st::groupCallVolumeSettingsSlider,
call->otherParticipantStateValue(
) | rpl::filter([=](const Group::ParticipantState &data) {
return data.peer == peer;
}),
call->rtmpVolume(),
Group::kMaxVolume,
false,
st::groupCallVolumeSettingsPadding));
const auto toggleMute = crl::guard(layout, [=](bool m, bool local) {
if (call) {
call->toggleMute({
.peer = peer,
.mute = m,
.locallyOnly = local,
});
}
});
const auto changeVolume = crl::guard(layout, [=](int v, bool local) {
if (call) {
call->changeVolume({
.peer = peer,
.volume = std::clamp(v, 1, Group::kMaxVolume),
.locallyOnly = local,
});
}
});
volumeItem->toggleMuteLocallyRequests(
) | rpl::start_with_next([=](bool muted) {
toggleMute(muted, true);
}, volumeItem->lifetime());
volumeItem->changeVolumeLocallyRequests(
) | rpl::start_with_next([=](int volume) {
changeVolume(volume, true);
}, volumeItem->lifetime());
}
if (peer->canManageGroupCall()) {
layout->add(object_ptr<Ui::SettingsButton>(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1348,6 +1348,21 @@ bool ResolveChatLink(
return true;
}
bool ResolveUniqueGift(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto slug = match->captured(1);
if (slug.isEmpty()) {
return false;
}
ResolveAndShowUniqueGift(controller->uiShow(), slug);
return true;
}
} // namespace
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
@ -1440,6 +1455,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
u"^stars_topup/?\\?(.+)(#|$)"_q,
ResolveTopUp
},
{
u"^nft/?\\?slug=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"_q,
ResolveUniqueGift
},
{
u"^user\\?(.+)(#|$)"_q,
AyuUrlHandlers::ResolveUser
@ -1597,6 +1616,9 @@ QString TryConvertUrlToLocal(QString url) {
} else if (const auto chatlinkMatch = regex_match(u"^m/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
const auto slug = chatlinkMatch->captured(1);
return u"tg://message?slug="_q + slug;
} else if (const auto nftMatch = regex_match(u"^nft/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
const auto slug = nftMatch->captured(1);
return u"tg://nft?slug="_q + slug;
} else if (const auto privateMatch = regex_match(u"^"
"c/(\\-?\\d+)"
"("
@ -1690,4 +1712,51 @@ bool StartUrlRequiresActivate(const QString &url) {
: !InternalPassportLink(url);
}
void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show,
const QString &slug,
::Settings::CreditsEntryBoxStyleOverrides st) {
struct Request {
base::weak_ptr<Main::Session> weak;
QString slug;
mtpRequestId id = 0;
};
static auto request = Request();
const auto session = &show->session();
if (request.weak.get() == session && request.slug == slug) {
return;
} else if (const auto strong = request.weak.get()) {
strong->api().request(request.id).cancel();
}
request.weak = session;
request.slug = slug;
const auto clear = [=] {
if (request.weak.get() == session && request.slug == slug) {
request = {};
}
};
request.id = session->api().request(
MTPpayments_GetUniqueStarGift(MTP_string(slug))
).done([=](const MTPpayments_UniqueStarGift &result) {
clear();
const auto &data = result.data();
session->data().processUsers(data.vusers());
if (const auto gift = Api::FromTL(session, data.vgift())) {
using namespace ::Settings;
show->show(Box(GlobalStarGiftBox, show, *gift, st));
}
}).fail([=](const MTP::Error &error) {
clear();
show->showToast(u"Error: "_q + error.type());
}).send();
}
void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show,
const QString &slug) {
ResolveAndShowUniqueGift(std::move(show), slug, {});
}
} // namespace Core

View file

@ -11,6 +11,14 @@ namespace qthelp {
class RegularExpressionMatch;
} // namespace qthelp
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Settings {
struct CreditsEntryBoxStyleOverrides;
} // namespace Settings
namespace Window {
class SessionController;
} // namespace Window
@ -34,4 +42,12 @@ struct LocalUrlHandler {
[[nodiscard]] bool StartUrlRequiresActivate(const QString &url);
void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show,
const QString &slug,
::Settings::CreditsEntryBoxStyleOverrides st);
void ResolveAndShowUniqueGift(
std::shared_ptr<ChatHelpers::Show> show,
const QString &slug);
} // namespace Core

View file

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

View file

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

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto 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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,6 +21,27 @@ namespace Data {
class DocumentMedia;
class Session;
struct UniqueGift;
struct EmojiStatusCollectible {
CollectibleId id = 0;
DocumentId documentId = 0;
QString title;
QString slug;
DocumentId patternDocumentId = 0;
QColor centerColor;
QColor edgeColor;
QColor patternColor;
QColor textColor;
explicit operator bool() const {
return id != 0;
}
};
struct EmojiStatusData {
EmojiStatusId id;
TimeId until = 0;
};
class EmojiStatuses final {
public:
@ -38,6 +59,7 @@ public:
void refreshColored();
void refreshChannelDefault();
void refreshChannelColored();
void refreshCollectibles();
enum class Type {
Recent,
@ -45,15 +67,21 @@ public:
Colored,
ChannelDefault,
ChannelColored,
Collectibles,
};
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
[[nodiscard]] const std::vector<EmojiStatusId> &list(Type type) const;
[[nodiscard]] EmojiStatusData parse(const MTPEmojiStatus &status);
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
[[nodiscard]] rpl::producer<> collectiblesUpdates() const;
void set(DocumentId id, TimeId until = 0);
void set(not_null<PeerData*> peer, DocumentId id, TimeId until = 0);
void set(EmojiStatusId id, TimeId until = 0);
void set(not_null<PeerData*> peer, EmojiStatusId id, TimeId until = 0);
[[nodiscard]] EmojiStatusId fromUniqueGift(const Data::UniqueGift &gift);
[[nodiscard]] EmojiStatusCollectible *collectibleInfo(CollectibleId id);
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
@ -79,31 +107,42 @@ private:
void requestColored();
void requestChannelDefault();
void requestChannelColored();
void requestCollectibles();
void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data);
void updateColored(const MTPDmessages_stickerSet &data);
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
void updateChannelColored(const MTPDmessages_stickerSet &data);
void updateCollectibles(const MTPDaccount_emojiStatuses &data);
void processClearingIn(TimeId wait);
void processClearing();
[[nodiscard]] std::vector<EmojiStatusId> parse(
const MTPDaccount_emojiStatuses &data);
template <typename Request>
void requestGroups(not_null<GroupsType*> type, Request &&request);
const not_null<Session*> _owner;
std::vector<DocumentId> _recent;
std::vector<DocumentId> _default;
std::vector<DocumentId> _colored;
std::vector<DocumentId> _channelDefault;
std::vector<DocumentId> _channelColored;
std::vector<EmojiStatusId> _recent;
std::vector<EmojiStatusId> _default;
std::vector<EmojiStatusId> _colored;
std::vector<EmojiStatusId> _channelDefault;
std::vector<EmojiStatusId> _channelColored;
std::vector<EmojiStatusId> _collectibles;
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _coloredUpdated;
rpl::event_stream<> _channelDefaultUpdated;
rpl::event_stream<> _channelColoredUpdated;
rpl::event_stream<> _collectiblesUpdated;
base::flat_map<
CollectibleId,
std::shared_ptr<EmojiStatusCollectible>> _collectibleData;
mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false;
@ -119,6 +158,9 @@ private:
mtpRequestId _channelColoredRequestId = 0;
mtpRequestId _collectiblesRequestId = 0;
uint64 _collectiblesHash = 0;
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
@ -133,10 +175,4 @@ private:
};
struct EmojiStatusData {
DocumentId id = 0;
TimeId until = 0;
};
[[nodiscard]] EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status);
} // namespace Data

View file

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

View file

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

View file

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

View file

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

View file

@ -652,6 +652,20 @@ bool PeerData::canManageTopics() const {
return false;
}
bool PeerData::canManageGifts() const {
if (const auto channel = asChannel()) {
return channel->canPostMessages();
}
return isSelf();
}
bool PeerData::canTransferGifts() const {
if (const auto channel = asChannel()) {
return channel->amCreator();
}
return isSelf();
}
bool PeerData::canEditMessagesIndefinitely() const {
if (const auto user = asUser()) {
return user->isSelf();
@ -911,6 +925,16 @@ void PeerData::fullUpdated() {
setLoadedStatus(LoadedStatus::Full);
}
UserData *PeerData::asBot() {
return isBot() ? static_cast<UserData*>(this) : nullptr;
}
const UserData *PeerData::asBot() const {
return isBot()
? static_cast<const UserData*>(this)
: nullptr;
}
UserData *PeerData::asUser() {
return isUser() ? static_cast<UserData*>(this) : nullptr;
}
@ -1115,11 +1139,11 @@ bool PeerData::changeBackgroundEmojiId(DocumentId id) {
}
void PeerData::setEmojiStatus(const MTPEmojiStatus &status) {
const auto parsed = Data::ParseEmojiStatus(status);
const auto parsed = owner().emojiStatuses().parse(status);
setEmojiStatus(parsed.id, parsed.until);
}
void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
void PeerData::setEmojiStatus(EmojiStatusId emojiStatusId, TimeId until) {
if (_emojiStatusId != emojiStatusId) {
_emojiStatusId = emojiStatusId;
session().changes().peerUpdated(this, UpdateFlag::EmojiStatus);
@ -1127,10 +1151,17 @@ void PeerData::setEmojiStatus(DocumentId emojiStatusId, TimeId until) {
owner().emojiStatuses().registerAutomaticClear(this, until);
}
DocumentId PeerData::emojiStatusId() const {
EmojiStatusId PeerData::emojiStatusId() const {
return _emojiStatusId;
}
bool PeerData::isBot() const {
if (const auto user = asUser()) {
return user->isBot();
}
return false;
}
bool PeerData::isSelf() const {
if (const auto user = asUser()) {
return (user->flags() & UserDataFlag::Self);
@ -1515,6 +1546,15 @@ void PeerData::setStoriesState(StoriesState state) {
}
}
int PeerData::peerGiftsCount() const {
if (const auto user = asUser()) {
return user->peerGiftsCount();
} else if (const auto channel = asChannel()) {
return channel->peerGiftsCount();
}
return 0;
}
void PeerData::setIsBlocked(bool is) {
const auto status = is
? BlockStatus::Blocked

View file

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

View file

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

View file

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

View file

@ -37,7 +37,11 @@ struct UniqueGiftOriginalDetails {
};
struct UniqueGift {
CollectibleId id = 0;
QString slug;
QString title;
QString ownerAddress;
QString ownerName;
PeerId ownerId = 0;
int number = 0;
int starsForTransfer = -1;
@ -71,13 +75,60 @@ struct StarGift {
const StarGift &) = default;
};
struct UserStarGift {
class SavedStarGiftId {
public:
[[nodiscard]] static SavedStarGiftId User(MsgId messageId) {
auto result = SavedStarGiftId();
result.entityId = uint64(messageId.bare);
return result;
}
[[nodiscard]] static SavedStarGiftId Chat(
not_null<PeerData*> peer,
uint64 savedId) {
auto result = SavedStarGiftId();
result.peer = peer;
result.entityId = savedId;
return result;
}
[[nodiscard]] bool isUser() const {
return !peer;
}
[[nodiscard]] bool isChat() const {
return peer != nullptr;
}
[[nodiscard]] MsgId userMessageId() const {
return peer ? MsgId(0) : MsgId(entityId);
}
[[nodiscard]] PeerData *chat() const {
return peer;
}
[[nodiscard]] uint64 chatSavedId() const {
return peer ? entityId : 0;
}
explicit operator bool() const {
return entityId != 0;
}
friend inline bool operator==(
const SavedStarGiftId &a,
const SavedStarGiftId &b) = default;
private:
PeerData *peer = nullptr;
uint64 entityId = 0;
};
struct SavedStarGift {
StarGift info;
SavedStarGiftId manageId;
TextWithEntities message;
int64 starsConverted = 0;
int64 starsUpgradedBySender = 0;
PeerId fromId = 0;
MsgId messageId = 0;
TimeId date = 0;
bool upgradable = false;
bool anonymous = false;

View file

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

View file

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

View file

@ -37,6 +37,7 @@ using Options = base::flags<Option>;
namespace Data {
struct FileOrigin;
struct EmojiStatusCollectible;
struct UploadState {
explicit UploadState(int64 size) : size(size) {
@ -139,6 +140,23 @@ using WallPaperId = uint64;
using CallId = uint64;
using BotAppId = uint64;
using EffectId = uint64;
using CollectibleId = uint64;
struct EmojiStatusId {
DocumentId documentId = 0;
std::shared_ptr<Data::EmojiStatusCollectible> collectible;
explicit operator bool() const {
return documentId || collectible;
}
friend inline auto operator<=>(
const EmojiStatusId &,
const EmojiStatusId &) = default;
friend inline bool operator==(
const EmojiStatusId &,
const EmojiStatusId &) = default;
};
constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL);

View file

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

View file

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

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_emoji_statuses.h"
#include "data/data_file_origin.h"
#include "data/data_forum_topic.h" // ParseTopicIconEmojiEntity.
#include "data/data_peer.h"
@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h"
#include "storage/file_download.h" // kMaxFileInMemory
#include "ui/chat/chats_filter_tag.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/credits_graphics.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/text/custom_emoji_instance.h"
@ -117,6 +119,10 @@ private:
return u"force-static:"_q;
}
[[nodiscard]] QString CollectiblePrefix() {
return u"collectible:"_q;
}
[[nodiscard]] QString InternalPadding(QMargins value) {
return value.isNull() ? QString() : QString(",%1,%2,%3,%4"
).arg(value.left()
@ -568,6 +574,20 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
const auto ratio = style::DevicePixelRatio();
const auto size = EmojiSizeFromTag(tag) / ratio;
return userpic(data, std::move(update), size);
} else if (data.startsWith(CollectiblePrefix())) {
const auto id = data.mid(CollectiblePrefix().size()).toULongLong();
const auto emojiStatuses = &session().data().emojiStatuses();
auto info = emojiStatuses->collectibleInfo(id);
Assert(info != nullptr);
const auto documentId = info->documentId;
auto inner = create(documentId, base::duplicate(update), tag);
return Ui::Premium::MakeCollectibleEmoji(
data,
info->centerColor,
info->edgeColor,
std::move(inner),
std::move(update),
FrameSizeFromTag(tag) / style::DevicePixelRatio());
} else if (const auto parsed = Data::ParseTopicIconEmojiEntity(data)) {
return MakeTopicIconEmoji(parsed, std::move(update), tag);
}
@ -1147,4 +1167,14 @@ Ui::Text::CustomEmojiFactory ReactedMenuFactory(
};
}
QString CollectibleCustomEmojiId(Data::EmojiStatusCollectible &data) {
return CollectiblePrefix() + QString::number(data.id);
}
QString EmojiStatusCustomId(const EmojiStatusId &id) {
return id.collectible
? CollectibleCustomEmojiId(*id.collectible)
: SerializeCustomEmojiId(id.documentId);
}
} // namespace Data

View file

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

View file

@ -88,8 +88,7 @@ void MaybeShowPremiumToast(
return;
}
const auto filter = [=](const auto ...) {
const auto usage = ChatHelpers::WindowUsage::PremiumPromo;
if (const auto controller = show->resolveWindow(usage)) {
if (const auto controller = show->resolveWindow()) {
Settings::ShowPremium(controller, ref);
}
return false;
@ -815,6 +814,25 @@ void Stickers::setPackAndEmoji(
}
}
not_null<StickersSet*> Stickers::collectibleSet() {
const auto setId = CollectibleSetId;
auto &sets = setsRef();
auto it = sets.find(setId);
if (it == sets.cend()) {
it = sets.emplace(setId, std::make_unique<StickersSet>(
&owner(),
setId,
uint64(0), // accessHash
uint64(0), // hash
tr::lng_collectible_emoji(tr::now),
QString(),
0, // count
SetFlag::Special,
TimeId(0))).first;
}
return it->second.get();
}
void Stickers::specialSetReceived(
uint64 setId,
const QString &setTitle,

View file

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

View file

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

View file

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

View file

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

View file

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

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