diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6ffd7dd8f..966b61072 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2444,6 +2444,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_credits_box_history_entry_giveaway_name" = "Received Prize"; "lng_credits_box_history_entry_gift_sent" = "Sent Gift"; "lng_credits_box_history_entry_gift_converted" = "Converted Gift"; +"lng_credits_box_history_entry_gift_unavailable" = "Unavailable"; +"lng_credits_box_history_entry_gift_sold_out" = "This gift has sold out"; "lng_credits_box_history_entry_gift_out_about" = "With Stars, **{user}** will be able to unlock content and services on Telegram.\n{link}"; "lng_credits_box_history_entry_gift_in_about" = "Use Stars to unlock content and services on Telegram. {link}"; "lng_credits_box_history_entry_gift_about_link" = "See Examples {emoji}"; @@ -2990,6 +2992,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_reason_unclaimed" = "Incomplete Giveaway"; "lng_gift_link_reason_chosen" = "You were selected by the channel"; "lng_gift_link_label_date" = "Date"; +"lng_gift_link_label_first_sale" = "First Sale"; +"lng_gift_link_label_last_sale" = "Last Sale"; +"lng_gift_link_label_value" = "Value"; "lng_gift_link_also_send" = "You can also {link} to a friend as a gift."; "lng_gift_link_also_send_link" = "send this link"; "lng_gift_link_use" = "Use Link"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 84ce45783..4328f04ce 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -776,6 +776,8 @@ std::optional FromTL( .document = document, .limitedLeft = remaining.value_or_empty(), .limitedCount = total.value_or_empty(), + .firstSaleDate = data.vfirst_sale_date().value_or_empty(), + .lastSaleDate = data.vlast_sale_date().value_or_empty(), }; } @@ -789,7 +791,7 @@ std::optional FromTL( return {}; } return UserStarGift{ - .gift = std::move(*parsed), + .info = std::move(*parsed), .message = (data.vmessage() ? TextWithEntities{ .text = qs(data.vmessage()->data().vtext()), diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 805b68e03..f0567cbe0 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -80,10 +80,16 @@ struct StarGift { not_null document; int limitedLeft = 0; int limitedCount = 0; + TimeId firstSaleDate = 0; + TimeId lastSaleDate = 0; + + friend inline bool operator==( + const StarGift &, + const StarGift &) = default; }; struct UserStarGift { - StarGift gift; + StarGift info; TextWithEntities message; int64 convertStars = 0; PeerId fromId = 0; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index fd640e496..0c5f66faf 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -213,11 +213,14 @@ void AddTableRow( not_null AddTableRow( not_null table, rpl::producer label, - rpl::producer value) { + rpl::producer value, + const Fn)> &makeContext = nullptr) { auto widget = object_ptr( table, std::move(value), - st::giveawayGiftCodeValue); + st::giveawayGiftCodeValue, + st::defaultPopupMenu, + std::move(makeContext)); const auto result = widget.data(); AddTableRow( table, @@ -946,19 +949,49 @@ void AddStarGiftTable( st::giveawayGiftCodeTable), st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); + const auto session = &controller->session(); + const auto makeContext = [session](Fn update) { + return Core::MarkedTextContext{ + .session = session, + .customEmojiRepaint = std::move(update), + }; + }; if (peerId) { AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), controller, peerId); - } else { + } else if (!entry.soldOutInfo) { AddTableRow( table, tr::lng_credits_box_history_entry_peer_in(), MakeHiddenPeerTableValue(table, controller), st::giveawayGiftCodePeerMargin); } + if (!entry.firstSaleDate.isNull()) { + AddTableRow( + table, + tr::lng_gift_link_label_first_sale(), + rpl::single(Ui::Text::WithEntities( + langDateTime(entry.firstSaleDate)))); + } + if (!entry.lastSaleDate.isNull()) { + AddTableRow( + table, + tr::lng_gift_link_label_last_sale(), + rpl::single(Ui::Text::WithEntities( + langDateTime(entry.lastSaleDate)))); + } + { + auto star = session->data().customEmojiManager().creditsEmoji(); + AddTableRow( + table, + tr::lng_gift_link_label_value(), + rpl::single( + star.append(Lang::FormatCountDecimal(entry.credits))), + makeContext); + } if (!entry.date.isNull()) { AddTableRow( table, @@ -967,14 +1000,14 @@ void AddStarGiftTable( } if (entry.limitedCount > 0) { auto amount = rpl::single(TextWithEntities{ - QString::number(entry.limitedCount) + Lang::FormatCountDecimal(entry.limitedCount) }); AddTableRow( table, tr::lng_gift_availability(), ((entry.limitedLeft > 0) ? tr::lng_gift_availability_left( - lt_count, + lt_count_decimal, rpl::single(entry.limitedLeft * 1.), lt_amount, std::move(amount), diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 5a863f605..6c8203934 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/random.h" +#include "base/unixtime.h" #include "api/api_premium.h" #include "boxes/peer_list_controllers.h" #include "boxes/send_credits_box.h" @@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/ui_integration.h" +#include "data/data_credits.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" @@ -40,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_checkout_process.h" #include "payments/payments_non_panel_process.h" #include "settings/settings_credits.h" +#include "settings/settings_credits_graphics.h" #include "settings/settings_premium.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" @@ -213,7 +216,7 @@ auto GenerateGiftMedia( return tr::lng_action_gift_got_stars_text( tr::now, lt_count, - gift.convertStars, + gift.info.convertStars, Ui::Text::RichLangValue); }); auto description = data.text.empty() @@ -280,7 +283,7 @@ void ShowSentToast( return tr::lng_gift_sent_about( tr::now, lt_count, - gift.stars, + gift.info.stars, Ui::Text::RichLangValue); }); const auto strong = window->showToast({ @@ -338,7 +341,10 @@ void PreviewWrap::prepare(rpl::producer details) { const auto cost = v::match(descriptor, [&](GiftTypePremium data) { return FillAmountAndCurrency(data.cost, data.currency, true); }, [&](GiftTypeStars data) { - return tr::lng_gift_stars_title(tr::now, lt_count, data.stars); + return tr::lng_gift_stars_title( + tr::now, + lt_count, + data.info.stars); }); const auto text = tr::lng_action_gift_received( tr::now, @@ -508,14 +514,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { const auto &gifts = api->starGifts(); list.reserve(gifts.size()); for (auto &gift : gifts) { - list.push_back({ - .id = gift.id, - .stars = gift.stars, - .convertStars = gift.convertStars, - .document = gift.document, - .limitedCount = gift.limitedCount, - .limitedLeft = gift.limitedLeft, - }); + list.push_back({ .info = gift }); } auto &map = Map[session]; if (map.last != list) { @@ -587,7 +586,8 @@ struct GiftPriceTabs { auto sameKey = 0; for (const auto &gift : gifts) { if (same) { - const auto key = gift.stars * (gift.limitedCount ? -1 : 1); + const auto key = gift.info.stars + * (gift.info.limitedCount ? -1 : 1); if (!sameKey) { sameKey = key; } else if (sameKey != key) { @@ -595,12 +595,12 @@ struct GiftPriceTabs { } } - if (gift.limitedCount + if (gift.info.limitedCount && (result.size() < 2 || result[1] != kPriceTabLimited)) { result.insert(begin(result) + 1, kPriceTabLimited); } - if (!ranges::contains(result, gift.stars)) { - result.push_back(gift.stars); + if (!ranges::contains(result, gift.info.stars)) { + result.push_back(gift.info.stars); } } if (same) { @@ -838,16 +838,38 @@ void SendGift( const auto processNonPanelPaymentFormFactory = Payments::ProcessNonPanelPaymentFormFactory(window, done); Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ - .giftId = gift.id, + .giftId = gift.info.id, .randomId = details.randomId, .message = details.text, .user = peer->asUser(), - .limitedCount = gift.limitedCount, + .limitedCount = gift.info.limitedCount, .anonymous = details.anonymous, }, done, processNonPanelPaymentFormFactory); }); } +void SoldOutBox( + not_null box, + not_null window, + const GiftTypeStars &gift) { + Settings::ReceiptCreditsBox( + box, + window, + Data::CreditsHistoryEntry{ + .firstSaleDate = base::unixtime::parse(gift.info.firstSaleDate), + .lastSaleDate = base::unixtime::parse(gift.info.lastSaleDate), + .credits = uint64(gift.info.stars), + .bareGiftStickerId = gift.info.document->id, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .limitedCount = gift.info.limitedCount, + .limitedLeft = gift.info.limitedLeft, + .soldOutInfo = true, + .gift = true, + }, + Data::SubscriptionEntry()); + +} + void SendGiftBox( not_null box, not_null window, @@ -873,7 +895,7 @@ void SendGiftBox( }; }, [&](const GiftTypeStars &data) { return Ui::CreditsEmojiSmall(session).append( - Lang::FormatCountDecimal(std::abs(data.stars))); + Lang::FormatCountDecimal(std::abs(data.info.stars))); }); }()); @@ -1076,15 +1098,10 @@ void SendGiftBox( button->setClickedCallback([=] { const auto star = std::get_if(&descriptor); - if (star && star->limitedCount && !star->limitedLeft) { - window->showToast({ - .title = tr::lng_gift_sold_out_title(tr::now), - .text = tr::lng_gift_sold_out_text( - tr::now, - lt_count_decimal, - star->limitedCount, - Ui::Text::RichLangValue), - }); + if (star + && star->info.limitedCount + && !star->info.limitedLeft) { + window->show(Box(SoldOutBox, window, *star)); } else { window->show( Box(SendGiftBox, window, peer, api, descriptor)); @@ -1187,8 +1204,8 @@ void AddBlock( ) | rpl::map([=](std::vector &&gifts, int price) { gifts.erase(ranges::remove_if(gifts, [&](const GiftTypeStars &gift) { return (price == kPriceTabLimited) - ? (!gift.limitedCount) - : (price && gift.stars != price); + ? (!gift.info.limitedCount) + : (price && gift.info.stars != price); }), end(gifts)); return GiftsDescriptor{ gifts | ranges::to>(), diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 3397cf7ea..3f865b246 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -52,6 +52,8 @@ struct CreditsHistoryEntry final { QString title; TextWithEntities description; QDateTime date; + QDateTime firstSaleDate; + QDateTime lastSaleDate; PhotoId photoId = 0; std::vector extended; uint64 credits = 0; @@ -70,6 +72,7 @@ struct CreditsHistoryEntry final { bool anonymous = false; bool savedToProfile = false; bool fromGiftsList = false; + bool soldOutInfo = false; bool reaction = false; bool refunded = false; bool pending = false; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp index 7d1f5cebe..ebf27afcf 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.cpp @@ -86,12 +86,12 @@ void GiftButton::setDescriptor(const GiftDescriptor &descriptor) { { 1., st::windowActiveTextFg->c }, }); }, [&](const GiftTypeStars &data) { - const auto soldOut = data.limitedCount + const auto soldOut = data.info.limitedCount && !data.userpic - && !data.limitedLeft; + && !data.info.limitedLeft; _price.setMarkedText( st::semiboldTextStyle, - _delegate->star().append(' ' + QString::number(data.stars)), + _delegate->star().append(' ' + QString::number(data.info.stars)), kMarkupTextOptions, _delegate->textContext()); _userpic = !data.userpic @@ -282,8 +282,8 @@ void GiftButton::paintEvent(QPaintEvent *e) { } return QString(); }, [&](const GiftTypeStars &data) { - if (const auto count = data.limitedCount) { - const auto soldOut = !data.userpic && !data.limitedLeft; + if (const auto count = data.info.limitedCount) { + const auto soldOut = !data.userpic && !data.info.limitedLeft; p.setBrush(soldOut ? st::attentionButtonFg : st::windowActiveTextFg); @@ -463,9 +463,7 @@ DocumentData *LookupGiftSticker( return v::match(descriptor, [&](GiftTypePremium data) { return packs.lookup(data.months); }, [&](GiftTypeStars data) { - return data.document - ? data.document - : packs.lookup(packs.monthsForStars(data.stars)); + return data.info.document.get(); }); } diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 47f591e05..f8e094e1b 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "api/api_premium.h" #include "ui/abstract_button.h" #include "ui/effects/premium_stars_colored.h" #include "ui/text/text.h" @@ -43,13 +44,8 @@ struct GiftTypePremium { }; struct GiftTypeStars { - uint64 id = 0; - int64 stars = 0; - int64 convertStars = 0; - DocumentData *document = nullptr; + Api::StarGift info; PeerData *from = nullptr; - int limitedCount = 0; - int limitedLeft = 0; bool userpic = false; bool hidden = false; bool mine = false; diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp index 46ec0f1d7..722752f2d 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_widget.cpp @@ -34,14 +34,10 @@ constexpr auto kPerPage = 50; not_null to, const Api::UserStarGift &gift) { return GiftTypeStars{ - .id = gift.gift.id, - .stars = gift.gift.stars, - .convertStars = gift.gift.convertStars, - .document = gift.gift.document, + .info = gift.info, .from = ((gift.anonymous || !gift.fromId) ? nullptr : to->owner().peer(gift.fromId).get()), - .limitedCount = gift.gift.limitedCount, .userpic = true, .hidden = gift.hidden, .mine = to->isSelf(), diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 72acb3884..ebd69c454 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -1862,7 +1862,7 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; -starGift#aea174ee flags:# limited:flags.0?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int convert_stars:long = StarGift; +starGift#49c577cd flags:# limited:flags.0?true sold_out:flags.1?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int = StarGift; payments.starGiftsNotModified#a388a368 = payments.StarGifts; payments.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts; @@ -2565,4 +2565,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 190 +// LAYER 191 diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 7e813cf10..c7463400b 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -731,7 +731,7 @@ void ReceiptCreditsBox( const Data::SubscriptionEntry &s) { const auto item = controller->session().data().message( PeerId(e.barePeerId), MsgId(e.bareMsgId)); - const auto isStarGift = (e.convertStars > 0); + const auto isStarGift = (e.convertStars > 0) || e.soldOutInfo; const auto creditsHistoryStarGift = isStarGift && !e.id.isEmpty(); const auto sentStarGift = creditsHistoryStarGift && !e.in; const auto convertedStarGift = creditsHistoryStarGift && e.converted; @@ -880,6 +880,8 @@ void ReceiptCreditsBox( ? tr::lng_credits_box_history_entry_subscription(tr::now) : !e.title.isEmpty() ? e.title + : e.soldOutInfo + ? tr::lng_credits_box_history_entry_gift_unavailable(tr::now) : sentStarGift ? tr::lng_credits_box_history_entry_gift_sent(tr::now) : convertedStarGift @@ -920,7 +922,11 @@ void ReceiptCreditsBox( .session = session, .customEmojiRepaint = [=] { amount->update(); }, }; - if (s) { + if (e.soldOutInfo) { + text->setText( + st::defaultTextStyle, + tr::lng_credits_box_history_entry_gift_sold_out(tr::now)); + } else if (s) { text->setMarkedText( st::defaultTextStyle, tr::lng_credits_subscription_subtitle( @@ -960,7 +966,9 @@ void ReceiptCreditsBox( amount->paintRequest( ) | rpl::start_with_next([=] { auto p = Painter(amount); - p.setPen(s + p.setPen(e.soldOutInfo + ? st::menuIconAttentionColor + : s ? st::windowSubTextFg : e.pending ? st::creditsStroke @@ -1424,16 +1432,14 @@ void UserStarGiftBox( Data::CreditsHistoryEntry{ .description = data.message, .date = base::unixtime::parse(data.date), - .credits = uint64(data.gift.stars), + .credits = uint64(data.info.stars), .bareMsgId = uint64(data.messageId.bare), .barePeerId = data.fromId.value, - .bareGiftStickerId = (data.gift.document - ? data.gift.document->id - : 0), + .bareGiftStickerId = data.info.document->id, .peerType = Data::CreditsHistoryEntry::PeerType::Peer, - .limitedCount = data.gift.limitedCount, - .limitedLeft = data.gift.limitedLeft, - .convertStars = int(data.gift.convertStars), + .limitedCount = data.info.limitedCount, + .limitedLeft = data.info.limitedLeft, + .convertStars = int(data.info.convertStars), .converted = false, .anonymous = data.anonymous, .savedToProfile = !data.hidden,