Merge tag 'v4.11.6' into dev

# Conflicts:
#	Telegram/CMakeLists.txt
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/core/version.h
#	Telegram/lib_ui
This commit is contained in:
ZavaruKitsu 2023-11-10 13:26:08 +03:00
commit a5b446f6df
114 changed files with 2632 additions and 906 deletions

View file

@ -288,6 +288,8 @@ PRIVATE
boxes/peers/peer_short_info_box.h boxes/peers/peer_short_info_box.h
boxes/peers/prepare_short_info_box.cpp boxes/peers/prepare_short_info_box.cpp
boxes/peers/prepare_short_info_box.h 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.cpp
boxes/about_box.h boxes/about_box.h
boxes/about_sponsored_box.cpp 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/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/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/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 "../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.service" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/dbus-1/services")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ayugram.desktop.metainfo.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -2030,6 +2030,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_terms_link" = "here"; "lng_premium_gift_terms_link" = "here";
"lng_boost_channel_button" = "Boost Channel"; "lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again";
"lng_boost_level#one" = "Level {count}"; "lng_boost_level#one" = "Level {count}";
"lng_boost_level#other" = "Level {count}"; "lng_boost_level#other" = "Level {count}";
"lng_boost_channel_title_first" = "Enable stories for channel"; "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_channel_post_stories#other" = "post **{count} stories** per day";
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!"; "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_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_title" = "Already Boosted!";
"lng_boost_error_already_text" = "You are already boosting this channel."; "lng_boost_error_already_text" = "You are already boosting this channel.";
"lng_boost_error_premium_title" = "Premium needed!"; "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_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_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace"; "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_title_color" = "Enable colors";
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; "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_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_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium"; "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_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time"; "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."; "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_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_used_footer" = "This link was used on {date}.";
"lng_gift_link_expired" = "Gift code link expired"; "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_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts."; "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_existing" = "Existing boosts";
"lng_boosts_premium_audience" = "Premium subscribers"; "lng_boosts_premium_audience" = "Premium subscribers";
"lng_boosts_next_level" = "Boosts to level up"; "lng_boosts_next_level" = "Boosts to level up";
"lng_boosts_list_title#one" = "{count} booster"; "lng_boosts_list_title#one" = "{count} Boost";
"lng_boosts_list_title#other" = "{count} boosters"; "lng_boosts_list_title#other" = "{count} Boosts";
"lng_boosts_list_subtext" = "Your channel is currently boosted by these users."; "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#one" = "Show {count} More Boosts";
"lng_boosts_show_more_boosts#other" = "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#one" = "{count} Gifts";
"lng_boosts_list_tab_gifts#other" = "{count} Gifts"; "lng_boosts_list_tab_gifts#other" = "{count} Gifts";
"lng_boosts_prepaid_giveaway_title" = "Prepaid giveaways";
"lng_boosts_prepaid_giveaway_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 // Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program..."; "lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

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

View file

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

View file

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

View file

@ -390,18 +390,44 @@ rpl::producer<rpl::no_value, QString> PremiumGiftCodeOptions::request() {
}; };
} }
rpl::producer<rpl::no_value, QString> 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<int> &PremiumGiftCodeOptions::availablePresets() const { const std::vector<int> &PremiumGiftCodeOptions::availablePresets() const {
return _availablePresets; return _availablePresets;
} }
[[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) {
return _optionsForOnePerson.months[monthsIndex];
}
Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice( Payments::InvoicePremiumGiftCode PremiumGiftCodeOptions::invoice(
int users, int users,
int monthsIndex) { int months) {
const auto randomId = base::RandomValue<uint64>(); const auto randomId = base::RandomValue<uint64>();
const auto token = Token{ const auto token = Token{ users, months };
users,
_optionsForOnePerson.months[monthsIndex],
};
const auto &store = _stores[token]; const auto &store = _stores[token];
return Payments::InvoicePremiumGiftCode{ return Payments::InvoicePremiumGiftCode{
.randomId = randomId, .randomId = randomId,

View file

@ -152,9 +152,13 @@ public:
[[nodiscard]] rpl::producer<rpl::no_value, QString> request(); [[nodiscard]] rpl::producer<rpl::no_value, QString> request();
[[nodiscard]] Data::SubscriptionOptions options(int amount); [[nodiscard]] Data::SubscriptionOptions options(int amount);
[[nodiscard]] const std::vector<int> &availablePresets() const; [[nodiscard]] const std::vector<int> &availablePresets() const;
[[nodiscard]] int monthsFromPreset(int monthsIndex);
[[nodiscard]] Payments::InvoicePremiumGiftCode invoice( [[nodiscard]] Payments::InvoicePremiumGiftCode invoice(
int users, int users,
int monthsIndex); int months);
[[nodiscard]] rpl::producer<rpl::no_value, QString> applyPrepaid(
const Payments::InvoicePremiumGiftCode &invoice,
uint64 prepaidId);
[[nodiscard]] int giveawayBoostsPerPremium() const; [[nodiscard]] int giveawayBoostsPerPremium() const;
[[nodiscard]] int giveawayCountriesMax() const; [[nodiscard]] int giveawayCountriesMax() const;

View file

@ -518,8 +518,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
? (100. * premiumMemberCount / participantCount) ? (100. * premiumMemberCount / participantCount)
: 0; : 0;
const auto slots = data.vmy_boost_slots();
_boostStatus.overview = Data::BoostsOverview{ _boostStatus.overview = Data::BoostsOverview{
.isBoosted = data.is_my_boost(), .mine = slots ? int(slots->v.size()) : 0,
.level = std::max(data.vlevel().v, 0), .level = std::max(data.vlevel().v, 0),
.boostCount = std::max( .boostCount = std::max(
data.vboosts().v, data.vboosts().v,
@ -533,6 +534,20 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
}; };
_boostStatus.link = qs(data.vboost_url()); _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; using namespace Data;
requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) { requestBoosts({ .gifts = false }, [=](BoostsListSlice &&slice) {
_boostStatus.firstSliceBoosts = std::move(slice); _boostStatus.firstSliceBoosts = std::move(slice);
@ -540,7 +555,6 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
_boostStatus.firstSliceGifts = std::move(s); _boostStatus.firstSliceGifts = std::move(s);
consumer.put_done(); consumer.put_done();
}); });
consumer.put_done();
}); });
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
consumer.put_error_copy(error.type()); consumer.put_error_copy(error.type());
@ -574,6 +588,7 @@ void Boosts::requestBoosts(
auto list = std::vector<Data::Boost>(); auto list = std::vector<Data::Boost>();
list.reserve(data.vboosts().v.size()); list.reserve(data.vboosts().v.size());
constexpr auto kMonthsDivider = int(30 * 86400);
for (const auto &boost : data.vboosts().v) { for (const auto &boost : data.vboosts().v) {
const auto &data = boost.data(); const auto &data = boost.data();
const auto path = data.vused_gift_slug() const auto path = data.vused_gift_slug()
@ -596,7 +611,8 @@ void Boosts::requestBoosts(
? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v } ? FullMsgId{ _peer->id, data.vgiveaway_msg_id()->v }
: FullMsgId(), : FullMsgId(),
QDateTime::fromSecsSinceEpoch(data.vdate().v), QDateTime::fromSecsSinceEpoch(data.vdate().v),
data.vexpires().v, QDateTime::fromSecsSinceEpoch(data.vexpires().v),
(data.vexpires().v - data.vdate().v) / kMonthsDivider,
std::move(giftCodeLink), std::move(giftCodeLink),
data.vmultiplier().value_or_empty(), data.vmultiplier().value_or_empty(),
}); });

View file

@ -362,7 +362,7 @@ void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListSetRowChecked(row, !row->checked()); delegate()->peerListSetRowChecked(row, !row->checked());
updateTitle(); updateTitle();
} else { } else {
delegate()->peerListShowBox(_limitBox(count)); delegate()->peerListUiShow()->showBox(_limitBox(count));
} }
} }

View file

@ -575,19 +575,19 @@ void LinkController::addLinkBlock(not_null<Ui::VerticalLayout*> container) {
CopyInviteLink(delegate()->peerListUiShow(), link); CopyInviteLink(delegate()->peerListUiShow(), link);
}); });
const auto shareLink = crl::guard(weak, [=] { const auto shareLink = crl::guard(weak, [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(&_window->session(), link)); ShareInviteLinkBox(&_window->session(), link));
}); });
const auto getLinkQr = crl::guard(weak, [=] { const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_filters_link_qr_about())); InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
}); });
const auto editLink = crl::guard(weak, [=] { const auto editLink = crl::guard(weak, [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
Box(ChatFilterLinkBox, &_window->session(), _data)); Box(ChatFilterLinkBox, &_window->session(), _data));
}); });
const auto deleteLink = crl::guard(weak, [=] { const auto deleteLink = crl::guard(weak, [=] {
delegate()->peerListShowBox(DeleteLinkBox(_window, _data)); delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, _data));
}); });
const auto createMenu = [=] { const auto createMenu = [=] {
@ -846,7 +846,7 @@ void LinksController::rebuild(const std::vector<InviteLinkData> &rows) {
void LinksController::rowClicked(not_null<PeerListRow*> row) { void LinksController::rowClicked(not_null<PeerListRow*> row) {
const auto link = static_cast<LinkRow*>(row.get())->data(); const auto link = static_cast<LinkRow*>(row.get())->data();
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
ShowLinkBox(_window, _currentFilter(), link)); ShowLinkBox(_window, _currentFilter(), link));
} }
@ -881,19 +881,19 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
CopyInviteLink(delegate()->peerListUiShow(), link); CopyInviteLink(delegate()->peerListUiShow(), link);
}; };
const auto shareLink = [=] { const auto shareLink = [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(&_window->session(), link)); ShareInviteLinkBox(&_window->session(), link));
}; };
const auto getLinkQr = [=] { const auto getLinkQr = [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_filters_link_qr_about())); InviteLinkQrBox(link, tr::lng_filters_link_qr_about()));
}; };
const auto editLink = [=] { const auto editLink = [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
Box(ChatFilterLinkBox, &_window->session(), data)); Box(ChatFilterLinkBox, &_window->session(), data));
}; };
const auto deleteLink = [=] { const auto deleteLink = [=] {
delegate()->peerListShowBox(DeleteLinkBox(_window, data)); delegate()->peerListUiShow()->showBox(DeleteLinkBox(_window, data));
}; };
auto result = base::make_unique_q<Ui::PopupMenu>( auto result = base::make_unique_q<Ui::PopupMenu>(
parent, parent,

View file

@ -7,9 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/gift_premium_box.h" #include "boxes/gift_premium_box.h"
#include "apiwrap.h"
#include "api/api_premium.h" #include "api/api_premium.h"
#include "api/api_premium_option.h" #include "api/api_premium_option.h"
#include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.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_graphics.h"
#include "ui/effects/premium_stars_colored.h" #include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h" #include "ui/effects/premium_top_bar.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h" #include "ui/widgets/gradient_round_button.h"
@ -93,7 +95,7 @@ void GiftBox(
+ st::defaultUserpicButton.size.height())); + st::defaultUserpicButton.size.height()));
using ColoredMiniStars = Ui::Premium::ColoredMiniStars; using ColoredMiniStars = Ui::Premium::ColoredMiniStars;
const auto stars = box->lifetime().make_state<ColoredMiniStars>(top); const auto stars = box->lifetime().make_state<ColoredMiniStars>(top, true);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>( const auto userpic = Ui::CreateChild<Ui::UserpicButton>(
top, top,
@ -358,6 +360,73 @@ void AddTableRow(
st::giveawayGiftCodePeerMargin); st::giveawayGiftCodePeerMargin);
} }
void AddTable(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionNavigation*> controller,
const Api::GiftCode &current,
bool skipReason) {
auto table = container->add(
object_ptr<Ui::TableLayout>(
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 } // namespace
GiftPremiumValidator::GiftPremiumValidator( GiftPremiumValidator::GiftPremiumValidator(
@ -462,65 +531,7 @@ void GiftCodeBox(
MakeLinkCopyIcon(box)), MakeLinkCopyIcon(box)),
st::giveawayGiftCodeLinkMargin); st::giveawayGiftCodeLinkMargin);
auto table = box->addRow( AddTable(box->verticalLayout(), controller, state->data.current(), false);
object_ptr<Ui::TableLayout>(
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)))));
}
auto shareLink = tr::lng_gift_link_also_send_link( auto shareLink = tr::lng_gift_link_also_send_link(
) | rpl::map([](const QString &text) { ) | rpl::map([](const QString &text) {
@ -603,6 +614,113 @@ void GiftCodeBox(
}, button->lifetime()); }, button->lifetime());
} }
void GiftCodePendingBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> 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<Ui::Premium::TopBar>(
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<Ui::AbstractButton>(linkLabel);
spoiler->lifetime().make_state<Ui::Animations::Basic>([=] {
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<Ui::FlatLabel>(
box,
tr::lng_gift_link_pending_footer(),
st::giveawayGiftCodeFooter),
st::giveawayGiftCodeFooterMargin);
const auto close = Ui::CreateChild<Ui::IconButton>(
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( void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
const QString &slug) { const QString &slug) {

View file

@ -50,6 +50,10 @@ void GiftCodeBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
const QString &slug); const QString &slug);
void GiftCodePendingBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> controller,
const Api::GiftCode &data);
void ResolveGiftCode( void ResolveGiftCode(
not_null<Window::SessionNavigation*> controller, not_null<Window::SessionNavigation*> controller,
const QString &slug); const QString &slug);

View file

@ -567,12 +567,10 @@ void PasscodeBox::validateEmail(
} else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) { } else if (error.type() == u"EMAIL_HASH_EXPIRED"_q) {
const auto weak = Ui::MakeWeak(this); const auto weak = Ui::MakeWeak(this);
_clearUnconfirmedPassword.fire({}); _clearUnconfirmedPassword.fire({});
if (weak) { if (const auto strong = weak.data()) {
auto box = Ui::MakeInformBox({ strong->getDelegate()->show(
Lang::Hard::EmailConfirmationExpired() Ui::MakeInformBox(
}); Lang::Hard::EmailConfirmationExpired()),
weak->getDelegate()->show(
std::move(box),
Ui::LayerOption::CloseOther); Ui::LayerOption::CloseOther);
} }
} else { } else {

View file

@ -85,16 +85,6 @@ PeerListContentDelegateShow::PeerListContentDelegateShow(
: _show(show) { : _show(show) {
} }
void PeerListContentDelegateShow::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
_show->showBox(std::move(content), options);
}
void PeerListContentDelegateShow::peerListHideLayer() {
_show->hideLayer();
}
auto PeerListContentDelegateShow::peerListUiShow() auto PeerListContentDelegateShow::peerListUiShow()
-> std::shared_ptr<Main::SessionShow>{ -> std::shared_ptr<Main::SessionShow>{
return _show; return _show;
@ -324,16 +314,6 @@ void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) {
} }
} }
void PeerListBox::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
_show->showBox(std::move(content), options);
}
void PeerListBox::peerListHideLayer() {
_show->hideLayer();
}
std::shared_ptr<Main::SessionShow> PeerListBox::peerListUiShow() { std::shared_ptr<Main::SessionShow> PeerListBox::peerListUiShow() {
return _show; return _show;
} }
@ -1702,9 +1682,19 @@ crl::time PeerListContent::paintRow(
return refreshStatusIn; return refreshStatusIn;
} }
const auto opacity = row->opacity();
const auto &bg = selected const auto &bg = selected
? _st.item.button.textBgOver ? _st.item.button.textBgOver
: _st.item.button.textBg; : _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); p.fillRect(0, 0, outerWidth, _rowHeight, bg);
row->paintRipple(p, 0, 0, outerWidth); row->paintRipple(p, 0, 0, outerWidth);
row->paintUserpic( row->paintUserpic(

View file

@ -141,6 +141,9 @@ public:
} }
virtual void rightActionStopLastRipple() { virtual void rightActionStopLastRipple() {
} }
[[nodiscard]] virtual float64 opacity() {
return 1.;
}
// By default elements code falls back to a simple right action code. // By default elements code falls back to a simple right action code.
virtual int elementsCount() const; virtual int elementsCount() const;
@ -332,10 +335,6 @@ public:
virtual std::optional<QPoint> peerListLastRowMousePosition() = 0; virtual std::optional<QPoint> peerListLastRowMousePosition() = 0;
virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0; virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0; virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
virtual void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) = 0;
virtual void peerListHideLayer() = 0;
virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0; virtual std::shared_ptr<Main::SessionShow> peerListUiShow() = 0;
template <typename PeerDataRange> template <typename PeerDataRange>
@ -1007,14 +1006,6 @@ public:
object_ptr<Ui::FlatLabel> description) override { object_ptr<Ui::FlatLabel> description) override {
description.destroy(); description.destroy();
} }
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override {
Unexpected("...DelegateSimple::peerListShowBox");
}
void peerListHideLayer() override {
Unexpected("...DelegateSimple::peerListHideLayer");
}
std::shared_ptr<Main::SessionShow> peerListUiShow() override { std::shared_ptr<Main::SessionShow> peerListUiShow() override {
Unexpected("...DelegateSimple::peerListUiShow"); Unexpected("...DelegateSimple::peerListUiShow");
} }
@ -1025,10 +1016,6 @@ class PeerListContentDelegateShow : public PeerListContentDelegateSimple {
public: public:
explicit PeerListContentDelegateShow( explicit PeerListContentDelegateShow(
std::shared_ptr<Main::SessionShow> show); std::shared_ptr<Main::SessionShow> show);
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
private: private:
@ -1064,10 +1051,6 @@ public:
bool peerListIsRowChecked(not_null<PeerListRow*> row) override; bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override; int peerListSelectedRowsCount() override;
void peerListScrollToTop() override; void peerListScrollToTop() override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void setAddedTopScrollSkip(int skip); void setAddedTopScrollSkip(int skip);

View file

@ -685,7 +685,7 @@ void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
}, box->lifetime()); }, box->lifetime());
}); });
*weak = owned.data(); *weak = owned.data();
delegate()->peerListShowBox(std::move(owned)); delegate()->peerListUiShow()->showBox(std::move(owned));
return; return;
} }
const auto history = peer->owner().history(peer); const auto history = peer->owner().history(peer);

