diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 0ec5a06b0..6a71c866d 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -288,6 +288,8 @@ PRIVATE
boxes/peers/peer_short_info_box.h
boxes/peers/prepare_short_info_box.cpp
boxes/peers/prepare_short_info_box.h
+ boxes/peers/replace_boost_box.cpp
+ boxes/peers/replace_boost_box.h
boxes/about_box.cpp
boxes/about_box.h
boxes/about_sponsored_box.cpp
@@ -1922,6 +1924,7 @@ if (LINUX AND DESKTOP_APP_USE_PACKAGED)
install(FILES "Resources/art/icon128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "ayugram.png")
install(FILES "Resources/art/icon256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "ayugram.png")
install(FILES "Resources/art/icon512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "ayugram.png")
+ install(FILES "Resources/icons/tray_monochrome.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/symbolic/apps" RENAME "telegram-symbolic.svg")
install(FILES "../lib/xdg/ayugram.desktop.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")
diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed.png b/Telegram/Resources/icons/boosts/boost_unclaimed.png
new file mode 100644
index 000000000..fb093176f
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed.png differ
diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png b/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png
new file mode 100644
index 000000000..30d5bd6bc
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed@2x.png differ
diff --git a/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png b/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png
new file mode 100644
index 000000000..f63cca1c9
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unclaimed@3x.png differ
diff --git a/Telegram/Resources/icons/boosts/boost_unknown.png b/Telegram/Resources/icons/boosts/boost_unknown.png
new file mode 100644
index 000000000..78f642852
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown.png differ
diff --git a/Telegram/Resources/icons/boosts/boost_unknown@2x.png b/Telegram/Resources/icons/boosts/boost_unknown@2x.png
new file mode 100644
index 000000000..ad22dc971
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown@2x.png differ
diff --git a/Telegram/Resources/icons/boosts/boost_unknown@3x.png b/Telegram/Resources/icons/boosts/boost_unknown@3x.png
new file mode 100644
index 000000000..bbf4532a2
Binary files /dev/null and b/Telegram/Resources/icons/boosts/boost_unknown@3x.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_gift.png b/Telegram/Resources/icons/boosts/mini_gift.png
new file mode 100644
index 000000000..1008e5124
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_gift@2x.png b/Telegram/Resources/icons/boosts/mini_gift@2x.png
new file mode 100644
index 000000000..462d38764
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift@2x.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_gift@3x.png b/Telegram/Resources/icons/boosts/mini_gift@3x.png
new file mode 100644
index 000000000..8858b4fff
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_gift@3x.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_giveaway.png b/Telegram/Resources/icons/boosts/mini_giveaway.png
new file mode 100644
index 000000000..79ffc0f1d
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_giveaway@2x.png b/Telegram/Resources/icons/boosts/mini_giveaway@2x.png
new file mode 100644
index 000000000..bb5e94a65
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway@2x.png differ
diff --git a/Telegram/Resources/icons/boosts/mini_giveaway@3x.png b/Telegram/Resources/icons/boosts/mini_giveaway@3x.png
new file mode 100644
index 000000000..c9f853452
Binary files /dev/null and b/Telegram/Resources/icons/boosts/mini_giveaway@3x.png differ
diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge.png b/Telegram/Resources/icons/chat/link_photo_enlarge.png
new file mode 100644
index 000000000..c4ba35289
Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge.png differ
diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png b/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png
new file mode 100644
index 000000000..02e1328ed
Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge@2x.png differ
diff --git a/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png b/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png
new file mode 100644
index 000000000..3dbc8aad4
Binary files /dev/null and b/Telegram/Resources/icons/chat/link_photo_enlarge@3x.png differ
diff --git a/Telegram/Resources/icons/menu/forward.png b/Telegram/Resources/icons/menu/forward.png
index abdda6045..4a50099f4 100644
Binary files a/Telegram/Resources/icons/menu/forward.png and b/Telegram/Resources/icons/menu/forward.png differ
diff --git a/Telegram/Resources/icons/menu/forward@2x.png b/Telegram/Resources/icons/menu/forward@2x.png
index 5f51d642d..c27765246 100644
Binary files a/Telegram/Resources/icons/menu/forward@2x.png and b/Telegram/Resources/icons/menu/forward@2x.png differ
diff --git a/Telegram/Resources/icons/menu/forward@3x.png b/Telegram/Resources/icons/menu/forward@3x.png
index d89d37bc6..557b032ef 100644
Binary files a/Telegram/Resources/icons/menu/forward@3x.png and b/Telegram/Resources/icons/menu/forward@3x.png differ
diff --git a/Telegram/Resources/icons/menu/reply.png b/Telegram/Resources/icons/menu/reply.png
index cc12969ec..a40619d2e 100644
Binary files a/Telegram/Resources/icons/menu/reply.png and b/Telegram/Resources/icons/menu/reply.png differ
diff --git a/Telegram/Resources/icons/menu/reply@2x.png b/Telegram/Resources/icons/menu/reply@2x.png
index e4fe939f8..8ea749d73 100644
Binary files a/Telegram/Resources/icons/menu/reply@2x.png and b/Telegram/Resources/icons/menu/reply@2x.png differ
diff --git a/Telegram/Resources/icons/menu/reply@3x.png b/Telegram/Resources/icons/menu/reply@3x.png
index e5825a5a9..04c83d47f 100644
Binary files a/Telegram/Resources/icons/menu/reply@3x.png and b/Telegram/Resources/icons/menu/reply@3x.png differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 8c2ffe471..dc5161a5b 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -2030,6 +2030,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_terms_link" = "here";
"lng_boost_channel_button" = "Boost Channel";
+"lng_boost_again_button" = "Boost Again";
"lng_boost_level#one" = "Level {count}";
"lng_boost_level#other" = "Level {count}";
"lng_boost_channel_title_first" = "Enable stories for channel";
@@ -2051,6 +2052,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
+"lng_boost_need_more" = "More boosts needed";
+"lng_boost_need_more_text#one" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
+"lng_boost_need_more_text#other" = "To boost {channel}, gift **Telegram Premium** to a friend and get **{count}** boosts.";
+"lng_boost_need_more_again#one" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boost.";
+"lng_boost_need_more_again#other" = "To boost {channel} again, gift **Telegram Premium** to a friend and get **{count}** additional boosts.";
"lng_boost_error_already_title" = "Already Boosted!";
"lng_boost_error_already_text" = "You are already boosting this channel.";
"lng_boost_error_premium_title" = "Premium needed!";
@@ -2060,6 +2066,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}.";
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace";
+"lng_boost_reassign_title" = "Reassign boost";
+"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost or {gift}.";
+"lng_boost_reassign_gift#one" = "gift **Telegram Premium** to a friend to get **{count}** additional boost";
+"lng_boost_reassign_gift#other" = "gift **Telegram Premium** to a friend to get **{count}** additional boosts";
+"lng_boost_remove_title" = "Remove your boost from";
+"lng_boost_reassign_button" = "Reassign";
+"lng_boost_available_in" = "available in {duration}";
+"lng_boost_available_in_toast#one" = "Wait until the boost is available or get **{count}** more boost by gifting a **Telegram Premium** subscription.";
+"lng_boost_available_in_toast#other" = "Wait until the boost is available or get **{count}** more boosts by gifting a **Telegram Premium** subscription.";
+"lng_boost_reassign_done#one" = "{count} boost is reassigned from {channels}.";
+"lng_boost_reassign_done#other" = "{count} boosts are reassigned from {channels}.";
+"lng_boost_reassign_channels#one" = "{count} channel";
+"lng_boost_reassign_channels#other" = "{count} channels";
"lng_boost_channel_title_color" = "Enable colors";
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";
@@ -2097,6 +2116,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to those who joined the channel after the giveaway started or to users from specific countries.";
"lng_giveaway_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium";
+"lng_giveaway_start_sure" = "Are you sure you want to start this prepaid giveaway now? This action cannot be undone.";
"lng_giveaway_date_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time";
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
@@ -2199,6 +2219,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
"lng_gift_link_used_footer" = "This link was used on {date}.";
"lng_gift_link_expired" = "Gift code link expired";
+"lng_gift_link_pending_about" = "This link allows {user} to activate\na **Telegram Premium** subscription.";
+"lng_gift_link_pending_toast" = "Only the recipient can see the link.";
+"lng_gift_link_pending_footer" = "This link hasn't been activated yet.";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
@@ -4325,8 +4348,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_existing" = "Existing boosts";
"lng_boosts_premium_audience" = "Premium subscribers";
"lng_boosts_next_level" = "Boosts to level up";
-"lng_boosts_list_title#one" = "{count} booster";
-"lng_boosts_list_title#other" = "{count} boosters";
+"lng_boosts_list_title#one" = "{count} Boost";
+"lng_boosts_list_title#other" = "{count} Boosts";
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users.";
"lng_boosts_show_more_boosts#one" = "Show {count} More Boosts";
"lng_boosts_show_more_boosts#other" = "Show {count} More Boosts";
@@ -4343,6 +4366,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boosts_list_tab_gifts#one" = "{count} Gifts";
"lng_boosts_list_tab_gifts#other" = "{count} Gifts";
+"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
+"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";
+"lng_boosts_prepaid_giveaway_moths#one" = "{count}-month subscriptions";
+"lng_boosts_prepaid_giveaway_moths#other" = "{count}-month subscriptions";
+"lng_boosts_prepaid_giveaway_status#one" = "{count} subscription {duration}";
+"lng_boosts_prepaid_giveaway_status#other" = "{count} subscriptions {duration}";
+
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 522be5d7b..87d43e603 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.11.6.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 2f61f1e73..53ffeebbe 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,11,5,0
- PRODUCTVERSION 4,11,5,0
+ FILEVERSION 4,11,6,0
+ PRODUCTVERSION 4,11,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.11.5.0"
+ VALUE "FileVersion", "4.11.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.11.5.0"
+ VALUE "ProductVersion", "4.11.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index ff45afad7..22190fb9d 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,11,5,0
- PRODUCTVERSION 4,11,5,0
+ FILEVERSION 4,11,6,0
+ PRODUCTVERSION 4,11,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.11.5.0"
+ VALUE "FileVersion", "4.11.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.11.5.0"
+ VALUE "ProductVersion", "4.11.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp
index 629abc0d0..58950f6f4 100644
--- a/Telegram/SourceFiles/api/api_premium.cpp
+++ b/Telegram/SourceFiles/api/api_premium.cpp
@@ -390,18 +390,44 @@ rpl::producer PremiumGiftCodeOptions::request() {
};
}
+rpl::producer PremiumGiftCodeOptions::applyPrepaid(
+ const Payments::InvoicePremiumGiftCode &invoice,
+ uint64 prepaidId) {
+ return [=](auto consumer) {
+ auto lifetime = rpl::lifetime();
+ const auto channel = _peer->asChannel();
+ if (!channel) {
+ return lifetime;
+ }
+
+ _api.request(MTPpayments_LaunchPrepaidGiveaway(
+ _peer->input,
+ MTP_long(prepaidId),
+ Payments::InvoicePremiumGiftCodeGiveawayToTL(invoice)
+ )).done([=](const MTPUpdates &result) {
+ _peer->session().api().applyUpdates(result);
+ consumer.put_done();
+ }).fail([=](const MTP::Error &error) {
+ consumer.put_error_copy(error.type());
+ }).send();
+
+ return lifetime;
+ };
+}
+
const std::vector &PremiumGiftCodeOptions::availablePresets() const {
return _availablePresets;
}
+[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
+ return _optionsForOnePerson.months[monthsIndex];
+}
+
Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
int users,
- int monthsIndex) {
+ int months) {
const auto randomId = base::RandomValue();
- const auto token = Token{
- users,
- _optionsForOnePerson.months[monthsIndex],
- };
+ const auto token = Token{ users, months };
const auto &store = _stores[token];
return Payments::InvoicePremiumGiftCode{
.randomId = randomId,
diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h
index deb66df80..1199b4819 100644
--- a/Telegram/SourceFiles/api/api_premium.h
+++ b/Telegram/SourceFiles/api/api_premium.h
@@ -152,9 +152,13 @@ public:
[[nodiscard]] rpl::producer request();
[[nodiscard]] Data::SubscriptionOptions options(int amount);
[[nodiscard]] const std::vector &availablePresets() const;
+ [[nodiscard]] int monthsFromPreset(int monthsIndex);
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
int users,
- int monthsIndex);
+ int months);
+ [[nodiscard]] rpl::producer applyPrepaid(
+ const Payments::InvoicePremiumGiftCode &invoice,
+ uint64 prepaidId);
[[nodiscard]] int giveawayBoostsPerPremium() const;
[[nodiscard]] int giveawayCountriesMax() const;
diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp
index 3069843c1..383d0973c 100644
--- a/Telegram/SourceFiles/api/api_statistics.cpp
+++ b/Telegram/SourceFiles/api/api_statistics.cpp
@@ -518,8 +518,9 @@ rpl::producer Boosts::request() {
? (100. * premiumMemberCount / participantCount)
: 0;
+ const auto slots = data.vmy_boost_slots();
_boostStatus.overview = Data::BoostsOverview{
- .isBoosted = data.is_my_boost(),
+ .mine = slots ? int(slots->v.size()) : 0,
.level = std::max(data.vlevel().v, 0),
.boostCount = std::max(
data.vboosts().v,
@@ -533,6 +534,20 @@ rpl::producer Boosts::request() {
};
_boostStatus.link = qs(data.vboost_url());
+ if (data.vprepaid_giveaways()) {
+ _boostStatus.prepaidGiveaway = ranges::views::all(
+ data.vprepaid_giveaways()->v
+ ) | ranges::views::transform([](const MTPPrepaidGiveaway &r) {
+ return Data::BoostPrepaidGiveaway{
+ .months = r.data().vmonths().v,
+ .id = r.data().vid().v,
+ .quantity = r.data().vquantity().v,
+ .date = QDateTime::fromSecsSinceEpoch(
+ r.data().vdate().v),
+ };
+ }) | ranges::to_vector;
+ }
+
using namespace Data;
requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) {
_boostStatus.firstSliceBoosts = std::move(slice);
@@ -540,7 +555,6 @@ rpl::producer Boosts::request() {
_boostStatus.firstSliceGifts = std::move(s);
consumer.put_done();
});
- consumer.put_done();
});
}).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type());
@@ -574,6 +588,7 @@ void Boosts::requestBoosts(
auto list = std::vector();
list.reserve(data.vboosts().v.size());
+ constexpr auto kMonthsDivider = int(30 * 86400);
for (const auto &boost : data.vboosts().v) {
const auto &data = boost.data();
const auto path = data.vused_gift_slug()
@@ -596,7 +611,8 @@ void Boosts::requestBoosts(
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
: FullMsgId(),
QDateTime::fromSecsSinceEpoch(data.vdate().v),
- data.vexpires().v,
+ QDateTime::fromSecsSinceEpoch(data.vexpires().v),
+ (data.vexpires().v - data.vdate().v) / kMonthsDivider,
std::move(giftCodeLink),
data.vmultiplier().value_or_empty(),
});
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
index 61e937dfe..989a9867b 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_chats_list.cpp
@@ -362,7 +362,7 @@ void EditFilterChatsListController::rowClicked(not_null row) {
delegate()->peerListSetRowChecked(row, !row->checked());
updateTitle();
} else {
- delegate()->peerListShowBox(_limitBox(count));
+ delegate()->peerListUiShow()->showBox(_limitBox(count));
}
}
diff --git a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
index d731ead51..9dcf6fd80 100644
--- a/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
+++ b/Telegram/SourceFiles/boxes/filters/edit_filter_links.cpp
@@ -575,19 +575,19 @@ void LinkController::addLinkBlock(not_null container) {
CopyInviteLink(delegate()->peerListUiShow(), link);
});
const auto shareLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(&_window->session(), link));
});
const auto getLinkQr = crl::guard(weak, [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
});
const auto editLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
Box(ChatFilterLinkBox, &_window->session(), _data));
});
const auto deleteLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(DeleteLinkBox(_window, _data));
+ delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, _data));
});
const auto createMenu = [=] {
@@ -846,7 +846,7 @@ void LinksController::rebuild(const std::vector &rows) {
void LinksController::rowClicked(not_null row) {
const auto link = static_cast(row.get())->data();
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
ShowLinkBox(_window, _currentFilter(), link));
}
@@ -881,19 +881,19 @@ base::unique_qptr LinksController::createRowContextMenu(
CopyInviteLink(delegate()->peerListUiShow(), link);
};
const auto shareLink = [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(&_window->session(), link));
};
const auto getLinkQr = [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
};
const auto editLink = [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
Box(ChatFilterLinkBox, &_window->session(), data));
};
const auto deleteLink = [=] {
- delegate()->peerListShowBox(DeleteLinkBox(_window, data));
+ delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, data));
};
auto result = base::make_unique_q(
parent,
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index 9a87a58fc..1244964d2 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/gift_premium_box.h"
-#include "apiwrap.h"
#include "api/api_premium.h"
#include "api/api_premium_option.h"
+#include "apiwrap.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.h"
@@ -31,7 +31,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h"
+#include "ui/effects/spoiler_mess.h"
#include "ui/layers/generic_box.h"
+#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
@@ -93,7 +95,7 @@ void GiftBox(
+ st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
- const auto stars = box->lifetime().make_state(top);
+ const auto stars = box->lifetime().make_state(top, true);
const auto userpic = Ui::CreateChild(
top,
@@ -358,6 +360,73 @@ void AddTableRow(
st::giveawayGiftCodePeerMargin);
}
+void AddTable(
+ not_null container,
+ not_null controller,
+ const Api::GiftCode ¤t,
+ bool skipReason) {
+ auto table = container->add(
+ object_ptr(
+ container,
+ st::giveawayGiftCodeTable),
+ st::giveawayGiftCodeTableMargin);
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_from(),
+ controller,
+ current.from);
+ if (current.to) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_to(),
+ controller,
+ current.to);
+ } else {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_to(),
+ tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
+ }
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_gift(),
+ tr::lng_gift_link_gift_premium(
+ lt_duration,
+ GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
+ Ui::Text::WithEntities));
+ if (!skipReason) {
+ const auto reason = AddTableRow(
+ table,
+ tr::lng_gift_link_label_reason(),
+ (current.giveawayId
+ ? ((current.to
+ ? tr::lng_gift_link_reason_giveaway
+ : tr::lng_gift_link_reason_unclaimed)(
+ ) | Ui::Text::ToLink())
+ : current.giveaway
+ ? ((current.to
+ ? tr::lng_gift_link_reason_giveaway
+ : tr::lng_gift_link_reason_unclaimed)(
+ Ui::Text::WithEntities
+ ) | rpl::type_erased())
+ : tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
+ reason->setClickHandlerFilter([=](const auto &...) {
+ controller->showPeerHistory(
+ current.from,
+ Window::SectionShow::Way::Forward,
+ current.giveawayId);
+ return false;
+ });
+ }
+ if (current.date) {
+ AddTableRow(
+ table,
+ tr::lng_gift_link_label_date(),
+ rpl::single(Ui::Text::WithEntities(
+ langDateTime(base::unixtime::parse(current.date)))));
+ }
+}
+
} // namespace
GiftPremiumValidator::GiftPremiumValidator(
@@ -462,65 +531,7 @@ void GiftCodeBox(
MakeLinkCopyIcon(box)),
st::giveawayGiftCodeLinkMargin);
- auto table = box->addRow(
- object_ptr(
- box,
- st::giveawayGiftCodeTable),
- st::giveawayGiftCodeTableMargin);
- const auto current = state->data.current();
- AddTableRow(
- table,
- tr::lng_gift_link_label_from(),
- controller,
- current.from);
- if (current.to) {
- AddTableRow(
- table,
- tr::lng_gift_link_label_to(),
- controller,
- current.to);
- } else {
- AddTableRow(
- table,
- tr::lng_gift_link_label_to(),
- tr::lng_gift_link_label_to_unclaimed(Ui::Text::WithEntities));
- }
- AddTableRow(
- table,
- tr::lng_gift_link_label_gift(),
- tr::lng_gift_link_gift_premium(
- lt_duration,
- GiftDurationValue(current.months) | Ui::Text::ToWithEntities(),
- Ui::Text::WithEntities));
- const auto reason = AddTableRow(
- table,
- tr::lng_gift_link_label_reason(),
- (current.giveawayId
- ? ((current.to
- ? tr::lng_gift_link_reason_giveaway
- : tr::lng_gift_link_reason_unclaimed)(
- ) | Ui::Text::ToLink())
- : current.giveaway
- ? ((current.to
- ? tr::lng_gift_link_reason_giveaway
- : tr::lng_gift_link_reason_unclaimed)(
- Ui::Text::WithEntities
- ) | rpl::type_erased())
- : tr::lng_gift_link_reason_chosen(Ui::Text::WithEntities)));
- reason->setClickHandlerFilter([=](const auto &...) {
- controller->showPeerHistory(
- current.from,
- Window::SectionShow::Way::Forward,
- current.giveawayId);
- return false;
- });
- if (current.date) {
- AddTableRow(
- table,
- tr::lng_gift_link_label_date(),
- rpl::single(Ui::Text::WithEntities(
- langDateTime(base::unixtime::parse(current.date)))));
- }
+ AddTable(box->verticalLayout(), controller, state->data.current(), false);
auto shareLink = tr::lng_gift_link_also_send_link(
) | rpl::map([](const QString &text) {
@@ -603,6 +614,113 @@ void GiftCodeBox(
}, button->lifetime());
}
+
+void GiftCodePendingBox(
+ not_null box,
+ not_null controller,
+ const Api::GiftCode &data) {
+ box->setWidth(st::boxWideWidth);
+ box->setStyle(st::giveawayGiftCodeBox);
+ box->setNoContentMargin(true);
+
+ {
+ const auto peerTo = controller->session().data().peer(data.to);
+ const auto clickContext = [=, weak = base::make_weak(controller)] {
+ if (const auto strong = weak.get()) {
+ strong->uiShow()->showBox(
+ PrepareShortInfoBox(peerTo, strong));
+ }
+ return QVariant();
+ };
+ const auto &st = st::giveawayGiftCodeCover;
+ const auto resultToName = st.about.style.font->elided(
+ peerTo->shortName(),
+ st.about.minWidth / 2,
+ Qt::ElideMiddle);
+ const auto bar = box->setPinnedToTopContent(
+ object_ptr(
+ box,
+ st,
+ clickContext,
+ tr::lng_gift_link_title(),
+ tr::lng_gift_link_pending_about(
+ lt_user,
+ rpl::single(Ui::Text::Link(resultToName)),
+ Ui::Text::RichLangValue),
+ true));
+
+ const auto max = st::giveawayGiftCodeTopHeight;
+ bar->setMaximumHeight(max);
+ bar->setMinimumHeight(st::infoLayerTopBarHeight);
+
+ bar->resize(bar->width(), bar->maximumHeight());
+ }
+
+ {
+ const auto linkLabel = box->addRow(
+ Ui::MakeLinkLabel(box, nullptr, nullptr, nullptr, nullptr),
+ st::giveawayGiftCodeLinkMargin);
+ const auto spoiler = Ui::CreateChild(linkLabel);
+ spoiler->lifetime().make_state([=] {
+ spoiler->update();
+ })->start();
+ linkLabel->sizeValue(
+ ) | rpl::start_with_next([=](const QSize &s) {
+ spoiler->setGeometry(Rect(s));
+ }, spoiler->lifetime());
+ const auto spoilerCached = Ui::SpoilerMessCached(
+ Ui::DefaultTextSpoilerMask(),
+ st::giveawayGiftCodeLink.textFg->c);
+ const auto textHeight = st::giveawayGiftCodeLink.style.font->height;
+ spoiler->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(spoiler);
+ const auto rect = spoiler->rect();
+ const auto r = rect
+ - QMargins(
+ st::boxRowPadding.left(),
+ (rect.height() - textHeight) / 2,
+ st::boxRowPadding.right(),
+ (rect.height() - textHeight) / 2);
+ Ui::FillSpoilerRect(p, r, spoilerCached.frame());
+ }, spoiler->lifetime());
+ spoiler->setClickedCallback([show = box->uiShow()] {
+ show->showToast(tr::lng_gift_link_pending_toast(tr::now));
+ });
+ spoiler->show();
+ }
+
+ AddTable(box->verticalLayout(), controller, data, true);
+
+ box->addRow(
+ object_ptr(
+ box,
+ tr::lng_gift_link_pending_footer(),
+ st::giveawayGiftCodeFooter),
+ st::giveawayGiftCodeFooterMargin);
+
+ const auto close = Ui::CreateChild(
+ box.get(),
+ st::boxTitleClose);
+ const auto closeCallback = [=] { box->closeBox(); };
+ close->setClickedCallback(closeCallback);
+ box->widthValue(
+ ) | rpl::start_with_next([=](int width) {
+ close->moveToRight(0, 0);
+ }, box->lifetime());
+
+ const auto button = box->addButton(tr::lng_close(), closeCallback);
+ const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
+ const auto buttonWidth = st::boxWideWidth
+ - buttonPadding.left()
+ - buttonPadding.right();
+ button->widthValue() | rpl::filter([=] {
+ return (button->widthNoMargins() != buttonWidth);
+ }) | rpl::start_with_next([=] {
+ button->resizeToWidth(buttonWidth);
+ }, button->lifetime());
+}
+
void ResolveGiftCode(
not_null controller,
const QString &slug) {
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index a1062dead..1891ce5ff 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -50,6 +50,10 @@ void GiftCodeBox(
not_null box,
not_null controller,
const QString &slug);
+void GiftCodePendingBox(
+ not_null box,
+ not_null controller,
+ const Api::GiftCode &data);
void ResolveGiftCode(
not_null controller,
const QString &slug);
diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp
index dd165ac61..e320b434f 100644
--- a/Telegram/SourceFiles/boxes/passcode_box.cpp
+++ b/Telegram/SourceFiles/boxes/passcode_box.cpp
@@ -567,12 +567,10 @@ void PasscodeBox::validateEmail(
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
const auto weak = Ui::MakeWeak(this);
_clearUnconfirmedPassword.fire({});
- if (weak) {
- auto box = Ui::MakeInformBox({
- Lang::Hard::EmailConfirmationExpired()
- });
- weak->getDelegate()->show(
- std::move(box),
+ if (const auto strong = weak.data()) {
+ strong->getDelegate()->show(
+ Ui::MakeInformBox(
+ Lang::Hard::EmailConfirmationExpired()),
Ui::LayerOption::CloseOther);
}
} else {
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index abd4f4b32..9f12a539e 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -85,16 +85,6 @@ PeerListContentDelegateShow::PeerListContentDelegateShow(
: _show(show) {
}
-void PeerListContentDelegateShow::peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options) {
- _show->showBox(std::move(content), options);
-}
-
-void PeerListContentDelegateShow::peerListHideLayer() {
- _show->hideLayer();
-}
-
auto PeerListContentDelegateShow::peerListUiShow()
-> std::shared_ptr{
return _show;
@@ -324,16 +314,6 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) {
}
}
-void PeerListBox::peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options) {
- _show->showBox(std::move(content), options);
-}
-
-void PeerListBox::peerListHideLayer() {
- _show->hideLayer();
-}
-
std::shared_ptr PeerListBox::peerListUiShow() {
return _show;
}
@@ -1702,9 +1682,19 @@ crl::time PeerListContent::paintRow(
return refreshStatusIn;
}
+ const auto opacity = row->opacity();
const auto &bg = selected
? _st.item.button.textBgOver
: _st.item.button.textBg;
+ if (opacity < 1.) {
+ p.setOpacity(opacity);
+ }
+ const auto guard = gsl::finally([&] {
+ if (opacity < 1.) {
+ p.setOpacity(1.);
+ }
+ });
+
p.fillRect(0, 0, outerWidth, _rowHeight, bg);
row->paintRipple(p, 0, 0, outerWidth);
row->paintUserpic(
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h
index 0f557da68..e99469b9e 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.h
+++ b/Telegram/SourceFiles/boxes/peer_list_box.h
@@ -141,6 +141,9 @@ public:
}
virtual void rightActionStopLastRipple() {
}
+ [[nodiscard]] virtual float64 opacity() {
+ return 1.;
+ }
// By default elements code falls back to a simple right action code.
virtual int elementsCount() const;
@@ -332,10 +335,6 @@ public:
virtual std::optional peerListLastRowMousePosition() = 0;
virtual void peerListSortRows(Fn compare) = 0;
virtual int peerListPartitionRows(Fn border) = 0;
- virtual void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) = 0;
- virtual void peerListHideLayer() = 0;
virtual std::shared_ptr peerListUiShow() = 0;
template
@@ -1007,14 +1006,6 @@ public:
object_ptr description) override {
description.destroy();
}
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override {
- Unexpected("...DelegateSimple::peerListShowBox");
- }
- void peerListHideLayer() override {
- Unexpected("...DelegateSimple::peerListHideLayer");
- }
std::shared_ptr peerListUiShow() override {
Unexpected("...DelegateSimple::peerListUiShow");
}
@@ -1025,10 +1016,6 @@ class PeerListContentDelegateShow : public PeerListContentDelegateSimple {
public:
explicit PeerListContentDelegateShow(
std::shared_ptr show);
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
- void peerListHideLayer() override;
std::shared_ptr peerListUiShow() override;
private:
@@ -1064,10 +1051,6 @@ public:
bool peerListIsRowChecked(not_null row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
- void peerListHideLayer() override;
std::shared_ptr peerListUiShow() override;
void setAddedTopScrollSkip(int skip);
diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
index df1a24fba..bad9dbd89 100644
--- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp
@@ -685,7 +685,7 @@ void ChooseRecipientBoxController::rowClicked(not_null row) {
}, box->lifetime());
});
*weak = owned.data();
- delegate()->peerListShowBox(std::move(owned));
+ delegate()->peerListUiShow()->showBox(std::move(owned));
return;
}
const auto history = peer->owner().history(peer);
diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.cpp b/Telegram/SourceFiles/boxes/peer_lists_box.cpp
index e445a21e2..80e2a9334 100644
--- a/Telegram/SourceFiles/boxes/peer_lists_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_lists_box.cpp
@@ -372,16 +372,6 @@ void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
_box->_select->entity()->finishItemsBunch();
}
-void PeerListsBox::Delegate::peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options) {
- _show->showBox(std::move(content), options);
-}
-
-void PeerListsBox::Delegate::peerListHideLayer() {
- _show->hideLayer();
-}
-
auto PeerListsBox::Delegate::peerListUiShow()
-> std::shared_ptr {
return _show;
diff --git a/Telegram/SourceFiles/boxes/peer_lists_box.h b/Telegram/SourceFiles/boxes/peer_lists_box.h
index 94732b3e2..20377548a 100644
--- a/Telegram/SourceFiles/boxes/peer_lists_box.h
+++ b/Telegram/SourceFiles/boxes/peer_lists_box.h
@@ -54,10 +54,6 @@ private:
_box->addSelectItem(row, anim::type::instant);
}
void peerListFinishSelectedRowsBunch() override;
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
- void peerListHideLayer() override;
std::shared_ptr peerListUiShow() override;
private:
diff --git a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
index b40e1a0d2..575522e42 100644
--- a/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/add_participants_box.cpp
@@ -366,7 +366,7 @@ bool AddParticipantsBoxController::needsInviteLinkButton() {
QPointer AddParticipantsBoxController::showBox(
object_ptr box) const {
const auto weak = Ui::MakeWeak(box.data());
- delegate()->peerListShowBox(std::move(box));
+ delegate()->peerListUiShow()->showBox(std::move(box));
return weak;
}
@@ -668,7 +668,7 @@ void AddSpecialBoxController::migrate(
QPointer AddSpecialBoxController::showBox(
object_ptr box) const {
const auto weak = Ui::MakeWeak(box.data());
- delegate()->peerListShowBox(std::move(box));
+ delegate()->peerListUiShow()->showBox(std::move(box));
return weak;
}
diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
index 767b0c915..7e27128c4 100644
--- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp
@@ -433,7 +433,7 @@ void ChoosePeerBoxController::rowClicked(not_null row) {
if (const auto user = peer->asUser()) {
done();
} else {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
MakeConfirmBox(_bot, peer, _query, done));
}
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
index 48bc383c9..c3e4b3d34 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
@@ -168,7 +168,7 @@ void Controller::choose(not_null chat) {
const auto onstack = _callback;
onstack(chat);
};
- delegate()->peerListShowBox(Ui::MakeConfirmBox({
+ delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
.text = text,
.confirmed = sure,
.confirmText = tr::lng_manage_discussion_group_link(tr::now),
@@ -199,7 +199,7 @@ void Controller::choose(not_null chat) {
};
chat->session().api().migrateChat(chat, crl::guard(this, done));
};
- delegate()->peerListShowBox(Ui::MakeConfirmBox({
+ delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
.text = text,
.confirmed = sure,
.confirmText = tr::lng_manage_discussion_group_link(tr::now),
diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
index 425e21c75..1801cc154 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp
@@ -1281,7 +1281,7 @@ void ParticipantsBoxController::rebuild() {
QPointer ParticipantsBoxController::showBox(
object_ptr box) const {
const auto weak = Ui::MakeWeak(box.data());
- delegate()->peerListShowBox(std::move(box));
+ delegate()->peerListUiShow()->showBox(std::move(box));
return weak;
}
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
index 1eede85e3..1913675af 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "base/unixtime.h"
+#include "boxes/peers/replace_boost_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
@@ -514,21 +515,17 @@ void Apply(
close();
return;
}
- const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer));
}
};
+ auto counters = ParseBoostCounters(result);
+ counters.mine = 0; // Don't show current level as just-reached.
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
- .boost = {
- .level = data.vlevel().v,
- .boosts = data.vboosts().v,
- .thisLevelBoosts = data.vcurrent_level_boosts().v,
- .nextLevelBoosts = next,
- },
+ .boost = counters,
.requiredLevel = required,
}, openStatistics, nullptr));
cancel();
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
index 00f9eac3f..1a32c9978 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_link.cpp
@@ -347,21 +347,24 @@ void Controller::addHeaderBlock(not_null container) {
const auto copyLink = crl::guard(weak, [=] {
CopyInviteLink(delegate()->peerListUiShow(), link);
});
- const auto shareLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(ShareInviteLinkBox(_peer, link));
+ const auto shareLink = crl::guard(weak, [=, peer = _peer] {
+ delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(peer, link));
});
const auto getLinkQr = crl::guard(weak, [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
});
const auto revokeLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(RevokeLinkBox(_peer, admin, link));
+ delegate()->peerListUiShow()->showBox(
+ RevokeLinkBox(_peer, admin, link));
});
const auto editLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(EditLinkBox(_peer, _data.current()));
+ delegate()->peerListUiShow()->showBox(
+ EditLinkBox(_peer, _data.current()));
});
const auto deleteLink = crl::guard(weak, [=] {
- delegate()->peerListShowBox(DeleteLinkBox(_peer, admin, link));
+ delegate()->peerListUiShow()->showBox(
+ DeleteLinkBox(_peer, admin, link));
});
const auto createMenu = [=] {
@@ -1332,7 +1335,7 @@ object_ptr ShowInviteLinkBox(
auto data = rpl::single(link) | rpl::then(std::move(updates));
auto initBox = [=, data = rpl::duplicate(data)](
- not_null box) {
+ not_null box) {
rpl::duplicate(
data
) | rpl::start_with_next([=](const LinkData &link) {
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
index 4d7254388..7ed35cdf8 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_invite_links.cpp
@@ -537,7 +537,7 @@ void LinksController::appendSlice(const InviteLinksSlice &slice) {
}
void LinksController::rowClicked(not_null row) {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
ShowInviteLinkBox(_peer, static_cast(row.get())->data()));
}
@@ -573,25 +573,27 @@ base::unique_qptr LinksController::createRowContextMenu(
st::popupMenuWithIcons);
if (data.revoked) {
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] {
- delegate()->peerListShowBox(DeleteLinkBox(_peer, _admin, link));
+ delegate()->peerListUiShow()->showBox(
+ DeleteLinkBox(_peer, _admin, link));
}, &st::menuIconDelete);
} else {
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
CopyInviteLink(delegate()->peerListUiShow(), link);
}, &st::menuIconCopy);
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(_peer, link));
}, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
}, &st::menuIconQrCode);
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
- delegate()->peerListShowBox(EditLinkBox(_peer, data));
+ delegate()->peerListUiShow()->showBox(EditLinkBox(_peer, data));
}, &st::menuIconEdit);
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] {
- delegate()->peerListShowBox(RevokeLinkBox(_peer, _admin, link));
+ delegate()->peerListUiShow()->showBox(
+ RevokeLinkBox(_peer, _admin, link));
}, &st::menuIconRemove);
}
return result;
@@ -799,7 +801,7 @@ void AdminsController::loadMoreRows() {
}
void AdminsController::rowClicked(not_null row) {
- delegate()->peerListShowBox(
+ delegate()->peerListUiShow()->showBox(
Box(ManageInviteLinksBox, _peer, row->peer()->asUser(), 0, 0));
}
diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
new file mode 100644
index 000000000..0129b8596
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.cpp
@@ -0,0 +1,613 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "boxes/peers/replace_boost_box.h"
+
+#include "base/event_filter.h"
+#include "base/unixtime.h"
+#include "boxes/peer_list_box.h"
+#include "data/data_channel.h"
+#include "data/data_session.h"
+#include "lang/lang_keys.h"
+#include "main/main_account.h"
+#include "main/main_app_config.h"
+#include "main/main_session.h"
+#include "main/session/session_show.h"
+#include "ui/boxes/boost_box.h"
+#include "ui/boxes/confirm_box.h"
+#include "ui/chat/chat_style.h"
+#include "ui/controls/userpic_button.h"
+#include "ui/effects/premium_graphics.h"
+#include "ui/layers/generic_box.h"
+#include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
+#include "ui/widgets/labels.h"
+#include "ui/wrap/padding_wrap.h"
+#include "ui/wrap/vertical_layout.h"
+#include "ui/empty_userpic.h"
+#include "ui/painter.h"
+#include "styles/style_boxes.h"
+#include "styles/style_premium.h"
+
+namespace {
+
+constexpr auto kWaitingOpacity = 0.5;
+
+class Row final : public PeerListRow {
+public:
+ Row(
+ not_null session,
+ TakenBoostSlot slot,
+ TimeId unixtimeNow,
+ crl::time preciseNow);
+
+ void updateStatus(TimeId unixtimeNow, crl::time preciseNow);
+ [[nodiscard]] TakenBoostSlot data() const {
+ return _data;
+ }
+ [[nodiscard]] bool waiting() const {
+ return _waiting;
+ }
+
+ QString generateName() override;
+ QString generateShortName() override;
+ PaintRoundImageCallback generatePaintUserpicCallback(
+ bool forceRound) override;
+ float64 opacity() override;
+
+private:
+ [[nodiscard]]PaintRoundImageCallback peerPaintUserpicCallback();
+
+ TakenBoostSlot _data;
+ PeerData *_peer = nullptr;
+ std::shared_ptr _empty;
+ Ui::PeerUserpicView _userpic;
+ crl::time _startPreciseTime = 0;
+ TimeId _startUnixtime = 0;
+ bool _waiting = false;
+
+};
+
+class Controller final : public PeerListController {
+public:
+ Controller(not_null to, std::vector from);
+
+ [[nodiscard]] rpl::producer> selectedValue() const {
+ return _selected.value();
+ }
+
+ Main::Session &session() const override;
+ void prepare() override;
+ void rowClicked(not_null row) override;
+ bool trackSelectedList() override {
+ return false;
+ }
+
+private:
+ void updateWaitingState();
+
+ not_null _to;
+ std::vector _from;
+ rpl::variable> _selected;
+ rpl::variable>> _selectedPeers;
+ base::Timer _waitingTimer;
+ bool _hasWaitingRows = false;
+
+};
+
+Row::Row(
+ not_null session,
+ TakenBoostSlot slot,
+ TimeId unixtimeNow,
+ crl::time preciseNow)
+: PeerListRow(PeerListRowId(slot.id))
+, _data(slot)
+, _peer(session->data().peerLoaded(_data.peerId))
+, _startPreciseTime(preciseNow)
+, _startUnixtime(unixtimeNow) {
+ updateStatus(unixtimeNow, preciseNow);
+}
+
+void Row::updateStatus(TimeId unixtimeNow, crl::time preciseNow) {
+ _waiting = (_data.cooldown > unixtimeNow);
+ if (_waiting) {
+ const auto initial = crl::time(_data.cooldown - _startUnixtime);
+ const auto elapsed = (preciseNow + 500 - _startPreciseTime) / 1000;
+ const auto seconds = initial
+ - std::clamp(elapsed, crl::time(), initial);
+ const auto hours = seconds / 3600;
+ const auto minutes = seconds / 60;
+ const auto duration = (hours > 0)
+ ? u"%1:%2:%3"_q.arg(
+ hours
+ ).arg(minutes % 60, 2, 10, QChar('0')
+ ).arg(seconds % 60, 2, 10, QChar('0'))
+ : u"%1:%2"_q.arg(
+ minutes
+ ).arg(seconds % 60, 2, 10, QChar('0'));
+ setCustomStatus(
+ tr::lng_boost_available_in(tr::now, lt_duration, duration));
+ } else {
+ const auto date = base::unixtime::parse(_data.expires);
+ setCustomStatus(tr::lng_boosts_list_status(
+ tr::now,
+ lt_date,
+ langDayOfMonth(date.date())));
+ }
+}
+
+QString Row::generateName() {
+ return _peer ? _peer->name() : u" "_q;
+}
+
+QString Row::generateShortName() {
+ return _peer ? _peer->shortName() : generateName();
+}
+
+PaintRoundImageCallback Row::generatePaintUserpicCallback(
+ bool forceRound) {
+ if (_peer) {
+ return (forceRound && _peer->isForum())
+ ? ForceRoundUserpicCallback(_peer)
+ : peerPaintUserpicCallback();
+ } else if (!_empty) {
+ const auto colorIndex = _data.id % Ui::kColorIndexCount;
+ _empty = std::make_shared(
+ Ui::EmptyUserpic::UserpicColor(colorIndex),
+ u" "_q);
+ }
+ const auto empty = _empty;
+ return [=](Painter &p, int x, int y, int outerWidth, int size) {
+ empty->paintCircle(p, x, y, outerWidth, size);
+ };
+}
+
+float64 Row::opacity() {
+ return _waiting ? kWaitingOpacity : 1.;
+}
+
+PaintRoundImageCallback Row::peerPaintUserpicCallback() {
+ const auto peer = _peer;
+ if (!_userpic.cloud && peer->hasUserpic()) {
+ _userpic = peer->createUserpicView();
+ }
+ auto userpic = _userpic;
+ return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
+ peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
+ };
+}
+
+Controller::Controller(
+ not_null to,
+ std::vector from)
+: _to(to)
+, _from(std::move(from))
+, _waitingTimer([=] { updateWaitingState(); }) {
+}
+
+Main::Session &Controller::session() const {
+ return _to->session();
+}
+
+void Controller::prepare() {
+ delegate()->peerListSetTitle(tr::lng_boost_reassign_title());
+
+ const auto session = &_to->session();
+ auto above = object_ptr((QWidget*)nullptr);
+ above->add(
+ CreateBoostReplaceUserpics(
+ above.data(),
+ _selectedPeers.value(),
+ _to),
+ st::boxRowPadding + st::boostReplaceUserpicsPadding);
+ above->add(
+ object_ptr(
+ above.data(),
+ tr::lng_boost_reassign_text(
+ lt_channel,
+ rpl::single(Ui::Text::Bold(_to->name())),
+ lt_gift,
+ tr::lng_boost_reassign_gift(
+ lt_count,
+ rpl::single(1. * BoostsForGift(session)),
+ Ui::Text::RichLangValue),
+ Ui::Text::RichLangValue),
+ st::boostReassignText),
+ st::boxRowPadding);
+ delegate()->peerListSetAboveWidget(std::move(above));
+
+ const auto now = base::unixtime::now();
+ const auto precise = crl::now();
+ ranges::stable_sort(_from, ranges::less(), [&](TakenBoostSlot slot) {
+ return (slot.cooldown > now) ? slot.cooldown : -slot.cooldown;
+ });
+ for (const auto &slot : _from) {
+ auto row = std::make_unique(session, slot, now, precise);
+ if (row->waiting()) {
+ _hasWaitingRows = true;
+ }
+ delegate()->peerListAppendRow(std::move(row));
+ }
+
+ if (_hasWaitingRows) {
+ _waitingTimer.callEach(1000);
+ }
+
+ delegate()->peerListRefreshRows();
+}
+
+void Controller::updateWaitingState() {
+ _hasWaitingRows = false;
+ const auto now = base::unixtime::now();
+ const auto precise = crl::now();
+ const auto count = delegate()->peerListFullRowsCount();
+ for (auto i = 0; i != count; ++i) {
+ const auto bare = delegate()->peerListRowAt(i);
+ const auto row = static_cast(bare.get());
+ if (row->waiting()) {
+ row->updateStatus(now, precise);
+ delegate()->peerListUpdateRow(row);
+ if (row->waiting()) {
+ _hasWaitingRows = true;
+ }
+ }
+ }
+ if (!_hasWaitingRows) {
+ _waitingTimer.cancel();
+ }
+}
+
+void Controller::rowClicked(not_null row) {
+ const auto slot = static_cast(row.get())->data();
+ if (slot.cooldown > base::unixtime::now()) {
+ delegate()->peerListUiShow()->showToast({
+ .text = tr::lng_boost_available_in_toast(
+ tr::now,
+ lt_count,
+ BoostsForGift(&session()),
+ Ui::Text::RichLangValue),
+ .adaptive = true,
+ });
+ return;
+ }
+ auto now = _selected.current();
+ const auto id = slot.id;
+ const auto checked = !row->checked();
+ delegate()->peerListSetRowChecked(row, checked);
+ const auto peer = slot.peerId
+ ? _to->owner().peerLoaded(slot.peerId)
+ : nullptr;
+ auto peerRemoved = false;
+ if (checked) {
+ now.push_back(id);
+ } else {
+ now.erase(ranges::remove(now, id), end(now));
+
+ peerRemoved = true;
+ for (const auto left : now) {
+ const auto i = ranges::find(_from, left, &TakenBoostSlot::id);
+ Assert(i != end(_from));
+ if (i->peerId == slot.peerId) {
+ peerRemoved = false;
+ break;
+ }
+ }
+ }
+ _selected = std::move(now);
+
+ if (peer) {
+ auto selectedPeers = _selectedPeers.current();
+ const auto i = ranges::find(selectedPeers, not_null(peer));
+ if (peerRemoved) {
+ Assert(i != end(selectedPeers));
+ selectedPeers.erase(i);
+ _selectedPeers = std::move(selectedPeers);
+ } else if (i == end(selectedPeers) && checked) {
+ selectedPeers.insert(begin(selectedPeers), peer);
+ _selectedPeers = std::move(selectedPeers);
+ }
+ }
+}
+
+object_ptr ReassignBoostFloodBox(int seconds) {
+ const auto days = seconds / 86400;
+ const auto hours = seconds / 3600;
+ const auto minutes = seconds / 60;
+ return Ui::MakeInformBox({
+ .text = tr::lng_boost_error_flood_text(
+ lt_left,
+ rpl::single(Ui::Text::Bold((days > 1)
+ ? tr::lng_days(tr::now, lt_count, days)
+ : (hours > 1)
+ ? tr::lng_hours(tr::now, lt_count, hours)
+ : (minutes > 1)
+ ? tr::lng_minutes(tr::now, lt_count, minutes)
+ : tr::lng_seconds(tr::now, lt_count, seconds))),
+ Ui::Text::RichLangValue),
+ .title = tr::lng_boost_error_flood_title(),
+ });
+}
+
+object_ptr ReassignBoostSingleBox(
+ not_null to,
+ TakenBoostSlot from,
+ Fn slots, int sources)> reassign,
+ Fn cancel) {
+ const auto reassigned = std::make_shared();
+ const auto slot = from.id;
+ const auto peer = to->owner().peer(from.peerId);
+ const auto confirmed = [=](Fn close) {
+ *reassigned = true;
+ reassign({ slot }, 1);
+ close();
+ };
+
+ auto result = Box([=](not_null box) {
+ Ui::ConfirmBox(box, {
+ .text = tr::lng_boost_now_instead(
+ lt_channel,
+ rpl::single(Ui::Text::Bold(peer->name())),
+ lt_other,
+ rpl::single(Ui::Text::Bold(to->name())),
+ Ui::Text::WithEntities),
+ .confirmed = confirmed,
+ .confirmText = tr::lng_boost_now_replace(),
+ .labelPadding = st::boxRowPadding,
+ });
+ box->verticalLayout()->insert(
+ 0,
+ CreateBoostReplaceUserpics(
+ box,
+ rpl::single(std::vector{ peer }),
+ to),
+ st::boxRowPadding + st::boostReplaceUserpicsPadding);
+ });
+
+ result->boxClosing() | rpl::filter([=] {
+ return !*reassigned;
+ }) | rpl::start_with_next(cancel, result->lifetime());
+
+ return result;
+}
+
+} // namespace
+
+ForChannelBoostSlots ParseForChannelBoostSlots(
+ not_null channel,
+ const QVector &boosts) {
+ auto result = ForChannelBoostSlots();
+ const auto now = base::unixtime::now();
+ for (const auto &my : boosts) {
+ const auto &data = my.data();
+ const auto id = data.vslot().v;
+ const auto cooldown = data.vcooldown_until_date().value_or(0);
+ const auto peerId = data.vpeer()
+ ? peerFromMTP(*data.vpeer())
+ : PeerId();
+ if (!peerId && cooldown <= now) {
+ result.free.push_back(id);
+ } else if (peerId == channel->id) {
+ result.already.push_back(id);
+ } else {
+ result.other.push_back({
+ .id = id,
+ .expires = data.vexpires().v,
+ .peerId = peerId,
+ .cooldown = cooldown,
+ });
+ }
+ }
+ return result;
+}
+
+Ui::BoostCounters ParseBoostCounters(
+ const MTPpremium_BoostsStatus &status) {
+ const auto &data = status.data();
+ const auto slots = data.vmy_boost_slots();
+ return {
+ .level = data.vlevel().v,
+ .boosts = data.vboosts().v,
+ .thisLevelBoosts = data.vcurrent_level_boosts().v,
+ .nextLevelBoosts = data.vnext_level_boosts().value_or_empty(),
+ .mine = slots ? int(slots->v.size()) : 0,
+ };
+}
+
+int BoostsForGift(not_null session) {
+ const auto key = u"boosts_per_sent_gift"_q;
+ return session->account().appConfig().get(key, 0);
+}
+
+[[nodiscard]] int SourcesCount(
+ const std::vector &from,
+ const std::vector &slots) {
+ auto checked = base::flat_set();
+ checked.reserve(slots.size());
+ for (const auto slot : slots) {
+ const auto i = ranges::find(from, slot, &TakenBoostSlot::id);
+ Assert(i != end(from));
+ checked.emplace(i->peerId);
+ }
+ return checked.size();
+}
+
+object_ptr ReassignBoostsBox(
+ not_null to,
+ std::vector from,
+ Fn slots, int sources)> reassign,
+ Fn cancel) {
+ Expects(!from.empty());
+
+ const auto now = base::unixtime::now();
+ if (from.size() == 1 && from.front().cooldown > now) {
+ cancel();
+ return ReassignBoostFloodBox(from.front().cooldown - now);
+ } else if (from.size() == 1 && from.front().peerId) {
+ return ReassignBoostSingleBox(to, from.front(), reassign, cancel);
+ }
+ const auto reassigned = std::make_shared();
+ auto controller = std::make_unique(to, from);
+ const auto raw = controller.get();
+ auto initBox = [=](not_null box) {
+ raw->selectedValue(
+ ) | rpl::start_with_next([=](std::vector slots) {
+ box->clearButtons();
+ if (!slots.empty()) {
+ const auto sources = SourcesCount(from, slots);
+ box->addButton(tr::lng_boost_reassign_button(), [=] {
+ *reassigned = true;
+ reassign(slots, sources);
+ });
+ }
+ box->addButton(tr::lng_cancel(), [=] {
+ box->closeBox();
+ });
+ }, box->lifetime());
+
+ box->boxClosing() | rpl::filter([=] {
+ return !*reassigned;
+ }) | rpl::start_with_next(cancel, box->lifetime());
+ };
+ return Box(std::move(controller), std::move(initBox));
+}
+
+object_ptr CreateBoostReplaceUserpics(
+ not_null parent,
+ rpl::producer>> from,
+ not_null to) {
+ struct State {
+ std::vector> from;
+ std::vector> buttons;
+ QImage layer;
+ rpl::variable count = 0;
+ bool painting = false;
+ };
+ const auto full = st::boostReplaceUserpic.size.height()
+ + st::boostReplaceIconAdd.y()
+ + st::lineWidth;
+ auto result = object_ptr(parent, full);
+ const auto raw = result.data();
+ const auto &st = st::boostReplaceUserpic;
+ const auto right = CreateChild(raw, to, st);
+ const auto overlay = CreateChild(raw);
+
+ const auto state = raw->lifetime().make_state();
+ std::move(
+ from
+ ) | rpl::start_with_next([=](
+ const std::vector> &list) {
+ const auto &st = st::boostReplaceUserpic;
+ auto was = base::take(state->from);
+ auto buttons = base::take(state->buttons);
+ state->from.reserve(list.size());
+ state->buttons.reserve(list.size());
+ for (const auto &peer : list) {
+ state->from.push_back(peer);
+ const auto i = ranges::find(was, peer);
+ if (i != end(was)) {
+ const auto index = int(i - begin(was));
+ Assert(buttons[index] != nullptr);
+ state->buttons.push_back(std::move(buttons[index]));
+ } else {
+ state->buttons.push_back(
+ std::make_unique(raw, peer, st));
+ const auto raw = state->buttons.back().get();
+ base::install_event_filter(raw, [=](not_null e) {
+ return (e->type() == QEvent::Paint && !state->painting)
+ ? base::EventFilterResult::Cancel
+ : base::EventFilterResult::Continue;
+ });
+ }
+ }
+ state->count.force_assign(int(list.size()));
+ overlay->update();
+ }, raw->lifetime());
+
+ rpl::combine(
+ raw->widthValue(),
+ state->count.value()
+ ) | rpl::start_with_next([=](int width, int count) {
+ const auto skip = st::boostReplaceUserpicsSkip;
+ const auto left = width - 2 * right->width() - skip;
+ const auto shift = std::min(
+ st::boostReplaceUserpicsShift,
+ (count > 1 ? (left / (count - 1)) : width));
+ const auto total = right->width()
+ + (count ? (skip + right->width() + (count - 1) * shift) : 0);
+ auto x = (width - total) / 2;
+ for (const auto &single : state->buttons) {
+ single->moveToLeft(x, 0);
+ x += shift;
+ }
+ if (count) {
+ x += right->width() - shift + skip;
+ }
+ right->moveToLeft(x, 0);
+ overlay->setGeometry(QRect(0, 0, width, raw->height()));
+ }, raw->lifetime());
+
+ overlay->paintRequest(
+ ) | rpl::filter([=] {
+ return !state->buttons.empty();
+ }) | rpl::start_with_next([=] {
+ const auto outerw = overlay->width();
+ const auto ratio = style::DevicePixelRatio();
+ if (state->layer.size() != QSize(outerw, full) * ratio) {
+ state->layer = QImage(
+ QSize(outerw, full) * ratio,
+ QImage::Format_ARGB32_Premultiplied);
+ state->layer.setDevicePixelRatio(ratio);
+ }
+ state->layer.fill(Qt::transparent);
+
+ auto q = QPainter(&state->layer);
+ auto hq = PainterHighQualityEnabler(q);
+ const auto stroke = st::boostReplaceIconOutline;
+ const auto half = stroke / 2.;
+ auto pen = st::windowBg->p;
+ pen.setWidthF(stroke * 2.);
+ state->painting = true;
+ for (const auto &button : state->buttons) {
+ q.setPen(pen);
+ q.setBrush(Qt::NoBrush);
+ q.drawEllipse(button->geometry());
+ const auto position = button->pos();
+ button->render(&q, position, QRegion(), QWidget::DrawChildren);
+ }
+ state->painting = false;
+ const auto last = state->buttons.back().get();
+ const auto add = st::boostReplaceIconAdd;
+ const auto skip = st::boostReplaceIconSkip;
+ const auto w = st::boostReplaceIcon.width() + 2 * skip;
+ const auto h = st::boostReplaceIcon.height() + 2 * skip;
+ const auto x = last->x() + last->width() - w + add.x();
+ const auto y = last->y() + last->height() - h + add.y();
+
+ auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
+ brush.setStops(Ui::Premium::ButtonGradientStops());
+ q.setBrush(brush);
+ pen.setWidthF(stroke);
+ q.setPen(pen);
+ q.drawEllipse(x - half, y - half, w + stroke, h + stroke);
+ st::boostReplaceIcon.paint(q, x + skip, y + skip, outerw);
+
+ const auto size = st::boostReplaceArrow.size();
+ st::boostReplaceArrow.paint(
+ q,
+ (last->x()
+ + last->width()
+ + (st::boostReplaceUserpicsSkip - size.width()) / 2),
+ (last->height() - size.height()) / 2,
+ outerw);
+
+ q.end();
+
+ auto p = QPainter(overlay);
+ p.drawImage(0, 0, state->layer);
+ }, overlay->lifetime());
+ return result;
+}
diff --git a/Telegram/SourceFiles/boxes/peers/replace_boost_box.h b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h
new file mode 100644
index 000000000..f15cf0b14
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/replace_boost_box.h
@@ -0,0 +1,53 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/object_ptr.h"
+
+namespace Main {
+class Session;
+} // namespace Main
+
+namespace Ui {
+struct BoostCounters;
+class BoxContent;
+class RpWidget;
+} // namespace Ui
+
+struct TakenBoostSlot {
+ int id = 0;
+ TimeId expires = 0;
+ PeerId peerId = 0;
+ TimeId cooldown = 0;
+};
+
+struct ForChannelBoostSlots {
+ std::vector free;
+ std::vector already;
+ std::vector other;
+};
+
+[[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots(
+ not_null channel,
+ const QVector &boosts);
+
+[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
+ const MTPpremium_BoostsStatus &status);
+
+[[nodiscard]] int BoostsForGift(not_null session);
+
+object_ptr ReassignBoostsBox(
+ not_null to,
+ std::vector from,
+ Fn slots, int sources)> reassign,
+ Fn cancel);
+
+[[nodiscard]] object_ptr CreateBoostReplaceUserpics(
+ not_null parent,
+ rpl::producer>> from,
+ not_null to);
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
index 928915afd..9ce74e022 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
@@ -115,10 +115,6 @@ public:
void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription(
object_ptr description) override;
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
- void peerListHideLayer() override;
std::shared_ptr peerListUiShow() override;
void peerListSetRowChecked(
not_null row,
@@ -183,14 +179,6 @@ void InactiveDelegate::peerListSetDescription(
description.destroy();
}
-void InactiveDelegate::peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options) {
-}
-
-void InactiveDelegate::peerListHideLayer() {
-}
-
std::shared_ptr InactiveDelegate::peerListUiShow() {
Unexpected("...InactiveDelegate::peerListUiShow");
}
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
index cc3c33ddb..586583ad8 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp
@@ -1975,14 +1975,6 @@ void Members::peerListSetDescription(
description.destroy();
}
-void Members::peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options) {
-}
-
-void Members::peerListHideLayer() {
-}
-
std::shared_ptr Members::peerListUiShow() {
Unexpected("...Members::peerListUiShow");
}
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h
index f8b4c8c52..47cb05914 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_members.h
@@ -88,10 +88,6 @@ private:
void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription(
object_ptr description) override;
- void peerListShowBox(
- object_ptr content,
- Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
- void peerListHideLayer() override;
std::shared_ptr peerListUiShow() override;
void setupAddMember(not_null call);
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 776a48e00..b44f257ca 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 4011005;
-constexpr auto AppVersionStr = "4.11.5";
+constexpr auto AppVersion = 4011006;
+constexpr auto AppVersionStr = "4.11.6";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h
index a62c70c92..bada02a91 100644
--- a/Telegram/SourceFiles/data/data_boosts.h
+++ b/Telegram/SourceFiles/data/data_boosts.h
@@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data {
struct BoostsOverview final {
- bool isBoosted = false;
+ int mine = 0;
int level = 0;
int boostCount = 0;
int currentLevelBoostCount = 0;
@@ -34,7 +34,8 @@ struct Boost final {
UserId userId = UserId(0);
FullMsgId giveawayMessage;
QDateTime date;
- crl::time expiresAt = 0;
+ QDateTime expiresAt;
+ int expiresAfterMonths = 0;
GiftCodeLink giftCodeLink;
int multiplier = 0;
};
@@ -50,10 +51,18 @@ struct BoostsListSlice final {
OffsetToken token;
};
+struct BoostPrepaidGiveaway final {
+ int months = 0;
+ uint64 id = 0;
+ int quantity = 0;
+ QDateTime date;
+};
+
struct BoostStatus final {
BoostsOverview overview;
BoostsListSlice firstSliceBoosts;
BoostsListSlice firstSliceGifts;
+ std::vector prepaidGiveaway;
QString link;
};
diff --git a/Telegram/SourceFiles/data/data_web_page.cpp b/Telegram/SourceFiles/data/data_web_page.cpp
index e8eef8978..95b042775 100644
--- a/Telegram/SourceFiles/data/data_web_page.cpp
+++ b/Telegram/SourceFiles/data/data_web_page.cpp
@@ -391,3 +391,7 @@ bool WebPageData::computeDefaultSmallMedia() const {
}
return false;
}
+
+bool WebPageData::suggestEnlargePhoto() const {
+ return !siteName.isEmpty() || !title.isEmpty() || !description.empty();
+}
diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h
index 9aea4a0f5..04f16a7b6 100644
--- a/Telegram/SourceFiles/data/data_web_page.h
+++ b/Telegram/SourceFiles/data/data_web_page.h
@@ -90,6 +90,7 @@ struct WebPageData {
[[nodiscard]] QString displayedSiteName() const;
[[nodiscard]] bool computeDefaultSmallMedia() const;
+ [[nodiscard]] bool suggestEnlargePhoto() const;
const WebPageId id = 0;
WebPageType type = WebPageType::None;
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index ff643044f..36429e437 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -1861,9 +1861,12 @@ std::unique_ptr HistoryInner::prepareDrag() {
session().data().setMimeForwardIds(std::move(forwardIds));
auto result = std::make_unique();
result->setData(u"application/x-td-forward"_q, "1");
- if (const auto media = pressedView->media()) {
- if (const auto document = media->getDocument()) {
- const auto filepath = document->filepath(true);
+ if (pressedHandler) {
+ const auto lnkDocument = reinterpret_cast(
+ pressedHandler->property(
+ kDocumentLinkMediaProperty).toULongLong());
+ if (lnkDocument) {
+ const auto filepath = lnkDocument->filepath(true);
if (!filepath.isEmpty()) {
QList urls;
urls.push_back(QUrl::fromLocalFile(filepath));
@@ -3098,6 +3101,8 @@ void HistoryInner::recountHistoryGeometry() {
accumulate_max(oldHistoryPaddingTop, _botAbout->height);
}
+ updateBotInfo(false);
+
_history->resizeToWidth(_contentWidth);
if (_migrated) {
_migrated->resizeToWidth(_contentWidth);
@@ -3121,7 +3126,6 @@ void HistoryInner::recountHistoryGeometry() {
}
}
- updateBotInfo(false);
if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
_botAbout->height = view->resizeGetHeight(_contentWidth);
_botAbout->top = qMin(
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index d08027dcd..cdd0811dd 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -3407,6 +3407,7 @@ void HistoryItem::createComponentsHelper(
: nullptr;
if (!config.reply.externalPeerId
&& topic
+ && to
&& topic->rootId() != to->topicRootId()) {
config.reply.externalPeerId = replyTo.messageId.peer;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_cursor_state.h b/Telegram/SourceFiles/history/view/history_view_cursor_state.h
index 82fcda954..d5bcaa6f9 100644
--- a/Telegram/SourceFiles/history/view/history_view_cursor_state.h
+++ b/Telegram/SourceFiles/history/view/history_view_cursor_state.h
@@ -22,6 +22,7 @@ enum class CursorState : char {
None,
Text,
Date,
+ Enlarge,
Forwarded,
};
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 410b1a337..2db6bddb8 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -3639,9 +3639,12 @@ std::unique_ptr ListWidget::prepareDrag() {
session().data().setMimeForwardIds(std::move(forwardIds));
auto result = std::make_unique();
result->setData(u"application/x-td-forward"_q, "1");
- if (const auto media = pressedView->media()) {
- if (const auto document = media->getDocument()) {
- const auto filepath = document->filepath(true);
+ if (pressedHandler) {
+ const auto lnkDocument = reinterpret_cast(
+ pressedHandler->property(
+ kDocumentLinkMediaProperty).toULongLong());
+ if (lnkDocument) {
+ const auto filepath = lnkDocument->filepath(true);
if (!filepath.isEmpty()) {
QList urls;
urls.push_back(QUrl::fromLocalFile(filepath));
diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp
index 350ba3668..f65613ea3 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp
@@ -105,6 +105,7 @@ void Giveaway::fillFromData(not_null giveaway) {
st::msgMinWidth),
.thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel),
.link = channel->openLink(),
+ .colorIndex = channel->colorIndex(),
});
}
const auto channels = int(_channels.size());
@@ -342,18 +343,6 @@ void Giveaway::paintChannels(
const auto st = context.st;
const auto stm = context.messageStyle();
const auto selected = context.selected();
- const auto colorIndex = parent()->colorIndex();
- const auto cache = context.outbg
- ? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
- : st->coloredReplyCache(selected, colorIndex).get();
- if (_channelCorners[0].isNull() || _channelBg != cache->bg) {
- _channelBg = cache->bg;
- _channelCorners = Images::CornersMask(size / 2);
- for (auto &image : _channelCorners) {
- style::colorizeImage(image, cache->bg, &image);
- }
- }
- p.setPen(cache->icon);
const auto padding = st::chatGiveawayChannelPadding;
for (const auto &channel : _channels) {
const auto &thumbnail = channel.thumbnail;
@@ -364,7 +353,19 @@ void Giveaway::paintChannels(
});
}
- Ui::DrawRoundedRect(p, geometry, _channelBg, _channelCorners);
+ const auto colorIndex = channel.colorIndex;
+ const auto cache = context.outbg
+ ? stm->replyCache[st->colorPatternIndex(colorIndex)].get()
+ : st->coloredReplyCache(selected, colorIndex).get();
+ if (channel.corners[0].isNull() || channel.bg != cache->bg) {
+ channel.bg = cache->bg;
+ channel.corners = Images::CornersMask(size / 2);
+ for (auto &image : channel.corners) {
+ style::colorizeImage(image, cache->bg, &image);
+ }
+ }
+ p.setPen(cache->icon);
+ Ui::DrawRoundedRect(p, geometry, channel.bg, channel.corners);
if (channel.ripple) {
channel.ripple->paint(
p,
diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h
index 6529ad497..250e214c2 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.h
@@ -69,6 +69,9 @@ private:
QRect geometry;
ClickHandlerPtr link;
mutable std::unique_ptr ripple;
+ mutable std::array corners;
+ mutable QColor bg;
+ uint8 colorIndex = 0;
};
void paintBadge(Painter &p, const PaintContext &context) const;
@@ -94,10 +97,8 @@ private:
Ui::Text::String _winnersTitle;
Ui::Text::String _winners;
- mutable QColor _channelBg;
mutable QColor _badgeFg;
mutable QColor _badgeBorder;
- mutable std::array _channelCorners;
mutable QImage _badge;
mutable QImage _badgeCache;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
index e61202239..b0df17d48 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
@@ -325,7 +325,7 @@ void UnwrappedMedia::drawSurrounding(
recty += skip;
} else if (via) {
p.setFont(st::msgDateFont);
- p.drawTextLeft(rectx, recty + st::msgReplyPadding.top(), 2 * textx + textw, via->text);
+ p.drawTextLeft(textx, recty + st::msgReplyPadding.top(), 2 * textx + textw, via->text);
const auto skip = st::msgServiceNameFont->height
+ (reply ? st::msgReplyPadding.top() : 0);
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index b3d79c6fe..85a2997a8 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_auto_download.h"
+#include "data/data_web_page.h"
#include "core/application.h"
#include "styles/style_chat.h"
@@ -242,6 +243,7 @@ QSize Photo::countCurrentSize(int newWidth) {
maxWidth());
newWidth = qMax(pix.width(), minWidth);
auto newHeight = qMax(pix.height(), st::minPhotoSize);
+ auto imageHeight = newHeight;
if (_parent->hasBubble() && !_caption.isEmpty()) {
auto captionMaxWidth = st::msgPadding.left()
+ _caption.maxWidth()
@@ -252,7 +254,7 @@ QSize Photo::countCurrentSize(int newWidth) {
}
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
- newHeight = adjustHeightForLessCrop(
+ imageHeight = newHeight = adjustHeightForLessCrop(
dimensions,
{ newWidth, newHeight });
const auto captionw = newWidth
@@ -266,6 +268,15 @@ QSize Photo::countCurrentSize(int newWidth) {
newHeight += st::msgPadding.bottom();
}
}
+ const auto enlargeInner = st::historyPageEnlargeSize;
+ const auto enlargeOuter = 2 * st::historyPageEnlargeSkip + enlargeInner;
+ const auto showEnlarge = (_parent->media() != this)
+ && _parent->data()->media()
+ && _parent->data()->media()->webpage()
+ && _parent->data()->media()->webpage()->suggestEnlargePhoto()
+ && (newWidth >= enlargeOuter)
+ && (imageHeight >= enlargeOuter);
+ _showEnlarge = showEnlarge ? 1 : 0;
return { newWidth, newHeight };
}
@@ -351,15 +362,16 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
fillImageOverlay(p, rthumb, rounding, context);
}
}
- if (radial || (!loaded && !_data->loading())) {
- const auto radialOpacity = (radial && loaded && !_data->uploading())
- ? _animation->radial.opacity() :
- 1.;
- const auto innerSize = st::msgFileLayout.thumbSize;
- QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
+
+ const auto showEnlarge = loaded && _showEnlarge;
+ const auto paintInCenter = (radial || (!loaded && !_data->loading()));
+ if (paintInCenter || showEnlarge) {
p.setPen(Qt::NoPen);
if (context.selected()) {
p.setBrush(st->msgDateImgBgSelected());
+ } else if (showEnlarge) {
+ const auto over = ClickHandler::showAsActive(_openl);
+ p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg());
} else if (isThumbAnimation()) {
const auto over = _animation->a_thumbOver.value(1.);
p.setBrush(anim::brush(st->msgDateImgBg(), st->msgDateImgBgOver(), over));
@@ -367,6 +379,13 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
const auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg());
}
+ }
+ if (paintInCenter) {
+ const auto radialOpacity = (radial && loaded && !_data->uploading())
+ ? _animation->radial.opacity() :
+ 1.;
+ const auto innerSize = st::msgFileLayout.thumbSize;
+ QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
p.setOpacity(radialOpacity * p.opacity());
@@ -386,6 +405,13 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
}
}
+ if (showEnlarge) {
+ auto hq = PainterHighQualityEnabler(p);
+ const auto rect = enlargeRect();
+ const auto radius = st::historyPageEnlargeRadius;
+ p.drawRoundedRect(rect, radius, radius);
+ sti->historyPageEnlarge.paintInCenter(p, rect);
+ }
// date
if (!_caption.isEmpty()) {
@@ -631,6 +657,18 @@ QSize Photo::photoSize() const {
return QSize(_data->width(), _data->height());
}
+QRect Photo::enlargeRect() const {
+ const auto skip = st::historyPageEnlargeSkip;
+ const auto enlargeInner = st::historyPageEnlargeSize;
+ const auto enlargeOuter = 2 * skip + enlargeInner;
+ return {
+ width() - enlargeOuter + skip,
+ skip,
+ enlargeInner,
+ enlargeInner,
+ };
+}
+
TextState Photo::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
@@ -673,6 +711,11 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
: _data->loading()
? _cancell
: _savel;
+ if (_showEnlarge
+ && result.link == _openl
+ && enlargeRect().contains(point)) {
+ result.cursor = CursorState::Enlarge;
+ }
}
if (_caption.isEmpty() && _parent->media() == this) {
auto fullRight = paintx + paintw;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index 7213dca21..4b123cc7c 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -163,6 +163,7 @@ private:
QPoint photoPosition) const;
[[nodiscard]] QSize photoSize() const;
+ [[nodiscard]] QRect enlargeRect() const;
void togglePollingStory(bool enabled) const;
@@ -178,6 +179,7 @@ private:
mutable uint32 _imageCacheForum : 1 = 0;
mutable uint32 _imageCacheBlurred : 1 = 0;
mutable uint32 _pollingStory : 1 = 0;
+ mutable uint32 _showEnlarge : 1 = 0;
};
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
index 56299bf45..c622585b9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
@@ -783,7 +783,11 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
auto attachTop = tshift - bubble.top();
if (rtl()) attachLeft = width() - attachLeft - _attach->width();
result = _attach->textState(point - QPoint(attachLeft, attachTop), request);
- result.link = replaceAttachLink(result.link);
+ if (result.cursor == CursorState::Enlarge) {
+ result.cursor = CursorState::None;
+ } else {
+ result.link = replaceAttachLink(result.link);
+ }
}
}
if (!result.link && outer.contains(point)) {
diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp
index c1781ea03..235e2f130 100644
--- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp
+++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h"
#include "countries/countries_instance.h"
#include "data/data_peer.h"
+#include "info/boosts/giveaway/boost_badge.h"
#include "info/boosts/giveaway/giveaway_list_controllers.h"
#include "info/boosts/giveaway/giveaway_type_row.h"
#include "info/boosts/giveaway/select_countries_box.h"
@@ -24,9 +25,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h"
#include "settings/settings_premium.h" // Settings::ShowPremium
#include "ui/boxes/choose_date_time.h"
+#include "ui/boxes/confirm_box.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
+#include "ui/painter.h"
+#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
@@ -67,47 +71,172 @@ constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
};
}
+[[nodiscard]] QWidget *FindFirstShadowInBox(not_null box) {
+ for (const auto &child : box->children()) {
+ if (child && child->isWidgetType()) {
+ const auto w = static_cast(child);
+ if (w->height() == st::lineWidth) {
+ return w;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void AddPremiumTopBarWithDefaultTitleBar(
+ not_null box,
+ rpl::producer<> showFinished,
+ rpl::producer titleText) {
+ struct State final {
+ Ui::Animations::Simple animation;
+ Ui::Text::String title;
+
+ Ui::RpWidget close;
+ };
+ const auto state = box->lifetime().make_state();
+ box->setNoContentMargin(true);
+
+ std::move(
+ titleText
+ ) | rpl::start_with_next([=](const QString &s) {
+ state->title.setText(st::startGiveawayBox.title.style, s);
+ }, box->lifetime());
+
+ const auto hPadding = rect::m::sum::h(st::boxRowPadding);
+ const auto titlePaintContext = Ui::Text::PaintContext{
+ .position = st::boxTitlePosition,
+ .outerWidth = (st::boxWideWidth - hPadding),
+ .availableWidth = (st::boxWideWidth - hPadding),
+ };
+
+ const auto isCloseBarShown = [=] { return box->scrollTop() > 0; };
+
+ const auto closeTopBar = box->setPinnedToTopContent(
+ object_ptr(box));
+ closeTopBar->resize(box->width(), st::boxTitleHeight);
+ closeTopBar->paintRequest(
+ ) | rpl::start_with_next([=](const QRect &r) {
+ auto p = Painter(closeTopBar);
+ const auto radius = st::boxRadius;
+ const auto progress = state->animation.value(isCloseBarShown()
+ ? 1.
+ : 0.);
+ const auto resultRect = r + QMargins{ 0, 0, 0, radius };
+ {
+ auto hq = PainterHighQualityEnabler(p);
+
+ if (progress < 1.) {
+ auto path = QPainterPath();
+ path.addRect(resultRect);
+ path.addRect(
+ st::boxRowPadding.left(),
+ 0,
+ resultRect.width() - hPadding,
+ resultRect.height());
+ p.setClipPath(path);
+ PainterHighQualityEnabler hq(p);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st::boxDividerBg);
+ p.drawRoundedRect(resultRect, radius, radius);
+ }
+ if (progress > 0.) {
+ p.setOpacity(progress);
+
+ p.setClipping(false);
+ p.setPen(Qt::NoPen);
+ p.setBrush(st::boxBg);
+ p.drawRoundedRect(resultRect, radius, radius);
+
+ p.setPen(st::startGiveawayBox.title.textFg);
+ p.setBrush(Qt::NoBrush);
+ state->title.draw(p, titlePaintContext);
+ }
+ }
+ }, closeTopBar->lifetime());
+
+ {
+ const auto close = Ui::CreateChild(
+ closeTopBar.get(),
+ st::startGiveawayBoxTitleClose);
+ close->setClickedCallback([=] { box->closeBox(); });
+ closeTopBar->widthValue(
+ ) | rpl::start_with_next([=](int w) {
+ const auto &pos = st::giveawayGiftCodeCoverClosePosition;
+ close->moveToRight(pos.x(), pos.y());
+ }, box->lifetime());
+ close->show();
+ }
+
+ const auto bar = Ui::CreateChild(
+ box.get(),
+ st::startGiveawayCover,
+ nullptr,
+ tr::lng_giveaway_new_title(),
+ tr::lng_giveaway_new_about(Ui::Text::RichLangValue),
+ true,
+ false);
+ bar->setAttribute(Qt::WA_TransparentForMouseEvents);
+
+ box->addRow(
+ object_ptr(
+ box.get(),
+ st::giveawayGiftCodeTopHeight
+ - st::boxTitleHeight
+ + st::boxDividerHeight
+ + st::settingsSectionSkip,
+ st::boxDividerBg,
+ RectPart::Bottom),
+ {});
+ bar->setPaused(true);
+ bar->setRoundEdges(false);
+ bar->setMaximumHeight(st::giveawayGiftCodeTopHeight);
+ bar->setMinimumHeight(st::infoLayerTopBarHeight);
+ bar->resize(bar->width(), bar->maximumHeight());
+ box->widthValue(
+ ) | rpl::start_with_next([=](int w) {
+ bar->resizeToWidth(w - hPadding);
+ bar->moveToLeft(st::boxRowPadding.left(), bar->y());
+ }, box->lifetime());
+
+ std::move(
+ showFinished
+ ) | rpl::take(1) | rpl::start_with_next([=] {
+ closeTopBar->raise();
+ if (const auto shadow = FindFirstShadowInBox(box)) {
+ bar->stackUnder(shadow);
+ }
+ bar->setPaused(false);
+ box->scrolls(
+ ) | rpl::map(isCloseBarShown) | rpl::distinct_until_changed(
+ ) | rpl::start_with_next([=](bool showBar) {
+ state->animation.stop();
+ state->animation.start(
+ [=] { closeTopBar->update(); },
+ showBar ? 0. : 1.,
+ showBar ? 1. : 0.,
+ st::slideWrapDuration);
+ }, box->lifetime());
+ box->scrolls(
+ ) | rpl::start_with_next([=] {
+ bar->moveToLeft(bar->x(), -box->scrollTop());
+ }, box->lifetime());
+ }, box->lifetime());
+
+ bar->show();
+}
+
} // namespace
void CreateGiveawayBox(
not_null box,
not_null controller,
- not_null peer) {
+ not_null peer,
+ Fn reloadOnDone,
+ std::optional prepaid) {
box->setWidth(st::boxWideWidth);
const auto weakWindow = base::make_weak(controller->parentController());
- const auto bar = box->verticalLayout()->add(
- object_ptr(
- box,
- st::giveawayGiftCodeCover,
- nullptr,
- tr::lng_giveaway_new_title(),
- tr::lng_giveaway_new_about(Ui::Text::RichLangValue),
- true));
- {
- bar->setPaused(true);
- bar->setMaximumHeight(st::giveawayGiftCodeTopHeight);
- bar->setMinimumHeight(st::infoLayerTopBarHeight);
- bar->resize(bar->width(), bar->maximumHeight());
-
- const auto container = box->verticalLayout();
- const auto &padding = st::giveawayGiftCodeCoverDividerPadding;
- Settings::AddSkip(container, padding.top());
- Settings::AddDivider(container);
- Settings::AddSkip(container, padding.bottom());
-
- const auto close = Ui::CreateChild(
- container.get(),
- st::boxTitleClose);
- close->setClickedCallback([=] { box->closeBox(); });
- box->widthValue(
- ) | rpl::start_with_next([=](int) {
- const auto &pos = st::giveawayGiftCodeCoverClosePosition;
- close->moveToRight(pos.x(), pos.y());
- }, box->lifetime());
- }
-
using GiveawayType = Giveaway::GiveawayTypeRow::Type;
using GiveawayGroup = Ui::RadioenumGroup;
struct State final {
@@ -127,11 +256,25 @@ void CreateGiveawayBox(
rpl::variable dateValue;
rpl::variable> countriesValue;
- bool confirmButtonBusy = false;
+ rpl::variable confirmButtonBusy = true;
};
const auto state = box->lifetime().make_state(peer);
const auto typeGroup = std::make_shared();
+ auto showFinished = Ui::BoxShowFinishes(box);
+ AddPremiumTopBarWithDefaultTitleBar(
+ box,
+ rpl::duplicate(showFinished),
+ rpl::conditional(
+ state->typeValue.value(
+ ) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
+ tr::lng_giveaway_start(),
+ tr::lng_giveaway_award()));
+ {
+ const auto &padding = st::giveawayGiftCodeCoverDividerPadding;
+ Settings::AddSkip(box->verticalLayout(), padding.bottom());
+ }
+
const auto loading = box->addRow(
object_ptr>(
box,
@@ -157,7 +300,24 @@ void CreateGiveawayBox(
object_ptr(box)));
contentWrap->toggle(false, anim::type::instant);
- {
+ if (prepaid) {
+ contentWrap->entity()->add(
+ object_ptr(
+ box,
+ GiveawayType::Prepaid,
+ prepaid->id,
+ tr::lng_boosts_prepaid_giveaway_single(),
+ tr::lng_boosts_prepaid_giveaway_status(
+ lt_count,
+ rpl::single(prepaid->quantity) | tr::to_count(),
+ lt_duration,
+ tr::lng_premium_gift_duration_months(
+ lt_count,
+ rpl::single(prepaid->months) | tr::to_count())),
+ QImage())
+ )->setAttribute(Qt::WA_TransparentForMouseEvents);
+ }
+ if (!prepaid) {
const auto row = contentWrap->entity()->add(
object_ptr(
box,
@@ -168,7 +328,7 @@ void CreateGiveawayBox(
state->typeValue.force_assign(GiveawayType::Random);
});
}
- {
+ if (!prepaid) {
const auto row = contentWrap->entity()->add(
object_ptr(
box,
@@ -209,7 +369,8 @@ void CreateGiveawayBox(
using Controller = Giveaway::AwardMembersListController;
auto listController = std::make_unique(
controller,
- peer);
+ peer,
+ state->selectedToAward);
listController->setCheckError(CreateErrorCallback(
state->apiOptions.giveawayAddPeersMax(),
tr::lng_giveaway_maximum_users_error));
@@ -237,10 +398,19 @@ void CreateGiveawayBox(
randomWrap->toggle(type == GiveawayType::Random, anim::type::instant);
}, randomWrap->lifetime());
+ randomWrap->toggleOn(
+ state->typeValue.value(
+ ) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
+ anim::type::instant);
+
const auto sliderContainer = randomWrap->entity()->add(
object_ptr(randomWrap));
const auto fillSliderContainer = [=] {
const auto availablePresets = state->apiOptions.availablePresets();
+ if (prepaid) {
+ state->sliderValue = prepaid->quantity;
+ return;
+ }
if (availablePresets.empty()) {
return;
}
@@ -274,8 +444,20 @@ void CreateGiveawayBox(
const auto &padding = st::giveawayGiftCodeSliderPadding;
Settings::AddSkip(sliderContainer, padding.top());
+
+ class Slider : public Ui::MediaSlider {
+ public:
+ using Ui::MediaSlider::MediaSlider;
+
+ protected:
+ void wheelEvent(QWheelEvent *e) override {
+ e->ignore();
+ }
+
+ };
+
const auto slider = sliderContainer->add(
- object_ptr(sliderContainer, st::settingsScale),
+ object_ptr(sliderContainer, st::settingsScale),
st::boxRowPadding);
Settings::AddSkip(sliderContainer, padding.bottom());
slider->resize(slider->width(), st::settingsScale.seekSize.height());
@@ -468,6 +650,24 @@ void CreateGiveawayBox(
Settings::AddSkip(countriesContainer);
}
+ const auto addTerms = [=](not_null c) {
+ auto terms = object_ptr(
+ c,
+ tr::lng_premium_gift_terms(
+ lt_link,
+ tr::lng_premium_gift_terms_link(
+ ) | rpl::map([](const QString &t) {
+ return Ui::Text::Link(t, 1);
+ }),
+ Ui::Text::WithEntities),
+ st::boxDividerLabel);
+ terms->setLink(1, std::make_shared([=] {
+ box->closeBox();
+ Settings::ShowPremium(&peer->session(), QString());
+ }));
+ c->add(std::move(terms));
+ };
+
{
const auto dateContainer = randomWrap->entity()->add(
object_ptr(randomWrap));
@@ -505,18 +705,38 @@ void CreateGiveawayBox(
});
Settings::AddSkip(dateContainer);
- Settings::AddDividerText(
- dateContainer,
- tr::lng_giveaway_date_about(
- lt_count,
- state->sliderValue.value() | tr::to_count()));
- Settings::AddSkip(dateContainer);
+ if (prepaid) {
+ auto terms = object_ptr(dateContainer);
+ terms->add(object_ptr(
+ terms,
+ tr::lng_giveaway_date_about(
+ lt_count,
+ state->sliderValue.value() | tr::to_count()),
+ st::boxDividerLabel));
+ Settings::AddSkip(terms.data());
+ Settings::AddSkip(terms.data());
+ addTerms(terms.data());
+ dateContainer->add(object_ptr(
+ dateContainer,
+ std::move(terms),
+ st::settingsDividerLabelPadding));
+ } else {
+ Settings::AddDividerText(
+ dateContainer,
+ tr::lng_giveaway_date_about(
+ lt_count,
+ state->sliderValue.value() | tr::to_count()));
+ Settings::AddSkip(dateContainer);
+ }
}
const auto durationGroup = std::make_shared(0);
const auto listOptions = contentWrap->entity()->add(
object_ptr(box));
const auto rebuildListOptions = [=](int amountUsers) {
+ if (prepaid) {
+ return;
+ }
while (listOptions->count()) {
delete listOptions->widgetAt(0);
}
@@ -535,29 +755,16 @@ void CreateGiveawayBox(
Settings::AddSkip(listOptions);
- auto terms = object_ptr(
- listOptions,
- tr::lng_premium_gift_terms(
- lt_link,
- tr::lng_premium_gift_terms_link(
- ) | rpl::map([](const QString &t) {
- return Ui::Text::Link(t, 1);
- }),
- Ui::Text::WithEntities),
- st::boxDividerLabel);
- terms->setLink(1, std::make_shared([=] {
- box->closeBox();
- Settings::ShowPremium(&peer->session(), QString());
- }));
+ auto termsContainer = object_ptr(listOptions);
+ addTerms(termsContainer.data());
listOptions->add(object_ptr(
listOptions,
- std::move(terms),
+ std::move(termsContainer),
st::settingsDividerLabelPadding));
box->verticalLayout()->resizeToWidth(box->width());
};
- {
-
+ if (!prepaid) {
rpl::combine(
state->sliderValue.value(),
state->typeValue.value()
@@ -567,27 +774,54 @@ void CreateGiveawayBox(
? state->selectedToAward.size()
: users);
}, box->lifetime());
+ } else {
+ typeGroup->setValue(GiveawayType::Random);
}
{
- // TODO mini-icon.
- const auto &stButton = st::premiumGiftBox;
+ using namespace Info::Statistics;
+ const auto &stButton = st::startGiveawayBox;
box->setStyle(stButton);
auto button = object_ptr(
box,
- state->toAwardAmountChanged.events_starting_with(
- rpl::empty_value()
- ) | rpl::map([=] {
- return (typeGroup->value() == GiveawayType::SpecificUsers)
- ? tr::lng_giveaway_award()
- : tr::lng_giveaway_start();
- }) | rpl::flatten_latest(),
+ rpl::never(),
st::giveawayGiftCodeStartButton);
+
+ AddLabelWithBadgeToButton(
+ button,
+ rpl::conditional(
+ state->typeValue.value(
+ ) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
+ tr::lng_giveaway_start(),
+ tr::lng_giveaway_award()),
+ state->sliderValue.value(
+ ) | rpl::map([=](int v) -> int {
+ return state->apiOptions.giveawayBoostsPerPremium() * v;
+ }),
+ state->confirmButtonBusy.value() | rpl::map(!rpl::mappers::_1));
+
+ {
+ const auto loadingAnimation = InfiniteRadialAnimationWidget(
+ button,
+ st::giveawayGiftCodeStartButton.height / 2);
+ button->sizeValue(
+ ) | rpl::start_with_next([=](const QSize &s) {
+ const auto size = loadingAnimation->size();
+ loadingAnimation->moveToLeft(
+ (s.width() - size.width()) / 2,
+ (s.height() - size.height()) / 2);
+ }, loadingAnimation->lifetime());
+ loadingAnimation->showOn(state->confirmButtonBusy.value());
+ }
+
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
- button->resizeToWidth(box->width()
- - stButton.buttonPadding.left()
- - stButton.buttonPadding.right());
+ state->typeValue.value(
+ ) | rpl::start_with_next([=, raw = button.data()] {
+ raw->resizeToWidth(box->width()
+ - stButton.buttonPadding.left()
+ - stButton.buttonPadding.right());
+ }, button->lifetime());
button->setClickedCallback([=] {
- if (state->confirmButtonBusy) {
+ if (state->confirmButtonBusy.current()) {
return;
}
const auto type = typeGroup->value();
@@ -600,7 +834,10 @@ void CreateGiveawayBox(
isSpecific
? state->selectedToAward.size()
: state->sliderValue.current(),
- durationGroup->value());
+ prepaid
+ ? prepaid->months
+ : state->apiOptions.monthsFromPreset(
+ durationGroup->value()));
if (isSpecific) {
if (state->selectedToAward.empty()) {
return;
@@ -633,12 +870,15 @@ void CreateGiveawayBox(
const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) {
- if (const auto strong = weak.data()) {
- state->confirmButtonBusy = false;
- strong->window()->setFocus();
- strong->closeBox();
+ const auto isPaid = result == Payments::CheckoutResult::Paid;
+ if (result == Payments::CheckoutResult::Pending || isPaid) {
+ if (const auto strong = weak.data()) {
+ strong->window()->setFocus();
+ strong->closeBox();
+ }
}
- if (result == Payments::CheckoutResult::Paid) {
+ if (isPaid) {
+ reloadOnDone();
const auto filter = [=](const auto &...) {
if (const auto window = weakWindow.get()) {
window->showSection(Info::Boosts::Make(peer));
@@ -665,28 +905,71 @@ void CreateGiveawayBox(
.adaptive = true,
.filter = filter,
});
+ } else {
+ state->confirmButtonBusy = false;
}
};
- Payments::CheckoutProcess::Start(std::move(invoice), done);
+ const auto startPrepaid = [=](Fn close) {
+ if (!weak) {
+ close();
+ return;
+ }
+ state->apiOptions.applyPrepaid(
+ invoice,
+ prepaid->id
+ ) | rpl::start_with_error_done([=](const QString &error) {
+ if (const auto window = weakWindow.get()) {
+ window->uiShow()->showToast(error);
+ close();
+ done(Payments::CheckoutResult::Cancelled);
+ }
+ }, [=] {
+ close();
+ done(Payments::CheckoutResult::Paid);
+ }, box->lifetime());
+ };
+ if (prepaid) {
+ const auto cancel = [=](Fn close) {
+ if (weak) {
+ state->confirmButtonBusy = false;
+ }
+ close();
+ };
+ show->show(Ui::MakeConfirmBox({
+ .text = tr::lng_giveaway_start_sure(tr::now),
+ .confirmed = startPrepaid,
+ .cancelled = cancel,
+ }));
+ } else {
+ Payments::CheckoutProcess::Start(std::move(invoice), done);
+ }
});
box->addButton(std::move(button));
}
state->typeValue.force_assign(GiveawayType::Random);
- box->setShowFinishedCallback([=] {
+ std::move(
+ showFinished
+ ) | rpl::take(1) | rpl::start_with_next([=] {
if (!loading->toggled()) {
return;
}
- bar->setPaused(false);
- state->lifetimeApi = state->apiOptions.request(
- ) | rpl::start_with_error_done([=](const QString &error) {
- }, [=] {
+ const auto done = [=] {
state->lifetimeApi.destroy();
loading->toggle(false, anim::type::instant);
+ state->confirmButtonBusy = false;
fillSliderContainer();
rebuildListOptions(1);
contentWrap->toggle(true, anim::type::instant);
contentWrap->resizeToWidth(box->width());
- });
- });
+ };
+ if (prepaid) {
+ return done();
+ }
+ state->lifetimeApi = state->apiOptions.request(
+ ) | rpl::start_with_error_done([=](const QString &error) {
+ box->uiShow()->showToast(error);
+ box->closeBox();
+ }, done);
+ }, box->lifetime());
}
diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.h b/Telegram/SourceFiles/info/boosts/create_giveaway_box.h
index 7463732dc..d1fb6736f 100644
--- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.h
+++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.h
@@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
+namespace Data {
+struct BoostPrepaidGiveaway;
+} // namespace Data
+
namespace Info {
class Controller;
} // namespace Info
@@ -20,4 +24,6 @@ class GenericBox;
void CreateGiveawayBox(
not_null box,
not_null controller,
- not_null peer);
+ not_null peer,
+ Fn reloadOnDone,
+ std::optional prepaidGiveaway);
diff --git a/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.cpp b/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.cpp
new file mode 100644
index 000000000..13e8fb192
--- /dev/null
+++ b/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.cpp
@@ -0,0 +1,173 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "info/boosts/giveaway/boost_badge.h"
+
+#include "ui/effects/radial_animation.h"
+#include "ui/painter.h"
+#include "ui/rect.h"
+#include "ui/rp_widget.h"
+#include "ui/widgets/labels.h"
+#include "styles/style_giveaway.h"
+#include "styles/style_statistics.h"
+#include "styles/style_widgets.h"
+
+namespace Info::Statistics {
+
+not_null InfiniteRadialAnimationWidget(
+ not_null parent,
+ int size) {
+ class Widget final : public Ui::RpWidget {
+ public:
+ Widget(not_null p, int size)
+ : Ui::RpWidget(p)
+ , _animation([=] { update(); }, st::startGiveawayButtonLoading) {
+ resize(size, size);
+ shownValue() | rpl::start_with_next([=](bool v) {
+ return v
+ ? _animation.start()
+ : _animation.stop(anim::type::instant);
+ }, lifetime());
+ }
+
+ protected:
+ void paintEvent(QPaintEvent *e) override {
+ auto p = QPainter(this);
+ p.setPen(st::activeButtonFg);
+ p.setBrush(st::activeButtonFg);
+ const auto r = rect()
+ - Margins(st::startGiveawayButtonLoading.thickness);
+ _animation.draw(p, r.topLeft(), r.size(), width());
+ }
+
+ private:
+ Ui::InfiniteRadialAnimation _animation;
+
+ };
+
+ return Ui::CreateChild(parent.get(), size);
+}
+
+QImage CreateBadge(
+ const style::TextStyle &textStyle,
+ const QString &text,
+ int badgeHeight,
+ const style::margins &textPadding,
+ const style::color &bg,
+ const style::color &fg,
+ float64 bgOpacity,
+ const style::margins &iconPadding,
+ const style::icon &icon) {
+ auto badgeText = Ui::Text::String(textStyle, text);
+ const auto badgeTextWidth = badgeText.maxWidth();
+ const auto badgex = 0;
+ const auto badgey = 0;
+ const auto badgeh = 0 + badgeHeight;
+ const auto badgew = badgeTextWidth
+ + rect::m::sum::h(textPadding);
+ auto result = QImage(
+ QSize(badgew, badgeh) * style::DevicePixelRatio(),
+ QImage::Format_ARGB32_Premultiplied);
+ result.fill(Qt::transparent);
+ result.setDevicePixelRatio(style::DevicePixelRatio());
+ {
+ auto p = Painter(&result);
+
+ p.setPen(Qt::NoPen);
+ p.setBrush(bg);
+
+ const auto r = QRect(badgex, badgey, badgew, badgeh);
+ {
+ auto hq = PainterHighQualityEnabler(p);
+ auto o = ScopedPainterOpacity(p, bgOpacity);
+ p.drawRoundedRect(r, badgeh / 2, badgeh / 2);
+ }
+
+ p.setPen(fg);
+ p.setBrush(Qt::NoBrush);
+ badgeText.drawLeftElided(
+ p,
+ r.x() + textPadding.left(),
+ badgey + textPadding.top(),
+ badgew,
+ badgew * 2);
+
+ icon.paint(
+ p,
+ QPoint(r.x() + iconPadding.left(), r.y() + iconPadding.top()),
+ badgew * 2);
+ }
+ return result;
+}
+
+void AddLabelWithBadgeToButton(
+ not_null parent,
+ rpl::producer text,
+ rpl::producer number,
+ rpl::producer shown) {
+ struct State {
+ QImage badge;
+ };
+ const auto state = parent->lifetime().make_state();
+ const auto label = Ui::CreateChild(
+ parent.get(),
+ st::startGiveawayButtonLabelSimple);
+ std::move(
+ text
+ ) | rpl::start_with_next([=](const QString &s) {
+ label->setText(s);
+ }, label->lifetime());
+ const auto count = Ui::CreateChild(parent.get());
+ count->paintRequest(
+ ) | rpl::start_with_next([=] {
+ auto p = QPainter(count);
+ p.drawImage(0, 0, state->badge);
+ }, count->lifetime());
+ std::move(
+ number
+ ) | rpl::start_with_next([=](int c) {
+ state->badge = Info::Statistics::CreateBadge(
+ st::startGiveawayButtonTextStyle,
+ QString::number(c),
+ st::boostsListBadgeHeight,
+ st::startGiveawayButtonBadgeTextPadding,
+ st::activeButtonFg,
+ st::activeButtonBg,
+ 1.,
+ st::boostsListMiniIconPadding,
+ st::startGiveawayButtonMiniIcon);
+ count->resize(state->badge.size() / style::DevicePixelRatio());
+ count->update();
+ }, count->lifetime());
+
+ std::move(
+ shown
+ ) | rpl::start_with_next([=](bool shown) {
+ count->setVisible(shown);
+ label->setVisible(shown);
+ }, count->lifetime());
+
+ rpl::combine(
+ parent->sizeValue(),
+ label->sizeValue(),
+ count->sizeValue()
+ ) | rpl::start_with_next([=](
+ const QSize &s,
+ const QSize &s1,
+ const QSize &s2) {
+ const auto sum = st::startGiveawayButtonMiniIconSkip
+ + s1.width()
+ + s2.width();
+ const auto contentLeft = (s.width() - sum) / 2;
+ label->moveToLeft(contentLeft, (s.height() - s1.height()) / 2);
+ count->moveToLeft(
+ contentLeft + sum - s2.width(),
+ (s.height() - s2.height()) / 2 + st::boostsListMiniIconSkip);
+ }, parent->lifetime());
+}
+
+} // namespace Info::Statistics
diff --git a/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.h b/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.h
new file mode 100644
index 000000000..fe2427e9c
--- /dev/null
+++ b/Telegram/SourceFiles/info/boosts/giveaway/boost_badge.h
@@ -0,0 +1,41 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+namespace style {
+struct TextStyle;
+} // namespace style
+
+namespace Ui {
+class RpWidget;
+} // namespace Ui
+
+namespace Info::Statistics {
+
+[[nodiscard]] QImage CreateBadge(
+ const style::TextStyle &textStyle,
+ const QString &text,
+ int badgeHeight,
+ const style::margins &textPadding,
+ const style::color &bg,
+ const style::color &fg,
+ float64 bgOpacity,
+ const style::margins &iconPadding,
+ const style::icon &icon);
+
+[[nodiscard]] not_null InfiniteRadialAnimationWidget(
+ not_null parent,
+ int size);
+
+void AddLabelWithBadgeToButton(
+ not_null parent,
+ rpl::producer text,
+ rpl::producer number,
+ rpl::producer shown);
+
+} // namespace Info::Statistics
diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style
index db9ad6f04..4f04fdabb 100644
--- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style
+++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style
@@ -163,3 +163,34 @@ giveawayRefundedLabel: FlatLabel(boxLabel) {
textFg: attentionButtonFg;
}
giveawayRefundedPadding: margins(8px, 10px, 8px, 10px);
+
+startGiveawayBox: Box(premiumGiftBox) {
+ shadowIgnoreTopSkip: true;
+}
+startGiveawayScrollArea: ScrollArea(boxScroll) {
+ deltax: 3px;
+ deltat: 50px;
+}
+startGiveawayBoxTitleClose: IconButton(boxTitleClose) {
+ ripple: universalRippleAnimation;
+}
+startGiveawayCover: PremiumCover(giveawayGiftCodeCover) {
+ bg: boxDividerBg;
+ additionalShadowForDarkThemes: false;
+}
+
+startGiveawayButtonLabelSimple: LabelSimple {
+ font: semiboldFont;
+ textFg: activeButtonFg;
+}
+startGiveawayButtonMiniIcon: icon{{ "boosts/boost_mini2", activeButtonBg }};
+startGiveawayButtonMiniIconSkip: 5px;
+startGiveawayButtonBadgeTextPadding: margins(16px, -1px, 6px, 0px);
+startGiveawayButtonTextStyle: TextStyle(defaultTextStyle) {
+ font: semiboldFont;
+}
+
+startGiveawayButtonLoading: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
+ color: activeButtonFg;
+ thickness: 2px;
+}
diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp
index dd6df700f..975dab5b3 100644
--- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp
+++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway_list_controllers.cpp
@@ -9,9 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_channel.h"
+#include "data/data_folder.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_user.h"
+#include "dialogs/dialogs_indexed_list.h"
+#include "history/history.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/boxes/confirm_box.h"
@@ -107,8 +110,16 @@ void ChannelRow::rightActionStopLastRipple() {
AwardMembersListController::AwardMembersListController(
not_null navigation,
- not_null peer)
-: ParticipantsBoxController(navigation, peer, ParticipantsRole::Members) {
+ not_null peer,
+ std::vector