View file

@ -372,16 +372,6 @@ void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
_box->_select->entity()->finishItemsBunch(); _box->_select->entity()->finishItemsBunch();
} }
void PeerListsBox::Delegate::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
_show->showBox(std::move(content), options);
}
void PeerListsBox::Delegate::peerListHideLayer() {
_show->hideLayer();
}
auto PeerListsBox::Delegate::peerListUiShow() auto PeerListsBox::Delegate::peerListUiShow()
-> std::shared_ptr<Main::SessionShow> { -> std::shared_ptr<Main::SessionShow> {
return _show; return _show;

View file

@ -54,10 +54,6 @@ private:
_box->addSelectItem(row, anim::type::instant); _box->addSelectItem(row, anim::type::instant);
} }
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
private: private:

View file

@ -366,7 +366,7 @@ bool AddParticipantsBoxController::needsInviteLinkButton() {
QPointer<Ui::BoxContent> AddParticipantsBoxController::showBox( QPointer<Ui::BoxContent> AddParticipantsBoxController::showBox(
object_ptr<Ui::BoxContent> box) const { object_ptr<Ui::BoxContent> box) const {
const auto weak = Ui::MakeWeak(box.data()); const auto weak = Ui::MakeWeak(box.data());
delegate()->peerListShowBox(std::move(box)); delegate()->peerListUiShow()->showBox(std::move(box));
return weak; return weak;
} }
@ -668,7 +668,7 @@ void AddSpecialBoxController::migrate(
QPointer<Ui::BoxContent> AddSpecialBoxController::showBox( QPointer<Ui::BoxContent> AddSpecialBoxController::showBox(
object_ptr<Ui::BoxContent> box) const { object_ptr<Ui::BoxContent> box) const {
const auto weak = Ui::MakeWeak(box.data()); const auto weak = Ui::MakeWeak(box.data());
delegate()->peerListShowBox(std::move(box)); delegate()->peerListUiShow()->showBox(std::move(box));
return weak; return weak;
} }

View file

@ -433,7 +433,7 @@ void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
if (const auto user = peer->asUser()) { if (const auto user = peer->asUser()) {
done(); done();
} else { } else {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
MakeConfirmBox(_bot, peer, _query, done)); MakeConfirmBox(_bot, peer, _query, done));
} }
} }

View file

@ -168,7 +168,7 @@ void Controller::choose(not_null<ChannelData*> chat) {
const auto onstack = _callback; const auto onstack = _callback;
onstack(chat); onstack(chat);
}; };
delegate()->peerListShowBox(Ui::MakeConfirmBox({ delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
.text = text, .text = text,
.confirmed = sure, .confirmed = sure,
.confirmText = tr::lng_manage_discussion_group_link(tr::now), .confirmText = tr::lng_manage_discussion_group_link(tr::now),
@ -199,7 +199,7 @@ void Controller::choose(not_null<ChatData*> chat) {
}; };
chat->session().api().migrateChat(chat, crl::guard(this, done)); chat->session().api().migrateChat(chat, crl::guard(this, done));
}; };
delegate()->peerListShowBox(Ui::MakeConfirmBox({ delegate()->peerListUiShow()->showBox(Ui::MakeConfirmBox({
.text = text, .text = text,
.confirmed = sure, .confirmed = sure,
.confirmText = tr::lng_manage_discussion_group_link(tr::now), .confirmText = tr::lng_manage_discussion_group_link(tr::now),

View file

@ -1281,7 +1281,7 @@ void ParticipantsBoxController::rebuild() {
QPointer<Ui::BoxContent> ParticipantsBoxController::showBox( QPointer<Ui::BoxContent> ParticipantsBoxController::showBox(
object_ptr<Ui::BoxContent> box) const { object_ptr<Ui::BoxContent> box) const {
const auto weak = Ui::MakeWeak(box.data()); const auto weak = Ui::MakeWeak(box.data());
delegate()->peerListShowBox(std::move(box)); delegate()->peerListUiShow()->showBox(std::move(box));
return weak; return weak;
} }

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
@ -514,21 +515,17 @@ void Apply(
close(); close();
return; return;
} }
const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] { const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow( if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) { ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer)); 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{ show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()), .link = qs(data.vboost_url()),
.boost = { .boost = counters,
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
},
.requiredLevel = required, .requiredLevel = required,
}, openStatistics, nullptr)); }, openStatistics, nullptr));
cancel(); cancel();

View file

@ -347,21 +347,24 @@ void Controller::addHeaderBlock(not_null<Ui::VerticalLayout*> container) {
const auto copyLink = crl::guard(weak, [=] { const auto copyLink = crl::guard(weak, [=] {
CopyInviteLink(delegate()->peerListUiShow(), link); CopyInviteLink(delegate()->peerListUiShow(), link);
}); });
const auto shareLink = crl::guard(weak, [=] { const auto shareLink = crl::guard(weak, [=, peer = _peer] {
delegate()->peerListShowBox(ShareInviteLinkBox(_peer, link)); delegate()->peerListUiShow()->showBox(ShareInviteLinkBox(peer, link));
}); });
const auto getLinkQr = crl::guard(weak, [=] { const auto getLinkQr = crl::guard(weak, [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_group_invite_qr_about())); InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
}); });
const auto revokeLink = crl::guard(weak, [=] { 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, [=] { 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, [=] { const auto deleteLink = crl::guard(weak, [=] {
delegate()->peerListShowBox(DeleteLinkBox(_peer, admin, link)); delegate()->peerListUiShow()->showBox(
DeleteLinkBox(_peer, admin, link));
}); });
const auto createMenu = [=] { const auto createMenu = [=] {
@ -1332,7 +1335,7 @@ object_ptr<Ui::BoxContent> ShowInviteLinkBox(
auto data = rpl::single(link) | rpl::then(std::move(updates)); auto data = rpl::single(link) | rpl::then(std::move(updates));
auto initBox = [=, data = rpl::duplicate(data)]( auto initBox = [=, data = rpl::duplicate(data)](
not_null<Ui::BoxContent*> box) { not_null<Ui::BoxContent*> box) {
rpl::duplicate( rpl::duplicate(
data data
) | rpl::start_with_next([=](const LinkData &link) { ) | rpl::start_with_next([=](const LinkData &link) {

View file

@ -537,7 +537,7 @@ void LinksController::appendSlice(const InviteLinksSlice &slice) {
} }
void LinksController::rowClicked(not_null<PeerListRow*> row) { void LinksController::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data())); ShowInviteLinkBox(_peer, static_cast<Row*>(row.get())->data()));
} }
@ -573,25 +573,27 @@ base::unique_qptr<Ui::PopupMenu> LinksController::createRowContextMenu(
st::popupMenuWithIcons); st::popupMenuWithIcons);
if (data.revoked) { if (data.revoked) {
result->addAction(tr::lng_group_invite_context_delete(tr::now), [=] { 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); }, &st::menuIconDelete);
} else { } else {
result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] { result->addAction(tr::lng_group_invite_context_copy(tr::now), [=] {
CopyInviteLink(delegate()->peerListUiShow(), link); CopyInviteLink(delegate()->peerListUiShow(), link);
}, &st::menuIconCopy); }, &st::menuIconCopy);
result->addAction(tr::lng_group_invite_context_share(tr::now), [=] { result->addAction(tr::lng_group_invite_context_share(tr::now), [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
ShareInviteLinkBox(_peer, link)); ShareInviteLinkBox(_peer, link));
}, &st::menuIconShare); }, &st::menuIconShare);
result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] { result->addAction(tr::lng_group_invite_context_qr(tr::now), [=] {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
InviteLinkQrBox(link, tr::lng_group_invite_qr_about())); InviteLinkQrBox(link, tr::lng_group_invite_qr_about()));
}, &st::menuIconQrCode); }, &st::menuIconQrCode);
result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] { result->addAction(tr::lng_group_invite_context_edit(tr::now), [=] {
delegate()->peerListShowBox(EditLinkBox(_peer, data)); delegate()->peerListUiShow()->showBox(EditLinkBox(_peer, data));
}, &st::menuIconEdit); }, &st::menuIconEdit);
result->addAction(tr::lng_group_invite_context_revoke(tr::now), [=] { 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); }, &st::menuIconRemove);
} }
return result; return result;
@ -799,7 +801,7 @@ void AdminsController::loadMoreRows() {
} }
void AdminsController::rowClicked(not_null<PeerListRow*> row) { void AdminsController::rowClicked(not_null<PeerListRow*> row) {
delegate()->peerListShowBox( delegate()->peerListUiShow()->showBox(
Box(ManageInviteLinksBox, _peer, row->peer()->asUser(), 0, 0)); Box(ManageInviteLinksBox, _peer, row->peer()->asUser(), 0, 0));
} }

View file

@ -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<Main::Session*> 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<Ui::EmptyUserpic> _empty;
Ui::PeerUserpicView _userpic;
crl::time _startPreciseTime = 0;
TimeId _startUnixtime = 0;
bool _waiting = false;
};
class Controller final : public PeerListController {
public:
Controller(not_null<ChannelData*> to, std::vector<TakenBoostSlot> from);
[[nodiscard]] rpl::producer<std::vector<int>> selectedValue() const {
return _selected.value();
}
Main::Session &session() const override;
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
bool trackSelectedList() override {
return false;
}
private:
void updateWaitingState();
not_null<ChannelData*> _to;
std::vector<TakenBoostSlot> _from;
rpl::variable<std::vector<int>> _selected;
rpl::variable<std::vector<not_null<PeerData*>>> _selectedPeers;
base::Timer _waitingTimer;
bool _hasWaitingRows = false;
};
Row::Row(
not_null<Main::Session*> 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>(
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<ChannelData*> to,
std::vector<TakenBoostSlot> 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<Ui::VerticalLayout>((QWidget*)nullptr);
above->add(
CreateBoostReplaceUserpics(
above.data(),
_selectedPeers.value(),
_to),
st::boxRowPadding + st::boostReplaceUserpicsPadding);
above->add(
object_ptr<Ui::FlatLabel>(
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<Row>(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<Row*>(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<PeerListRow*> row) {
const auto slot = static_cast<Row*>(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<Ui::BoxContent> 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<Ui::BoxContent> ReassignBoostSingleBox(
not_null<ChannelData*> to,
TakenBoostSlot from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void()> cancel) {
const auto reassigned = std::make_shared<bool>();
const auto slot = from.id;
const auto peer = to->owner().peer(from.peerId);
const auto confirmed = [=](Fn<void()> close) {
*reassigned = true;
reassign({ slot }, 1);
close();
};
auto result = Box([=](not_null<Ui::GenericBox*> 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<ChannelData*> channel,
const QVector<MTPMyBoost> &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<Main::Session*> session) {
const auto key = u"boosts_per_sent_gift"_q;
return session->account().appConfig().get<int>(key, 0);
}
[[nodiscard]] int SourcesCount(
const std::vector<TakenBoostSlot> &from,
const std::vector<int> &slots) {
auto checked = base::flat_set<PeerId>();
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<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void()> 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<bool>();
auto controller = std::make_unique<Controller>(to, from);
const auto raw = controller.get();
auto initBox = [=](not_null<Ui::BoxContent*> box) {
raw->selectedValue(
) | rpl::start_with_next([=](std::vector<int> 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<PeerListBox>(std::move(controller), std::move(initBox));
}
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to) {
struct State {
std::vector<not_null<PeerData*>> from;
std::vector<std::unique_ptr<Ui::UserpicButton>> buttons;
QImage layer;
rpl::variable<int> count = 0;
bool painting = false;
};
const auto full = st::boostReplaceUserpic.size.height()
+ st::boostReplaceIconAdd.y()
+ st::lineWidth;
auto result = object_ptr<Ui::FixedHeightWidget>(parent, full);
const auto raw = result.data();
const auto &st = st::boostReplaceUserpic;
const auto right = CreateChild<Ui::UserpicButton>(raw, to, st);
const auto overlay = CreateChild<Ui::RpWidget>(raw);
const auto state = raw->lifetime().make_state<State>();
std::move(
from
) | rpl::start_with_next([=](
const std::vector<not_null<PeerData*>> &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<Ui::UserpicButton>(raw, peer, st));
const auto raw = state->buttons.back().get();
base::install_event_filter(raw, [=](not_null<QEvent*> 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;
}

View file

@ -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<int> free;
std::vector<int> already;
std::vector<TakenBoostSlot> other;
};
[[nodiscard]] ForChannelBoostSlots ParseForChannelBoostSlots(
not_null<ChannelData*> channel,
const QVector<MTPMyBoost> &boosts);
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
const MTPpremium_BoostsStatus &status);
[[nodiscard]] int BoostsForGift(not_null<Main::Session*> session);
object_ptr<Ui::BoxContent> ReassignBoostsBox(
not_null<ChannelData*> to,
std::vector<TakenBoostSlot> from,
Fn<void(std::vector<int> slots, int sources)> reassign,
Fn<void()> cancel);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
not_null<Ui::RpWidget*> parent,
rpl::producer<std::vector<not_null<PeerData*>>> from,
not_null<PeerData*> to);

View file

@ -115,10 +115,6 @@ public:
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription( void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override; object_ptr<Ui::FlatLabel> description) override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void peerListSetRowChecked( void peerListSetRowChecked(
not_null<PeerListRow*> row, not_null<PeerListRow*> row,
@ -183,14 +179,6 @@ void InactiveDelegate::peerListSetDescription(
description.destroy(); description.destroy();
} }
void InactiveDelegate::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
}
void InactiveDelegate::peerListHideLayer() {
}
std::shared_ptr<Main::SessionShow> InactiveDelegate::peerListUiShow() { std::shared_ptr<Main::SessionShow> InactiveDelegate::peerListUiShow() {
Unexpected("...InactiveDelegate::peerListUiShow"); Unexpected("...InactiveDelegate::peerListUiShow");
} }

View file

@ -1975,14 +1975,6 @@ void Members::peerListSetDescription(
description.destroy(); description.destroy();
} }
void Members::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
}
void Members::peerListHideLayer() {
}
std::shared_ptr<Main::SessionShow> Members::peerListUiShow() { std::shared_ptr<Main::SessionShow> Members::peerListUiShow() {
Unexpected("...Members::peerListUiShow"); Unexpected("...Members::peerListUiShow");
} }

View file

@ -88,10 +88,6 @@ private:
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription( void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override; object_ptr<Ui::FlatLabel> description) override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
void setupAddMember(not_null<GroupCall*> call); void setupAddMember(not_null<GroupCall*> call);

View file

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

View file

@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data { namespace Data {
struct BoostsOverview final { struct BoostsOverview final {
bool isBoosted = false; int mine = 0;
int level = 0; int level = 0;
int boostCount = 0; int boostCount = 0;
int currentLevelBoostCount = 0; int currentLevelBoostCount = 0;
@ -34,7 +34,8 @@ struct Boost final {
UserId userId = UserId(0); UserId userId = UserId(0);
FullMsgId giveawayMessage; FullMsgId giveawayMessage;
QDateTime date; QDateTime date;
crl::time expiresAt = 0; QDateTime expiresAt;
int expiresAfterMonths = 0;
GiftCodeLink giftCodeLink; GiftCodeLink giftCodeLink;
int multiplier = 0; int multiplier = 0;
}; };
@ -50,10 +51,18 @@ struct BoostsListSlice final {
OffsetToken token; OffsetToken token;
}; };
struct BoostPrepaidGiveaway final {
int months = 0;
uint64 id = 0;
int quantity = 0;
QDateTime date;
};
struct BoostStatus final { struct BoostStatus final {
BoostsOverview overview; BoostsOverview overview;
BoostsListSlice firstSliceBoosts; BoostsListSlice firstSliceBoosts;
BoostsListSlice firstSliceGifts; BoostsListSlice firstSliceGifts;
std::vector<BoostPrepaidGiveaway> prepaidGiveaway;
QString link; QString link;
}; };

View file

@ -391,3 +391,7 @@ bool WebPageData::computeDefaultSmallMedia() const {
} }
return false; return false;
} }
bool WebPageData::suggestEnlargePhoto() const {
return !siteName.isEmpty() || !title.isEmpty() || !description.empty();
}

View file

@ -90,6 +90,7 @@ struct WebPageData {
[[nodiscard]] QString displayedSiteName() const; [[nodiscard]] QString displayedSiteName() const;
[[nodiscard]] bool computeDefaultSmallMedia() const; [[nodiscard]] bool computeDefaultSmallMedia() const;
[[nodiscard]] bool suggestEnlargePhoto() const;
const WebPageId id = 0; const WebPageId id = 0;
WebPageType type = WebPageType::None; WebPageType type = WebPageType::None;

View file

@ -1861,9 +1861,12 @@ std::unique_ptr<QMimeData> HistoryInner::prepareDrag() {
session().data().setMimeForwardIds(std::move(forwardIds)); session().data().setMimeForwardIds(std::move(forwardIds));
auto result = std::make_unique<QMimeData>(); auto result = std::make_unique<QMimeData>();
result->setData(u"application/x-td-forward"_q, "1"); result->setData(u"application/x-td-forward"_q, "1");
if (const auto media = pressedView->media()) { if (pressedHandler) {
if (const auto document = media->getDocument()) { const auto lnkDocument = reinterpret_cast<DocumentData*>(
const auto filepath = document->filepath(true); pressedHandler->property(
kDocumentLinkMediaProperty).toULongLong());
if (lnkDocument) {
const auto filepath = lnkDocument->filepath(true);
if (!filepath.isEmpty()) { if (!filepath.isEmpty()) {
QList<QUrl> urls; QList<QUrl> urls;
urls.push_back(QUrl::fromLocalFile(filepath)); urls.push_back(QUrl::fromLocalFile(filepath));
@ -3098,6 +3101,8 @@ void HistoryInner::recountHistoryGeometry() {
accumulate_max(oldHistoryPaddingTop, _botAbout->height); accumulate_max(oldHistoryPaddingTop, _botAbout->height);
} }
updateBotInfo(false);
_history->resizeToWidth(_contentWidth); _history->resizeToWidth(_contentWidth);
if (_migrated) { if (_migrated) {
_migrated->resizeToWidth(_contentWidth); _migrated->resizeToWidth(_contentWidth);
@ -3121,7 +3126,6 @@ void HistoryInner::recountHistoryGeometry() {
} }
} }
updateBotInfo(false);
if (const auto view = _botAbout ? _botAbout->view() : nullptr) { if (const auto view = _botAbout ? _botAbout->view() : nullptr) {
_botAbout->height = view->resizeGetHeight(_contentWidth); _botAbout->height = view->resizeGetHeight(_contentWidth);
_botAbout->top = qMin( _botAbout->top = qMin(

View file

@ -3407,6 +3407,7 @@ void HistoryItem::createComponentsHelper(
: nullptr; : nullptr;
if (!config.reply.externalPeerId if (!config.reply.externalPeerId
&& topic && topic
&& to
&& topic->rootId() != to->topicRootId()) { && topic->rootId() != to->topicRootId()) {
config.reply.externalPeerId = replyTo.messageId.peer; config.reply.externalPeerId = replyTo.messageId.peer;
} }

View file

@ -22,6 +22,7 @@ enum class CursorState : char {
None, None,
Text, Text,
Date, Date,
Enlarge,
Forwarded, Forwarded,
}; };

View file

@ -3639,9 +3639,12 @@ std::unique_ptr<QMimeData> ListWidget::prepareDrag() {
session().data().setMimeForwardIds(std::move(forwardIds)); session().data().setMimeForwardIds(std::move(forwardIds));
auto result = std::make_unique<QMimeData>(); auto result = std::make_unique<QMimeData>();
result->setData(u"application/x-td-forward"_q, "1"); result->setData(u"application/x-td-forward"_q, "1");
if (const auto media = pressedView->media()) { if (pressedHandler) {
if (const auto document = media->getDocument()) { const auto lnkDocument = reinterpret_cast<DocumentData*>(
const auto filepath = document->filepath(true); pressedHandler->property(
kDocumentLinkMediaProperty).toULongLong());
if (lnkDocument) {
const auto filepath = lnkDocument->filepath(true);
if (!filepath.isEmpty()) { if (!filepath.isEmpty()) {
QList<QUrl> urls; QList<QUrl> urls;
urls.push_back(QUrl::fromLocalFile(filepath)); urls.push_back(QUrl::fromLocalFile(filepath));

View file

@ -105,6 +105,7 @@ void Giveaway::fillFromData(not_null<Data::Giveaway*> giveaway) {
st::msgMinWidth), st::msgMinWidth),
.thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel), .thumbnail = Dialogs::Stories::MakeUserpicThumbnail(channel),
.link = channel->openLink(), .link = channel->openLink(),
.colorIndex = channel->colorIndex(),
}); });
} }
const auto channels = int(_channels.size()); const auto channels = int(_channels.size());
@ -342,18 +343,6 @@ void Giveaway::paintChannels(
const auto st = context.st; const auto st = context.st;
const auto stm = context.messageStyle(); const auto stm = context.messageStyle();
const auto selected = context.selected(); 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; const auto padding = st::chatGiveawayChannelPadding;
for (const auto &channel : _channels) { for (const auto &channel : _channels) {
const auto &thumbnail = channel.thumbnail; 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) { if (channel.ripple) {
channel.ripple->paint( channel.ripple->paint(
p, p,

View file

@ -69,6 +69,9 @@ private:
QRect geometry; QRect geometry;
ClickHandlerPtr link; ClickHandlerPtr link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple; mutable std::unique_ptr<Ui::RippleAnimation> ripple;
mutable std::array<QImage, 4> corners;
mutable QColor bg;
uint8 colorIndex = 0;
}; };
void paintBadge(Painter &p, const PaintContext &context) const; void paintBadge(Painter &p, const PaintContext &context) const;
@ -94,10 +97,8 @@ private:
Ui::Text::String _winnersTitle; Ui::Text::String _winnersTitle;
Ui::Text::String _winners; Ui::Text::String _winners;
mutable QColor _channelBg;
mutable QColor _badgeFg; mutable QColor _badgeFg;
mutable QColor _badgeBorder; mutable QColor _badgeBorder;
mutable std::array<QImage, 4> _channelCorners;
mutable QImage _badge; mutable QImage _badge;
mutable QImage _badgeCache; mutable QImage _badgeCache;

View file

@ -325,7 +325,7 @@ void UnwrappedMedia::drawSurrounding(
recty += skip; recty += skip;
} else if (via) { } else if (via) {
p.setFont(st::msgDateFont); 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 const auto skip = st::msgServiceNameFont->height
+ (reply ? st::msgReplyPadding.top() : 0); + (reply ? st::msgReplyPadding.top() : 0);

View file

@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_click_handler.h" #include "data/data_file_click_handler.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_auto_download.h" #include "data/data_auto_download.h"
#include "data/data_web_page.h"
#include "core/application.h" #include "core/application.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -242,6 +243,7 @@ QSize Photo::countCurrentSize(int newWidth) {
maxWidth()); maxWidth());
newWidth = qMax(pix.width(), minWidth); newWidth = qMax(pix.width(), minWidth);
auto newHeight = qMax(pix.height(), st::minPhotoSize); auto newHeight = qMax(pix.height(), st::minPhotoSize);
auto imageHeight = newHeight;
if (_parent->hasBubble() && !_caption.isEmpty()) { if (_parent->hasBubble() && !_caption.isEmpty()) {
auto captionMaxWidth = st::msgPadding.left() auto captionMaxWidth = st::msgPadding.left()
+ _caption.maxWidth() + _caption.maxWidth()
@ -252,7 +254,7 @@ QSize Photo::countCurrentSize(int newWidth) {
} }
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth); const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth); newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
newHeight = adjustHeightForLessCrop( imageHeight = newHeight = adjustHeightForLessCrop(
dimensions, dimensions,
{ newWidth, newHeight }); { newWidth, newHeight });
const auto captionw = newWidth const auto captionw = newWidth
@ -266,6 +268,15 @@ QSize Photo::countCurrentSize(int newWidth) {
newHeight += st::msgPadding.bottom(); 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 }; return { newWidth, newHeight };
} }
@ -351,15 +362,16 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
fillImageOverlay(p, rthumb, rounding, context); fillImageOverlay(p, rthumb, rounding, context);
} }
} }
if (radial || (!loaded && !_data->loading())) {
const auto radialOpacity = (radial && loaded && !_data->uploading()) const auto showEnlarge = loaded && _showEnlarge;
? _animation->radial.opacity() : const auto paintInCenter = (radial || (!loaded && !_data->loading()));
1.; if (paintInCenter || showEnlarge) {
const auto innerSize = st::msgFileLayout.thumbSize;
QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
if (context.selected()) { if (context.selected()) {
p.setBrush(st->msgDateImgBgSelected()); p.setBrush(st->msgDateImgBgSelected());
} else if (showEnlarge) {
const auto over = ClickHandler::showAsActive(_openl);
p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg());
} else if (isThumbAnimation()) { } else if (isThumbAnimation()) {
const auto over = _animation->a_thumbOver.value(1.); const auto over = _animation->a_thumbOver.value(1.);
p.setBrush(anim::brush(st->msgDateImgBg(), st->msgDateImgBgOver(), over)); 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); const auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg()); 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()); 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); _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 // date
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
@ -631,6 +657,18 @@ QSize Photo::photoSize() const {
return QSize(_data->width(), _data->height()); 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 { TextState Photo::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent); auto result = TextState(_parent);
@ -673,6 +711,11 @@ TextState Photo::textState(QPoint point, StateRequest request) const {
: _data->loading() : _data->loading()
? _cancell ? _cancell
: _savel; : _savel;
if (_showEnlarge
&& result.link == _openl
&& enlargeRect().contains(point)) {
result.cursor = CursorState::Enlarge;
}
} }
if (_caption.isEmpty() && _parent->media() == this) { if (_caption.isEmpty() && _parent->media() == this) {
auto fullRight = paintx + paintw; auto fullRight = paintx + paintw;

View file

@ -163,6 +163,7 @@ private:
QPoint photoPosition) const; QPoint photoPosition) const;
[[nodiscard]] QSize photoSize() const; [[nodiscard]] QSize photoSize() const;
[[nodiscard]] QRect enlargeRect() const;
void togglePollingStory(bool enabled) const; void togglePollingStory(bool enabled) const;
@ -178,6 +179,7 @@ private:
mutable uint32 _imageCacheForum : 1 = 0; mutable uint32 _imageCacheForum : 1 = 0;
mutable uint32 _imageCacheBlurred : 1 = 0; mutable uint32 _imageCacheBlurred : 1 = 0;
mutable uint32 _pollingStory : 1 = 0; mutable uint32 _pollingStory : 1 = 0;
mutable uint32 _showEnlarge : 1 = 0;
}; };

View file

@ -783,7 +783,11 @@ TextState WebPage::textState(QPoint point, StateRequest request) const {
auto attachTop = tshift - bubble.top(); auto attachTop = tshift - bubble.top();
if (rtl()) attachLeft = width() - attachLeft - _attach->width(); if (rtl()) attachLeft = width() - attachLeft - _attach->width();
result = _attach->textState(point - QPoint(attachLeft, attachTop), request); 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)) { if (!result.link && outer.contains(point)) {

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "countries/countries_instance.h" #include "countries/countries_instance.h"
#include "data/data_peer.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_list_controllers.h"
#include "info/boosts/giveaway/giveaway_type_row.h" #include "info/boosts/giveaway/giveaway_type_row.h"
#include "info/boosts/giveaway/select_countries_box.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_common.h"
#include "settings/settings_premium.h" // Settings::ShowPremium #include "settings/settings_premium.h" // Settings::ShowPremium
#include "ui/boxes/choose_date_time.h" #include "ui/boxes/choose_date_time.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h" #include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
@ -67,47 +71,172 @@ constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
}; };
} }
[[nodiscard]] QWidget *FindFirstShadowInBox(not_null<Ui::BoxContent*> box) {
for (const auto &child : box->children()) {
if (child && child->isWidgetType()) {
const auto w = static_cast<QWidget*>(child);
if (w->height() == st::lineWidth) {
return w;
}
}
}
return nullptr;
}
void AddPremiumTopBarWithDefaultTitleBar(
not_null<Ui::GenericBox*> box,
rpl::producer<> showFinished,
rpl::producer<QString> titleText) {
struct State final {
Ui::Animations::Simple animation;
Ui::Text::String title;
Ui::RpWidget close;
};
const auto state = box->lifetime().make_state<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<Ui::RpWidget>(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<Ui::IconButton>(
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<Ui::Premium::TopBar>(
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<Ui::BoxContentDivider>(
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 } // namespace
void CreateGiveawayBox( void CreateGiveawayBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Info::Controller*> controller, not_null<Info::Controller*> controller,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
Fn<void()> reloadOnDone,
std::optional<Data::BoostPrepaidGiveaway> prepaid) {
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
const auto weakWindow = base::make_weak(controller->parentController()); const auto weakWindow = base::make_weak(controller->parentController());
const auto bar = box->verticalLayout()->add(
object_ptr<Ui::Premium::TopBar>(
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<Ui::IconButton>(
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 GiveawayType = Giveaway::GiveawayTypeRow::Type;
using GiveawayGroup = Ui::RadioenumGroup<GiveawayType>; using GiveawayGroup = Ui::RadioenumGroup<GiveawayType>;
struct State final { struct State final {
@ -127,11 +256,25 @@ void CreateGiveawayBox(
rpl::variable<TimeId> dateValue; rpl::variable<TimeId> dateValue;
rpl::variable<std::vector<QString>> countriesValue; rpl::variable<std::vector<QString>> countriesValue;
bool confirmButtonBusy = false; rpl::variable<bool> confirmButtonBusy = true;
}; };
const auto state = box->lifetime().make_state<State>(peer); const auto state = box->lifetime().make_state<State>(peer);
const auto typeGroup = std::make_shared<GiveawayGroup>(); const auto typeGroup = std::make_shared<GiveawayGroup>();
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( const auto loading = box->addRow(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box, box,
@ -157,7 +300,24 @@ void CreateGiveawayBox(
object_ptr<Ui::VerticalLayout>(box))); object_ptr<Ui::VerticalLayout>(box)));
contentWrap->toggle(false, anim::type::instant); contentWrap->toggle(false, anim::type::instant);
{ if (prepaid) {
contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>(
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( const auto row = contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>( object_ptr<Giveaway::GiveawayTypeRow>(
box, box,
@ -168,7 +328,7 @@ void CreateGiveawayBox(
state->typeValue.force_assign(GiveawayType::Random); state->typeValue.force_assign(GiveawayType::Random);
}); });
} }
{ if (!prepaid) {
const auto row = contentWrap->entity()->add( const auto row = contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>( object_ptr<Giveaway::GiveawayTypeRow>(
box, box,
@ -209,7 +369,8 @@ void CreateGiveawayBox(
using Controller = Giveaway::AwardMembersListController; using Controller = Giveaway::AwardMembersListController;
auto listController = std::make_unique<Controller>( auto listController = std::make_unique<Controller>(
controller, controller,
peer); peer,
state->selectedToAward);
listController->setCheckError(CreateErrorCallback( listController->setCheckError(CreateErrorCallback(
state->apiOptions.giveawayAddPeersMax(), state->apiOptions.giveawayAddPeersMax(),
tr::lng_giveaway_maximum_users_error)); tr::lng_giveaway_maximum_users_error));
@ -237,10 +398,19 @@ void CreateGiveawayBox(
randomWrap->toggle(type == GiveawayType::Random, anim::type::instant); randomWrap->toggle(type == GiveawayType::Random, anim::type::instant);
}, randomWrap->lifetime()); }, randomWrap->lifetime());
randomWrap->toggleOn(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
anim::type::instant);
const auto sliderContainer = randomWrap->entity()->add( const auto sliderContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap)); object_ptr<Ui::VerticalLayout>(randomWrap));
const auto fillSliderContainer = [=] { const auto fillSliderContainer = [=] {
const auto availablePresets = state->apiOptions.availablePresets(); const auto availablePresets = state->apiOptions.availablePresets();
if (prepaid) {
state->sliderValue = prepaid->quantity;
return;
}
if (availablePresets.empty()) { if (availablePresets.empty()) {
return; return;
} }
@ -274,8 +444,20 @@ void CreateGiveawayBox(
const auto &padding = st::giveawayGiftCodeSliderPadding; const auto &padding = st::giveawayGiftCodeSliderPadding;
Settings::AddSkip(sliderContainer, padding.top()); 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( const auto slider = sliderContainer->add(
object_ptr<Ui::MediaSlider>(sliderContainer, st::settingsScale), object_ptr<Slider>(sliderContainer, st::settingsScale),
st::boxRowPadding); st::boxRowPadding);
Settings::AddSkip(sliderContainer, padding.bottom()); Settings::AddSkip(sliderContainer, padding.bottom());
slider->resize(slider->width(), st::settingsScale.seekSize.height()); slider->resize(slider->width(), st::settingsScale.seekSize.height());
@ -468,6 +650,24 @@ void CreateGiveawayBox(
Settings::AddSkip(countriesContainer); Settings::AddSkip(countriesContainer);
} }
const auto addTerms = [=](not_null<Ui::VerticalLayout*> c) {
auto terms = object_ptr<Ui::FlatLabel>(
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<LambdaClickHandler>([=] {
box->closeBox();
Settings::ShowPremium(&peer->session(), QString());
}));
c->add(std::move(terms));
};
{ {
const auto dateContainer = randomWrap->entity()->add( const auto dateContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap)); object_ptr<Ui::VerticalLayout>(randomWrap));
@ -505,18 +705,38 @@ void CreateGiveawayBox(
}); });
Settings::AddSkip(dateContainer); Settings::AddSkip(dateContainer);
Settings::AddDividerText( if (prepaid) {
dateContainer, auto terms = object_ptr<Ui::VerticalLayout>(dateContainer);
tr::lng_giveaway_date_about( terms->add(object_ptr<Ui::FlatLabel>(
lt_count, terms,
state->sliderValue.value() | tr::to_count())); tr::lng_giveaway_date_about(
Settings::AddSkip(dateContainer); 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<Ui::DividerLabel>(
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<Ui::RadiobuttonGroup>(0); const auto durationGroup = std::make_shared<Ui::RadiobuttonGroup>(0);
const auto listOptions = contentWrap->entity()->add( const auto listOptions = contentWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(box)); object_ptr<Ui::VerticalLayout>(box));
const auto rebuildListOptions = [=](int amountUsers) { const auto rebuildListOptions = [=](int amountUsers) {
if (prepaid) {
return;
}
while (listOptions->count()) { while (listOptions->count()) {
delete listOptions->widgetAt(0); delete listOptions->widgetAt(0);
} }
@ -535,29 +755,16 @@ void CreateGiveawayBox(
Settings::AddSkip(listOptions); Settings::AddSkip(listOptions);
auto terms = object_ptr<Ui::FlatLabel>( auto termsContainer = object_ptr<Ui::VerticalLayout>(listOptions);
listOptions, addTerms(termsContainer.data());
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<LambdaClickHandler>([=] {
box->closeBox();
Settings::ShowPremium(&peer->session(), QString());
}));
listOptions->add(object_ptr<Ui::DividerLabel>( listOptions->add(object_ptr<Ui::DividerLabel>(
listOptions, listOptions,
std::move(terms), std::move(termsContainer),
st::settingsDividerLabelPadding)); st::settingsDividerLabelPadding));
box->verticalLayout()->resizeToWidth(box->width()); box->verticalLayout()->resizeToWidth(box->width());
}; };
{ if (!prepaid) {
rpl::combine( rpl::combine(
state->sliderValue.value(), state->sliderValue.value(),
state->typeValue.value() state->typeValue.value()
@ -567,27 +774,54 @@ void CreateGiveawayBox(
? state->selectedToAward.size() ? state->selectedToAward.size()
: users); : users);
}, box->lifetime()); }, box->lifetime());
} else {
typeGroup->setValue(GiveawayType::Random);
} }
{ {
// TODO mini-icon. using namespace Info::Statistics;
const auto &stButton = st::premiumGiftBox; const auto &stButton = st::startGiveawayBox;
box->setStyle(stButton); box->setStyle(stButton);
auto button = object_ptr<Ui::RoundButton>( auto button = object_ptr<Ui::RoundButton>(
box, box,
state->toAwardAmountChanged.events_starting_with( rpl::never<QString>(),
rpl::empty_value()
) | rpl::map([=] {
return (typeGroup->value() == GiveawayType::SpecificUsers)
? tr::lng_giveaway_award()
: tr::lng_giveaway_start();
}) | rpl::flatten_latest(),
st::giveawayGiftCodeStartButton); 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->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
button->resizeToWidth(box->width() state->typeValue.value(
- stButton.buttonPadding.left() ) | rpl::start_with_next([=, raw = button.data()] {
- stButton.buttonPadding.right()); raw->resizeToWidth(box->width()
- stButton.buttonPadding.left()
- stButton.buttonPadding.right());
}, button->lifetime());
button->setClickedCallback([=] { button->setClickedCallback([=] {
if (state->confirmButtonBusy) { if (state->confirmButtonBusy.current()) {
return; return;
} }
const auto type = typeGroup->value(); const auto type = typeGroup->value();
@ -600,7 +834,10 @@ void CreateGiveawayBox(
isSpecific isSpecific
? state->selectedToAward.size() ? state->selectedToAward.size()
: state->sliderValue.current(), : state->sliderValue.current(),
durationGroup->value()); prepaid
? prepaid->months
: state->apiOptions.monthsFromPreset(
durationGroup->value()));
if (isSpecific) { if (isSpecific) {
if (state->selectedToAward.empty()) { if (state->selectedToAward.empty()) {
return; return;
@ -633,12 +870,15 @@ void CreateGiveawayBox(
const auto show = box->uiShow(); const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get()); const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) { const auto done = [=](Payments::CheckoutResult result) {
if (const auto strong = weak.data()) { const auto isPaid = result == Payments::CheckoutResult::Paid;
state->confirmButtonBusy = false; if (result == Payments::CheckoutResult::Pending || isPaid) {
strong->window()->setFocus(); if (const auto strong = weak.data()) {
strong->closeBox(); strong->window()->setFocus();
strong->closeBox();
}
} }
if (result == Payments::CheckoutResult::Paid) { if (isPaid) {
reloadOnDone();
const auto filter = [=](const auto &...) { const auto filter = [=](const auto &...) {
if (const auto window = weakWindow.get()) { if (const auto window = weakWindow.get()) {
window->showSection(Info::Boosts::Make(peer)); window->showSection(Info::Boosts::Make(peer));
@ -665,28 +905,71 @@ void CreateGiveawayBox(
.adaptive = true, .adaptive = true,
.filter = filter, .filter = filter,
}); });
} else {
state->confirmButtonBusy = false;
} }
}; };
Payments::CheckoutProcess::Start(std::move(invoice), done); const auto startPrepaid = [=](Fn<void()> 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<void()> 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)); box->addButton(std::move(button));
} }
state->typeValue.force_assign(GiveawayType::Random); state->typeValue.force_assign(GiveawayType::Random);
box->setShowFinishedCallback([=] { std::move(
showFinished
) | rpl::take(1) | rpl::start_with_next([=] {
if (!loading->toggled()) { if (!loading->toggled()) {
return; return;
} }
bar->setPaused(false); const auto done = [=] {
state->lifetimeApi = state->apiOptions.request(
) | rpl::start_with_error_done([=](const QString &error) {
}, [=] {
state->lifetimeApi.destroy(); state->lifetimeApi.destroy();
loading->toggle(false, anim::type::instant); loading->toggle(false, anim::type::instant);
state->confirmButtonBusy = false;
fillSliderContainer(); fillSliderContainer();
rebuildListOptions(1); rebuildListOptions(1);
contentWrap->toggle(true, anim::type::instant); contentWrap->toggle(true, anim::type::instant);
contentWrap->resizeToWidth(box->width()); 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());
} }

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData; class PeerData;
namespace Data {
struct BoostPrepaidGiveaway;
} // namespace Data
namespace Info { namespace Info {
class Controller; class Controller;
} // namespace Info } // namespace Info
@ -20,4 +24,6 @@ class GenericBox;
void CreateGiveawayBox( void CreateGiveawayBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Info::Controller*> controller, not_null<Info::Controller*> controller,
not_null<PeerData*> peer); not_null<PeerData*> peer,
Fn<void()> reloadOnDone,
std::optional<Data::BoostPrepaidGiveaway> prepaidGiveaway);

View file

@ -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<Ui::RpWidget*> InfiniteRadialAnimationWidget(
not_null<Ui::RpWidget*> parent,
int size) {
class Widget final : public Ui::RpWidget {
public:
Widget(not_null<Ui::RpWidget*> 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<Widget>(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<Ui::RpWidget*> parent,
rpl::producer<QString> text,
rpl::producer<int> number,
rpl::producer<bool> shown) {
struct State {
QImage badge;
};
const auto state = parent->lifetime().make_state<State>();
const auto label = Ui::CreateChild<Ui::LabelSimple>(
parent.get(),
st::startGiveawayButtonLabelSimple);
std::move(
text
) | rpl::start_with_next([=](const QString &s) {
label->setText(s);
}, label->lifetime());
const auto count = Ui::CreateChild<Ui::RpWidget>(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

View file

@ -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<Ui::RpWidget*> InfiniteRadialAnimationWidget(
not_null<Ui::RpWidget*> parent,
int size);
void AddLabelWithBadgeToButton(
not_null<Ui::RpWidget*> parent,
rpl::producer<QString> text,
rpl::producer<int> number,
rpl::producer<bool> shown);
} // namespace Info::Statistics

View file

@ -163,3 +163,34 @@ giveawayRefundedLabel: FlatLabel(boxLabel) {
textFg: attentionButtonFg; textFg: attentionButtonFg;
} }
giveawayRefundedPadding: margins(8px, 10px, 8px, 10px); 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;
}

View file

@ -9,9 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_folder.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "dialogs/dialogs_indexed_list.h"
#include "history/history.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
@ -107,8 +110,16 @@ void ChannelRow::rightActionStopLastRipple() {
AwardMembersListController::AwardMembersListController( AwardMembersListController::AwardMembersListController(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) not_null<PeerData*> peer,
: ParticipantsBoxController(navigation, peer, ParticipantsRole::Members) { std::vector<not_null<PeerData*>> selected)
: ParticipantsBoxController(navigation, peer, ParticipantsRole::Members)
, _selected(std::move(selected)) {
}
void AwardMembersListController::prepare() {
ParticipantsBoxController::prepare();
delegate()->peerListAddSelectedPeers(base::take(_selected));
delegate()->peerListRefreshRows();
} }
void AwardMembersListController::rowClicked(not_null<PeerListRow*> row) { void AwardMembersListController::rowClicked(not_null<PeerListRow*> row) {
@ -148,7 +159,26 @@ MyChannelsListController::MyChannelsListController(
std::make_unique<PeerListGlobalSearchController>(&peer->session())) std::make_unique<PeerListGlobalSearchController>(&peer->session()))
, _peer(peer) , _peer(peer)
, _show(show) , _show(show)
, _selected(std::move(selected)) { , _selected(std::move(selected))
, _otherChannels(std::make_unique<std::vector<not_null<ChannelData*>>>()) {
{
const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
for (const auto &row : list->all()) {
if (const auto history = row->history()) {
const auto channel = history->peer->asChannel();
if (channel && !channel->isMegagroup()) {
_otherChannels->push_back(channel);
}
}
}
};
auto &data = _peer->owner();
addList(data.chatsList()->indexed());
if (const auto folder = data.folderLoaded(Data::Folder::kId)) {
addList(folder->chatsList()->indexed());
}
addList(data.contactsNoChatsList());
}
} }
std::unique_ptr<PeerListRow> MyChannelsListController::createSearchRow( std::unique_ptr<PeerListRow> MyChannelsListController::createSearchRow(
@ -167,6 +197,24 @@ std::unique_ptr<PeerListRow> MyChannelsListController::createRestoredRow(
return nullptr; return nullptr;
} }
void MyChannelsListController::loadMoreRows() {
if (_apiLifetime || !_otherChannels) {
return;
} else if (_lastAddedIndex >= _otherChannels->size()) {
_otherChannels.release();
return;
}
constexpr auto kPerPage = int(40);
const auto till = std::min(
int(_otherChannels->size()),
_lastAddedIndex + kPerPage);
while (_lastAddedIndex < till) {
delegate()->peerListAppendRow(
createRow(_otherChannels->at(_lastAddedIndex++)));
}
delegate()->peerListRefreshRows();
}
void MyChannelsListController::rowClicked(not_null<PeerListRow*> row) { void MyChannelsListController::rowClicked(not_null<PeerListRow*> row) {
const auto channel = row->peer()->asChannel(); const auto channel = row->peer()->asChannel();
const auto checked = !row->checked(); const auto checked = !row->checked();

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_participants_box.h" #include "boxes/peers/edit_participants_box.h"
class ChannelData;
class PeerData; class PeerData;
class PeerListRow; class PeerListRow;
@ -27,7 +28,10 @@ class AwardMembersListController : public ParticipantsBoxController {
public: public:
AwardMembersListController( AwardMembersListController(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer); not_null<PeerData*> peer,
std::vector<not_null<PeerData*>> selected);
void prepare() override;
void setCheckError(Fn<bool(int)> callback); void setCheckError(Fn<bool(int)> callback);
@ -41,6 +45,8 @@ public:
private: private:
Fn<bool(int)> _checkErrorCallback; Fn<bool(int)> _checkErrorCallback;
std::vector<not_null<PeerData*>> _selected;
}; };
class MyChannelsListController : public PeerListController { class MyChannelsListController : public PeerListController {
@ -55,6 +61,7 @@ public:
Main::Session &session() const override; Main::Session &session() const override;
void prepare() override; void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void loadMoreRows() override;
std::unique_ptr<PeerListRow> createSearchRow( std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override; not_null<PeerData*> peer) override;
@ -71,6 +78,8 @@ private:
Fn<bool(int)> _checkErrorCallback; Fn<bool(int)> _checkErrorCallback;
std::vector<not_null<PeerData*>> _selected; std::vector<not_null<PeerData*>> _selected;
std::unique_ptr<std::vector<not_null<ChannelData*>>> _otherChannels;
int _lastAddedIndex = 0;
rpl::lifetime _apiLifetime; rpl::lifetime _apiLifetime;

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_statistics.h"
namespace Giveaway { namespace Giveaway {
@ -24,31 +25,49 @@ GiveawayTypeRow::GiveawayTypeRow(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
Type type, Type type,
rpl::producer<QString> subtitle) rpl::producer<QString> subtitle)
: GiveawayTypeRow(
parent,
type,
(type == Type::SpecificUsers) ? kColorIndexSpecific : kColorIndexRandom,
(type == Type::SpecificUsers)
? tr::lng_giveaway_award_option()
: (type == Type::Random)
? tr::lng_giveaway_create_option()
: (type == Type::AllMembers)
? tr::lng_giveaway_users_all()
: tr::lng_giveaway_users_new(),
std::move(subtitle),
QImage()) {
}
GiveawayTypeRow::GiveawayTypeRow(
not_null<Ui::RpWidget*> parent,
Type type,
int colorIndex,
rpl::producer<QString> title,
rpl::producer<QString> subtitle,
QImage badge)
: RippleButton(parent, st::defaultRippleAnimation) : RippleButton(parent, st::defaultRippleAnimation)
, _type(type) , _type(type)
, _st((_type == Type::SpecificUsers || _type == Type::Random) , _st((_type == Type::SpecificUsers || _type == Type::Random)
? st::giveawayTypeListItem ? st::giveawayTypeListItem
: (_type == Type::Prepaid)
? st::boostsListBox.item
: st::giveawayGiftCodeMembersPeerList.item) : st::giveawayGiftCodeMembersPeerList.item)
, _userpic( , _userpic(
Ui::EmptyUserpic::UserpicColor((_type == Type::SpecificUsers) Ui::EmptyUserpic::UserpicColor(Ui::EmptyUserpic::ColorIndex(colorIndex)),
? kColorIndexSpecific
: kColorIndexRandom),
QString()) QString())
, _name( , _badge(std::move(badge)) {
_st.nameStyle,
(type == Type::SpecificUsers)
? tr::lng_giveaway_award_option(tr::now)
: (type == Type::Random)
? tr::lng_giveaway_create_option(tr::now)
: (type == Type::AllMembers)
? tr::lng_giveaway_users_all(tr::now)
: tr::lng_giveaway_users_new(tr::now),
Ui::NameTextOptions()) {
std::move( std::move(
subtitle subtitle
) | rpl::start_with_next([=] (const QString &s) { ) | rpl::start_with_next([=] (const QString &s) {
_status.setText(st::defaultTextStyle, s, Ui::NameTextOptions()); _status.setText(st::defaultTextStyle, s, Ui::NameTextOptions());
}, lifetime()); }, lifetime());
std::move(
title
) | rpl::start_with_next([=] (const QString &s) {
_name.setText(_st.nameStyle, s, Ui::NameTextOptions());
}, lifetime());
} }
int GiveawayTypeRow::resizeGetHeight(int) { int GiveawayTypeRow::resizeGetHeight(int) {
@ -62,7 +81,10 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
const auto skipRight = _st.photoPosition.x(); const auto skipRight = _st.photoPosition.x();
const auto outerWidth = width(); const auto outerWidth = width();
const auto isSpecific = (_type == Type::SpecificUsers); const auto isSpecific = (_type == Type::SpecificUsers);
const auto hasUserpic = (_type == Type::Random) || isSpecific; const auto isPrepaid = (_type == Type::Prepaid);
const auto hasUserpic = (_type == Type::Random)
|| isSpecific
|| isPrepaid;
if (paintOver) { if (paintOver) {
p.fillRect(e->rect(), _st.button.textBgOver); p.fillRect(e->rect(), _st.button.textBgOver);
@ -92,8 +114,19 @@ void GiveawayTypeRow::paintEvent(QPaintEvent *e) {
const auto namey = _st.namePosition.y(); const auto namey = _st.namePosition.y();
const auto namew = outerWidth - namex - skipRight; const auto namew = outerWidth - namex - skipRight;
const auto badgew = _badge.width() / style::DevicePixelRatio();
p.setPen(_st.nameFg); p.setPen(_st.nameFg);
_name.drawLeftElided(p, namex, namey, namew, width()); _name.drawLeftElided(p, namex, namey, namew - badgew, width());
if (!_badge.isNull()) {
p.drawImage(
std::min(
namex + _name.maxWidth() + st::boostsListBadgePadding.left(),
outerWidth - badgew - skipRight),
namey + st::boostsListMiniIconSkip,
_badge);
}
const auto statusx = _st.statusPosition.x(); const auto statusx = _st.statusPosition.x();
const auto statusy = _st.statusPosition.y(); const auto statusy = _st.statusPosition.y();

View file

@ -25,6 +25,8 @@ public:
AllMembers, AllMembers,
OnlyNewMembers, OnlyNewMembers,
Prepaid,
}; };
GiveawayTypeRow( GiveawayTypeRow(
@ -32,6 +34,14 @@ public:
Type type, Type type,
rpl::producer<QString> subtitle); rpl::producer<QString> subtitle);
GiveawayTypeRow(
not_null<Ui::RpWidget*> parent,
Type type,
int colorIndex,
rpl::producer<QString> title,
rpl::producer<QString> subtitle,
QImage badge);
void addRadio(std::shared_ptr<Ui::RadioenumGroup<Type>> typeGroup); void addRadio(std::shared_ptr<Ui::RadioenumGroup<Type>> typeGroup);
protected: protected:
@ -47,6 +57,8 @@ private:
Ui::Text::String _status; Ui::Text::String _status;
Ui::Text::String _name; Ui::Text::String _name;
QImage _badge;
}; };
} // namespace Giveaway } // namespace Giveaway

View file

@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "info/boosts/create_giveaway_box.h" #include "info/boosts/create_giveaway_box.h"
#include "info/boosts/giveaway/boost_badge.h"
#include "info/boosts/giveaway/giveaway_type_row.h"
#include "info/boosts/info_boosts_widget.h" #include "info/boosts/info_boosts_widget.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
@ -24,13 +26,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "statistics/widgets/chart_header_widget.h" #include "statistics/widgets/chart_header_widget.h"
#include "ui/boxes/boost_box.h" #include "ui/boxes/boost_box.h"
#include "ui/controls/invite_link_buttons.h"
#include "ui/controls/invite_link_label.h" #include "ui/controls/invite_link_label.h"
#include "ui/effects/ripple_animation.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_statistics.h" #include "styles/style_statistics.h"
@ -179,10 +184,35 @@ void FillShareLink(
label->clicks( label->clicks(
) | rpl::start_with_next(copyLink, label->lifetime()); ) | rpl::start_with_next(copyLink, label->lifetime());
const auto copyShareWrap = content->add( {
object_ptr<Ui::VerticalLayout>(content)); const auto wrap = content->add(
Ui::AddCopyShareLinkButtons(copyShareWrap, copyLink, shareLink); object_ptr<Ui::FixedHeightWidget>(
copyShareWrap->widgetAt(0)->showChildren(); content,
st::inviteLinkButton.height),
st::inviteLinkButtonsPadding);
const auto copy = CreateChild<Ui::RoundButton>(
wrap,
tr::lng_group_invite_context_copy(),
st::inviteLinkCopy);
copy->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
copy->setClickedCallback(copyLink);
const auto share = CreateChild<Ui::RoundButton>(
wrap,
tr::lng_group_invite_context_share(),
st::inviteLinkShare);
share->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
share->setClickedCallback(shareLink);
wrap->widthValue(
) | rpl::start_with_next([=](int width) {
const auto buttonWidth = (width - st::inviteLinkButtonsSkip) / 2;
copy->setFullWidth(buttonWidth);
share->setFullWidth(buttonWidth);
copy->moveToLeft(0, 0, width);
share->moveToRight(0, 0, width);
}, wrap->lifetime());
wrap->showChildren();
}
::Settings::AddSkip(content, st::boostsLinkFieldPadding.bottom()); ::Settings::AddSkip(content, st::boostsLinkFieldPadding.bottom());
} }
@ -190,7 +220,8 @@ void FillGetBoostsButton(
not_null<Ui::VerticalLayout*> content, not_null<Ui::VerticalLayout*> content,
not_null<Controller*> controller, not_null<Controller*> controller,
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer) { not_null<PeerData*> peer,
Fn<void()> reloadOnDone) {
if (!Api::PremiumGiftCodeOptions(peer).giveawayGiftsPurchaseAvailable()) { if (!Api::PremiumGiftCodeOptions(peer).giveawayGiftsPurchaseAvailable()) {
return; return;
} }
@ -203,7 +234,12 @@ void FillGetBoostsButton(
tr::lng_boosts_get_boosts(), tr::lng_boosts_get_boosts(),
st)); st));
button->setClickedCallback([=] { button->setClickedCallback([=] {
show->showBox(Box(CreateGiveawayBox, controller, peer)); show->showBox(Box(
CreateGiveawayBox,
controller,
peer,
reloadOnDone,
std::nullopt));
}); });
Ui::CreateChild<Info::Profile::FloatingIcon>( Ui::CreateChild<Info::Profile::FloatingIcon>(
button, button,
@ -253,21 +289,27 @@ void InnerWidget::fill() {
const auto &status = _state; const auto &status = _state;
const auto inner = this; const auto inner = this;
const auto reloadOnDone = crl::guard(this, [=] {
while (Ui::VerticalLayout::count()) {
delete Ui::VerticalLayout::widgetAt(0);
}
load();
});
{ {
auto dividerContent = object_ptr<Ui::VerticalLayout>(inner); auto dividerContent = object_ptr<Ui::VerticalLayout>(inner);
Ui::FillBoostLimit( Ui::FillBoostLimit(
fakeShowed->events(), fakeShowed->events(),
rpl::single(status.overview.isBoosted),
dividerContent.data(), dividerContent.data(),
Ui::BoostCounters{ rpl::single(Ui::BoostCounters{
.level = status.overview.level, .level = status.overview.level,
.boosts = status.overview.boostCount, .boosts = status.overview.boostCount,
.thisLevelBoosts .thisLevelBoosts
= status.overview.currentLevelBoostCount, = status.overview.currentLevelBoostCount,
.nextLevelBoosts .nextLevelBoosts
= status.overview.nextLevelBoostCount, = status.overview.nextLevelBoostCount,
.mine = status.overview.isBoosted, .mine = status.overview.mine,
}, }),
st::statisticsLimitsLinePadding); st::statisticsLimitsLinePadding);
inner->add(object_ptr<Ui::DividerLabel>( inner->add(object_ptr<Ui::DividerLabel>(
inner, inner,
@ -281,6 +323,49 @@ void InnerWidget::fill() {
::Settings::AddDivider(inner); ::Settings::AddDivider(inner);
::Settings::AddSkip(inner); ::Settings::AddSkip(inner);
if (!status.prepaidGiveaway.empty()) {
const auto multiplier = Api::PremiumGiftCodeOptions(_peer)
.giveawayBoostsPerPremium();
::Settings::AddSkip(inner);
AddHeader(inner, tr::lng_boosts_prepaid_giveaway_title);
::Settings::AddSkip(inner);
for (const auto &g : status.prepaidGiveaway) {
using namespace Giveaway;
const auto button = inner->add(object_ptr<GiveawayTypeRow>(
inner,
GiveawayTypeRow::Type::Prepaid,
g.id,
tr::lng_boosts_prepaid_giveaway_quantity(
lt_count,
rpl::single(g.quantity) | tr::to_count()),
tr::lng_boosts_prepaid_giveaway_moths(
lt_count,
rpl::single(g.months) | tr::to_count()),
Info::Statistics::CreateBadge(
st::statisticsDetailsBottomCaptionStyle,
QString::number(g.quantity * multiplier),
st::boostsListBadgeHeight,
st::boostsListBadgeTextPadding,
st::premiumButtonBg2,
st::premiumButtonFg,
1.,
st::boostsListMiniIconPadding,
st::boostsListMiniIcon)));
button->setClickedCallback([=] {
_controller->uiShow()->showBox(Box(
CreateGiveawayBox,
_controller,
_peer,
reloadOnDone,
g));
});
}
::Settings::AddSkip(inner);
::Settings::AddDivider(inner);
::Settings::AddSkip(inner);
}
const auto hasBoosts = (status.firstSliceBoosts.multipliedTotal > 0); const auto hasBoosts = (status.firstSliceBoosts.multipliedTotal > 0);
const auto hasGifts = (status.firstSliceGifts.multipliedTotal > 0); const auto hasGifts = (status.firstSliceGifts.multipliedTotal > 0);
if (hasBoosts || hasGifts) { if (hasBoosts || hasGifts) {
@ -289,9 +374,19 @@ void InnerWidget::fill() {
ResolveGiftCode(_controller, boost.giftCodeLink.slug); ResolveGiftCode(_controller, boost.giftCodeLink.slug);
} else if (boost.userId) { } else if (boost.userId) {
const auto user = _peer->owner().user(boost.userId); const auto user = _peer->owner().user(boost.userId);
crl::on_main(this, [=] { if (boost.isGift || boost.isGiveaway) {
_controller->showPeerInfo(user); const auto d = Api::GiftCode{
}); .from = _peer->id,
.to = user->id,
.date = TimeId(boost.date.toSecsSinceEpoch()),
.months = boost.expiresAfterMonths,
};
_show->showBox(Box(GiftCodePendingBox, _controller, d));
} else {
crl::on_main(this, [=] {
_controller->showPeerInfo(user);
});
}
} else if (!boost.isUnclaimed) { } else if (!boost.isUnclaimed) {
_show->showToast(tr::lng_boosts_list_pending_about(tr::now)); _show->showToast(tr::lng_boosts_list_pending_about(tr::now));
} }
@ -321,16 +416,39 @@ void InnerWidget::fill() {
header->setSubTitle({}); header->setSubTitle({});
} }
class Slider final : public Ui::SettingsSlider {
public:
using Ui::SettingsSlider::SettingsSlider;
void setNaturalWidth(int w) {
_naturalWidth = w;
}
int naturalWidth() const override {
return _naturalWidth;
}
private:
int _naturalWidth = 0;
};
const auto slider = inner->add( const auto slider = inner->add(
object_ptr<Ui::SlideWrap<Ui::SettingsSlider>>( object_ptr<Ui::SlideWrap<Slider>>(
inner, inner,
object_ptr<Ui::SettingsSlider>( object_ptr<Slider>(inner, st::defaultTabsSlider)),
inner, st::boxRowPadding);
st::defaultTabsSlider)));
slider->toggle(!hasOneTab, anim::type::instant); slider->toggle(!hasOneTab, anim::type::instant);
slider->entity()->addSection(boostsTabText); slider->entity()->addSection(boostsTabText);
slider->entity()->addSection(giftsTabText); slider->entity()->addSection(giftsTabText);
{
const auto &st = st::defaultTabsSlider;
slider->entity()->setNaturalWidth(0
+ st.labelStyle.font->width(boostsTabText)
+ st.labelStyle.font->width(giftsTabText)
+ rect::m::sum::h(st::boxRowPadding));
}
const auto boostsWrap = inner->add( const auto boostsWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner, inner,
@ -339,10 +457,9 @@ void InnerWidget::fill() {
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner, inner,
object_ptr<Ui::VerticalLayout>(inner))); object_ptr<Ui::VerticalLayout>(inner)));
boostsWrap->toggle(hasOneTab ? true : hasBoosts, anim::type::instant);
giftsWrap->toggle(hasOneTab ? false : hasGifts, anim::type::instant);
slider->entity()->sectionActivated( rpl::single(hasOneTab ? (hasGifts ? 1 : 0) : 0) | rpl::then(
slider->entity()->sectionActivated()
) | rpl::start_with_next([=](int index) { ) | rpl::start_with_next([=](int index) {
boostsWrap->toggle(!index, anim::type::instant); boostsWrap->toggle(!index, anim::type::instant);
giftsWrap->toggle(index, anim::type::instant); giftsWrap->toggle(index, anim::type::instant);
@ -366,6 +483,7 @@ void InnerWidget::fill() {
::Settings::AddDividerText(inner, tr::lng_boosts_list_subtext()); ::Settings::AddDividerText(inner, tr::lng_boosts_list_subtext());
} }
::Settings::AddSkip(inner);
::Settings::AddSkip(inner); ::Settings::AddSkip(inner);
AddHeader(inner, tr::lng_boosts_link_title); AddHeader(inner, tr::lng_boosts_link_title);
::Settings::AddSkip(inner, st::boostsLinkSkip); ::Settings::AddSkip(inner, st::boostsLinkSkip);
@ -373,10 +491,10 @@ void InnerWidget::fill() {
::Settings::AddSkip(inner); ::Settings::AddSkip(inner);
::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext()); ::Settings::AddDividerText(inner, tr::lng_boosts_link_subtext());
FillGetBoostsButton(inner, _controller, _show, _peer); FillGetBoostsButton(inner, _controller, _show, _peer, reloadOnDone);
resizeToWidth(width()); resizeToWidth(width());
crl::on_main([=]{ fakeShowed->fire({}); }); crl::on_main(this, [=]{ fakeShowed->fire({}); });
} }
void InnerWidget::saveState(not_null<Memento*> memento) { void InnerWidget::saveState(not_null<Memento*> memento) {

View file

@ -71,7 +71,7 @@ not_null<PeerData*> Widget::peer() const {
} }
bool Widget::showInternal(not_null<ContentMemento*> memento) { bool Widget::showInternal(not_null<ContentMemento*> memento) {
return false; return (memento->statisticsPeer() == peer());
} }
rpl::producer<QString> Widget::title() { rpl::producer<QString> Widget::title() {

View file

@ -281,16 +281,6 @@ void InnerWidget::peerListSetDescription(
description.destroy(); description.destroy();
} }
void InnerWidget::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
_show->showBox(std::move(content), options);
}
void InnerWidget::peerListHideLayer() {
_show->hideLayer();
}
std::shared_ptr<Main::SessionShow> InnerWidget::peerListUiShow() { std::shared_ptr<Main::SessionShow> InnerWidget::peerListUiShow() {
return _show; return _show;
} }

View file

@ -64,10 +64,6 @@ private:
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription( void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override; object_ptr<Ui::FlatLabel> description) override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
object_ptr<ListWidget> setupList( object_ptr<ListWidget> setupList(

View file

@ -284,7 +284,8 @@ bool Controller::validateMementoPeer(
return memento->peer() == peer() return memento->peer() == peer()
&& memento->migratedPeerId() == migratedPeerId() && memento->migratedPeerId() == migratedPeerId()
&& memento->settingsSelf() == settingsSelf() && memento->settingsSelf() == settingsSelf()
&& memento->storiesPeer() == storiesPeer(); && memento->storiesPeer() == storiesPeer()
&& memento->statisticsPeer() == statisticsPeer();
} }
void Controller::setSection(not_null<ContentMemento*> memento) { void Controller::setSection(not_null<ContentMemento*> memento) {

View file

@ -51,10 +51,6 @@ public:
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription( void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override; object_ptr<Ui::FlatLabel> description) override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
}; };
@ -92,14 +88,6 @@ void ListDelegate::peerListSetDescription(
description.destroy(); description.destroy();
} }
void ListDelegate::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
}
void ListDelegate::peerListHideLayer() {
}
std::shared_ptr<Main::SessionShow> ListDelegate::peerListUiShow() { std::shared_ptr<Main::SessionShow> ListDelegate::peerListUiShow() {
Unexpected("...ListDelegate::peerListUiShow"); Unexpected("...ListDelegate::peerListUiShow");
} }

View file

@ -459,16 +459,6 @@ void Members::peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) {
void Members::peerListFinishSelectedRowsBunch() { void Members::peerListFinishSelectedRowsBunch() {
} }
void Members::peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options) {
_show->showBox(std::move(content), options);
}
void Members::peerListHideLayer() {
_show->hideLayer();
}
std::shared_ptr<Main::SessionShow> Members::peerListUiShow() { std::shared_ptr<Main::SessionShow> Members::peerListUiShow() {
return _show; return _show;
} }

View file

@ -78,10 +78,6 @@ private:
void peerListFinishSelectedRowsBunch() override; void peerListFinishSelectedRowsBunch() override;
void peerListSetDescription( void peerListSetDescription(
object_ptr<Ui::FlatLabel> description) override; object_ptr<Ui::FlatLabel> description) override;
void peerListShowBox(
object_ptr<Ui::BoxContent> content,
Ui::LayerOptions options = Ui::LayerOption::KeepOther) override;
void peerListHideLayer() override;
std::shared_ptr<Main::SessionShow> peerListUiShow() override; std::shared_ptr<Main::SessionShow> peerListUiShow() override;
//void peerListAppendRow( //void peerListAppendRow(

View file

@ -15,6 +15,7 @@ struct SavedState final {
Data::AnyStatistics stats; Data::AnyStatistics stats;
base::flat_map<MsgId, QImage> recentPostPreviews; base::flat_map<MsgId, QImage> recentPostPreviews;
Data::PublicForwardsSlice publicForwardsFirstSlice; Data::PublicForwardsSlice publicForwardsFirstSlice;
int recentPostsExpanded = 0;
}; };
} // namespace Info::Statistics } // namespace Info::Statistics

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_statistics.h" #include "api/api_statistics.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "base/call_delayed.h"
#include "base/event_filter.h" #include "base/event_filter.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -697,25 +698,53 @@ void InnerWidget::fillRecentPosts() {
} }
}; };
auto foundLoaded = false; const auto buttonWrap = container->add(
for (const auto &recent : stats.recentMessageInteractions) { object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
const auto messageWrap = content->add( container,
object_ptr<Ui::VerticalLayout>(content)); object_ptr<Ui::SettingsButton>(
const auto msgId = recent.messageId; container,
if (const auto item = _peer->owner().message(_peer, msgId)) { tr::lng_stories_show_more())));
addMessage(messageWrap, item, recent);
foundLoaded = true; constexpr auto kPerPage = int(10);
continue; const auto max = stats.recentMessageInteractions.size();
if (_state.recentPostsExpanded) {
_state.recentPostsExpanded = std::max(
_state.recentPostsExpanded - kPerPage,
0);
}
const auto showMore = [=] {
const auto from = _state.recentPostsExpanded;
_state.recentPostsExpanded = std::min(
int(max),
_state.recentPostsExpanded + kPerPage);
if (_state.recentPostsExpanded == max) {
buttonWrap->toggle(false, anim::type::instant);
} }
const auto callback = crl::guard(content, [=] { for (auto i = from; i < _state.recentPostsExpanded; i++) {
const auto &recent = stats.recentMessageInteractions[i];
const auto messageWrap = content->add(
object_ptr<Ui::VerticalLayout>(content));
const auto msgId = recent.messageId;
if (const auto item = _peer->owner().message(_peer, msgId)) { if (const auto item = _peer->owner().message(_peer, msgId)) {
addMessage(messageWrap, item, recent); addMessage(messageWrap, item, recent);
content->resizeToWidth(content->width()); continue;
} }
}); const auto callback = crl::guard(content, [=] {
_peer->session().api().requestMessageData(_peer, msgId, callback); if (const auto item = _peer->owner().message(_peer, msgId)) {
} addMessage(messageWrap, item, recent);
if (!foundLoaded) { content->resizeToWidth(content->width());
}
});
_peer->session().api().requestMessageData(_peer, msgId, callback);
}
container->resizeToWidth(container->width());
};
const auto delay = st::defaultRippleAnimation.hideDuration;
buttonWrap->entity()->setClickedCallback([=] {
base::call_delayed(delay, crl::guard(container, showMore));
});
showMore();
if (_messagePreviews.empty()) {
wrap->toggle(false, anim::type::instant); wrap->toggle(false, anim::type::instant);
} }
} }

View file

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "info/boosts/giveaway/boost_badge.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
@ -35,58 +36,6 @@ using BoostCallback = Fn<void(const Data::Boost &)>;
constexpr auto kColorIndexUnclaimed = int(3); constexpr auto kColorIndexUnclaimed = int(3);
constexpr auto kColorIndexPending = int(4); constexpr auto kColorIndexPending = int(4);
[[nodiscard]] QImage Badge(
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 AddArrow(not_null<Ui::RpWidget*> parent) { void AddArrow(not_null<Ui::RpWidget*> parent) {
const auto arrow = Ui::CreateChild<Ui::RpWidget>(parent.get()); const auto arrow = Ui::CreateChild<Ui::RpWidget>(parent.get());
arrow->paintRequest( arrow->paintRequest(
@ -442,19 +391,16 @@ BoostRow::BoostRow(const Data::Boost &boost)
void BoostRow::init() { void BoostRow::init() {
invalidateBadges(); invalidateBadges();
constexpr auto kMonthsDivider = int(30 * 86400);
const auto months = (_boost.expiresAt - _boost.date.toSecsSinceEpoch())
/ kMonthsDivider;
auto status = !PeerListRow::special() auto status = !PeerListRow::special()
? tr::lng_boosts_list_status( ? tr::lng_boosts_list_status(
tr::now, tr::now,
lt_date, lt_date,
langDateTime(_boost.date)) langDayOfMonth(_boost.expiresAt.date()))
: tr::lng_months_tiny(tr::now, lt_count, months) : tr::lng_months_tiny(tr::now, lt_count, _boost.expiresAfterMonths)
+ ' ' + ' '
+ QChar(0x2022) + QChar(0x2022)
+ ' ' + ' '
+ langDateTime(_boost.date); + langDayOfMonth(_boost.date.date());
PeerListRow::setCustomStatus(std::move(status)); PeerListRow::setCustomStatus(std::move(status));
} }
@ -476,12 +422,17 @@ PaintRoundImageCallback BoostRow::generatePaintUserpicCallback(bool force) {
} }
return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
_userpic.paintCircle(p, x, y, outerWidth, size); _userpic.paintCircle(p, x, y, outerWidth, size);
(_boost.isUnclaimed
? st::boostsListUnclaimedIcon
: st::boostsListUnknownIcon).paintInCenter(
p,
{ x, y, size, size });
}; };
} }
void BoostRow::invalidateBadges() { void BoostRow::invalidateBadges() {
_badge = _boost.multiplier _badge = _boost.multiplier
? Badge( ? CreateBadge(
st::statisticsDetailsBottomCaptionStyle, st::statisticsDetailsBottomCaptionStyle,
QString::number(_boost.multiplier), QString::number(_boost.multiplier),
st::boostsListBadgeHeight, st::boostsListBadgeHeight,
@ -501,7 +452,7 @@ void BoostRow::invalidateBadges() {
? st::boostsListGiveawayMiniIcon ? st::boostsListGiveawayMiniIcon
: st::boostsListGiftMiniIcon; : st::boostsListGiftMiniIcon;
_rightBadge = (_boost.isGift || _boost.isGiveaway) _rightBadge = (_boost.isGift || _boost.isGiveaway)
? Badge( ? CreateBadge(
st::boostsListRightBadgeTextStyle, st::boostsListRightBadgeTextStyle,
_boost.isGiveaway _boost.isGiveaway
? tr::lng_gift_link_reason_giveaway(tr::now) ? tr::lng_gift_link_reason_giveaway(tr::now)
@ -745,7 +696,6 @@ void AddMembersList(
container, container,
tr::lng_stories_show_more())), tr::lng_stories_show_more())),
{ 0, -st::settingsButton.padding.top(), 0, 0 }); { 0, -st::settingsButton.padding.top(), 0, 0 });
const auto button = wrap->entity();
const auto showMore = [=] { const auto showMore = [=] {
state->limit = std::min(int(max), state->limit + kPerPage); state->limit = std::min(int(max), state->limit + kPerPage);
@ -755,7 +705,7 @@ void AddMembersList(
} }
container->resizeToWidth(container->width()); container->resizeToWidth(container->width());
}; };
button->setClickedCallback(showMore); wrap->entity()->setClickedCallback(showMore);
showMore(); showMore();
} }

View file

@ -215,6 +215,7 @@ void MessagePreview::paintEvent(QPaintEvent *e) {
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(), .now = crl::now(),
.elisionHeight = st::statisticsDetailsPopupHeaderStyle.font->height, .elisionHeight = st::statisticsDetailsPopupHeaderStyle.font->height,
.elisionLines = 1,
}); });
_views.draw(p, { _views.draw(p, {
.position = { width() - _viewsWidth, topTextTop }, .position = { width() - _viewsWidth, topTextTop },

View file

@ -130,6 +130,39 @@ not_null<Main::Session*> SessionFromId(const InvoiceId &id) {
return &giveaway.boostPeer->session(); return &giveaway.boostPeer->session();
} }
MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL(
const InvoicePremiumGiftCode &invoice) {
const auto &giveaway = v::get<InvoicePremiumGiftCodeGiveaway>(
invoice.purpose);
using Flag = MTPDinputStorePaymentPremiumGiveaway::Flag;
return MTP_inputStorePaymentPremiumGiveaway(
MTP_flags(Flag()
| (giveaway.onlyNewSubscribers
? Flag::f_only_new_subscribers
: Flag())
| (giveaway.additionalChannels.empty()
? Flag()
: Flag::f_additional_peers)
| (giveaway.countries.empty()
? Flag()
: Flag::f_countries_iso2)),
giveaway.boostPeer->input,
MTP_vector_from_range(ranges::views::all(
giveaway.additionalChannels
) | ranges::views::transform([](not_null<ChannelData*> c) {
return MTPInputPeer(c->input);
})),
MTP_vector_from_range(ranges::views::all(
giveaway.countries
) | ranges::views::transform([](QString value) {
return MTP_string(value);
})),
MTP_long(invoice.randomId),
MTP_int(giveaway.untilDate),
MTP_string(invoice.currency),
MTP_long(invoice.amount));
}
Form::Form(InvoiceId id, bool receipt) Form::Form(InvoiceId id, bool receipt)
: _id(id) : _id(id)
, _session(SessionFromId(id)) , _session(SessionFromId(id))
@ -305,36 +338,8 @@ MTPInputInvoice Form::inputInvoice() const {
MTP_long(giftCode.amount)), MTP_long(giftCode.amount)),
option); option);
} else { } else {
const auto &giveaway = v::get<InvoicePremiumGiftCodeGiveaway>(
giftCode.purpose);
using Flag = MTPDinputStorePaymentPremiumGiveaway::Flag;
return MTP_inputInvoicePremiumGiftCode( return MTP_inputInvoicePremiumGiftCode(
MTP_inputStorePaymentPremiumGiveaway( InvoicePremiumGiftCodeGiveawayToTL(giftCode),
MTP_flags(Flag()
| (giveaway.onlyNewSubscribers
? Flag::f_only_new_subscribers
: Flag())
| (giveaway.additionalChannels.empty()
? Flag()
: Flag::f_additional_peers)
| (giveaway.countries.empty()
? Flag()
: Flag::f_countries_iso2)),
giveaway.boostPeer->input,
MTP_vector_from_range(ranges::views::all(
giveaway.additionalChannels
) | ranges::views::transform([](not_null<ChannelData*> c) {
return MTPInputPeer(c->input);
})),
MTP_vector_from_range(ranges::views::all(
giveaway.countries
) | ranges::views::transform([](QString value) {
return MTP_string(value);
})),
MTP_long(giftCode.randomId),
MTP_int(giveaway.untilDate),
MTP_string(giftCode.currency),
MTP_long(giftCode.amount)),
option); option);
} }
} }

View file

@ -219,6 +219,9 @@ struct InvoiceId {
[[nodiscard]] not_null<Main::Session*> SessionFromId(const InvoiceId &id); [[nodiscard]] not_null<Main::Session*> SessionFromId(const InvoiceId &id);
[[nodiscard]] MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL(
const InvoicePremiumGiftCode &invoice);
class Form final : public base::has_weak_ptr { class Form final : public base::has_weak_ptr {
public: public:
Form(InvoiceId id, bool receipt); Form(InvoiceId id, bool receipt);

View file

@ -43,7 +43,7 @@ constexpr auto kTooltipDelay = crl::time(10000);
QOperatingSystemVersion::Windows, QOperatingSystemVersion::Windows,
10, 10,
0, 0,
17763); 18282);
static const auto kSupported = (kSystemVersion >= kDarkModeAddedVersion); static const auto kSupported = (kSystemVersion >= kDarkModeAddedVersion);
if (!kSupported) { if (!kSupported) {
return std::nullopt; return std::nullopt;

View file

@ -527,7 +527,7 @@ TopBarUser::TopBarUser(
, _content(this) , _content(this)
, _title(_content, st::settingsPremiumUserTitle) , _title(_content, st::settingsPremiumUserTitle)
, _about(_content, st::userPremiumCover.about) , _about(_content, st::userPremiumCover.about)
, _ministars(_content) , _ministars(_content, true)
, _smallTop({ , _smallTop({
.widget = object_ptr<Ui::RpWidget>(this), .widget = object_ptr<Ui::RpWidget>(this),
.text = Ui::Text::String( .text = Ui::Text::String(

View file

@ -159,9 +159,11 @@ boostsListBadgeHeight: 16px;
boostsListRightBadgeTextStyle: TextStyle(defaultTextStyle) { boostsListRightBadgeTextStyle: TextStyle(defaultTextStyle) {
font: font(12px semibold); font: font(12px semibold);
} }
boostsListRightBadgeTextPadding: margins(16px, 1px, 6px, 0px); boostsListRightBadgeTextPadding: margins(22px, 1px, 8px, 0px);
boostsListRightBadgePadding: margins(4px, 5px, 8px, 0px); boostsListRightBadgePadding: margins(4px, 5px, 12px, 0px);
boostsListRightBadgeHeight: 20px; boostsListRightBadgeHeight: 20px;
boostsListGiftMiniIconPadding: margins(1px, 2px, 0px, 0px); boostsListGiftMiniIconPadding: margins(4px, 2px, 0px, 0px);
boostsListGiftMiniIcon: icon{{ "boosts/boost_mini2", historyPeer8UserpicBg2 }}; boostsListGiftMiniIcon: icon{{ "boosts/mini_gift", historyPeer8UserpicBg2 }};
boostsListGiveawayMiniIcon: icon{{ "boosts/boost_mini2", historyPeer4UserpicBg2 }}; boostsListGiveawayMiniIcon: icon{{ "boosts/mini_giveaway", historyPeer4UserpicBg2 }};
boostsListUnclaimedIcon: icon{{ "boosts/boost_unknown", premiumButtonFg }};
boostsListUnknownIcon: icon{{ "boosts/boost_unclaimed", premiumButtonFg }};

View file

@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/boost_box.h" #include "ui/boxes/boost_box.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/fireworks_animation.h" #include "ui/effects/fireworks_animation.h"
#include "ui/effects/premium_graphics.h" #include "ui/effects/premium_graphics.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/painter.h"
#include "styles/style_giveaway.h" #include "styles/style_giveaway.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_premium.h" #include "styles/style_premium.h"
@ -20,6 +22,94 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
namespace Ui { namespace Ui {
namespace {
[[nodiscard]] BoostCounters AdjustByReached(BoostCounters data) {
const auto exact = (data.boosts == data.thisLevelBoosts);
const auto reached = !data.nextLevelBoosts || (exact && data.mine > 0);
if (reached) {
if (data.nextLevelBoosts) {
--data.level;
}
data.boosts = data.nextLevelBoosts = std::max({
data.boosts,
data.thisLevelBoosts,
1
});
data.thisLevelBoosts = 0;
} else {
data.boosts = std::max(data.thisLevelBoosts, data.boosts);
data.nextLevelBoosts = std::max(
data.nextLevelBoosts,
data.boosts + 1);
}
return data;
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeTitle(
not_null<Ui::GenericBox*> box,
rpl::producer<QString> title,
rpl::producer<QString> repeated) {
auto result = object_ptr<Ui::RpWidget>(box);
struct State {
not_null<Ui::FlatLabel*> title;
not_null<Ui::FlatLabel*> repeated;
};
const auto notEmpty = [](const QString &text) {
return !text.isEmpty();
};
const auto state = box->lifetime().make_state<State>(State{
.title = Ui::CreateChild<Ui::FlatLabel>(
result.data(),
rpl::duplicate(title),
st::boostTitle),
.repeated = Ui::CreateChild<Ui::FlatLabel>(
result.data(),
rpl::duplicate(repeated) | rpl::filter(notEmpty),
st::boostTitleBadge),
});
state->title->show();
state->repeated->showOn(std::move(repeated) | rpl::map(notEmpty));
result->resize(result->width(), st::boostTitle.style.font->height);
rpl::combine(
result->widthValue(),
rpl::duplicate(title),
state->repeated->shownValue(),
state->repeated->widthValue()
) | rpl::start_with_next([=](int outer, auto&&, bool shown, int badge) {
const auto repeated = shown ? badge : 0;
const auto skip = st::boostTitleBadgeSkip;
const auto available = outer - repeated - skip;
const auto use = std::min(state->title->textMaxWidth(), available);
state->title->resizeToWidth(use);
const auto left = (outer - use - skip - repeated) / 2;
state->title->moveToLeft(left, 0);
const auto mleft = st::boostTitleBadge.margin.left();
const auto mtop = st::boostTitleBadge.margin.top();
state->repeated->moveToLeft(left + use + skip + mleft, mtop);
}, result->lifetime());
const auto badge = state->repeated;
badge->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(badge);
auto hq = PainterHighQualityEnabler(p);
const auto radius = std::min(badge->width(), badge->height()) / 2;
p.setPen(Qt::NoPen);
auto brush = QLinearGradient(
QPointF(badge->width(), badge->height()),
QPointF());
brush.setStops(Ui::Premium::ButtonGradientStops());
p.setBrush(brush);
p.drawRoundedRect(badge->rect(), radius, radius);
}, badge->lifetime());
return result;
}
} // namespace
void StartFireworks(not_null<QWidget*> parent) { void StartFireworks(not_null<QWidget*> parent) {
const auto result = Ui::CreateChild<RpWidget>(parent.get()); const auto result = Ui::CreateChild<RpWidget>(parent.get());
@ -42,62 +132,69 @@ void StartFireworks(not_null<QWidget*> parent) {
void BoostBox( void BoostBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
BoostBoxData data, BoostBoxData data,
Fn<void(Fn<void(bool)>)> boost) { Fn<void(Fn<void(BoostCounters)>)> boost) {
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setStyle(st::boostBox); box->setStyle(st::boostBox);
const auto full = !data.boost.nextLevelBoosts; //AssertIsDebug();
//data.boost = {
// .level = 2,
// .boosts = 3,
// .thisLevelBoosts = 2,
// .nextLevelBoosts = 5,
// .mine = 2,
//};
struct State { struct State {
rpl::variable<bool> you = false; rpl::variable<BoostCounters> data;
rpl::variable<bool> full;
bool submitted = false; bool submitted = false;
}; };
const auto state = box->lifetime().make_state<State>(State{ const auto state = box->lifetime().make_state<State>();
.you = data.boost.mine, state->data = std::move(data.boost);
});
FillBoostLimit( FillBoostLimit(
BoxShowFinishes(box), BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(), box->verticalLayout(),
data.boost, state->data.value(),
st::boxRowPadding); st::boxRowPadding);
{
const auto &d = data.boost;
if (!d.nextLevelBoosts
|| ((d.thisLevelBoosts == d.boosts) && d.mine)) {
--data.boost.level;
}
}
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
const auto name = data.name; const auto name = data.name;
auto title = state->you.value() | rpl::map([=](bool your) {
return your auto title = state->data.value(
) | rpl::map([=](BoostCounters counters) {
return (counters.mine > 0)
? tr::lng_boost_channel_you_title( ? tr::lng_boost_channel_you_title(
lt_channel, lt_channel,
rpl::single(data.name)) rpl::single(name))
: full : !counters.nextLevelBoosts
? tr::lng_boost_channel_title_max() ? tr::lng_boost_channel_title_max()
: !data.boost.level : !counters.level
? tr::lng_boost_channel_title_first() ? tr::lng_boost_channel_title_first()
: tr::lng_boost_channel_title_more(); : tr::lng_boost_channel_title_more();
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
auto text = state->you.value() | rpl::map([=](bool your) { auto repeated = state->data.value(
const auto bold = Ui::Text::Bold(data.name); ) | rpl::map([=](BoostCounters counters) {
const auto now = data.boost.boosts + (your ? 1 : 0); return (counters.mine > 1) ? u"x%1"_q.arg(counters.mine) : u""_q;
const auto left = (data.boost.nextLevelBoosts > now) });
? (data.boost.nextLevelBoosts - now)
auto text = state->data.value(
) | rpl::map([=](BoostCounters counters) {
const auto bold = Ui::Text::Bold(name);
const auto now = counters.boosts;
const auto full = !counters.nextLevelBoosts;
const auto left = (counters.nextLevelBoosts > now)
? (counters.nextLevelBoosts - now)
: 0; : 0;
auto post = tr::lng_boost_channel_post_stories( auto post = tr::lng_boost_channel_post_stories(
lt_count, lt_count,
rpl::single(float64(data.boost.level + 1)), rpl::single(float64(counters.level + (left ? 1 : 0))),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
return (your || full) return (counters.mine || full)
? ((!full && left > 0) ? (left
? (!data.boost.level ? (!counters.level
? tr::lng_boost_channel_you_first( ? tr::lng_boost_channel_you_first(
lt_count, lt_count,
rpl::single(float64(left)), rpl::single(float64(left)),
@ -108,16 +205,16 @@ void BoostBox(
lt_post, lt_post,
std::move(post), std::move(post),
Ui::Text::RichLangValue)) Ui::Text::RichLangValue))
: (!data.boost.level : (!counters.level
? tr::lng_boost_channel_reached_first( ? tr::lng_boost_channel_reached_first(
Ui::Text::RichLangValue) Ui::Text::RichLangValue)
: tr::lng_boost_channel_reached_more( : tr::lng_boost_channel_reached_more(
lt_count, lt_count,
rpl::single(float64(data.boost.level + 1)), rpl::single(float64(counters.level)),
lt_post, lt_post,
std::move(post), std::move(post),
Ui::Text::RichLangValue))) Ui::Text::RichLangValue)))
: !data.boost.level : !counters.level
? tr::lng_boost_channel_needs_first( ? tr::lng_boost_channel_needs_first(
lt_count, lt_count,
rpl::single(float64(left)), rpl::single(float64(left)),
@ -133,12 +230,11 @@ void BoostBox(
std::move(post), std::move(post),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
box->addRow( box->addRow(
object_ptr<Ui::FlatLabel>( MakeTitle(box, std::move(title), std::move(repeated)),
box,
std::move(title),
st::boostTitle),
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
box->addRow( box->addRow(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
@ -147,28 +243,85 @@ void BoostBox(
(st::boxRowPadding (st::boxRowPadding
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
auto submit = full const auto allowMulti = data.allowMulti;
? (tr::lng_box_ok() | rpl::type_erased()) auto submit = state->data.value(
: state->you.value( ) | rpl::map([=](BoostCounters counters) {
) | rpl::map([](bool mine) { return (!counters.nextLevelBoosts || (counters.mine && !allowMulti))
return mine ? tr::lng_box_ok() : tr::lng_boost_channel_button(); ? tr::lng_box_ok()
}) | rpl::flatten_latest(); : (counters.mine > 0)
? tr::lng_boost_again_button()
: tr::lng_boost_channel_button();
}) | rpl::flatten_latest();
const auto button = box->addButton(rpl::duplicate(submit), [=] { const auto button = box->addButton(rpl::duplicate(submit), [=] {
if (state->submitted) { if (state->submitted) {
return; return;
} else if (!full && !state->you.current()) { } else if (state->data.current().nextLevelBoosts > 0
&& (allowMulti || !state->data.current().mine)) {
state->submitted = true; state->submitted = true;
boost(crl::guard(box, [=](bool success) { const auto was = state->data.current().mine;
//AssertIsDebug();
//state->submitted = false;
//if (state->data.current().level == 5
// && state->data.current().boosts == 11) {
// state->data = BoostCounters{
// .level = 5,
// .boosts = 14,
// .thisLevelBoosts = 9,
// .nextLevelBoosts = 15,
// .mine = 14,
// };
//} else if (state->data.current().level == 5) {
// state->data = BoostCounters{
// .level = 7,
// .boosts = 16,
// .thisLevelBoosts = 15,
// .nextLevelBoosts = 19,
// .mine = 16,
// };
//} else if (state->data.current().level == 4) {
// state->data = BoostCounters{
// .level = 5,
// .boosts = 11,
// .thisLevelBoosts = 9,
// .nextLevelBoosts = 15,
// .mine = 9,
// };
//} else if (state->data.current().level == 3) {
// state->data = BoostCounters{
// .level = 4,
// .boosts = 7,
// .thisLevelBoosts = 7,
// .nextLevelBoosts = 9,
// .mine = 5,
// };
//} else {
// state->data = BoostCounters{
// .level = 3,
// .boosts = 5,
// .thisLevelBoosts = 5,
// .nextLevelBoosts = 7,
// .mine = 3,
// };
//}
//return;
boost(crl::guard(box, [=](BoostCounters result) {
state->submitted = false; state->submitted = false;
if (success) {
StartFireworks(box->parentWidget()); if (result.thisLevelBoosts || result.nextLevelBoosts) {
state->you = true; if (result.mine > was) {
StartFireworks(box->parentWidget());
}
state->data = result;
} }
})); }));
} else { } else {
box->closeBox(); box->closeBox();
} }
}); });
rpl::combine( rpl::combine(
std::move(submit), std::move(submit),
box->widthValue() box->widthValue()
@ -261,6 +414,49 @@ object_ptr<Ui::RpWidget> MakeLinkLabel(
return result; return result;
} }
void BoostBoxAlready(not_null<GenericBox*> box) {
ConfirmBox(box, {
.text = tr::lng_boost_error_already_text(Text::RichLangValue),
.title = tr::lng_boost_error_already_title(),
.inform = true,
});
}
void GiftForBoostsBox(
not_null<GenericBox*> box,
QString channel,
int receive,
bool again) {
ConfirmBox(box, {
.text = (again
? tr::lng_boost_need_more_again
: tr::lng_boost_need_more_text)(
lt_count,
rpl::single(receive) | tr::to_count(),
lt_channel,
rpl::single(TextWithEntities{ channel }),
Text::RichLangValue),
.title = tr::lng_boost_need_more(),
.inform = true,
});
}
void GiftedNoBoostsBox(not_null<GenericBox*> box) {
InformBox(box, {
.text = tr::lng_boost_error_gifted_text(Text::RichLangValue),
.title = tr::lng_boost_error_gifted_title(),
});
}
void PremiumForBoostsBox(not_null<GenericBox*> box, Fn<void()> buyPremium) {
ConfirmBox(box, {
.text = tr::lng_boost_error_premium_text(Text::RichLangValue),
.confirmed = buyPremium,
.confirmText = tr::lng_boost_error_premium_yes(),
.title = tr::lng_boost_error_premium_title(),
});
}
void AskBoostBox( void AskBoostBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
AskBoostBoxData data, AskBoostBoxData data,
@ -269,19 +465,10 @@ void AskBoostBox(
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setStyle(st::boostBox); box->setStyle(st::boostBox);
struct State {
rpl::variable<bool> you = false;
bool submitted = false;
};
const auto state = box->lifetime().make_state<State>(State{
.you = data.boost.mine,
});
FillBoostLimit( FillBoostLimit(
BoxShowFinishes(box), BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(), box->verticalLayout(),
data.boost, rpl::single(data.boost),
st::boxRowPadding); st::boxRowPadding);
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
@ -338,56 +525,22 @@ void AskBoostBox(
void FillBoostLimit( void FillBoostLimit(
rpl::producer<> showFinished, rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
BoostCounters data, rpl::producer<BoostCounters> data,
style::margins limitLinePadding) { style::margins limitLinePadding) {
const auto full = !data.nextLevelBoosts;
if (data.mine && data.boosts > 0) {
--data.boosts;
}
if (full) {
data.nextLevelBoosts = data.boosts
+ (data.mine ? 1 : 0);
data.thisLevelBoosts = 0;
if (data.level > 0) {
--data.level;
}
} else if (data.mine
&& data.level > 0
&& data.boosts < data.thisLevelBoosts) {
--data.level;
data.nextLevelBoosts = data.thisLevelBoosts;
data.thisLevelBoosts = 0;
}
const auto addSkip = [&](int skip) { const auto addSkip = [&](int skip) {
container->add(object_ptr<Ui::FixedHeightWidget>(container, skip)); container->add(object_ptr<Ui::FixedHeightWidget>(container, skip));
}; };
addSkip(st::boostSkipTop); addSkip(st::boostSkipTop);
const auto levelWidth = [&](int add) { const auto ratio = [=](BoostCounters counters) {
return st::normalFont->width( const auto min = counters.thisLevelBoosts;
tr::lng_boost_level(tr::now, lt_count, data.level + add)); const auto max = counters.nextLevelBoosts;
};
const auto paddings = 2 * st::premiumLineTextSkip; Assert(counters.boosts >= min && counters.boosts <= max);
const auto labelLeftWidth = paddings + levelWidth(0);
const auto labelRightWidth = paddings + levelWidth(1);
const auto ratio = [=](int boosts) {
const auto min = std::min(
data.boosts,
data.thisLevelBoosts);
const auto max = std::max({
data.boosts,
data.nextLevelBoosts,
1,
});
Assert(boosts >= min && boosts <= max);
const auto count = (max - min); const auto count = (max - min);
const auto index = (boosts - min); const auto index = (counters.boosts - min);
if (!index) { if (!index) {
return 0.; return 0.;
} else if (index == count) { } else if (index == count) {
@ -399,26 +552,33 @@ void FillBoostLimit(
- st::boxPadding.left() - st::boxPadding.left()
- st::boxPadding.right(); - st::boxPadding.right();
const auto average = available / float64(count); const auto average = available / float64(count);
const auto levelWidth = [&](int add) {
return st::normalFont->width(
tr::lng_boost_level(
tr::now,
lt_count,
counters.level + add));
};
const auto paddings = 2 * st::premiumLineTextSkip;
const auto labelLeftWidth = paddings + levelWidth(0);
const auto labelRightWidth = paddings + levelWidth(1);
const auto first = std::max(average, labelLeftWidth * 1.); const auto first = std::max(average, labelLeftWidth * 1.);
const auto last = std::max(average, labelRightWidth * 1.); const auto last = std::max(average, labelRightWidth * 1.);
const auto other = (available - first - last) / (count - 2); const auto other = (available - first - last) / (count - 2);
return (first + (index - 1) * other) / available; return (first + (index - 1) * other) / available;
}; };
const auto min = std::min(data.boosts, data.thisLevelBoosts); auto adjustedData = rpl::duplicate(data) | rpl::map(AdjustByReached);
const auto now = data.boosts;
const auto max = (data.nextLevelBoosts > min) auto bubbleRowState = rpl::duplicate(
? (data.nextLevelBoosts) adjustedData
: (data.boosts > 0) ) | rpl::combine_previous(
? data.boosts BoostCounters()
: 1; ) | rpl::map([=](BoostCounters previous, BoostCounters counters) {
auto bubbleRowState = (
std::move(you)
) | rpl::map([=](bool mine) {
const auto index = mine ? (now + 1) : now;
return Premium::BubbleRowState{ return Premium::BubbleRowState{
.counter = index, .counter = counters.boosts,
.ratio = ratio(index), .ratio = ratio(counters),
.animateFromZero = (counters.level != previous.level),
.dynamic = true, .dynamic = true,
}; };
}); });
@ -427,7 +587,6 @@ void FillBoostLimit(
st::boostBubble, st::boostBubble,
std::move(showFinished), std::move(showFinished),
rpl::duplicate(bubbleRowState), rpl::duplicate(bubbleRowState),
max,
true, true,
nullptr, nullptr,
&st::premiumIconBoost, &st::premiumIconBoost,
@ -437,20 +596,33 @@ void FillBoostLimit(
const auto level = [](int level) { const auto level = [](int level) {
return tr::lng_boost_level(tr::now, lt_count, level); return tr::lng_boost_level(tr::now, lt_count, level);
}; };
auto ratioValue = std::move( auto limitState = std::move(
bubbleRowState bubbleRowState
) | rpl::map([](const Premium::BubbleRowState &state) { ) | rpl::map([](const Premium::BubbleRowState &state) {
return state.ratio; return Premium::LimitRowState{
.ratio = state.ratio,
.animateFromZero = state.animateFromZero,
.dynamic = state.dynamic
};
});
auto left = rpl::duplicate(
adjustedData
) | rpl::map([=](BoostCounters counters) {
return level(counters.level);
});
auto right = rpl::duplicate(
adjustedData
) | rpl::map([=](BoostCounters counters) {
return level(counters.level + 1);
}); });
Premium::AddLimitRow( Premium::AddLimitRow(
container, container,
st::boostLimits, st::boostLimits,
Premium::LimitRowLabels{ Premium::LimitRowLabels{
.leftLabel = level(data.level), .leftLabel = std::move(left),
.rightLabel = level(data.level + 1), .rightLabel = std::move(right),
.dynamic = true,
}, },
std::move(ratioValue), std::move(limitState),
limitLinePadding); limitLinePadding);
} }

View file

@ -23,18 +23,32 @@ struct BoostCounters {
int boosts = 0; int boosts = 0;
int thisLevelBoosts = 0; int thisLevelBoosts = 0;
int nextLevelBoosts = 0; // Zero means no next level is available. int nextLevelBoosts = 0; // Zero means no next level is available.
bool mine = false; int mine = 0;
friend inline constexpr bool operator==(
BoostCounters,
BoostCounters) = default;
}; };
struct BoostBoxData { struct BoostBoxData {
QString name; QString name;
BoostCounters boost; BoostCounters boost;
bool allowMulti = false;
}; };
void BoostBox( void BoostBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
BoostBoxData data, BoostBoxData data,
Fn<void(Fn<void(bool)>)> boost); Fn<void(Fn<void(BoostCounters)>)> boost);
void BoostBoxAlready(not_null<GenericBox*> box);
void GiftForBoostsBox(
not_null<GenericBox*> box,
QString channel,
int receive,
bool again);
void GiftedNoBoostsBox(not_null<GenericBox*> box);
void PremiumForBoostsBox(not_null<GenericBox*> box, Fn<void()> buyPremium);
struct AskBoostBoxData { struct AskBoostBoxData {
QString link; QString link;
@ -57,9 +71,8 @@ void AskBoostBox(
void FillBoostLimit( void FillBoostLimit(
rpl::producer<> showFinished, rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
BoostCounters data, rpl::producer<BoostCounters> data,
style::margins limitLinePadding); style::margins limitLinePadding);
} // namespace Ui } // namespace Ui

View file

@ -100,11 +100,4 @@ object_ptr<Ui::GenericBox> MakeConfirmBox(ConfirmBoxArgs &&args) {
return Box(ConfirmBox, std::move(args)); return Box(ConfirmBox, std::move(args));
} }
object_ptr<Ui::GenericBox> MakeInformBox(v::text::data text) {
return MakeConfirmBox({
.text = std::move(text),
.inform = true,
});
}
} // namespace Ui } // namespace Ui

View file

@ -40,10 +40,24 @@ struct ConfirmBoxArgs {
bool strictCancel = false; bool strictCancel = false;
}; };
void ConfirmBox(not_null<Ui::GenericBox*> box, ConfirmBoxArgs &&args); void ConfirmBox(not_null<GenericBox*> box, ConfirmBoxArgs &&args);
[[nodiscard]] object_ptr<Ui::GenericBox> MakeConfirmBox( inline void InformBox(not_null<GenericBox*> box, ConfirmBoxArgs &&args) {
ConfirmBoxArgs &&args); args.inform = true;
[[nodiscard]] object_ptr<Ui::GenericBox> MakeInformBox(v::text::data text); ConfirmBox(box, std::move(args));
}
[[nodiscard]] object_ptr<GenericBox> MakeConfirmBox(ConfirmBoxArgs &&args);
[[nodiscard]] inline object_ptr<GenericBox> MakeInformBox(
ConfirmBoxArgs &&args) {
args.inform = true;
return MakeConfirmBox(std::move(args));
}
[[nodiscard]] inline object_ptr<GenericBox> MakeInformBox(
v::text::data text) {
return MakeInformBox({ .text = std::move(text) });
}
} // namespace Ui } // namespace Ui

View file

@ -640,6 +640,12 @@ historyPageButtonLine: 1px;
historyPageButtonHeight: 36px; historyPageButtonHeight: 36px;
historyPageButtonPadding: margins(13px, 8px, 13px, 8px); historyPageButtonPadding: margins(13px, 8px, 13px, 8px);
historyPageEnlarge: icon{{ "chat/link_photo_enlarge", historyFileThumbRadialFg }};
historyPageEnlargeSelected: icon{{ "chat/link_photo_enlarge", historyFileThumbRadialFgSelected }};
historyPageEnlargeSize: 36px;
historyPageEnlargeSkip: 4px;
historyPageEnlargeRadius: 8px;
historyCommentsButtonHeight: 40px; historyCommentsButtonHeight: 40px;
historyCommentsSkipLeft: 9px; historyCommentsSkipLeft: 9px;
historyCommentsSkipText: 10px; historyCommentsSkipText: 10px;

View file

@ -505,6 +505,10 @@ ChatStyle::ChatStyle(rpl::producer<ColorIndicesCompressed> colorIndices) {
&MessageImageStyle::historyVideoMessageMute, &MessageImageStyle::historyVideoMessageMute,
st::historyVideoMessageMute, st::historyVideoMessageMute,
st::historyVideoMessageMuteSelected); st::historyVideoMessageMuteSelected);
make(
&MessageImageStyle::historyPageEnlarge,
st::historyPageEnlarge,
st::historyPageEnlargeSelected);
updateDarkValue(); updateDarkValue();
} }

View file

@ -117,6 +117,7 @@ struct MessageImageStyle {
style::icon historyVideoDownload = { Qt::Uninitialized }; style::icon historyVideoDownload = { Qt::Uninitialized };
style::icon historyVideoCancel = { Qt::Uninitialized }; style::icon historyVideoCancel = { Qt::Uninitialized };
style::icon historyVideoMessageMute = { Qt::Uninitialized }; style::icon historyVideoMessageMute = { Qt::Uninitialized };
style::icon historyPageEnlarge = { Qt::Uninitialized };
}; };
struct ReactionPaintInfo { struct ReactionPaintInfo {

View file

@ -1108,57 +1108,4 @@ not_null<Ui::UserpicButton*> CreateUploadSubButton(
return upload; return upload;
} }
object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> from,
not_null<PeerData*> to) {
const auto full = st::boostReplaceUserpic.size.height()
+ st::boostReplaceIconAdd.y()
+ st::lineWidth;
auto result = object_ptr<FixedHeightWidget>(parent, full);
const auto raw = result.data();
const auto &st = st::boostReplaceUserpic;
const auto left = CreateChild<UserpicButton>(raw, from, st);
const auto right = CreateChild<UserpicButton>(raw, to, st);
const auto overlay = CreateChild<RpWidget>(raw);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto skip = st::boostReplaceUserpicsSkip;
const auto total = left->width() + skip + right->width();
left->moveToLeft((width - total) / 2, 0);
right->moveToLeft(left->x() + left->width() + skip, 0);
overlay->setGeometry(QRect(0, 0, width, raw->height()));
}, raw->lifetime());
overlay->paintRequest(
) | rpl::start_with_next([=] {
const auto outerw = overlay->width();
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 = left->x() + left->width() - w + add.x();
const auto y = left->y() + left->height() - h + add.y();
const auto stroke = st::boostReplaceIconOutline;
const auto half = stroke / 2.;
auto p = QPainter(overlay);
auto hq = PainterHighQualityEnabler(p);
auto pen = st::windowBg->p;
pen.setWidthF(stroke);
p.setPen(pen);
auto brush = QLinearGradient(QPointF(x + w, y + h), QPointF(x, y));
brush.setStops(Premium::ButtonGradientStops());
p.setBrush(brush);
p.drawEllipse(x - half, y - half, w + stroke, h + stroke);
st::boostReplaceIcon.paint(p, x + skip, y + skip, outerw);
const auto size = st::boostReplaceArrow.size();
st::boostReplaceArrow.paint(
p,
(outerw - size.width()) / 2,
(left->height() - size.height()) / 2,
outerw);
}, overlay->lifetime());
return result;
}
} // namespace Ui } // namespace Ui

View file

@ -204,9 +204,4 @@ private:
not_null<UserData*> contact, not_null<UserData*> contact,
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateBoostReplaceUserpics(
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> from,
not_null<PeerData*> to);
} // namespace Ui } // namespace Ui

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